<commit_before>package memdb

import (
	"fmt"
	"strings"

	"github.com/hashicorp/go-immutable-radix"
)

// tableIndex is a tuple of (Table, Index) used for lookups
type tableIndex struct {
	Table string
	Index string
}

// Txn is a transaction against a MemDB.
// This can be a read or write transaction.
type Txn struct {
	db      *MemDB
	write   bool
	rootTxn *iradix.Txn
	after   []func()

	modified map[tableIndex]*iradix.Txn
}

// readableIndex returns a transaction usable for reading the given
// index in a table. If a write transaction is in progress, we may need
// to use an existing modified txn.
func (txn *Txn) readableIndex(table, index string) *iradix.Txn {
	// Look for existing transaction
	if txn.write && txn.modified != nil {
		key := tableIndex{table, index}
		exist, ok := txn.modified[key]
		if ok {
			return exist
		}
	}

	// Create a read transaction
	path := indexPath(table, index)
	raw, _ := txn.rootTxn.Get(path)
	indexTxn := raw.(*iradix.Tree).Txn()
	return indexTxn
}

// writableIndex returns a transaction usable for modifying the
// given index in a table.
func (txn *Txn) writableIndex(table, index string) *iradix.Txn {
	if txn.modified == nil {
		txn.modified = make(map[tableIndex]*iradix.Txn)
	}

	// Look for existing transaction
	key := tableIndex{table, index}
	exist, ok := txn.modified[key]
	if ok {
		return exist
	}

	// Start a new transaction
	path := indexPath(table, index)
	raw, _ := txn.rootTxn.Get(path)
	indexTxn := raw.(*iradix.Tree).Txn()

	// Keep this open for the duration of the txn
	txn.modified[key] = indexTxn
	return indexTxn
}

// Abort is used to cancel this transaction.
// This is a noop for read transactions.
func (txn *Txn) Abort() {
	// Noop for a read transaction
	if !txn.write {
		return
	}

	// Check if already aborted or committed
	if txn.rootTxn == nil {
		return
	}

	// Clear the txn
	txn.rootTxn = nil
	txn.modified = nil

	// Release the writer lock since this is invalid
	txn.db.writer.Unlock()
}

// Commit is used to finalize this transaction.
// This is a noop for read transactions.
func (txn *Txn) Commit() {
	// Noop for a read transaction
	if !txn.write {
		return
	}

	// Check if already aborted or committed
	if txn.rootTxn == nil {
		return
	}

	// Commit each sub-transaction scoped to (table, index)
	for key, subTxn := range txn.modified {
		path := indexPath(key.Table, key.Index)
		final := subTxn.Commit()
		txn.rootTxn.Insert(path, final)
	}

	// Update the root of the DB
	txn.db.root = txn.rootTxn.Commit()

	// Clear the txn
	txn.rootTxn = nil
	txn.modified = nil

	// Release the writer lock since this is invalid
	txn.db.writer.Unlock()

	// Run the deferred functions, if any
	for i := len(txn.after); i > 0; i-- {
		fn := txn.after[i-1]
		fn()
	}
}

// Insert is used to add or update an object into the given table
func (txn *Txn) Insert(table string, obj interface{}) error {
	if !txn.write {
		return fmt.Errorf("cannot insert in read-only transaction")
	}

	// Get the table schema
	tableSchema, ok := txn.db.schema.Tables[table]
	if !ok {
		return fmt.Errorf("invalid table '%s'", table)
	}

	// Get the primary ID of the object
	idSchema := tableSchema.Indexes["id"]
	ok, idVal, err := idSchema.Indexer.FromObject(obj)
	if err != nil {
		return fmt.Errorf("failed to build primary index: %v", err)
	}
	if !ok {
		return fmt.Errorf("object missing primary index")
	}

	// Lookup the object by ID first, to see if this is an update
	idTxn := txn.writableIndex(table, "id")
	existing, update := idTxn.Get(idVal)

	// On an update, there is an existing object with the given
	// primary ID. We do the update by deleting the current object
	// and inserting the new object.
	for name, indexSchema := range tableSchema.Indexes {
		indexTxn := txn.writableIndex(table, name)

		// Handle the update by deleting from the index first
		if update {
			ok, val, err := indexSchema.Indexer.FromObject(existing)
			if err != nil {
				return fmt.Errorf("failed to build index '%s': %v", name, err)
			}
			if ok {
				// Handle non-unique index by computing a unique index.
				// This is done by appending the primary key which must
				// be unique anyways.
				if !indexSchema.Unique {
					val = append(val, idVal...)
				}
				indexTxn.Delete(val)
			}
		}

		// Handle the insert after the update
		ok, val, err := indexSchema.Indexer.FromObject(obj)
		if err != nil {
			return fmt.Errorf("failed to build index '%s': %v", name, err)
		}
		if !ok {
			if indexSchema.AllowMissing {
				continue
			} else {
				return fmt.Errorf("missing value for index '%s'", name)
			}
		}

		// Handle non-unique index by computing a unique index.
		// This is done by appending the primary key which must
		// be unique anyways.
		if !indexSchema.Unique {
			val = append(val, idVal...)
		}
		indexTxn.Insert(val, obj)
	}
	return nil
}

// Delete is used to delete a single object from the given table
// This object must already exist in the table
func (txn *Txn) Delete(table string, obj interface{}) error {
	if !txn.write {
		return fmt.Errorf("cannot delete in read-only transaction")
	}

	// Get the table schema
	tableSchema, ok := txn.db.schema.Tables[table]
	if !ok {
		return fmt.Errorf("invalid table '%s'", table)
	}

	// Get the primary ID of the object
	idSchema := tableSchema.Indexes["id"]
	ok, idVal, err := idSchema.Indexer.FromObject(obj)
	if err != nil {
		return fmt.Errorf("failed to build primary index: %v", err)
	}
	if !ok {
		return fmt.Errorf("object missing primary index")
	}

	// Lookup the object by ID first, check fi we should continue
	idTxn := txn.writableIndex(table, "id")
	existing, ok := idTxn.Get(idVal)
	if !ok {
		return fmt.Errorf("not found")
	}

	// Remove the object from all the indexes
	for name, indexSchema := range tableSchema.Indexes {
		indexTxn := txn.writableIndex(table, name)

		// Handle the update by deleting from the index first
		ok, val, err := indexSchema.Indexer.FromObject(existing)
		if err != nil {
			return fmt.Errorf("failed to build index '%s': %v", name, err)
		}
		if ok {
			// Handle non-unique index by computing a unique index.
			// This is done by appending the primary key which must
			// be unique anyways.
			if !indexSchema.Unique {
				val = append(val, idVal...)
			}
			indexTxn.Delete(val)
		}
	}
	return nil
}

// DeleteAll is used to delete all the objects in a given table
// matching the constraints on the index
func (txn *Txn) DeleteAll(table, index string, args ...interface{}) (int, error) {
	if !txn.write {
		return 0, fmt.Errorf("cannot delete in read-only transaction")
	}

	// TODO: Currently we use Get to just every object and then
	// iterate and delete them all. This works because sliceIterator
	// has the full result set, but we may need to handle the iteraction
	// between the iterator and delete in the future.

	// Get all the objects
	iter, err := txn.Get(table, index, args...)
	if err != nil {
		return 0, err
	}

	// Delete all
	var num int
	for {
		obj := iter.Next()
		if obj == nil {
			break
		}
		if err := txn.Delete(table, obj); err != nil {
			return num, err
		}
		num++
	}
	return num, nil
}

// First is used to return the first matching object for
// the given constraints on the index
func (txn *Txn) First(table, index string, args ...interface{}) (interface{}, error) {
	// Get the index value
	indexSchema, val, err := txn.getIndexValue(table, index, args...)
	if err != nil {
		return nil, err
	}

	// Get the index itself
	indexTxn := txn.readableIndex(table, indexSchema.Name)

	// Do an exact lookup
	if indexSchema.Unique && val != nil && indexSchema.Name == index {
		obj, ok := indexTxn.Get(val)
		if !ok {
			return nil, nil
		}
		return obj, nil
	}

	// Handle non-unique index by doing a prefix walk
	// and getting the first value
	// TODO: Optimize this
	var firstVal interface{}
	indexRoot := indexTxn.Root()
	indexRoot.WalkPrefix(val, func(key []byte, val interface{}) bool {
		firstVal = val
		return true
	})
	return firstVal, nil
}

// getIndexValue is used to get the IndexSchema and the value
// used to scan the index given the parameters. This handles prefix based
// scans when the index has the "_prefix" suffix. The index must support
// prefix iteration.
func (txn *Txn) getIndexValue(table, index string, args ...interface{}) (*IndexSchema, []byte, error) {
	// Get the table schema
	tableSchema, ok := txn.db.schema.Tables[table]
	if !ok {
		return nil, nil, fmt.Errorf("invalid table '%s'", table)
	}

	// Check for a prefix scan
	prefixScan := false
	if strings.HasSuffix(index, "_prefix") {
		index = strings.TrimSuffix(index, "_prefix")
		prefixScan = true
	}

	// Get the index schema
	indexSchema, ok := tableSchema.Indexes[index]
	if !ok {
		return nil, nil, fmt.Errorf("invalid index '%s'", index)
	}

	// Hot-path for when there are no arguments
	if len(args) == 0 {
		return indexSchema, nil, nil
	}

	// Special case the prefix scanning
	if prefixScan {
		prefixIndexer, ok := indexSchema.Indexer.(PrefixIndexer)
		if !ok {
			return indexSchema, nil,
				fmt.Errorf("index '%s' does not support prefix scanning", index)
		}

		val, err := prefixIndexer.PrefixFromArgs(args...)
		if err != nil {
			return indexSchema, nil, fmt.Errorf("index error: %v", err)
		}
		return indexSchema, val, err
	}

	// Get the exact match index
	val, err := indexSchema.Indexer.FromArgs(args...)
	if err != nil {
		return indexSchema, nil, fmt.Errorf("index error: %v", err)
	}
	return indexSchema, val, err
}

// ResultIterator is used to iterate over a list of results
// from a Get query on a table.
type ResultIterator interface {
	Next() interface{}
}

// Get is used to construct a ResultIterator over all the
// rows that match the given constraints of an index.
func (txn *Txn) Get(table, index string, args ...interface{}) (ResultIterator, error) {
	// Get the index value to scan
	indexSchema, val, err := txn.getIndexValue(table, index, args...)
	if err != nil {
		return nil, err
	}

	// Get the index itself
	indexTxn := txn.readableIndex(table, indexSchema.Name)
	indexRoot := indexTxn.Root()

	// Collect all the objects by walking the prefix. This should obviously
	// be optimized by using an iterator over the radix tree, but that is
	// a lot more work so its a TODO for now.
	var results []interface{}
	indexRoot.WalkPrefix(val, func(key []byte, val interface{}) bool {
		results = append(results, val)
		return false
	})

	// Create a crappy iterator
	iter := &sliceIterator{
		nextIndex: 0,
		results:   results,
	}
	return iter, nil
}

// Defer is used to push a new arbitrary function onto a stack which gets
// called when a transaction is committed and finished.
func (txn *Txn) Defer(fn func()) {
	txn.after = append(txn.after, fn)
}

// Slice iterator is used to iterate over a slice of results.
// This is not very efficient as it means the results have already
// been materialized under the iterator.
type sliceIterator struct {
	nextIndex int
	results   []interface{}
}

func (s *sliceIterator) Next() interface{} {
	if s.nextIndex >= len(s.results) {
		return nil
	}
	result := s.results[s.nextIndex]
	s.nextIndex++
	return result
}
<commit_msg>Better documentation for deferred functions<commit_after>package memdb

import (
	"fmt"
	"strings"

	"github.com/hashicorp/go-immutable-radix"
)

// tableIndex is a tuple of (Table, Index) used for lookups
type tableIndex struct {
	Table string
	Index string
}

// Txn is a transaction against a MemDB.
// This can be a read or write transaction.
type Txn struct {
	db      *MemDB
	write   bool
	rootTxn *iradix.Txn
	after   []func()

	modified map[tableIndex]*iradix.Txn
}

// readableIndex returns a transaction usable for reading the given
// index in a table. If a write transaction is in progress, we may need
// to use an existing modified txn.
func (txn *Txn) readableIndex(table, index string) *iradix.Txn {
	// Look for existing transaction
	if txn.write && txn.modified != nil {
		key := tableIndex{table, index}
		exist, ok := txn.modified[key]
		if ok {
			return exist
		}
	}

	// Create a read transaction
	path := indexPath(table, index)
	raw, _ := txn.rootTxn.Get(path)
	indexTxn := raw.(*iradix.Tree).Txn()
	return indexTxn
}

// writableIndex returns a transaction usable for modifying the
// given index in a table.
func (txn *Txn) writableIndex(table, index string) *iradix.Txn {
	if txn.modified == nil {
		txn.modified = make(map[tableIndex]*iradix.Txn)
	}

	// Look for existing transaction
	key := tableIndex{table, index}
	exist, ok := txn.modified[key]
	if ok {
		return exist
	}

	// Start a new transaction
	path := indexPath(table, index)
	raw, _ := txn.rootTxn.Get(path)
	indexTxn := raw.(*iradix.Tree).Txn()

	// Keep this open for the duration of the txn
	txn.modified[key] = indexTxn
	return indexTxn
}

// Abort is used to cancel this transaction.
// This is a noop for read transactions.
func (txn *Txn) Abort() {
	// Noop for a read transaction
	if !txn.write {
		return
	}

	// Check if already aborted or committed
	if txn.rootTxn == nil {
		return
	}

	// Clear the txn
	txn.rootTxn = nil
	txn.modified = nil

	// Release the writer lock since this is invalid
	txn.db.writer.Unlock()
}

// Commit is used to finalize this transaction.
// This is a noop for read transactions.
func (txn *Txn) Commit() {
	// Noop for a read transaction
	if !txn.write {
		return
	}

	// Check if already aborted or committed
	if txn.rootTxn == nil {
		return
	}

	// Commit each sub-transaction scoped to (table, index)
	for key, subTxn := range txn.modified {
		path := indexPath(key.Table, key.Index)
		final := subTxn.Commit()
		txn.rootTxn.Insert(path, final)
	}

	// Update the root of the DB
	txn.db.root = txn.rootTxn.Commit()

	// Clear the txn
	txn.rootTxn = nil
	txn.modified = nil

	// Release the writer lock since this is invalid
	txn.db.writer.Unlock()

	// Run the deferred functions, if any
	for i := len(txn.after); i > 0; i-- {
		fn := txn.after[i-1]
		fn()
	}
}

// Insert is used to add or update an object into the given table
func (txn *Txn) Insert(table string, obj interface{}) error {
	if !txn.write {
		return fmt.Errorf("cannot insert in read-only transaction")
	}

	// Get the table schema
	tableSchema, ok := txn.db.schema.Tables[table]
	if !ok {
		return fmt.Errorf("invalid table '%s'", table)
	}

	// Get the primary ID of the object
	idSchema := tableSchema.Indexes["id"]
	ok, idVal, err := idSchema.Indexer.FromObject(obj)
	if err != nil {
		return fmt.Errorf("failed to build primary index: %v", err)
	}
	if !ok {
		return fmt.Errorf("object missing primary index")
	}

	// Lookup the object by ID first, to see if this is an update
	idTxn := txn.writableIndex(table, "id")
	existing, update := idTxn.Get(idVal)

	// On an update, there is an existing object with the given
	// primary ID. We do the update by deleting the current object
	// and inserting the new object.
	for name, indexSchema := range tableSchema.Indexes {
		indexTxn := txn.writableIndex(table, name)

		// Handle the update by deleting from the index first
		if update {
			ok, val, err := indexSchema.Indexer.FromObject(existing)
			if err != nil {
				return fmt.Errorf("failed to build index '%s': %v", name, err)
			}
			if ok {
				// Handle non-unique index by computing a unique index.
				// This is done by appending the primary key which must
				// be unique anyways.
				if !indexSchema.Unique {
					val = append(val, idVal...)
				}
				indexTxn.Delete(val)
			}
		}

		// Handle the insert after the update
		ok, val, err := indexSchema.Indexer.FromObject(obj)
		if err != nil {
			return fmt.Errorf("failed to build index '%s': %v", name, err)
		}
		if !ok {
			if indexSchema.AllowMissing {
				continue
			} else {
				return fmt.Errorf("missing value for index '%s'", name)
			}
		}

		// Handle non-unique index by computing a unique index.
		// This is done by appending the primary key which must
		// be unique anyways.
		if !indexSchema.Unique {
			val = append(val, idVal...)
		}
		indexTxn.Insert(val, obj)
	}
	return nil
}

// Delete is used to delete a single object from the given table
// This object must already exist in the table
func (txn *Txn) Delete(table string, obj interface{}) error {
	if !txn.write {
		return fmt.Errorf("cannot delete in read-only transaction")
	}

	// Get the table schema
	tableSchema, ok := txn.db.schema.Tables[table]
	if !ok {
		return fmt.Errorf("invalid table '%s'", table)
	}

	// Get the primary ID of the object
	idSchema := tableSchema.Indexes["id"]
	ok, idVal, err := idSchema.Indexer.FromObject(obj)
	if err != nil {
		return fmt.Errorf("failed to build primary index: %v", err)
	}
	if !ok {
		return fmt.Errorf("object missing primary index")
	}

	// Lookup the object by ID first, check fi we should continue
	idTxn := txn.writableIndex(table, "id")
	existing, ok := idTxn.Get(idVal)
	if !ok {
		return fmt.Errorf("not found")
	}

	// Remove the object from all the indexes
	for name, indexSchema := range tableSchema.Indexes {
		indexTxn := txn.writableIndex(table, name)

		// Handle the update by deleting from the index first
		ok, val, err := indexSchema.Indexer.FromObject(existing)
		if err != nil {
			return fmt.Errorf("failed to build index '%s': %v", name, err)
		}
		if ok {
			// Handle non-unique index by computing a unique index.
			// This is done by appending the primary key which must
			// be unique anyways.
			if !indexSchema.Unique {
				val = append(val, idVal...)
			}
			indexTxn.Delete(val)
		}
	}
	return nil
}

// DeleteAll is used to delete all the objects in a given table
// matching the constraints on the index
func (txn *Txn) DeleteAll(table, index string, args ...interface{}) (int, error) {
	if !txn.write {
		return 0, fmt.Errorf("cannot delete in read-only transaction")
	}

	// TODO: Currently we use Get to just every object and then
	// iterate and delete them all. This works because sliceIterator
	// has the full result set, but we may need to handle the iteraction
	// between the iterator and delete in the future.

	// Get all the objects
	iter, err := txn.Get(table, index, args...)
	if err != nil {
		return 0, err
	}

	// Delete all
	var num int
	for {
		obj := iter.Next()
		if obj == nil {
			break
		}
		if err := txn.Delete(table, obj); err != nil {
			return num, err
		}
		num++
	}
	return num, nil
}

// First is used to return the first matching object for
// the given constraints on the index
func (txn *Txn) First(table, index string, args ...interface{}) (interface{}, error) {
	// Get the index value
	indexSchema, val, err := txn.getIndexValue(table, index, args...)
	if err != nil {
		return nil, err
	}

	// Get the index itself
	indexTxn := txn.readableIndex(table, indexSchema.Name)

	// Do an exact lookup
	if indexSchema.Unique && val != nil && indexSchema.Name == index {
		obj, ok := indexTxn.Get(val)
		if !ok {
			return nil, nil
		}
		return obj, nil
	}

	// Handle non-unique index by doing a prefix walk
	// and getting the first value
	// TODO: Optimize this
	var firstVal interface{}
	indexRoot := indexTxn.Root()
	indexRoot.WalkPrefix(val, func(key []byte, val interface{}) bool {
		firstVal = val
		return true
	})
	return firstVal, nil
}

// getIndexValue is used to get the IndexSchema and the value
// used to scan the index given the parameters. This handles prefix based
// scans when the index has the "_prefix" suffix. The index must support
// prefix iteration.
func (txn *Txn) getIndexValue(table, index string, args ...interface{}) (*IndexSchema, []byte, error) {
	// Get the table schema
	tableSchema, ok := txn.db.schema.Tables[table]
	if !ok {
		return nil, nil, fmt.Errorf("invalid table '%s'", table)
	}

	// Check for a prefix scan
	prefixScan := false
	if strings.HasSuffix(index, "_prefix") {
		index = strings.TrimSuffix(index, "_prefix")
		prefixScan = true
	}

	// Get the index schema
	indexSchema, ok := tableSchema.Indexes[index]
	if !ok {
		return nil, nil, fmt.Errorf("invalid index '%s'", index)
	}

	// Hot-path for when there are no arguments
	if len(args) == 0 {
		return indexSchema, nil, nil
	}

	// Special case the prefix scanning
	if prefixScan {
		prefixIndexer, ok := indexSchema.Indexer.(PrefixIndexer)
		if !ok {
			return indexSchema, nil,
				fmt.Errorf("index '%s' does not support prefix scanning", index)
		}

		val, err := prefixIndexer.PrefixFromArgs(args...)
		if err != nil {
			return indexSchema, nil, fmt.Errorf("index error: %v", err)
		}
		return indexSchema, val, err
	}

	// Get the exact match index
	val, err := indexSchema.Indexer.FromArgs(args...)
	if err != nil {
		return indexSchema, nil, fmt.Errorf("index error: %v", err)
	}
	return indexSchema, val, err
}

// ResultIterator is used to iterate over a list of results
// from a Get query on a table.
type ResultIterator interface {
	Next() interface{}
}

// Get is used to construct a ResultIterator over all the
// rows that match the given constraints of an index.
func (txn *Txn) Get(table, index string, args ...interface{}) (ResultIterator, error) {
	// Get the index value to scan
	indexSchema, val, err := txn.getIndexValue(table, index, args...)
	if err != nil {
		return nil, err
	}

	// Get the index itself
	indexTxn := txn.readableIndex(table, indexSchema.Name)
	indexRoot := indexTxn.Root()

	// Collect all the objects by walking the prefix. This should obviously
	// be optimized by using an iterator over the radix tree, but that is
	// a lot more work so its a TODO for now.
	var results []interface{}
	indexRoot.WalkPrefix(val, func(key []byte, val interface{}) bool {
		results = append(results, val)
		return false
	})

	// Create a crappy iterator
	iter := &sliceIterator{
		nextIndex: 0,
		results:   results,
	}
	return iter, nil
}

// Defer is used to push a new arbitrary function onto a stack which
// gets called when a transaction is committed and finished. Deferred
// functions are called in LIFO order, and only invoked at the end of
// write transactions.
func (txn *Txn) Defer(fn func()) {
	txn.after = append(txn.after, fn)
}

// Slice iterator is used to iterate over a slice of results.
// This is not very efficient as it means the results have already
// been materialized under the iterator.
type sliceIterator struct {
	nextIndex int
	results   []interface{}
}

func (s *sliceIterator) Next() interface{} {
	if s.nextIndex >= len(s.results) {
		return nil
	}
	result := s.results[s.nextIndex]
	s.nextIndex++
	return result
}
<|endoftext|>


<commit_before>// SPDX-License-Identifier: MIT

// Package web 一个微型的 web 框架
package web

import (
	"github.com/issue9/localeutil"

	"github.com/issue9/web/server"
)

// Version 当前框架的版本
const Version = "0.50.0"

type (
	Server         = server.Server
	Context        = server.Context
	Options        = server.Options
	MiddlewareFunc = server.MiddlewareFunc
	Middleware     = server.Middleware
	HandlerFunc    = server.HandlerFunc
	Router         = server.Router
	ResultFields   = server.ResultFields
	Result         = server.Result
	Module         = server.Module
	RouterOptions  = server.RouterOptions

	// LocaleStringer 本地化字符串需要实在的接口
	//
	// 部分 error 返回可能也实现了该接口。
	LocaleStringer = localeutil.LocaleStringer
)

// NewServer 从 Options 初始化 Server 对象
func NewServer(name, version string, o *Options) (*Server, error) {
	return server.New(name, version, o)
}

// Phrase 生成本地化的语言片段
func Phrase(key string, v ...any) LocaleStringer { return localeutil.Phrase(key, v...) }
<commit_msg>feat: 添加对 server.Responserr 的引用<commit_after>// SPDX-License-Identifier: MIT

// Package web 一个微型的 web 框架
package web

import (
	"github.com/issue9/localeutil"

	"github.com/issue9/web/server"
)

// Version 当前框架的版本
const Version = "0.50.0"

type (
	Server         = server.Server
	Context        = server.Context
	Options        = server.Options
	MiddlewareFunc = server.MiddlewareFunc
	Middleware     = server.Middleware
	HandlerFunc    = server.HandlerFunc
	Router         = server.Router
	ResultFields   = server.ResultFields
	Result         = server.Result
	Module         = server.Module
	RouterOptions  = server.RouterOptions
	Responser      = server.Responser

	// LocaleStringer 本地化字符串需要实在的接口
	//
	// 部分 error 返回可能也实现了该接口。
	LocaleStringer = localeutil.LocaleStringer
)

// NewServer 从 Options 初始化 Server 对象
func NewServer(name, version string, o *Options) (*Server, error) {
	return server.New(name, version, o)
}

// Phrase 生成本地化的语言片段
func Phrase(key string, v ...any) LocaleStringer { return localeutil.Phrase(key, v...) }
<|endoftext|>


<commit_before>package web

import (
    "bytes"
    "container/vector"
    "crypto/hmac"
    "encoding/base64"
    "fmt"
    "http"
    "io/ioutil"
    "log"
    "os"
    "path"
    "reflect"
    "regexp"
    "strconv"
    "strings"
    "time"
)

//secret key used to store cookies
var secret = ""

type conn interface {
    StartResponse(status int)
    SetHeader(hdr string, val string, unique bool)
    Write(data []byte) (n int, err os.Error)
    Close()
}

type Context struct {
    *Request
    *conn
    responseStarted bool
}

func (ctx *Context) StartResponse(status int) {
    ctx.conn.StartResponse(status)
    ctx.responseStarted = true
}

func (ctx *Context) Write(data []byte) (n int, err os.Error) {
    if !ctx.responseStarted {
        ctx.StartResponse(200)
    }

    //if it's a HEAD request, we just write blank data
    if ctx.Request.Method == "HEAD" {
        data = []byte{}
    }

    return ctx.conn.Write(data)
}
func (ctx *Context) WriteString(content string) {
    ctx.Write([]byte(content))
}

func (ctx *Context) Abort(status int, body string) {
    ctx.StartResponse(status)
    ctx.WriteString(body)
}

func (ctx *Context) Redirect(status int, url string) {
    ctx.SetHeader("Location", url, true)
    ctx.StartResponse(status)
    ctx.WriteString("Redirecting to: " + url)
}

func (ctx *Context) NotFound(message string) {
    ctx.StartResponse(404)
    ctx.WriteString(message)
}

//Sets a cookie -- duration is the amount of time in seconds. 0 = forever
func (ctx *Context) SetCookie(name string, value string, age int64) {
    if age == 0 {
        //do some really long time
    }

    utctime := time.UTC()
    utc1 := time.SecondsToUTC(utctime.Seconds() + 60*30)
    cookie := fmt.Sprintf("%s=%s; expires=%s", name, value, webTime(utc1))
    ctx.SetHeader("Set-Cookie", cookie, false)
}

func SetCookieSecret(key string) { secret = key }

func getCookieSig(val []byte, timestamp string) string {
    hm := hmac.NewSHA1([]byte(secret))

    hm.Write(val)
    hm.Write([]byte(timestamp))

    hex := fmt.Sprintf("%02x", hm.Sum())
    return hex
}

func (ctx *Context) SetSecureCookie(name string, val string, age int64) {
    //base64 encode the val
    if len(secret) == 0 {
        log.Stderrf("Secret Key for secure cookies has not been set. Please call web.SetCookieSecret\n")
        return
    }
    var buf bytes.Buffer
    encoder := base64.NewEncoder(base64.StdEncoding, &buf)
    encoder.Write([]byte(val))
    encoder.Close()
    vs := buf.String()
    vb := buf.Bytes()

    timestamp := strconv.Itoa64(time.Seconds())

    sig := getCookieSig(vb, timestamp)

    cookie := strings.Join([]string{vs, timestamp, sig}, "|")

    ctx.SetCookie(name, cookie, age)
}

func (ctx *Context) GetSecureCookie(name string) (string, bool) {

    cookie, ok := ctx.Request.Cookies[name]

    if !ok {
        return "", false
    }

    parts := strings.Split(cookie, "|", 3)

    val := parts[0]
    timestamp := parts[1]
    sig := parts[2]

    if getCookieSig([]byte(val), timestamp) != sig {
        return "", false
    }

    ts, _ := strconv.Atoi64(timestamp)

    if time.Seconds()-31*86400 > ts {
        return "", false
    }

    buf := bytes.NewBufferString(val)
    encoder := base64.NewDecoder(base64.StdEncoding, buf)

    res, _ := ioutil.ReadAll(encoder)
    return string(res), true
}

var contextType reflect.Type
var staticDir string

func init() {
    contextType = reflect.Typeof(Context{})
    //find the location of the exe file
    arg0 := path.Clean(os.Args[0])
    wd, _ := os.Getwd()
    var exeFile string
    if strings.HasPrefix(arg0, "/") {
        exeFile = arg0
    } else {
        //TODO for robustness, search each directory in $PATH
        exeFile = path.Join(wd, arg0)
    }
    root, _ := path.Split(exeFile)
    staticDir = path.Join(root, "static")
}

type route struct {
    r       string
    cr      *regexp.Regexp
    method  string
    handler *reflect.FuncValue
}

var routes vector.Vector

func addRoute(r string, method string, handler interface{}) {
    cr, err := regexp.Compile(r)
    if err != nil {
        log.Stderrf("Error in route regex %q\n", r)
        return
    }
    fv := reflect.NewValue(handler).(*reflect.FuncValue)
    routes.Push(route{r, cr, method, fv})
}

type httpConn struct {
    conn *http.Conn
}

func (c *httpConn) StartResponse(status int) { c.conn.WriteHeader(status) }

func (c *httpConn) SetHeader(hdr string, val string, unique bool) {
    //right now unique can't be implemented through the http package.
    //see issue 488
    c.conn.SetHeader(hdr, val)
}

func (c *httpConn) WriteString(content string) {
    buf := bytes.NewBufferString(content)
    c.conn.Write(buf.Bytes())
}

func (c *httpConn) Write(content []byte) (n int, err os.Error) {
    return c.conn.Write(content)
}

func (c *httpConn) Close() {
    rwc, buf, _ := c.conn.Hijack()
    if buf != nil {
        buf.Flush()
    }

    if rwc != nil {
        rwc.Close()
    }
}

func httpHandler(c *http.Conn, req *http.Request) {
    conn := httpConn{c}
    wreq := newRequest(req)
    routeHandler(wreq, &conn)
}

func routeHandler(req *Request, c conn) {
    requestPath := req.URL.Path

    //log the request
    if len(req.URL.RawQuery) == 0 {
        log.Stdout(req.Method + " " + requestPath)
    } else {
        log.Stdout(requestPath + "?" + req.URL.RawQuery)
    }

    //parse the form data (if it exists)
    perr := req.parseParams()
    if perr != nil {
        log.Stderrf("Failed to parse form data %q", perr.String())
    }

    //parse the cookies
    perr = req.parseCookies()
    if perr != nil {
        log.Stderrf("Failed to parse cookies %q", perr.String())
    }

    ctx := Context{req, &c, false}

    //set some default headers
    ctx.SetHeader("Content-Type", "text/html; charset=utf-8", true)
    ctx.SetHeader("Server", "web.go", true)

    tm := time.LocalTime()
    ctx.SetHeader("Date", webTime(tm), true)

    //try to serve a static file
    staticFile := path.Join(staticDir, requestPath)
    if fileExists(staticFile) && (req.Method == "GET" || req.Method == "HEAD") {
        serveFile(&ctx, staticFile)
        return
    }

    for i := 0; i < routes.Len(); i++ {
        route := routes.At(i).(route)
        cr := route.cr
        //if the methods don't match, skip this handler (except HEAD can be used in place of GET)
        if req.Method != route.method && !(req.Method == "HEAD" && route.method == "GET") {
            continue
        }

        if !cr.MatchString(requestPath) {
            continue
        }
        match := cr.MatchStrings(requestPath)

        if len(match[0]) != len(requestPath) {
            continue
        }

        var args vector.Vector

        handlerType := route.handler.Type().(*reflect.FuncType)

        //check if the first arg in the handler is a context type
        if handlerType.NumIn() > 0 {
            if a0, ok := handlerType.In(0).(*reflect.PtrType); ok {
                typ := a0.Elem()
                if typ == contextType {
                    args.Push(reflect.NewValue(&ctx))
                }
            }
        }

        for _, arg := range match[1:] {
            args.Push(reflect.NewValue(arg))
        }

        if args.Len() != handlerType.NumIn() {
            log.Stderrf("Incorrect number of arguments for %s\n", requestPath)
            ctx.Abort(500, "Server Error")
            return
        }

        valArgs := make([]reflect.Value, args.Len())
        for i := 0; i < args.Len(); i++ {
            valArgs[i] = args.At(i).(reflect.Value)
        }

        ret := route.handler.Call(valArgs)

        if len(ret) == 0 {
            return
        }

        sval, ok := ret[0].(*reflect.StringValue)

        if ok && !ctx.responseStarted {
            content := []byte(sval.Get())
            ctx.SetHeader("Content-Length", strconv.Itoa(len(content)), true)
            ctx.StartResponse(200)
            ctx.Write(content)
        }

        return
    }

    //try to serve index.html
    if indexPath := path.Join(staticDir, "index.html"); requestPath == "/" && fileExists(indexPath) {
        serveFile(&ctx, indexPath)
        return
    }

    ctx.Abort(404, "Page not found")
}

//runs the web application and serves http requests
func Run(addr string) {
    http.Handle("/", http.HandlerFunc(httpHandler))

    log.Stdoutf("web.go serving %s", addr)
    err := http.ListenAndServe(addr, nil)
    if err != nil {
        log.Exit("ListenAndServe:", err)
    }
}

//runs the web application and serves scgi requests
func RunScgi(addr string) {
    log.Stdoutf("web.go serving scgi %s", addr)
    listenAndServeScgi(addr)
}

//runs the web application by serving fastcgi requests
func RunFcgi(addr string) {
    log.Stdoutf("web.go serving fcgi %s", addr)
    listenAndServeFcgi(addr)
}

//Adds a handler for the 'GET' http method.
func Get(route string, handler interface{}) { addRoute(route, "GET", handler) }

//Adds a handler for the 'POST' http method.
func Post(route string, handler interface{}) { addRoute(route, "POST", handler) }

//Adds a handler for the 'PUT' http method.
func Put(route string, handler interface{}) { addRoute(route, "PUT", handler) }

//Adds a handler for the 'DELETE' http method.
func Delete(route string, handler interface{}) {
    addRoute(route, "DELETE", handler)
}

func webTime(t *time.Time) string {
    ftime := t.Format(time.RFC1123)
    if strings.HasSuffix(ftime, "UTC") {
        ftime = ftime[0:len(ftime)-3] + "GMT"
    }
    return ftime
}

func dirExists(dir string) bool {
    d, e := os.Stat(dir)
    switch {
    case e != nil:
        return false
    case !d.IsDirectory():
        return false
    }

    return true
}

func fileExists(dir string) bool {
    info, err := os.Stat(dir)
    if err != nil {
        return false
    } else if !info.IsRegular() {
        return false
    }

    return true
}

//changes the location of the static directory. by default, it's under the 'static' folder
//of the directory containing the web application
func SetStaticDir(dir string) os.Error {
    if !dirExists(dir) {
        msg := fmt.Sprintf("Failed to set static directory %q - does not exist", dir)
        return os.NewError(msg)
    }
    staticDir = dir

    return nil
}

func Urlencode(data map[string]string) string {
    var buf bytes.Buffer
    for k, v := range data {
        buf.WriteString(http.URLEscape(k))
        buf.WriteByte('=')
        buf.WriteString(http.URLEscape(v))
        buf.WriteByte('&')
    }
    s := buf.String()
    return s[0 : len(s)-1]
}
<commit_msg>Look for index.html, index.htm on requests for a subdirectory of the static directory<commit_after>package web

import (
    "bytes"
    "container/vector"
    "crypto/hmac"
    "encoding/base64"
    "fmt"
    "http"
    "io/ioutil"
    "log"
    "os"
    "path"
    "reflect"
    "regexp"
    "strconv"
    "strings"
    "time"
)

//secret key used to store cookies
var secret = ""

type conn interface {
    StartResponse(status int)
    SetHeader(hdr string, val string, unique bool)
    Write(data []byte) (n int, err os.Error)
    Close()
}

type Context struct {
    *Request
    *conn
    responseStarted bool
}

func (ctx *Context) StartResponse(status int) {
    ctx.conn.StartResponse(status)
    ctx.responseStarted = true
}

func (ctx *Context) Write(data []byte) (n int, err os.Error) {
    if !ctx.responseStarted {
        ctx.StartResponse(200)
    }

    //if it's a HEAD request, we just write blank data
    if ctx.Request.Method == "HEAD" {
        data = []byte{}
    }

    return ctx.conn.Write(data)
}
func (ctx *Context) WriteString(content string) {
    ctx.Write([]byte(content))
}

func (ctx *Context) Abort(status int, body string) {
    ctx.StartResponse(status)
    ctx.WriteString(body)
}

func (ctx *Context) Redirect(status int, url string) {
    ctx.SetHeader("Location", url, true)
    ctx.StartResponse(status)
    ctx.WriteString("Redirecting to: " + url)
}

func (ctx *Context) NotFound(message string) {
    ctx.StartResponse(404)
    ctx.WriteString(message)
}

//Sets a cookie -- duration is the amount of time in seconds. 0 = forever
func (ctx *Context) SetCookie(name string, value string, age int64) {
    if age == 0 {
        //do some really long time
    }

    utctime := time.UTC()
    utc1 := time.SecondsToUTC(utctime.Seconds() + 60*30)
    cookie := fmt.Sprintf("%s=%s; expires=%s", name, value, webTime(utc1))
    ctx.SetHeader("Set-Cookie", cookie, false)
}

func SetCookieSecret(key string) { secret = key }

func getCookieSig(val []byte, timestamp string) string {
    hm := hmac.NewSHA1([]byte(secret))

    hm.Write(val)
    hm.Write([]byte(timestamp))

    hex := fmt.Sprintf("%02x", hm.Sum())
    return hex
}

func (ctx *Context) SetSecureCookie(name string, val string, age int64) {
    //base64 encode the val
    if len(secret) == 0 {
        log.Stderrf("Secret Key for secure cookies has not been set. Please call web.SetCookieSecret\n")
        return
    }
    var buf bytes.Buffer
    encoder := base64.NewEncoder(base64.StdEncoding, &buf)
    encoder.Write([]byte(val))
    encoder.Close()
    vs := buf.String()
    vb := buf.Bytes()

    timestamp := strconv.Itoa64(time.Seconds())

    sig := getCookieSig(vb, timestamp)

    cookie := strings.Join([]string{vs, timestamp, sig}, "|")

    ctx.SetCookie(name, cookie, age)
}

func (ctx *Context) GetSecureCookie(name string) (string, bool) {

    cookie, ok := ctx.Request.Cookies[name]

    if !ok {
        return "", false
    }

    parts := strings.Split(cookie, "|", 3)

    val := parts[0]
    timestamp := parts[1]
    sig := parts[2]

    if getCookieSig([]byte(val), timestamp) != sig {
        return "", false
    }

    ts, _ := strconv.Atoi64(timestamp)

    if time.Seconds()-31*86400 > ts {
        return "", false
    }

    buf := bytes.NewBufferString(val)
    encoder := base64.NewDecoder(base64.StdEncoding, buf)

    res, _ := ioutil.ReadAll(encoder)
    return string(res), true
}

var contextType reflect.Type
var staticDir string

func init() {
    contextType = reflect.Typeof(Context{})
    //find the location of the exe file
    arg0 := path.Clean(os.Args[0])
    wd, _ := os.Getwd()
    var exeFile string
    if strings.HasPrefix(arg0, "/") {
        exeFile = arg0
    } else {
        //TODO for robustness, search each directory in $PATH
        exeFile = path.Join(wd, arg0)
    }
    root, _ := path.Split(exeFile)
    staticDir = path.Join(root, "static")
}

type route struct {
    r       string
    cr      *regexp.Regexp
    method  string
    handler *reflect.FuncValue
}

var routes vector.Vector

func addRoute(r string, method string, handler interface{}) {
    cr, err := regexp.Compile(r)
    if err != nil {
        log.Stderrf("Error in route regex %q\n", r)
        return
    }
    fv := reflect.NewValue(handler).(*reflect.FuncValue)
    routes.Push(route{r, cr, method, fv})
}

type httpConn struct {
    conn *http.Conn
}

func (c *httpConn) StartResponse(status int) { c.conn.WriteHeader(status) }

func (c *httpConn) SetHeader(hdr string, val string, unique bool) {
    //right now unique can't be implemented through the http package.
    //see issue 488
    c.conn.SetHeader(hdr, val)
}

func (c *httpConn) WriteString(content string) {
    buf := bytes.NewBufferString(content)
    c.conn.Write(buf.Bytes())
}

func (c *httpConn) Write(content []byte) (n int, err os.Error) {
    return c.conn.Write(content)
}

func (c *httpConn) Close() {
    rwc, buf, _ := c.conn.Hijack()
    if buf != nil {
        buf.Flush()
    }

    if rwc != nil {
        rwc.Close()
    }
}

func httpHandler(c *http.Conn, req *http.Request) {
    conn := httpConn{c}
    wreq := newRequest(req)
    routeHandler(wreq, &conn)
}

func routeHandler(req *Request, c conn) {
    requestPath := req.URL.Path

    //log the request
    if len(req.URL.RawQuery) == 0 {
        log.Stdout(req.Method + " " + requestPath)
    } else {
        log.Stdout(requestPath + "?" + req.URL.RawQuery)
    }

    //parse the form data (if it exists)
    perr := req.parseParams()
    if perr != nil {
        log.Stderrf("Failed to parse form data %q", perr.String())
    }

    //parse the cookies
    perr = req.parseCookies()
    if perr != nil {
        log.Stderrf("Failed to parse cookies %q", perr.String())
    }

    ctx := Context{req, &c, false}

    //set some default headers
    ctx.SetHeader("Content-Type", "text/html; charset=utf-8", true)
    ctx.SetHeader("Server", "web.go", true)

    tm := time.LocalTime()
    ctx.SetHeader("Date", webTime(tm), true)

    //try to serve a static file
    staticFile := path.Join(staticDir, requestPath)
    if fileExists(staticFile) && (req.Method == "GET" || req.Method == "HEAD") {
        serveFile(&ctx, staticFile)
        return
    }

    for i := 0; i < routes.Len(); i++ {
        route := routes.At(i).(route)
        cr := route.cr
        //if the methods don't match, skip this handler (except HEAD can be used in place of GET)
        if req.Method != route.method && !(req.Method == "HEAD" && route.method == "GET") {
            continue
        }

        if !cr.MatchString(requestPath) {
            continue
        }
        match := cr.MatchStrings(requestPath)

        if len(match[0]) != len(requestPath) {
            continue
        }

        var args vector.Vector

        handlerType := route.handler.Type().(*reflect.FuncType)

        //check if the first arg in the handler is a context type
        if handlerType.NumIn() > 0 {
            if a0, ok := handlerType.In(0).(*reflect.PtrType); ok {
                typ := a0.Elem()
                if typ == contextType {
                    args.Push(reflect.NewValue(&ctx))
                }
            }
        }

        for _, arg := range match[1:] {
            args.Push(reflect.NewValue(arg))
        }

        if args.Len() != handlerType.NumIn() {
            log.Stderrf("Incorrect number of arguments for %s\n", requestPath)
            ctx.Abort(500, "Server Error")
            return
        }

        valArgs := make([]reflect.Value, args.Len())
        for i := 0; i < args.Len(); i++ {
            valArgs[i] = args.At(i).(reflect.Value)
        }

        ret := route.handler.Call(valArgs)

        if len(ret) == 0 {
            return
        }

        sval, ok := ret[0].(*reflect.StringValue)

        if ok && !ctx.responseStarted {
            content := []byte(sval.Get())
            ctx.SetHeader("Content-Length", strconv.Itoa(len(content)), true)
            ctx.StartResponse(200)
            ctx.Write(content)
        }

        return
    }

    //try to serve index.html || index.htm
    if indexPath := path.Join(path.Join(staticDir, requestPath), "index.html"); fileExists(indexPath) {
        serveFile(&ctx, indexPath)
        return
    }

    if indexPath := path.Join(path.Join(staticDir, requestPath), "index.htm"); fileExists(indexPath) {
        serveFile(&ctx, indexPath)
        return
    }

    ctx.Abort(404, "Page not found")
}

//runs the web application and serves http requests
func Run(addr string) {
    http.Handle("/", http.HandlerFunc(httpHandler))

    log.Stdoutf("web.go serving %s", addr)
    err := http.ListenAndServe(addr, nil)
    if err != nil {
        log.Exit("ListenAndServe:", err)
    }
}

//runs the web application and serves scgi requests
func RunScgi(addr string) {
    log.Stdoutf("web.go serving scgi %s", addr)
    listenAndServeScgi(addr)
}

//runs the web application by serving fastcgi requests
func RunFcgi(addr string) {
    log.Stdoutf("web.go serving fcgi %s", addr)
    listenAndServeFcgi(addr)
}

//Adds a handler for the 'GET' http method.
func Get(route string, handler interface{}) { addRoute(route, "GET", handler) }

//Adds a handler for the 'POST' http method.
func Post(route string, handler interface{}) { addRoute(route, "POST", handler) }

//Adds a handler for the 'PUT' http method.
func Put(route string, handler interface{}) { addRoute(route, "PUT", handler) }

//Adds a handler for the 'DELETE' http method.
func Delete(route string, handler interface{}) {
    addRoute(route, "DELETE", handler)
}

func webTime(t *time.Time) string {
    ftime := t.Format(time.RFC1123)
    if strings.HasSuffix(ftime, "UTC") {
        ftime = ftime[0:len(ftime)-3] + "GMT"
    }
    return ftime
}

func dirExists(dir string) bool {
    d, e := os.Stat(dir)
    switch {
    case e != nil:
        return false
    case !d.IsDirectory():
        return false
    }

    return true
}

func fileExists(dir string) bool {
    info, err := os.Stat(dir)
    if err != nil {
        return false
    } else if !info.IsRegular() {
        return false
    }

    return true
}

//changes the location of the static directory. by default, it's under the 'static' folder
//of the directory containing the web application
func SetStaticDir(dir string) os.Error {
    if !dirExists(dir) {
        msg := fmt.Sprintf("Failed to set static directory %q - does not exist", dir)
        return os.NewError(msg)
    }
    staticDir = dir

    return nil
}

func Urlencode(data map[string]string) string {
    var buf bytes.Buffer
    for k, v := range data {
        buf.WriteString(http.URLEscape(k))
        buf.WriteByte('=')
        buf.WriteString(http.URLEscape(v))
        buf.WriteByte('&')
    }
    s := buf.String()
    return s[0 : len(s)-1]
}
<|endoftext|>


<commit_before>package gopaste

import (
	"bytes"
	"fmt"
	"github.com/aryann/difflib"
	"html/template"
	"log"
	"net/http"
	"net/url"
	"path/filepath"
	"sort"
	"strconv"
	"strings"
)

var tmpl *template.Template

func trunc(s string, max int) string {
	if len(s) < max {
		return s
	}

	last := max - 3
	return s[:last] + "..."
}

func init() {
	tmpl = template.New("web")
	tmpl.Funcs(template.FuncMap{
		"trunc": trunc,
	})
	if _, err := tmpl.ParseGlob("*.template"); err != nil {
		log.Fatalf("template parsing: %v\n", err)
	}

}

////////////////////////////////////////////////////////////////////////////////

type Query struct {
	Request  *http.Request
	Response http.ResponseWriter
	Action   string
	Args     []string
}

func NewQuery(w http.ResponseWriter, req *http.Request) *Query {
	data := &Query{
		Request:  req,
		Response: w,
	}

	path := req.URL.Path
	parts := strings.Split(strings.Trim(path, "/"), "/")

	data.Action = parts[0]
	if len(parts) > 1 {
		data.Args = parts[1:]
	}

	return data
}

func (s *Server) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	log.Printf("[web] %s %s %s", req.RemoteAddr, req.Method, req.URL.Path)

	q := NewQuery(w, req)
	if err := s.handle(q); err != nil {
		if e, ok := err.(HttpError); ok {
			http.Error(w, e.Error(), e.Code)
		} else {
			http.Error(w, err.Error(), http.StatusInternalServerError)
		}
	}
}

type ActionFunc func(*Server, *Query) error

var handlers = map[string]ActionFunc{
	"":         (*Server).doMain,
	"annotate": (*Server).doAnnotate,
	"browse":   (*Server).doBrowse,
	"diff":     (*Server).doDiff,
	"new":      (*Server).doNew,
	"raw":      (*Server).doRaw,
	"static":   (*Server).doStatic,
	"view":     (*Server).doView,
}

func (s *Server) handle(d *Query) error {
	handler := handlers[d.Action]
	if handler == nil {
		return HttpError{fmt.Sprintf("'%s' not found", d.Request.URL), http.StatusNotFound}
	}

	return handler(s, d)
}

////////////////////////////////////////////////////////////////////////////////

type AnyMap map[string]interface{}

type HttpError struct {
	Message string
	Code    int
}

func (e HttpError) Error() string {
	return fmt.Sprintf("ERROR %d: %s", e.Code, e.Message)
}

func parsePasteId(str string) (int64, error) {
	id, err := strconv.ParseInt(str, 10, 64)
	if err != nil {
		return InvalidPasteId, fmt.Errorf("invalid paste id '%s'", str)
	}
	return id, nil
}

// runTemplate executes a template and writes the results as HTML if successful
func runTemplate(w http.ResponseWriter, name string, data interface{}) error {
	buf := new(bytes.Buffer)
	err := tmpl.ExecuteTemplate(buf, name, data)
	if err != nil {
		return HttpError{fmt.Sprintf("error processing template %s: %v", name, err), http.StatusInternalServerError}
	}

	w.Header().Set("Content-Type", "text/html; charset=utf-8")
	buf.WriteTo(w)

	return nil
}

////////////////////////////////////////////////////////////////////////////////

func (s *Server) doMain(q *Query) error {
	opts := NewBrowseOpts()
	opts.PageSize = 10

	page, err := TopLevelPastes(s.Database, opts)
	if err != nil {
		return HttpError{err.Error(), http.StatusInternalServerError}
	}

	return runTemplate(q.Response, "main", AnyMap{
		"MainPage":  true,
		"Title":     "Home",
		"Page":      page,
		"Languages": LanguageNames,
		"Channels":  []string{}, // TODO
	})
}

////////////////////////////////////////////////////////////////////////////////

type BrowseOpts struct {
	Page     int
	PageSize int
	Search   map[string]string
}

func NewBrowseOpts() *BrowseOpts {
	return &BrowseOpts{
		Page:     1,
		PageSize: 50,
		Search:   make(map[string]string),
	}
}

func (o *BrowseOpts) Parse(args []string) error {
	for i := 0; i+1 < len(args); i += 2 {
		key, err := url.QueryUnescape(args[i])
		if err != nil {
			return err
		}
		val, err := url.QueryUnescape(args[i+1])
		if err != nil {
			return err
		}
		switch key {
		case "page":
			pagenum, err := strconv.ParseInt(val, 10, 64)
			if err != nil {
				return fmt.Errorf("invalid page number: %s", val)
			}
			o.Page = int(pagenum)
		default:
			o.Search[key] = val
		}
	}

	return nil
}

func (o *BrowseOpts) String() string {
	var parts, keys []string

	for key := range o.Search {
		keys = append(keys, key)
	}
	sort.Strings(keys)

	for _, key := range keys {
		parts = append(parts,
			url.QueryEscape(key),
			url.QueryEscape(o.Search[key]),
		)
	}

	if o.Page > 1 {
		parts = append(parts, "page", fmt.Sprint(o.Page))
	}

	return strings.Join(parts, "/")
}

func (o *BrowseOpts) NewPage(page int) *BrowseOpts {
	newOpts := NewBrowseOpts()
	newOpts.Page = page
	newOpts.PageSize = o.PageSize
	for k, v := range o.Search {
		newOpts.Search[k] = v
	}
	return newOpts
}

func (o *BrowseOpts) Prev() *BrowseOpts {
	return o.NewPage(o.Page - 1)
}

func (o *BrowseOpts) Next() *BrowseOpts {
	return o.NewPage(o.Page + 1)
}

func (o *BrowseOpts) Nearby(window int, max int) (pages []int) {
	min := 1
	var low, high int

	width := 2*window + 1
	if width >= max {
		low = min
		high = max
	} else {
		low = o.Page - window
		lowExtra := 0
		if low < min {
			lowExtra = min - low
			low = min
		}

		high = o.Page + window + lowExtra
		if high > max {
			low -= high - max
			high = max
		}
	}

	for n := low; n <= high; n++ {
		pages = append(pages, n)
	}

	return
}

func (s *Server) doBrowse(q *Query) error {
	opts := NewBrowseOpts()
	err := opts.Parse(q.Args)
	if err != nil {
		return HttpError{err.Error(), http.StatusBadRequest}
	}

	page, err := TopLevelPastes(s.Database, opts)
	if err != nil {
		return HttpError{err.Error(), http.StatusInternalServerError}
	}

	return runTemplate(q.Response, "browse", AnyMap{
		"Title": "Browse pastes",
		"Page":  page,
		"Opts":  opts,
	})
}

////////////////////////////////////////////////////////////////////////////////

// doDiff displays the difference between two pastes.
func (s *Server) doDiff(q *Query) error {
	if len(q.Args) < 2 {
		return HttpError{"invalid request", http.StatusBadRequest}
	}

	fromStr, toStr := q.Args[0], q.Args[1]

	fromId, err := parsePasteId(fromStr)
	if err != nil {
		return HttpError{err.Error(), http.StatusBadRequest}
	}

	toId, err := parsePasteId(toStr)
	if err != nil {
		return HttpError{err.Error(), http.StatusBadRequest}
	}

	from, err := GetPaste(s.Database, fromId)
	if err != nil {
		return HttpError{err.Error(), http.StatusInternalServerError}
	}
	if from == nil {
		return HttpError{fmt.Sprintf("paste %d not found", fromId), http.StatusNotFound}
	}

	to, err := GetPaste(s.Database, toId)
	if err != nil {
		return HttpError{err.Error(), http.StatusInternalServerError}
	}
	if to == nil {
		return HttpError{fmt.Sprintf("paste %d not found", toId), http.StatusNotFound}
	}

	diff := difflib.Diff(
		strings.Split(from.Content, "\n"),
		strings.Split(to.Content, "\n"),
	)

	diffText := ""
	for _, line := range diff {
		var prefix string
		switch line.Delta {
		case difflib.LeftOnly:
			prefix = "-"
		case difflib.RightOnly:
			prefix = "+"
		default:
			prefix = " "
		}
		diffText += prefix + line.Payload + "\n"
	}

	return runTemplate(q.Response, "diff", AnyMap{
		"Title":    fmt.Sprintf("Diff #%d / #%d", from.Id, to.Id),
		"From":     from,
		"To":       to,
		"DiffText": diffText,
	})
}

////////////////////////////////////////////////////////////////////////////////

// doNew adds a new top-level paste.
func (s *Server) doNew(q *Query) error {
	return s.handleNew(q, nil)
}

// doAnnotation adds a new annotation to an existing paste.
func (s *Server) doAnnotate(q *Query) error {
	if len(q.Args) < 1 {
		return HttpError{"invalid request", http.StatusBadRequest}
	}

	idStr := q.Args[0]
	id, err := parsePasteId(idStr)
	if err != nil {
		return HttpError{err.Error(), http.StatusBadRequest}
	}

	paste, err := GetPaste(s.Database, id)
	if err != nil {
		return HttpError{err.Error(), http.StatusInternalServerError}
	}
	if paste == nil {
		return HttpError{fmt.Sprintf("paste %d not found", id), http.StatusNotFound}
	}

	return s.handleNew(q, paste)
}

func (s *Server) handleNew(q *Query, parent *Paste) error {
	method := q.Request.Method
	switch method {
	case "GET", "HEAD":
		return s.displayNewPage(q, parent)
	case "POST":
		return s.insertNewPaste(q, parent)
	default:
		return HttpError{fmt.Sprintf("unsupported request method: %s", method), http.StatusNotImplemented}
	}
}

func (s *Server) displayNewPage(q *Query, parent *Paste) error {
	var title string
	if parent != nil {
		title = fmt.Sprintf("Annotating #%d: %s", parent.Id, parent.TitleDef())
	} else {
		title = "New paste"
	}

	return runTemplate(q.Response, "new", AnyMap{
		"Title":     title,
		"Annotates": parent,
		"Languages": LanguageNames,
		"Channels":  []string{}, // TODO
	})
}

func (s *Server) insertNewPaste(q *Query, parent *Paste) error {
	err := q.Request.ParseForm()
	if err != nil {
		return HttpError{fmt.Sprintf("error parsing form: %s", err.Error()), http.StatusInternalServerError}
	}

	paste := NewPaste(q.Request.PostForm)
	if parent != nil {
		paste.Annotates.Int64 = parent.RootId()
		paste.Annotates.Valid = true
		paste.Private = parent.Private
	}

	pasteId, err := InsertPaste(s.Database, paste)
	if err != nil {
		return HttpError{fmt.Sprintf("error inserting new paste: %s", err.Error()), http.StatusInternalServerError}
	}

	var newPath string
	if parent != nil {
		annotation, err := AnnotationOrdinal(s.Database, pasteId)
		if err != nil {
			return HttpError{fmt.Sprintf("error fetching paste %d: %s", pasteId, err.Error()), http.StatusInternalServerError}
		}

		newPath = fmt.Sprintf("/view/%d#a%d", parent.Id, annotation)
	} else {
		newPath = fmt.Sprintf("/view/%d", pasteId)
	}

	http.Redirect(q.Response, q.Request, newPath, http.StatusSeeOther)
	return nil
}

////////////////////////////////////////////////////////////////////////////////

// doRaw returns the verbatim content of a paste as plain text.
func (s *Server) doRaw(q *Query) error {
	if len(q.Args) < 1 {
		return HttpError{"invalid request", http.StatusBadRequest}
	}

	idStr := q.Args[0]
	id, err := parsePasteId(idStr)
	if err != nil {
		return HttpError{err.Error(), http.StatusBadRequest}
	}

	paste, err := GetPaste(s.Database, id)
	if err != nil {
		return HttpError{err.Error(), http.StatusInternalServerError}
	}
	if paste == nil {
		return HttpError{fmt.Sprintf("paste %d not found", id), http.StatusNotFound}
	}

	q.Response.Header().Set("Content-Type", "text/plain; charset=utf-8")
	fmt.Fprint(q.Response, paste.Content)

	return nil
}

////////////////////////////////////////////////////////////////////////////////

// doStatic serves a single static file.
func (s *Server) doStatic(q *Query) error {
	if len(q.Args) < 1 {
		return HttpError{"invalid request", http.StatusBadRequest}
	}

	path := []string{"static"}
	path = append(path, q.Args...)
	http.ServeFile(q.Response, q.Request, filepath.Join(path...))

	return nil
}

////////////////////////////////////////////////////////////////////////////////

// doView displays a paste and any annotations with syntax highlighting.
func (s *Server) doView(q *Query) error {
	if len(q.Args) < 1 {
		return HttpError{"invalid request", http.StatusBadRequest}
	}

	idStr := q.Args[0]
	id, err := parsePasteId(idStr)
	if err != nil {
		return HttpError{err.Error(), http.StatusBadRequest}
	}

	pasteData, err := GetPasteData(s.Database, id)
	if err != nil {
		return HttpError{err.Error(), http.StatusInternalServerError}
	}
	if pasteData == nil {
		return HttpError{fmt.Sprintf("paste %d not found", id), http.StatusNotFound}
	}

	return runTemplate(q.Response, "view", AnyMap{
		"Title":   fmt.Sprintf("Paste #%d: %s", pasteData.Paste.Id, pasteData.Paste.TitleDef()),
		"Content": pasteData,
	})
}
<commit_msg>Move some defs around<commit_after>package gopaste

import (
	"bytes"
	"fmt"
	"github.com/aryann/difflib"
	"html/template"
	"log"
	"net/http"
	"net/url"
	"path/filepath"
	"sort"
	"strconv"
	"strings"
)

var tmpl *template.Template

func trunc(s string, max int) string {
	if len(s) < max {
		return s
	}

	last := max - 3
	return s[:last] + "..."
}

func init() {
	tmpl = template.New("web")
	tmpl.Funcs(template.FuncMap{
		"trunc": trunc,
	})
	if _, err := tmpl.ParseGlob("*.template"); err != nil {
		log.Fatalf("template parsing: %v\n", err)
	}

}

////////////////////////////////////////////////////////////////////////////////

type Query struct {
	Request  *http.Request
	Response http.ResponseWriter
	Action   string
	Args     []string
}

func NewQuery(w http.ResponseWriter, req *http.Request) *Query {
	data := &Query{
		Request:  req,
		Response: w,
	}

	path := req.URL.Path
	parts := strings.Split(strings.Trim(path, "/"), "/")

	data.Action = parts[0]
	if len(parts) > 1 {
		data.Args = parts[1:]
	}

	return data
}

type HttpError struct {
	Message string
	Code    int
}

func (e HttpError) Error() string {
	return fmt.Sprintf("ERROR %d: %s", e.Code, e.Message)
}

func (s *Server) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	log.Printf("[web] %s %s %s", req.RemoteAddr, req.Method, req.URL.Path)

	q := NewQuery(w, req)
	if err := s.handle(q); err != nil {
		if e, ok := err.(HttpError); ok {
			http.Error(w, e.Error(), e.Code)
		} else {
			http.Error(w, err.Error(), http.StatusInternalServerError)
		}
	}
}

type ActionFunc func(*Server, *Query) error

var handlers = map[string]ActionFunc{
	"":         (*Server).doMain,
	"annotate": (*Server).doAnnotate,
	"browse":   (*Server).doBrowse,
	"diff":     (*Server).doDiff,
	"new":      (*Server).doNew,
	"raw":      (*Server).doRaw,
	"static":   (*Server).doStatic,
	"view":     (*Server).doView,
}

func (s *Server) handle(d *Query) error {
	handler := handlers[d.Action]
	if handler == nil {
		return HttpError{fmt.Sprintf("'%s' not found", d.Request.URL), http.StatusNotFound}
	}

	return handler(s, d)
}

////////////////////////////////////////////////////////////////////////////////

func parsePasteId(str string) (int64, error) {
	id, err := strconv.ParseInt(str, 10, 64)
	if err != nil {
		return InvalidPasteId, fmt.Errorf("invalid paste id '%s'", str)
	}
	return id, nil
}

// runTemplate executes a template and writes the results as HTML if successful
func runTemplate(w http.ResponseWriter, name string, data interface{}) error {
	buf := new(bytes.Buffer)
	err := tmpl.ExecuteTemplate(buf, name, data)
	if err != nil {
		return HttpError{fmt.Sprintf("error processing template %s: %v", name, err), http.StatusInternalServerError}
	}

	w.Header().Set("Content-Type", "text/html; charset=utf-8")
	buf.WriteTo(w)

	return nil
}

////////////////////////////////////////////////////////////////////////////////

type AnyMap map[string]interface{}

func (s *Server) doMain(q *Query) error {
	opts := NewBrowseOpts()
	opts.PageSize = 10

	page, err := TopLevelPastes(s.Database, opts)
	if err != nil {
		return HttpError{err.Error(), http.StatusInternalServerError}
	}

	return runTemplate(q.Response, "main", AnyMap{
		"MainPage":  true,
		"Title":     "Home",
		"Page":      page,
		"Languages": LanguageNames,
		"Channels":  []string{}, // TODO
	})
}

////////////////////////////////////////////////////////////////////////////////

type BrowseOpts struct {
	Page     int
	PageSize int
	Search   map[string]string
}

func NewBrowseOpts() *BrowseOpts {
	return &BrowseOpts{
		Page:     1,
		PageSize: 50,
		Search:   make(map[string]string),
	}
}

func (o *BrowseOpts) Parse(args []string) error {
	for i := 0; i+1 < len(args); i += 2 {
		key, err := url.QueryUnescape(args[i])
		if err != nil {
			return err
		}
		val, err := url.QueryUnescape(args[i+1])
		if err != nil {
			return err
		}
		switch key {
		case "page":
			pagenum, err := strconv.ParseInt(val, 10, 64)
			if err != nil {
				return fmt.Errorf("invalid page number: %s", val)
			}
			o.Page = int(pagenum)
		default:
			o.Search[key] = val
		}
	}

	return nil
}

func (o *BrowseOpts) String() string {
	var parts, keys []string

	for key := range o.Search {
		keys = append(keys, key)
	}
	sort.Strings(keys)

	for _, key := range keys {
		parts = append(parts,
			url.QueryEscape(key),
			url.QueryEscape(o.Search[key]),
		)
	}

	if o.Page > 1 {
		parts = append(parts, "page", fmt.Sprint(o.Page))
	}

	return strings.Join(parts, "/")
}

func (o *BrowseOpts) NewPage(page int) *BrowseOpts {
	newOpts := NewBrowseOpts()
	newOpts.Page = page
	newOpts.PageSize = o.PageSize
	for k, v := range o.Search {
		newOpts.Search[k] = v
	}
	return newOpts
}

func (o *BrowseOpts) Prev() *BrowseOpts {
	return o.NewPage(o.Page - 1)
}

func (o *BrowseOpts) Next() *BrowseOpts {
	return o.NewPage(o.Page + 1)
}

func (o *BrowseOpts) Nearby(window int, max int) (pages []int) {
	min := 1
	var low, high int

	width := 2*window + 1
	if width >= max {
		low = min
		high = max
	} else {
		low = o.Page - window
		lowExtra := 0
		if low < min {
			lowExtra = min - low
			low = min
		}

		high = o.Page + window + lowExtra
		if high > max {
			low -= high - max
			high = max
		}
	}

	for n := low; n <= high; n++ {
		pages = append(pages, n)
	}

	return
}

func (s *Server) doBrowse(q *Query) error {
	opts := NewBrowseOpts()
	err := opts.Parse(q.Args)
	if err != nil {
		return HttpError{err.Error(), http.StatusBadRequest}
	}

	page, err := TopLevelPastes(s.Database, opts)
	if err != nil {
		return HttpError{err.Error(), http.StatusInternalServerError}
	}

	return runTemplate(q.Response, "browse", AnyMap{
		"Title": "Browse pastes",
		"Page":  page,
		"Opts":  opts,
	})
}

////////////////////////////////////////////////////////////////////////////////

// doDiff displays the difference between two pastes.
func (s *Server) doDiff(q *Query) error {
	if len(q.Args) < 2 {
		return HttpError{"invalid request", http.StatusBadRequest}
	}

	fromStr, toStr := q.Args[0], q.Args[1]

	fromId, err := parsePasteId(fromStr)
	if err != nil {
		return HttpError{err.Error(), http.StatusBadRequest}
	}

	toId, err := parsePasteId(toStr)
	if err != nil {
		return HttpError{err.Error(), http.StatusBadRequest}
	}

	from, err := GetPaste(s.Database, fromId)
	if err != nil {
		return HttpError{err.Error(), http.StatusInternalServerError}
	}
	if from == nil {
		return HttpError{fmt.Sprintf("paste %d not found", fromId), http.StatusNotFound}
	}

	to, err := GetPaste(s.Database, toId)
	if err != nil {
		return HttpError{err.Error(), http.StatusInternalServerError}
	}
	if to == nil {
		return HttpError{fmt.Sprintf("paste %d not found", toId), http.StatusNotFound}
	}

	diff := difflib.Diff(
		strings.Split(from.Content, "\n"),
		strings.Split(to.Content, "\n"),
	)

	diffText := ""
	for _, line := range diff {
		var prefix string
		switch line.Delta {
		case difflib.LeftOnly:
			prefix = "-"
		case difflib.RightOnly:
			prefix = "+"
		default:
			prefix = " "
		}
		diffText += prefix + line.Payload + "\n"
	}

	return runTemplate(q.Response, "diff", AnyMap{
		"Title":    fmt.Sprintf("Diff #%d / #%d", from.Id, to.Id),
		"From":     from,
		"To":       to,
		"DiffText": diffText,
	})
}

////////////////////////////////////////////////////////////////////////////////

// doNew adds a new top-level paste.
func (s *Server) doNew(q *Query) error {
	return s.handleNew(q, nil)
}

// doAnnotation adds a new annotation to an existing paste.
func (s *Server) doAnnotate(q *Query) error {
	if len(q.Args) < 1 {
		return HttpError{"invalid request", http.StatusBadRequest}
	}

	idStr := q.Args[0]
	id, err := parsePasteId(idStr)
	if err != nil {
		return HttpError{err.Error(), http.StatusBadRequest}
	}

	paste, err := GetPaste(s.Database, id)
	if err != nil {
		return HttpError{err.Error(), http.StatusInternalServerError}
	}
	if paste == nil {
		return HttpError{fmt.Sprintf("paste %d not found", id), http.StatusNotFound}
	}

	return s.handleNew(q, paste)
}

func (s *Server) handleNew(q *Query, parent *Paste) error {
	method := q.Request.Method
	switch method {
	case "GET", "HEAD":
		return s.displayNewPage(q, parent)
	case "POST":
		return s.insertNewPaste(q, parent)
	default:
		return HttpError{fmt.Sprintf("unsupported request method: %s", method), http.StatusNotImplemented}
	}
}

func (s *Server) displayNewPage(q *Query, parent *Paste) error {
	var title string
	if parent != nil {
		title = fmt.Sprintf("Annotating #%d: %s", parent.Id, parent.TitleDef())
	} else {
		title = "New paste"
	}

	return runTemplate(q.Response, "new", AnyMap{
		"Title":     title,
		"Annotates": parent,
		"Languages": LanguageNames,
		"Channels":  []string{}, // TODO
	})
}

func (s *Server) insertNewPaste(q *Query, parent *Paste) error {
	err := q.Request.ParseForm()
	if err != nil {
		return HttpError{fmt.Sprintf("error parsing form: %s", err.Error()), http.StatusInternalServerError}
	}

	paste := NewPaste(q.Request.PostForm)
	if parent != nil {
		paste.Annotates.Int64 = parent.RootId()
		paste.Annotates.Valid = true
		paste.Private = parent.Private
	}

	pasteId, err := InsertPaste(s.Database, paste)
	if err != nil {
		return HttpError{fmt.Sprintf("error inserting new paste: %s", err.Error()), http.StatusInternalServerError}
	}

	var newPath string
	if parent != nil {
		annotation, err := AnnotationOrdinal(s.Database, pasteId)
		if err != nil {
			return HttpError{fmt.Sprintf("error fetching paste %d: %s", pasteId, err.Error()), http.StatusInternalServerError}
		}

		newPath = fmt.Sprintf("/view/%d#a%d", parent.Id, annotation)
	} else {
		newPath = fmt.Sprintf("/view/%d", pasteId)
	}

	http.Redirect(q.Response, q.Request, newPath, http.StatusSeeOther)
	return nil
}

////////////////////////////////////////////////////////////////////////////////

// doRaw returns the verbatim content of a paste as plain text.
func (s *Server) doRaw(q *Query) error {
	if len(q.Args) < 1 {
		return HttpError{"invalid request", http.StatusBadRequest}
	}

	idStr := q.Args[0]
	id, err := parsePasteId(idStr)
	if err != nil {
		return HttpError{err.Error(), http.StatusBadRequest}
	}

	paste, err := GetPaste(s.Database, id)
	if err != nil {
		return HttpError{err.Error(), http.StatusInternalServerError}
	}
	if paste == nil {
		return HttpError{fmt.Sprintf("paste %d not found", id), http.StatusNotFound}
	}

	q.Response.Header().Set("Content-Type", "text/plain; charset=utf-8")
	fmt.Fprint(q.Response, paste.Content)

	return nil
}

////////////////////////////////////////////////////////////////////////////////

// doStatic serves a single static file.
func (s *Server) doStatic(q *Query) error {
	if len(q.Args) < 1 {
		return HttpError{"invalid request", http.StatusBadRequest}
	}

	path := []string{"static"}
	path = append(path, q.Args...)
	http.ServeFile(q.Response, q.Request, filepath.Join(path...))

	return nil
}

////////////////////////////////////////////////////////////////////////////////

// doView displays a paste and any annotations with syntax highlighting.
func (s *Server) doView(q *Query) error {
	if len(q.Args) < 1 {
		return HttpError{"invalid request", http.StatusBadRequest}
	}

	idStr := q.Args[0]
	id, err := parsePasteId(idStr)
	if err != nil {
		return HttpError{err.Error(), http.StatusBadRequest}
	}

	pasteData, err := GetPasteData(s.Database, id)
	if err != nil {
		return HttpError{err.Error(), http.StatusInternalServerError}
	}
	if pasteData == nil {
		return HttpError{fmt.Sprintf("paste %d not found", id), http.StatusNotFound}
	}

	return runTemplate(q.Response, "view", AnyMap{
		"Title":   fmt.Sprintf("Paste #%d: %s", pasteData.Paste.Id, pasteData.Paste.TitleDef()),
		"Content": pasteData,
	})
}
<|endoftext|>


<commit_before>package main

// Slack outgoing webhooks are handled here. Requests come in and are run through
// the processor to generate a response, which is sent back to Slack.
//
// Create an outgoing webhook in your Slack here:
// https://my.slack.com/services/new/outgoing-webhook

import (
	"encoding/json"
	"log"
	"net/http"
	"strconv"
)

type WebhookResponse struct {
	Username string `json:"username"`
	Text     string `json:"text"`
}

func init() {
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		incomingText := r.PostFormValue("text")
		log.Printf("Handling incoming request: %s", incomingText)
		urls := extractUrls(incomingText)
		for _, url := range urls {
			log.Printf("Checking url: %s", url)
			uses := urlsUsed.Get(url)
			if len(uses) > 0 {
				log.Printf("Uses: %q", uses)
				var response WebhookResponse
				response.Username = botUsername
				response.Text = "Ooooooooooold!"
				log.Printf("Sending response: %s", response.Text)

				b, err := json.Marshal(response)
				if err != nil {
					log.Fatal(err)
				}

				w.Write(b)
			}

			urlsUsed.Add(url, r.PostFormValue("timestamp"))
		}
		go func() {
			urlsUsed.Save(stateFile)
		}()
	})
}

func StartServer(port int) {
	log.Printf("Starting HTTP server on %d", port)
	err := http.ListenAndServe(":"+strconv.Itoa(port), nil)
	if err != nil {
		log.Fatal("ListenAndServe: ", err)
	}
}
<commit_msg>Ignore messages from the slackbot user (mostly duplicate tweets)<commit_after>package main

// Slack outgoing webhooks are handled here. Requests come in and are run through
// the processor to generate a response, which is sent back to Slack.
//
// Create an outgoing webhook in your Slack here:
// https://my.slack.com/services/new/outgoing-webhook

import (
	"encoding/json"
	"log"
	"net/http"
	"strconv"
)

type WebhookResponse struct {
	Username string `json:"username"`
	Text     string `json:"text"`
}

func init() {
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		incomingText := r.PostFormValue("text")
		log.Printf("Handling incoming request: %s", incomingText)

		if r.PostFormValue("user_id") == "USLACKBOT" {
			return
		}

		urls := extractUrls(incomingText)
		for _, url := range urls {
			log.Printf("Checking url: %s", url)
			uses := urlsUsed.Get(url)
			if len(uses) > 0 {
				log.Printf("Uses: %q", uses)
				var response WebhookResponse
				response.Username = botUsername
				response.Text = "Ooooooooooold!"
				log.Printf("Sending response: %s", response.Text)

				b, err := json.Marshal(response)
				if err != nil {
					log.Fatal(err)
				}

				w.Write(b)
			}

			urlsUsed.Add(url, r.PostFormValue("timestamp"))
		}
		go func() {
			urlsUsed.Save(stateFile)
		}()
	})
}

func StartServer(port int) {
	log.Printf("Starting HTTP server on %d", port)
	err := http.ListenAndServe(":"+strconv.Itoa(port), nil)
	if err != nil {
		log.Fatal("ListenAndServe: ", err)
	}
}
<|endoftext|>


<commit_before>package dns

import (
	"time"
)

// Envelope is used when doing a zone transfer with a remote server.
type Envelope struct {
	RR    []RR  // The set of RRs in the answer section of the xfr reply message.
	Error error // If something went wrong, this contains the error.
}

// A Transfer defines parameters that are used during a zone transfer.
type Transfer struct {
	*Conn
	DialTimeout    time.Duration     // net.DialTimeout, defaults to 2 seconds
	ReadTimeout    time.Duration     // net.Conn.SetReadTimeout value for connections, defaults to 2 seconds
	WriteTimeout   time.Duration     // net.Conn.SetWriteTimeout value for connections, defaults to 2 seconds
	TsigSecret     map[string]string // Secret(s) for Tsig map[<zonename>]<base64 secret>, zonename must be fully qualified
	tsigTimersOnly bool
}

// Think we need to away to stop the transfer

// In performs an incoming transfer with the server in a.
func (t *Transfer) In(q *Msg, a string) (env chan *Envelope, err error) {
	timeout := dnsTimeout
	if t.DialTimeout != 0 {
		timeout = t.DialTimeout
	}
	t.Conn, err = DialTimeout("tcp", a, timeout)
	if err != nil {
		return nil, err
	}
	if err := t.WriteMsg(q); err != nil {
		return nil, err
	}
	env = make(chan *Envelope)
	go func() {
		if q.Question[0].Qtype == TypeAXFR {
			go t.inAxfr(q.Id, env)
			return
		}
		if q.Question[0].Qtype == TypeIXFR {
			go t.inIxfr(q.Id, env)
			return
		}
	}()
	return env, nil
}

func (t *Transfer) inAxfr(id uint16, c chan *Envelope) {
	first := true
	defer t.Close()
	defer close(c)
	timeout := dnsTimeout
	if t.ReadTimeout != 0 {
		timeout = t.ReadTimeout
	}
	for {
		t.Conn.SetReadDeadline(time.Now().Add(timeout))
		in, err := t.ReadMsg()
		if err != nil {
			c <- &Envelope{nil, err}
			return
		}
		if id != in.Id {
			c <- &Envelope{in.Answer, ErrId}
			return
		}
		if first {
			if !isSOAFirst(in) {
				c <- &Envelope{in.Answer, ErrSoa}
				return
			}
			first = !first
			// only one answer that is SOA, receive more
			if len(in.Answer) == 1 {
				t.tsigTimersOnly = true
				c <- &Envelope{in.Answer, nil}
				continue
			}
		}

		if !first {
			t.tsigTimersOnly = true // Subsequent envelopes use this.
			if isSOALast(in) {
				c <- &Envelope{in.Answer, nil}
				return
			}
			c <- &Envelope{in.Answer, nil}
		}
	}
	panic("dns: not reached")
}

func (t *Transfer) inIxfr(id uint16, c chan *Envelope) {
	serial := uint32(0) // The first serial seen is the current server serial
	first := true
	defer t.Close()
	defer close(c)
	timeout := dnsTimeout
	if t.ReadTimeout != 0 {
		timeout = t.ReadTimeout
	}
	for {
		t.SetReadDeadline(time.Now().Add(timeout))
		in, err := t.ReadMsg()
		if err != nil {
			c <- &Envelope{in.Answer, err}
			return
		}
		if id != in.Id {
			c <- &Envelope{in.Answer, ErrId}
			return
		}
		if first {
			// A single SOA RR signals "no changes"
			if len(in.Answer) == 1 && isSOAFirst(in) {
				c <- &Envelope{in.Answer, nil}
				return
			}

			// Check if the returned answer is ok
			if !isSOAFirst(in) {
				c <- &Envelope{in.Answer, ErrSoa}
				return
			}
			// This serial is important
			serial = in.Answer[0].(*SOA).Serial
			first = !first
		}

		// Now we need to check each message for SOA records, to see what we need to do
		if !first {
			t.tsigTimersOnly = true
			// If the last record in the IXFR contains the servers' SOA,  we should quit
			if v, ok := in.Answer[len(in.Answer)-1].(*SOA); ok {
				if v.Serial == serial {
					c <- &Envelope{in.Answer, nil}
					return
				}
			}
			c <- &Envelope{in.Answer, nil}
		}
	}
}

// Out performs an outgoing transfer with the client connecting in w.
// Basic use pattern:
//
//	ch := make(chan *dns.Envelope)
//	tr := new(dns.Transfer)
//	tr.Out(w, r, ch)
//	c <- &dns.Envelope{RR: []dns.RR{soa, rr1, rr2, rr3, soa}}
//	close(ch)
//	w.Hijack()
//	// w.Close() // Client closes connection
//
// The server is responsible for sending the correct sequence of RRs through the
// channel ch.
func (t *Transfer) Out(w ResponseWriter, q *Msg, ch chan *Envelope) error {
	for x := range ch {
		r := new(Msg)
		// Compress?
		r.SetReply(q)
		r.Authoritative = true
		// assume it fits TODO(miek): fix
		r.Answer = append(r.Answer, x.RR...)
		if err := w.WriteMsg(r); err != nil {
			return err
		}
	}
	w.TsigTimersOnly(true)
	return nil
}

// ReadMsg reads a message from the transfer connection t.
func (t *Transfer) ReadMsg() (*Msg, error) {
	m := new(Msg)
	p := make([]byte, MaxMsgSize)
	n, err := t.Read(p)
	if err != nil && n == 0 {
		return nil, err
	}
	p = p[:n]
	if err := m.Unpack(p); err != nil {
		return nil, err
	}
	if ts := m.IsTsig(); ts != nil && t.TsigSecret != nil {
		if _, ok := t.TsigSecret[ts.Hdr.Name]; !ok {
			return m, ErrSecret
		}
		// Need to work on the original message p, as that was used to calculate the tsig.
		err = TsigVerify(p, t.TsigSecret[ts.Hdr.Name], t.tsigRequestMAC, t.tsigTimersOnly)
	}
	return m, err
}

// WriteMsg writes a message through the transfer connection t.
func (t *Transfer) WriteMsg(m *Msg) (err error) {
	var out []byte
	if ts := m.IsTsig(); ts != nil && t.TsigSecret != nil {
		if _, ok := t.TsigSecret[ts.Hdr.Name]; !ok {
			return ErrSecret
		}
		out, t.tsigRequestMAC, err = TsigGenerate(m, t.TsigSecret[ts.Hdr.Name], t.tsigRequestMAC, t.tsigTimersOnly)
	} else {
		out, err = m.Pack()
	}
	if err != nil {
		return err
	}
	if _, err = t.Write(out); err != nil {
		return err
	}
	return nil
}

func isSOAFirst(in *Msg) bool {
	if len(in.Answer) > 0 {
		return in.Answer[0].Header().Rrtype == TypeSOA
	}
	return false
}

func isSOALast(in *Msg) bool {
	if len(in.Answer) > 0 {
		return in.Answer[len(in.Answer)-1].Header().Rrtype == TypeSOA
	}
	return false
}
<commit_msg>Fix TSig check on second DNS message (TCP) #180<commit_after>package dns

import (
	"time"
)

// Envelope is used when doing a zone transfer with a remote server.
type Envelope struct {
	RR    []RR  // The set of RRs in the answer section of the xfr reply message.
	Error error // If something went wrong, this contains the error.
}

// A Transfer defines parameters that are used during a zone transfer.
type Transfer struct {
	*Conn
	DialTimeout    time.Duration     // net.DialTimeout, defaults to 2 seconds
	ReadTimeout    time.Duration     // net.Conn.SetReadTimeout value for connections, defaults to 2 seconds
	WriteTimeout   time.Duration     // net.Conn.SetWriteTimeout value for connections, defaults to 2 seconds
	TsigSecret     map[string]string // Secret(s) for Tsig map[<zonename>]<base64 secret>, zonename must be fully qualified
	tsigTimersOnly bool
}

// Think we need to away to stop the transfer

// In performs an incoming transfer with the server in a.
func (t *Transfer) In(q *Msg, a string) (env chan *Envelope, err error) {
	timeout := dnsTimeout
	if t.DialTimeout != 0 {
		timeout = t.DialTimeout
	}
	t.Conn, err = DialTimeout("tcp", a, timeout)
	if err != nil {
		return nil, err
	}
	if err := t.WriteMsg(q); err != nil {
		return nil, err
	}
	env = make(chan *Envelope)
	go func() {
		if q.Question[0].Qtype == TypeAXFR {
			go t.inAxfr(q.Id, env)
			return
		}
		if q.Question[0].Qtype == TypeIXFR {
			go t.inIxfr(q.Id, env)
			return
		}
	}()
	return env, nil
}

func (t *Transfer) inAxfr(id uint16, c chan *Envelope) {
	first := true
	defer t.Close()
	defer close(c)
	timeout := dnsTimeout
	if t.ReadTimeout != 0 {
		timeout = t.ReadTimeout
	}
	for {
		t.Conn.SetReadDeadline(time.Now().Add(timeout))
		in, err := t.ReadMsg()
		if err != nil {
			c <- &Envelope{nil, err}
			return
		}
		if id != in.Id {
			c <- &Envelope{in.Answer, ErrId}
			return
		}
		if first {
			if !isSOAFirst(in) {
				c <- &Envelope{in.Answer, ErrSoa}
				return
			}
			first = !first
			// only one answer that is SOA, receive more
			if len(in.Answer) == 1 {
				t.tsigTimersOnly = true
				c <- &Envelope{in.Answer, nil}
				continue
			}
		}

		if !first {
			t.tsigTimersOnly = true // Subsequent envelopes use this.
			if isSOALast(in) {
				c <- &Envelope{in.Answer, nil}
				return
			}
			c <- &Envelope{in.Answer, nil}
		}
	}
	panic("dns: not reached")
}

func (t *Transfer) inIxfr(id uint16, c chan *Envelope) {
	serial := uint32(0) // The first serial seen is the current server serial
	first := true
	defer t.Close()
	defer close(c)
	timeout := dnsTimeout
	if t.ReadTimeout != 0 {
		timeout = t.ReadTimeout
	}
	for {
		t.SetReadDeadline(time.Now().Add(timeout))
		in, err := t.ReadMsg()
		if err != nil {
			c <- &Envelope{in.Answer, err}
			return
		}
		if id != in.Id {
			c <- &Envelope{in.Answer, ErrId}
			return
		}
		if first {
			// A single SOA RR signals "no changes"
			if len(in.Answer) == 1 && isSOAFirst(in) {
				c <- &Envelope{in.Answer, nil}
				return
			}

			// Check if the returned answer is ok
			if !isSOAFirst(in) {
				c <- &Envelope{in.Answer, ErrSoa}
				return
			}
			// This serial is important
			serial = in.Answer[0].(*SOA).Serial
			first = !first
		}

		// Now we need to check each message for SOA records, to see what we need to do
		if !first {
			t.tsigTimersOnly = true
			// If the last record in the IXFR contains the servers' SOA,  we should quit
			if v, ok := in.Answer[len(in.Answer)-1].(*SOA); ok {
				if v.Serial == serial {
					c <- &Envelope{in.Answer, nil}
					return
				}
			}
			c <- &Envelope{in.Answer, nil}
		}
	}
}

// Out performs an outgoing transfer with the client connecting in w.
// Basic use pattern:
//
//	ch := make(chan *dns.Envelope)
//	tr := new(dns.Transfer)
//	tr.Out(w, r, ch)
//	c <- &dns.Envelope{RR: []dns.RR{soa, rr1, rr2, rr3, soa}}
//	close(ch)
//	w.Hijack()
//	// w.Close() // Client closes connection
//
// The server is responsible for sending the correct sequence of RRs through the
// channel ch.
func (t *Transfer) Out(w ResponseWriter, q *Msg, ch chan *Envelope) error {
	for x := range ch {
		r := new(Msg)
		// Compress?
		r.SetReply(q)
		r.Authoritative = true
		// assume it fits TODO(miek): fix
		r.Answer = append(r.Answer, x.RR...)
		if err := w.WriteMsg(r); err != nil {
			return err
		}
	}
	w.TsigTimersOnly(true)
	return nil
}

// ReadMsg reads a message from the transfer connection t.
func (t *Transfer) ReadMsg() (*Msg, error) {
	m := new(Msg)
	p := make([]byte, MaxMsgSize)
	n, err := t.Read(p)
	if err != nil && n == 0 {
		return nil, err
	}
	p = p[:n]
	if err := m.Unpack(p); err != nil {
		return nil, err
	}
	if ts := m.IsTsig(); ts != nil && t.TsigSecret != nil {
		if _, ok := t.TsigSecret[ts.Hdr.Name]; !ok {
			return m, ErrSecret
		}
		// Need to work on the original message p, as that was used to calculate the tsig.
		err = TsigVerify(p, t.TsigSecret[ts.Hdr.Name], t.tsigRequestMAC, t.tsigTimersOnly)
		t.tsigRequestMAC = ts.MAC
	}
	return m, err
}

// WriteMsg writes a message through the transfer connection t.
func (t *Transfer) WriteMsg(m *Msg) (err error) {
	var out []byte
	if ts := m.IsTsig(); ts != nil && t.TsigSecret != nil {
		if _, ok := t.TsigSecret[ts.Hdr.Name]; !ok {
			return ErrSecret
		}
		out, t.tsigRequestMAC, err = TsigGenerate(m, t.TsigSecret[ts.Hdr.Name], t.tsigRequestMAC, t.tsigTimersOnly)
	} else {
		out, err = m.Pack()
	}
	if err != nil {
		return err
	}
	if _, err = t.Write(out); err != nil {
		return err
	}
	return nil
}

func isSOAFirst(in *Msg) bool {
	if len(in.Answer) > 0 {
		return in.Answer[0].Header().Rrtype == TypeSOA
	}
	return false
}

func isSOALast(in *Msg) bool {
	if len(in.Answer) > 0 {
		return in.Answer[len(in.Answer)-1].Header().Rrtype == TypeSOA
	}
	return false
}
<|endoftext|>


<commit_before>package docconv

import (
	"fmt"
	"github.com/extrame/xls"
	"io"
	"strings"
)

// ConvertXLS Convert MS Excel Spreadsheet
func ConvertXLS(r io.Reader) (string, map[string]string, error) {
	f, err := NewLocalFile(r, "/tmp", "sajari-convert-")
	if err != nil {
		return "", nil, fmt.Errorf("error creating local file: %v", err)
	}
	defer f.Done()

	fileStat, err := f.Stat()
	if err != nil {
		return "", nil, fmt.Errorf("error on getting file stats: %v", err)
	}
	w, err := xls.OpenReader(f, "utf-8")
	if err != nil {
		return "", nil, fmt.Errorf("error on xls parsing: %v", err)
	}

	// Meta data
	meta := make(map[string]string)
	meta["ModifiedDate"] = fmt.Sprintf("%d", fileStat.ModTime().Unix())

	// Document body
	// TODO: find out why the order isn't being respected
	var body string
	for i := 0; i < w.NumSheets(); i++ {
		for _, row := range w.GetSheet(i).Rows {
			r := make([]string, 0)
			for _, j := range row.Cols {
				if uint16(len(r)) <= j.LastCol() {
					r = append(r, make([]string, j.LastCol()-uint16(len(r))+1)...)
				}
				str := j.String(w)
				for k := uint16(0); k < j.LastCol()-j.FirstCol()+1; k++ {
					r[j.FirstCol()+k] = str[k]
				}
			}

			body += strings.Join(r[:], ",")
			body += "\n"
		}
	}

	return body, meta, nil
}
<commit_msg>fix row ordering on csv generation from xls<commit_after>package docconv

import (
	"fmt"
	"github.com/extrame/xls"
	"io"
	"strings"
)

// ConvertXLS Convert MS Excel Spreadsheet
func ConvertXLS(r io.Reader) (string, map[string]string, error) {
	f, err := NewLocalFile(r, "/tmp", "sajari-convert-")
	if err != nil {
		return "", nil, fmt.Errorf("error creating local file: %v", err)
	}
	defer f.Done()

	fileStat, err := f.Stat()
	if err != nil {
		return "", nil, fmt.Errorf("error on getting file stats: %v", err)
	}
	w, err := xls.OpenReader(f, "utf-8")
	if err != nil {
		return "", nil, fmt.Errorf("error on xls parsing: %v", err)
	}

	// Meta data
	meta := make(map[string]string)
	meta["ModifiedDate"] = fmt.Sprintf("%d", fileStat.ModTime().Unix())

	// Document body
	var body string
	for i := 0; i < w.NumSheets(); i++ {
		for j := 0; j <= int(w.GetSheet(i).MaxRow); j++ {
			row := w.GetSheet(i).Rows[uint16(j)]
			r := make([]string, 0)
			for _, col := range row.Cols {
				if uint16(len(r)) <= col.LastCol() {
					r = append(r, make([]string, col.LastCol()-uint16(len(r))+1)...)
				}
				str := col.String(w)
				for k := uint16(0); k < col.LastCol()-col.FirstCol()+1; k++ {
					r[col.FirstCol()+k] = str[k]
				}
			}

			body += strings.Join(r[:], ",")
			if j < int(w.GetSheet(i).MaxRow) {
				body += "\n"
			}
		}
	}

	return body, meta, nil
}
<|endoftext|>