<commit_before>// History: Sep 03 13 Thibaut Colar Creation

package phpserializer

import (
	"errors"
	"fmt"
	"io"
	"log"
	"reflect"
	"strconv"
	"text/scanner"
)

// Encode / Decode serialized PHP objects from Go / Golang
// Note that there are apparently **NO** PHP serialization format specs, so this might not be perfect.
// Defaults to SnakeToUnderscore & UnderscoreToSnake for Name conversion, but can be replaced.
type PhpSerializer struct {
	EncodeNameConverter NameConverter
	DecodeNameConverter NameConverter
}

// Decode serialized PHP content into target object
// Note: Not closing the reader
func (p PhpSerializer) Decode(reader io.Reader, v interface{}) (err error) {
	if p.DecodeNameConverter == nil {
		p.DecodeNameConverter = &UnderscoreToSnake{}
	}
	s := &scanner.Scanner{}
	s.Init(reader)
	obj := reflect.ValueOf(v)
	err = p.decode(s, obj, false)
	return err
}

func (p PhpSerializer) Encode(v interface{}, writer io.Writer) (err error) {
	if p.EncodeNameConverter == nil {
		p.EncodeNameConverter = &SnakeToUnderscore{}
	}
	return p.encode(v, writer)
}

// #################### Internals ############################################

// Encode a Go object into a serialized PHP and write it to the writer
// Note: Not closing the writer
func (p PhpSerializer) encode(v interface{}, writer io.Writer) (err error) {
	if p.EncodeNameConverter == nil {
		p.EncodeNameConverter = &SnakeToUnderscore{}
	}
	t := reflect.TypeOf(v)
	switch t.Kind() {
	case reflect.Struct:
		val := reflect.ValueOf(v)
		err = p.encodeStruct(val, writer)
	case reflect.Int: // TODO: are int8, int16 etc.. separate ?
		i, _ := v.(int)
		err = p.encodeInt(i, writer)
	case reflect.String:
		str, _ := v.(string)
		err = p.encodeString(str, writer)
	case reflect.Map:
		val := reflect.ValueOf(v)
		err = p.encodeMap(val, writer)
	default:
		return errors.New(fmt.Sprintf("Unsupported element %s - of type %s",
			reflect.ValueOf(v).String(),
			t.String()))
	}
	return err
}

// Encode a structure
func (p PhpSerializer) encodeStruct(v reflect.Value, writer io.Writer) (err error) {
	size := v.NumField()
	p.write(writer, fmt.Sprintf("a:%d:{", size))
	for i := 0; i < size; i++ {
		// field name
		fieldName := p.EncodeNameConverter.Convert(v.Type().Field(i).Name)
		err = p.encodeString(fieldName, writer)
		if err != nil {
			return err
		}
		// field value
		field := v.Field(i)
		err = p.Encode(field.Interface(), writer)
		if err != nil {
			return err
		}
	}
	err = p.write(writer, "}")
	return err
}

// Encode a string
func (p PhpSerializer) encodeString(str string, writer io.Writer) (err error) {
	err = p.write(writer, fmt.Sprintf(`s:%d:"%s";`, len(str), str))
	return err
}

// Encode an int
func (p PhpSerializer) encodeInt(i int, writer io.Writer) (err error) {
	err = p.write(writer, fmt.Sprintf("i:%d;", i))
	return err
}

// Encode a map
func (p PhpSerializer) encodeMap(v reflect.Value, writer io.Writer) (err error) {
	size := len(v.MapKeys())
	p.write(writer, fmt.Sprintf("a:%d:{", size))
	for _, key := range v.MapKeys() {
		err = p.Encode(key.Interface(), writer)
		if err != nil {
			return err
		}
		val := v.MapIndex(key)
		err = p.Encode(val.Interface(), writer)
		if err != nil {
			return err
		}
	}
	err = p.write(writer, "}")
	return err
}

// write a string to the writer
func (p PhpSerializer) write(writer io.Writer, str string) (err error) {
	_, err = writer.Write([]byte(str))
	return err
}

// Decode the next element found by the scanner and set value into v
func (p PhpSerializer) decode(s *scanner.Scanner, v reflect.Value, skipVal bool) (err error) {
	c := s.Peek()
	switch c {
	case 'i': // int
		err = p.decodeInt(s, v, skipVal)
	case 's': // string
		err = p.decodeString(s, v, skipVal)
	case 'a': // map / struct  TODO: can be an array/list too ?
		err = p.decodeMap(s, v, skipVal)
	case 'd': // decimal
		err = p.decodeFloat(s, v, skipVal)
	//case "b": // TODO: 'b' object types (bool ??)
	//case "O": // TODO: 'O' object types
	//case "N": // TODO: 'N' object types
	default:
		s.Scan()
		err = p.error(s, "Unexpected type %s", s.TokenText())
	}
	return err
}

// Decode a map into v (map or struct)
func (p PhpSerializer) decodeMap(s *scanner.Scanner, v reflect.Value, skipVal bool) (err error) {
	var size int
	if err = p.decodeToken(s, "a"); err != nil {
		return err
	}
	size, err = p.decodeSize(s)
	if err != nil {
		return err
	}
	if err = p.decodeToken(s, "{"); err != nil {
		return err
	}
	// decode key/value pairs
	for i := 0; i != size; i++ {
		var key interface{}
		err = p.decode(s, reflect.ValueOf(&key).Elem(), skipVal)
		if err != nil {
			return err
		}
		if skipVal {
			// this is just data we need to parse but not do anything with
			err = p.skipValue(s)
			if err != nil {
				return err
			}
		} else {
			if v.Kind() == reflect.Map {
				// targetting a map
				if v.IsNil() {
					// Initialize to an empty map if nil
					v.Set(reflect.MakeMap(v.Type()))
				}
				// Create an instance of the map value type
				mapVal := reflect.New(v.Type().Elem()).Elem()
				// and then decode that
				err = p.decode(s, mapVal, skipVal)
				if err != nil {
					return err
				}
				v.SetMapIndex(reflect.ValueOf(key), mapVal)
			} else {
				// targetting a struct
				var k string
				k, _ = key.(string)
				if k == "" {
					return errors.New(fmt.Sprintf("Struct Key '%s' is not a string.", key))
				}
				target := p.DecodeNameConverter.Convert(k)
				var val reflect.Value
				if v.Kind() == reflect.Struct {
					val = v.FieldByName(target)
				} else {
					val = v.Elem().FieldByName(target)
				}
				if val.IsValid() {
					err = p.decode(s, val, skipVal)
				} else {
					log.Printf("No targets field found named '%s', skipping it !", target)
					err = p.skipValue(s)
				}
				if err != nil {
					return err
				}
			}
		}
	}
	if p.decodeToken(s, "}") != nil {
		return err
	}
	return err
}

// Read but discard a value
func (p PhpSerializer) skipValue(s *scanner.Scanner) error {
	var dummy interface{}
	return p.decode(s, reflect.ValueOf(&dummy).Elem(), true)
}

// Decode an int int v
func (p PhpSerializer) decodeInt(s *scanner.Scanner, v reflect.Value, skipVal bool) (err error) {
	if err = p.decodeToken(s, "i"); err != nil {
		return err
	}
	if err = p.decodeToken(s, ":"); err != nil {
		return err
	}
	s.Scan()
	text := s.TokenText()
	var i int
	nb, err := strconv.Atoi(text)
	if err != nil {
		return err
	}
	i = nb
	if err = p.decodeToken(s, ";"); err != nil {
		return err
	}
	if !skipVal {
		v.Set(reflect.ValueOf(i))
	}
	return err
}

// Decode a float
func (p PhpSerializer) decodeFloat(s *scanner.Scanner, v reflect.Value, skipVal bool) (err error) {
	if err = p.decodeToken(s, "d"); err != nil {
		return err
	}
	if err = p.decodeToken(s, ":"); err != nil {
		return err
	}
	s.Scan()
	text := s.TokenText()
	var f float64
	nb, err := strconv.ParseFloat(text, 64)
	if err != nil {
		return err
	}
	f = nb
	if err = p.decodeToken(s, ";"); err != nil {
		return err
	}
	if !skipVal {
		v.Set(reflect.ValueOf(f))
	}
	return err
}

// Decode a string into v
func (p PhpSerializer) decodeString(s *scanner.Scanner, v reflect.Value, skipVal bool) (err error) {
	if err = p.decodeToken(s, "s"); err != nil {
		return err
	}
	size, err := p.decodeSize(s)
	if err != nil {
		return err
	}
	str := ""
	for len(str) < size+2 {
		str += string(s.Next())
	}
	if str[0] != '"' || str[len(str)-1] != '"' {
		return p.error(s, "Expected string to be surrounded by quotes !", str)
	}
	if err = p.decodeToken(s, ";"); err != nil {
		return err
	}
	str = str[1 : len(str)-1]
	if !skipVal {
		v.Set(reflect.ValueOf(str))
	}
	return err
}

// Decode a token and check against expected value
func (p PhpSerializer) decodeToken(s *scanner.Scanner, expected string) (err error) {
	s.Scan()
	text := s.TokenText()
	if text != expected {
		err = p.error(s, fmt.Sprintf("Expected '%s' !", expected), text)
	}
	return err
}

// Decode and return a size value (ex: ":12:")
func (p PhpSerializer) decodeSize(s *scanner.Scanner) (size int, err error) {
	if err = p.decodeToken(s, ":"); err != nil {
		return size, err
	}
	s.Scan()
	val := s.TokenText()
	size, err = strconv.Atoi(val)
	if err != nil {
		return size, p.error(s, err.Error(), val)
	}
	if err = p.decodeToken(s, ":"); err != nil {
		return size, err
	}
	return size, err
}

// Create a scanning error
func (p PhpSerializer) error(s *scanner.Scanner, msg string, lastTokenText string) error {
	pos := s.Pos()
	msg += fmt.Sprintf(". At Position(%d,%d), got '%s'", pos.Line, pos.Column, lastTokenText)
	return errors.New(msg)
}
<commit_msg>Support for boolean and floats, option to "ignore" marshall/cast errors<commit_after>// History: Sep 03 13 Thibaut Colar Creation

package phpserializer

import (
	"errors"
	"fmt"
	"io"
	"log"
	"reflect"
	"strconv"
	"text/scanner"
)

// Encode / Decode serialized PHP objects from Go / Golang
// Note that there are apparently **NO** PHP serialization format specs, so this might not be perfect.
// Defaults to SnakeToUnderscore & UnderscoreToSnake for Name conversion, but can be replaced.
type PhpSerializer struct {
	EncodeNameConverter NameConverter
	DecodeNameConverter NameConverter
	IgnoreCastErrors    bool // Wether to fail on type mistmatch or ignore and continue
}

// Decode serialized PHP content into target object
// Note: Not closing the reader
func (p PhpSerializer) Decode(reader io.Reader, v interface{}) (err error) {
	if p.DecodeNameConverter == nil {
		p.DecodeNameConverter = &UnderscoreToSnake{}
	}
	s := &scanner.Scanner{}
	s.Init(reader)
	obj := reflect.ValueOf(v)
	err = p.decode(s, obj, false)
	return err
}

func (p PhpSerializer) Encode(v interface{}, writer io.Writer) (err error) {
	if p.EncodeNameConverter == nil {
		p.EncodeNameConverter = &SnakeToUnderscore{}
	}
	return p.encode(v, writer)
}

// #################### Internals ############################################

// Encode a Go object into a serialized PHP and write it to the writer
// Note: Not closing the writer
func (p PhpSerializer) encode(v interface{}, writer io.Writer) (err error) {
	if p.EncodeNameConverter == nil {
		p.EncodeNameConverter = &SnakeToUnderscore{}
	}
	t := reflect.TypeOf(v)
	switch t.Kind() {
	case reflect.Struct:
		val := reflect.ValueOf(v)
		err = p.encodeStruct(val, writer)
	case reflect.Int:
		i, _ := v.(int)
		err = p.encodeInt(i, writer)
	case reflect.Bool:
		b, _ := v.(bool)
		err = p.encodeBool(b, writer)
	case reflect.String:
		str, _ := v.(string)
		err = p.encodeString(str, writer)
	case reflect.Float64:
		b, _ := v.(float64)
		err = p.encodeFloat64(b, writer)
	case reflect.Map:
		val := reflect.ValueOf(v)
		err = p.encodeMap(val, writer)
	default:
		return errors.New(fmt.Sprintf("Unsupported element %s - of type %s",
			reflect.ValueOf(v).String(),
			t.String()))
	}
	return err
}

// Encode a structure
func (p PhpSerializer) encodeStruct(v reflect.Value, writer io.Writer) (err error) {
	size := v.NumField()
	p.write(writer, fmt.Sprintf("a:%d:{", size))
	for i := 0; i < size; i++ {
		// field name
		fieldName := p.EncodeNameConverter.Convert(v.Type().Field(i).Name)
		err = p.encodeString(fieldName, writer)
		if err != nil {
			return err
		}
		// field value
		field := v.Field(i)
		err = p.Encode(field.Interface(), writer)
		if err != nil {
			return err
		}
	}
	err = p.write(writer, "}")
	return err
}

// Encode a float64
func (p PhpSerializer) encodeFloat64(f float64, writer io.Writer) (err error) {
	err = p.write(writer, fmt.Sprintf(`d:%f;`, f))
	return err
}

// Encode a string
func (p PhpSerializer) encodeString(str string, writer io.Writer) (err error) {
	err = p.write(writer, fmt.Sprintf(`s:%d:"%s";`, len(str), str))
	return err
}

// Encode a  boolean
func (p PhpSerializer) encodeBool(b bool, writer io.Writer) (err error) {
	val := 0
	if b {
		val = 1
	}
	err = p.write(writer, fmt.Sprintf(`b:%d;`, val))
	return err
}

// Encode an int
func (p PhpSerializer) encodeInt(i int, writer io.Writer) (err error) {
	err = p.write(writer, fmt.Sprintf("i:%d;", i))
	return err
}

// Encode a map
func (p PhpSerializer) encodeMap(v reflect.Value, writer io.Writer) (err error) {
	size := len(v.MapKeys())
	p.write(writer, fmt.Sprintf("a:%d:{", size))
	for _, key := range v.MapKeys() {
		err = p.Encode(key.Interface(), writer)
		if err != nil {
			return err
		}
		val := v.MapIndex(key)
		err = p.Encode(val.Interface(), writer)
		if err != nil {
			return err
		}
	}
	err = p.write(writer, "}")
	return err
}

// write a string to the writer
func (p PhpSerializer) write(writer io.Writer, str string) (err error) {
	_, err = writer.Write([]byte(str))
	return err
}

// Decode the next element found by the scanner and set value into v
func (p PhpSerializer) decode(s *scanner.Scanner, v reflect.Value, skipVal bool) (err error) {
	c := s.Peek()
	switch c {
	case 'i': // int
		err = p.decodeInt(s, v, skipVal)
	case 's': // string
		err = p.decodeString(s, v, skipVal)
	case 'a': // map / struct  TODO: can be an array/list too ?
		err = p.decodeMap(s, v, skipVal)
	case 'd': // decimal
		err = p.decodeFloat(s, v, skipVal)
	case 'b': //bool
		err = p.decodeBool(s, v, skipVal)
	case 'N': //Null
		err = p.decodeNull(s, v, skipVal)
	//case "O": // TODO ... maybe .... 'O' object types
	default:
		s.Scan()
		err = p.error(s, "Unexpected type %s", s.TokenText())
	}
	return err
}

func (p PhpSerializer) set(receiver reflect.Value, value reflect.Value) {
	kind := value.Kind()
	if receiver.Kind() != reflect.Interface {
		if receiver.Kind() != kind && p.IgnoreCastErrors {
			log.Printf("Ignoring %s, coud not cast into %s", receiver.Kind(), kind)
			return
		}
	}
	// Note: This will panic if value kind does not match receiver kind
	receiver.Set(value)
}

// decode a boolean
func (p PhpSerializer) decodeBool(s *scanner.Scanner, v reflect.Value, skipVal bool) (err error) {
	if err = p.decodeToken(s, "b"); err != nil {
		return err
	}
	if err = p.decodeToken(s, ":"); err != nil {
		return err
	}
	s.Scan()
	val := s.TokenText()
	b := val == "1"
	if err = p.decodeToken(s, ";"); err != nil {
		return err
	}
	if !skipVal {
		p.set(v, reflect.ValueOf(b))
	}
	return err
}

// decode a Null
func (p PhpSerializer) decodeNull(s *scanner.Scanner, v reflect.Value, skipVal bool) (err error) {
	if err = p.decodeToken(s, "N"); err != nil {
		return err
	}
	if err = p.decodeToken(s, ";"); err != nil {
		return err
	}
	// do nothing for no, could do v.SetPointer(nil) ?
	return err
}

// Decode a map into v (map or struct)
func (p PhpSerializer) decodeMap(s *scanner.Scanner, v reflect.Value, skipVal bool) (err error) {
	var size int
	if err = p.decodeToken(s, "a"); err != nil {
		return err
	}
	size, err = p.decodeSize(s)
	if err != nil {
		return err
	}
	if err = p.decodeToken(s, "{"); err != nil {
		return err
	}
	// decode key/value pairs
	for i := 0; i != size; i++ {
		var key interface{}
		err = p.decode(s, reflect.ValueOf(&key).Elem(), skipVal)
		if err != nil {
			return err
		}
		if skipVal {
			// this is just data we need to parse but not do anything with
			err = p.skipValue(s)
			if err != nil {
				return err
			}
		} else {
			if v.Kind() == reflect.Map {
				// targetting a map
				if v.IsNil() {
					// Initialize to an empty map if nil
					v.Set(reflect.MakeMap(v.Type()))
				}
				// Create an instance of the map value type
				mapVal := reflect.New(v.Type().Elem()).Elem()
				// and then decode that
				err = p.decode(s, mapVal, skipVal)
				if err != nil {
					return err
				}
				v.SetMapIndex(reflect.ValueOf(key), mapVal)
			} else {
				// targetting a struct
				var k string
				k, _ = key.(string)
				if k == "" {
					return errors.New(fmt.Sprintf("Struct Key '%s' is not a string.", key))
				}
				target := p.DecodeNameConverter.Convert(k)
				var val reflect.Value
				if v.Kind() == reflect.Struct {
					val = v.FieldByName(target)
				} else {
					val = v.Elem().FieldByName(target)
				}
				if val.IsValid() {
					err = p.decode(s, val, skipVal)
				} else {
					log.Printf("No targets field found named '%s', skipping it !", target)
					err = p.skipValue(s)
				}
				if err != nil {
					return err
				}
			}
		}
	}
	if p.decodeToken(s, "}") != nil {
		return err
	}
	return err
}

// Read but discard a value
func (p PhpSerializer) skipValue(s *scanner.Scanner) error {
	var dummy interface{}
	return p.decode(s, reflect.ValueOf(&dummy).Elem(), true)
}

// Decode an int int v
func (p PhpSerializer) decodeInt(s *scanner.Scanner, v reflect.Value, skipVal bool) (err error) {
	if err = p.decodeToken(s, "i"); err != nil {
		return err
	}
	if err = p.decodeToken(s, ":"); err != nil {
		return err
	}
	s.Scan()
	text := s.TokenText()
	var i int
	nb, err := strconv.Atoi(text)
	if err != nil {
		return err
	}
	i = nb
	if err = p.decodeToken(s, ";"); err != nil {
		return err
	}
	if !skipVal {
		p.set(v, reflect.ValueOf(i))
	}
	return err
}

// Decode a float
func (p PhpSerializer) decodeFloat(s *scanner.Scanner, v reflect.Value, skipVal bool) (err error) {
	if err = p.decodeToken(s, "d"); err != nil {
		return err
	}
	if err = p.decodeToken(s, ":"); err != nil {
		return err
	}
	s.Scan()
	text := s.TokenText()
	var f float64
	nb, err := strconv.ParseFloat(text, 64)
	if err != nil {
		return err
	}
	f = nb
	if err = p.decodeToken(s, ";"); err != nil {
		return err
	}
	if !skipVal {
		p.set(v, reflect.ValueOf(f))
	}
	return err
}

// Decode a string into v
func (p PhpSerializer) decodeString(s *scanner.Scanner, v reflect.Value, skipVal bool) (err error) {
	if err = p.decodeToken(s, "s"); err != nil {
		return err
	}
	size, err := p.decodeSize(s)
	if err != nil {
		return err
	}
	str := ""
	for len(str) < size+2 {
		str += string(s.Next())
	}
	if str[0] != '"' || str[len(str)-1] != '"' {
		return p.error(s, "Expected string to be surrounded by quotes !", str)
	}
	if err = p.decodeToken(s, ";"); err != nil {
		return err
	}
	str = str[1 : len(str)-1]
	if !skipVal {
		p.set(v, reflect.ValueOf(str))
	}
	return err
}

// Decode a token and check against expected value
func (p PhpSerializer) decodeToken(s *scanner.Scanner, expected string) (err error) {
	s.Scan()
	text := s.TokenText()
	//log.Print(text)
	if text != expected {
		err = p.error(s, fmt.Sprintf("Expected '%s' !", expected), text)
	}
	return err
}

// Decode and return a size value (ex: ":12:")
func (p PhpSerializer) decodeSize(s *scanner.Scanner) (size int, err error) {
	if err = p.decodeToken(s, ":"); err != nil {
		return size, err
	}
	s.Scan()
	val := s.TokenText()
	size, err = strconv.Atoi(val)
	if err != nil {
		return size, p.error(s, err.Error(), val)
	}
	if err = p.decodeToken(s, ":"); err != nil {
		return size, err
	}
	return size, err
}

// Create a scanning error
func (p PhpSerializer) error(s *scanner.Scanner, msg string, lastTokenText string) error {
	pos := s.Pos()
	msg += fmt.Sprintf(". At Position(%d,%d), got '%s'", pos.Line, pos.Column, lastTokenText)
	return errors.New(msg)
}
<|endoftext|>


<commit_before>package parser

import (
	"fmt"
	"net/http"

	"github.com/labstack/echo"

	"ratings/responses"
)

type (
	app struct {
		Key     string `json:"key" validate:"required,len=32,alphanum"` // The app key
		Version string `json:"version" validate:"required,alphanum,gte=1,lte=15"`
	}

	user struct {
		Name   string `json:"name" validate:"required,alphanum,gte=3,slte=70"`
		Email  string `json:"email" validate:"required,email,gte=3,lte=100"`
		MiBAID string `json:"mibaId" validate:"required,alphanum,gte=1"`
	}

	platform struct {
		Key     string `json:"key" validate:"required,len=32,alphanum"` // The platform key
		Version string `json:"version" validate:"required,alphanum,gte=1,lte=15"`
	}

	device struct {
		Name   string `json:"name" validate:"required,alphanum,gte=1,lte=30"`
		Brand  string `json:"brand" validate:"required,alphanum,gte=1,lte=30"`
		Screen screen `json:"screen" validate:"required"`
	}

	screen struct {
		Width  int `json:"width" validate:"required,numeric,gt=0"`
		Height int `json:"height" validate:"required,numeric,gt=0"`
		PPI    int `json:"ppi" validate:"required,numeric,gt=0"`
	}

	browser struct {
		Name    string `json:"name" validate:"required,alphanum,gte=1,lte=15"`
		Version string `json:"version" validate:"required,alphanum,gte=1,lte=15"`
	}

	// Request holds the mapped fields from the request's JSON body
	Request struct {
		Rating      uint8    `json:"rating" validate:"required,numeric,min=-127,max=127"`
		Description string   `json:"description" validate:"alphanum,gte=3,lte=30,omitempty"`
		Comment     string   `json:"comment" validate:"alphanum,gte=3,lte=1000,omitempty"`
		Range       string   `json:"range" validate:"required,len=32,alphanum"` // The range key
		App         app      `json:"app" validate:"required"`
		Platform    platform `json:"platform" validate:"required"`
		User        *user    `json:"user" validate:"omitempty"`
		Device      *device  `json:"device" validate:"omitempty"`
		Browser     *browser `json:"browser" validate:"omitempty"`
	}
)

// Parse parses a request's JSON body and maps it to a struct
func Parse(context echo.Context) *Request {
	request := new(Request)

	if err := context.Bind(request); err != nil {
		errorMessage := fmt.Sprintf("Error parsing request: %s", err.Error())

		responses.ErrorResponse(http.StatusBadRequest, errorMessage, context)

		return request
	}

	if err := context.Validate(request); err != nil {
		errorMessage := fmt.Sprintf("Error validating request: %s", err.Error())

		responses.ErrorResponse(http.StatusUnprocessableEntity, errorMessage, context)

		return request
	}

	return request
}
<commit_msg>Added whitespace validation rule<commit_after>package parser

import (
	"fmt"
	"net/http"

	"github.com/labstack/echo"

	"ratings/responses"
)

type (
	app struct {
		Key     string `json:"key" validate:"required,len=32,alphanum,excludesall= "` // The app key
		Version string `json:"version" validate:"required,alphanum,gte=1,lte=15,excludesall= "`
	}

	user struct {
		Name   string `json:"name" validate:"required,alphanum,gte=3,slte=70,excludesall= "`
		Email  string `json:"email" validate:"required,email,gte=3,lte=100,excludesall= "`
		MiBAID string `json:"mibaId" validate:"required,alphanum,gte=1,excludesall= "`
	}

	platform struct {
		Key     string `json:"key" validate:"required,len=32,alphanum,excludesall= "` // The platform key
		Version string `json:"version" validate:"required,alphanum,gte=1,lte=15,excludesall= "`
	}

	device struct {
		Name   string `json:"name" validate:"required,alphanum,gte=1,lte=30,excludesall= "`
		Brand  string `json:"brand" validate:"required,alphanum,gte=1,lte=30,excludesall= "`
		Screen screen `json:"screen" validate:"required"`
	}

	screen struct {
		Width  int `json:"width" validate:"required,numeric,gt=0"`
		Height int `json:"height" validate:"required,numeric,gt=0"`
		PPI    int `json:"ppi" validate:"required,numeric,gt=0"`
	}

	browser struct {
		Name    string `json:"name" validate:"required,alphanum,gte=1,lte=15,excludesall= "`
		Version string `json:"version" validate:"required,alphanum,gte=1,lte=15,excludesall= "`
	}

	// Request holds the mapped fields from the request's JSON body
	Request struct {
		Rating      uint8    `json:"rating" validate:"required,numeric,min=-127,max=127"`
		Description string   `json:"description" validate:"alphanum,gte=3,lte=30,omitempty,excludesall= "`
		Comment     string   `json:"comment" validate:"alphanum,gte=3,lte=1000,omitempty,excludesall= "`
		Range       string   `json:"range" validate:"required,len=32,alphanum,excludesall= "` // The range key
		App         app      `json:"app" validate:"required"`
		Platform    platform `json:"platform" validate:"required"`
		User        *user    `json:"user" validate:"omitempty"`
		Device      *device  `json:"device" validate:"omitempty"`
		Browser     *browser `json:"browser" validate:"omitempty"`
	}
)

// Parse parses a request's JSON body and maps it to a struct
func Parse(context echo.Context) *Request {
	request := new(Request)

	if err := context.Bind(request); err != nil {
		errorMessage := fmt.Sprintf("Error parsing request: %s", err.Error())

		responses.ErrorResponse(http.StatusBadRequest, errorMessage, context)

		return request
	}

	if err := context.Validate(request); err != nil {
		errorMessage := fmt.Sprintf("Error validating request: %s", err.Error())

		responses.ErrorResponse(http.StatusUnprocessableEntity, errorMessage, context)

		return request
	}

	return request
}
<|endoftext|>


<commit_before>package haxeremote

import (
	"encoding/base64"
	"errors"
	"fmt"
	"io/ioutil"
	"log"
	"math"
	"net/http"
	"net/url"
	"strconv"
	"sync"
)

var haxeRemoteFuncs = map[string]func(interface{}) interface{}{}
var haxeRemoteFuncsMutex sync.Mutex

func AddFunc(name string, fn func(interface{}) interface{}) {
	haxeRemoteFuncsMutex.Lock()
	haxeRemoteFuncs[name] = fn
	haxeRemoteFuncsMutex.Unlock()
}

func callHaxeRemoteFunc(name string, arg interface{}) (interface{}, error) {
	haxeRemoteFuncsMutex.Lock()
	fn, ok := haxeRemoteFuncs[name]
	haxeRemoteFuncsMutex.Unlock()
	if !ok {
		return nil, errors.New("could not find remote func " + name + " in CallHaxeRemoteFunc()")
	}
	return fn(arg), nil
}

func HttpHandler(rw http.ResponseWriter, req *http.Request) {
	// TODO add further validaton of the request as from a valid Haxe user here?

	if req.Header.Get("X-Haxe-Remoting") != "1" {
		rw.WriteHeader(http.StatusBadRequest)
		log.Panic("header does not contain X-Haxe-Remoting=1")
		return
	}

	p, e := ioutil.ReadAll(req.Body)
	//fmt.Printf("DEBUG Request: %#v\nBody: %s, err=%v\n", *req, p, e)
	if e != nil {
		rw.WriteHeader(http.StatusBadRequest)
		log.Panic(e)
		return
	}
	if len(p) < 4 {
		rw.WriteHeader(http.StatusBadRequest)
		log.Panic("no body")
		return
	}
	if string(p[:4]) != "__x=" {
		rw.WriteHeader(http.StatusBadRequest)
		log.Panic("__x= not found")
		return
	}
	une, err := url.QueryUnescape(string(p[4:]))
	if err != nil {
		rw.WriteHeader(http.StatusBadRequest)
		log.Panic(err)
		return
	}
	buf := []byte(une)
	//fmt.Printf("DEBUG URL unescaped = %s\n", string(buf))
	var targetA interface{}
	target := ""
	targetA, buf, err = Unserialize(buf)
	for i, t := range targetA.([]interface{}) {
		if i > 0 {
			target += "."
		}
		target += t.(string)
	}
	//fmt.Printf("DEBUG Unserialized Target decoded=%s, remaining=%s, error=%v\n", target, buf, err)
	var args interface{}
	args, buf, err = Unserialize(buf)
	//fmt.Printf("DEBUG Unserialized Args decoded=%v, remaining=%s, error=%v\n", args, buf, err)

	results, err := callHaxeRemoteFunc(target, args)
	if err != nil {
		rw.WriteHeader(http.StatusBadRequest)
		log.Panic(err)
		return
	}
	reply := "hxr" + Serialize(results)
	fmt.Fprintln(rw, reply)
	//fmt.Println("DEBUG reply: " + reply)
}

/* Haxe serialization prefixes :
a : array
b : hash
c : class
d : Float
e : reserved (float exp)
f : false
g : object end
h : array/list/hash end
i : Int
j : enum (by index)
k : NaN
l : list
m : -Inf
n : null
o : object
p : +Inf
q : haxe.ds.IntMap
r : reference
s : bytes (base64)
t : true
u : array nulls
v : date
w : enum
x : exception
y : urlencoded string
z : zero
A : Class<Dynamic>
B : Enum<Dynamic>
M : haxe.ds.ObjectMap
C : custom
*/

func Unserialize(buf []byte) (data interface{}, remains []byte, err error) {
	//fmt.Println("DEBUG Unserialize:", string(buf))
	if len(buf) == 0 {
		return nil, nil, nil
	}
	code := buf[0]
	remains = buf[1:]
	switch code {
	case 'a': // Array
		dataArray := []interface{}{}
		var item interface{}
	arrayLoop:
		item, remains, err = Unserialize(remains)
		if err != nil {
			return nil, nil, err
		}
		dataArray = append(dataArray, item)
	arrayLoopEnd:
		if len(remains) > 0 && err == nil {
			switch remains[0] {
			default:
				goto arrayLoop
			case 'u': // some number of null/nil entries
				remains = remains[1:]
				for i := range remains {
					switch remains[i] {
					case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
					default:
						strInt := remains[:i]
						remains = remains[i:]
						numNil := 0
						numNil, err = strconv.Atoi(string(strInt))
						for j := 0; j < numNil; j++ {
							dataArray = append(dataArray, nil)
						}
						goto arrayLoopEnd
					}
				}
				return nil, nil,
					errors.New("invalid u item in Haxe array serialization: " + string(remains))
			case 'h': // end of array
				remains = remains[1:]
			}
		}
		data = dataArray

	case 'i': // Integer
		for i := range remains {
			switch remains[i] {
			case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
			default:
				strInt := remains[:i]
				remains = remains[i:]
				data, err = strconv.Atoi(string(strInt))
				goto done
			}
		}

	case 'y': // String
		for i, j := range remains {
			switch j {
			case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
				// NoOp
			case ':':
				strInt := string(remains[:i])
				remains = remains[i+1:]
				var length int
				length, err = strconv.Atoi(strInt)
				//fmt.Printf("DEBUG string len: %s, %d, %v\n", strInt, length, err)
				if err == nil {
					raw := string(remains[:length])
					clean, err := url.QueryUnescape(raw)
					if err == nil {
						data = clean
					} else {
						data = raw
					}
					remains = remains[length:]
					//fmt.Printf("DEBUG string decoded, remaining: %s, %s\n", data, remains)
				}
				goto done
			default:
				err = errors.New("unrecognised string length: " + string(remains))
			}
		}

	case 'd': // Float
		for i := range remains {
			switch remains[i] {
			case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.', 'e':
			default:
				strFloat := string(remains[:i])
				remains = remains[i:]
				data, err = strconv.ParseFloat(strFloat, 64)
				goto done
			}
		}

		// the single letter values
	case 'n':
		data = nil
	case 't':
		data = true
	case 'f':
		data = false
	case 'k':
		data = math.NaN()
	case 'p':
		data = math.Inf(+1)
	case 'm':
		data = math.Inf(-1)
	case 'z': // TODO should zero be floating point?
		data = 0

	case 's': // haxe.io.Bytes
		for i, j := range remains {
			switch j {
			case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
				// NoOp
			case ':':
				strInt := string(remains[:i])
				remains = remains[i+1:]
				var length int
				length, err = strconv.Atoi(strInt)
				if err == nil {
					raw := string(remains[:length])
					//println("DEBUG raw=" + raw)
					data, err = base64.StdEncoding.DecodeString(raw)
					//fmt.Printf("DEBUG data %v:%T err=%v\n", data, data, err)
					remains = remains[length:]
				}
				goto done
			default:
				err = errors.New("unrecognised string length: " + string(remains))
			}
		}

		// TODO many more letters !

	default:
		err = errors.New("unhandled Haxe serialization code: " + string(code))
	}
done:
	return
}

func Serialize(data interface{}) string {
	if data == nil {
		return "n"
	}
	switch data.(type) {
	case int: // TODO special processing for 0=>z ?
		return fmt.Sprintf("i%d", data.(int))

	case string:
		result := url.QueryEscape(data.(string))
		return fmt.Sprintf("y%d:%s", len(result), result)

	case bool:
		if data.(bool) {
			return "t"
		}
		return "f"

	case float64: // TODO special processing for 0=>z ?
		val := data.(float64)
		if math.IsInf(val, -1) {
			return "m"
		}
		if math.IsInf(val, +1) {
			return "p"
		}
		if math.IsNaN(val) {
			return "k"
		}
		return "d" + strconv.FormatFloat(val, 'e', -1, 64)

	case []interface{}:
		ret := "a"
		nilCount := 0
		for _, a := range data.([]interface{}) {
			if a == nil {
				nilCount++
			} else {
				if nilCount > 0 {
					ret += fmt.Sprintf("u%d", nilCount)
					nilCount = 0
				}
				ret += Serialize(a)
			}
		}
		if nilCount > 0 {
			ret += fmt.Sprintf("u%d", nilCount)
		}
		return ret + "h"

	case []byte:
		strForm := base64.StdEncoding.EncodeToString(data.([]byte))
		return fmt.Sprintf("s%d:%s", len(strForm), strForm)

	default:
		panic(fmt.Sprintf("unhandled type to be serialized for Haxe %v : %T", data, data))
	}
}
<commit_msg>add debug msg comment<commit_after>package haxeremote

import (
	"encoding/base64"
	"errors"
	"fmt"
	"io/ioutil"
	"log"
	"math"
	"net/http"
	"net/url"
	"strconv"
	"sync"
)

var haxeRemoteFuncs = map[string]func(interface{}) interface{}{}
var haxeRemoteFuncsMutex sync.Mutex

func AddFunc(name string, fn func(interface{}) interface{}) {
	haxeRemoteFuncsMutex.Lock()
	haxeRemoteFuncs[name] = fn
	haxeRemoteFuncsMutex.Unlock()
}

func callHaxeRemoteFunc(name string, arg interface{}) (interface{}, error) {
	haxeRemoteFuncsMutex.Lock()
	fn, ok := haxeRemoteFuncs[name]
	haxeRemoteFuncsMutex.Unlock()
	if !ok {
		return nil, errors.New("could not find remote func " + name + " in CallHaxeRemoteFunc()")
	}
	return fn(arg), nil
}

func HttpHandler(rw http.ResponseWriter, req *http.Request) {
	// TODO add further validaton of the request as from a valid Haxe user here?

	if req.Header.Get("X-Haxe-Remoting") != "1" {
		rw.WriteHeader(http.StatusBadRequest)
		log.Panic("header does not contain X-Haxe-Remoting=1")
		return
	}

	p, e := ioutil.ReadAll(req.Body)
	//fmt.Printf("DEBUG Request: %#v\nBody: %s, err=%v\n", *req, p, e)
	if e != nil {
		rw.WriteHeader(http.StatusBadRequest)
		log.Panic(e)
		return
	}
	if len(p) < 4 {
		rw.WriteHeader(http.StatusBadRequest)
		log.Panic("no body")
		return
	}
	if string(p[:4]) != "__x=" {
		rw.WriteHeader(http.StatusBadRequest)
		log.Panic("__x= not found")
		return
	}
	une, err := url.QueryUnescape(string(p[4:]))
	if err != nil {
		rw.WriteHeader(http.StatusBadRequest)
		log.Panic(err)
		return
	}
	buf := []byte(une)
	//fmt.Printf("DEBUG URL unescaped = %s\n", string(buf))
	var targetA interface{}
	target := ""
	targetA, buf, err = Unserialize(buf)
	for i, t := range targetA.([]interface{}) {
		if i > 0 {
			target += "."
		}
		target += t.(string)
	}
	//fmt.Printf("DEBUG Unserialized Target decoded=%s, remaining=%s, error=%v\n", target, buf, err)
	var args interface{}
	args, buf, err = Unserialize(buf)
	//fmt.Printf("DEBUG Unserialized Args decoded=%v, remaining=%s, error=%v\n", args, buf, err)

	results, err := callHaxeRemoteFunc(target, args)
	if err != nil {
		rw.WriteHeader(http.StatusBadRequest)
		log.Panic(err)
		return
	}
	reply := "hxr" + Serialize(results)
	fmt.Fprintln(rw, reply)
	//fmt.Printf("DEBUG haxe http remote results: %v serialized-reply: %s\n", results, reply)
}

/* Haxe serialization prefixes :
a : array
b : hash
c : class
d : Float
e : reserved (float exp)
f : false
g : object end
h : array/list/hash end
i : Int
j : enum (by index)
k : NaN
l : list
m : -Inf
n : null
o : object
p : +Inf
q : haxe.ds.IntMap
r : reference
s : bytes (base64)
t : true
u : array nulls
v : date
w : enum
x : exception
y : urlencoded string
z : zero
A : Class<Dynamic>
B : Enum<Dynamic>
M : haxe.ds.ObjectMap
C : custom
*/

func Unserialize(buf []byte) (data interface{}, remains []byte, err error) {
	//fmt.Println("DEBUG Unserialize:", string(buf))
	if len(buf) == 0 {
		return nil, nil, nil
	}
	code := buf[0]
	remains = buf[1:]
	switch code {
	case 'a': // Array
		dataArray := []interface{}{}
		var item interface{}
	arrayLoop:
		item, remains, err = Unserialize(remains)
		if err != nil {
			return nil, nil, err
		}
		dataArray = append(dataArray, item)
	arrayLoopEnd:
		if len(remains) > 0 && err == nil {
			switch remains[0] {
			default:
				goto arrayLoop
			case 'u': // some number of null/nil entries
				remains = remains[1:]
				for i := range remains {
					switch remains[i] {
					case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
					default:
						strInt := remains[:i]
						remains = remains[i:]
						numNil := 0
						numNil, err = strconv.Atoi(string(strInt))
						for j := 0; j < numNil; j++ {
							dataArray = append(dataArray, nil)
						}
						goto arrayLoopEnd
					}
				}
				return nil, nil,
					errors.New("invalid u item in Haxe array serialization: " + string(remains))
			case 'h': // end of array
				remains = remains[1:]
			}
		}
		data = dataArray

	case 'i': // Integer
		for i := range remains {
			switch remains[i] {
			case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
			default:
				strInt := remains[:i]
				remains = remains[i:]
				data, err = strconv.Atoi(string(strInt))
				goto done
			}
		}

	case 'y': // String
		for i, j := range remains {
			switch j {
			case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
				// NoOp
			case ':':
				strInt := string(remains[:i])
				remains = remains[i+1:]
				var length int
				length, err = strconv.Atoi(strInt)
				//fmt.Printf("DEBUG string len: %s, %d, %v\n", strInt, length, err)
				if err == nil {
					raw := string(remains[:length])
					clean, err := url.QueryUnescape(raw)
					if err == nil {
						data = clean
					} else {
						data = raw
					}
					remains = remains[length:]
					//fmt.Printf("DEBUG string decoded, remaining: %s, %s\n", data, remains)
				}
				goto done
			default:
				err = errors.New("unrecognised string length: " + string(remains))
			}
		}

	case 'd': // Float
		for i := range remains {
			switch remains[i] {
			case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.', 'e':
			default:
				strFloat := string(remains[:i])
				remains = remains[i:]
				data, err = strconv.ParseFloat(strFloat, 64)
				goto done
			}
		}

		// the single letter values
	case 'n':
		data = nil
	case 't':
		data = true
	case 'f':
		data = false
	case 'k':
		data = math.NaN()
	case 'p':
		data = math.Inf(+1)
	case 'm':
		data = math.Inf(-1)
	case 'z': // TODO should zero be floating point?
		data = 0

	case 's': // haxe.io.Bytes
		for i, j := range remains {
			switch j {
			case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
				// NoOp
			case ':':
				strInt := string(remains[:i])
				remains = remains[i+1:]
				var length int
				length, err = strconv.Atoi(strInt)
				if err == nil {
					raw := string(remains[:length])
					//println("DEBUG raw=" + raw)
					data, err = base64.StdEncoding.DecodeString(raw)
					//fmt.Printf("DEBUG data %v:%T err=%v\n", data, data, err)
					remains = remains[length:]
				}
				goto done
			default:
				err = errors.New("unrecognised string length: " + string(remains))
			}
		}

		// TODO many more letters !

	default:
		err = errors.New("unhandled Haxe serialization code: " + string(code))
	}
done:
	return
}

func Serialize(data interface{}) string {
	if data == nil {
		return "n"
	}
	switch data.(type) {
	case int: // TODO special processing for 0=>z ?
		return fmt.Sprintf("i%d", data.(int))

	case string:
		result := url.QueryEscape(data.(string))
		return fmt.Sprintf("y%d:%s", len(result), result)

	case bool:
		if data.(bool) {
			return "t"
		}
		return "f"

	case float64: // TODO special processing for 0=>z ?
		val := data.(float64)
		if math.IsInf(val, -1) {
			return "m"
		}
		if math.IsInf(val, +1) {
			return "p"
		}
		if math.IsNaN(val) {
			return "k"
		}
		return "d" + strconv.FormatFloat(val, 'e', -1, 64)

	case []interface{}:
		ret := "a"
		nilCount := 0
		for _, a := range data.([]interface{}) {
			if a == nil {
				nilCount++
			} else {
				if nilCount > 0 {
					ret += fmt.Sprintf("u%d", nilCount)
					nilCount = 0
				}
				ret += Serialize(a)
			}
		}
		if nilCount > 0 {
			ret += fmt.Sprintf("u%d", nilCount)
		}
		return ret + "h"

	case []byte:
		strForm := base64.StdEncoding.EncodeToString(data.([]byte))
		return fmt.Sprintf("s%d:%s", len(strForm), strForm)

	default:
		panic(fmt.Sprintf("unhandled type to be serialized for Haxe %v : %T", data, data))
	}
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"

	"code.google.com/p/go.net/idna"
	"github.com/codegangsta/cli"
	"github.com/miekg/dns"
)

// POST||GET /notify
func HTTPNotifyHandler(w http.ResponseWriter, r *http.Request) {
	if r.FormValue("key") != apiKey {
		http.Error(w, "Auth failed: incorrect key", 403)
		return
	}
	log.Println("Got HTTP prefetch notification, reloading zones...")
	prefetch(zones, false)
	fmt.Fprintln(w, "ok")
}

// POST /notify/zones
func HTTPNotifyZonesHandler(w http.ResponseWriter, r *http.Request) {
	if r.Method != "POST" {
		http.Error(w, "Method not allowed", 405)
	}
	if r.FormValue("key") != apiKey {
		http.Error(w, "Auth failed: incorrect key", 403)
		return
	}
	log.Println("Got HTTP zone push notification, updating cache...")
	tmpmap := make(map[string][]Record)
	body, err := ioutil.ReadAll(r.Body)
	if err != nil {
		http.Error(w, "Error reading response body: "+err.Error(), 400)
		return
	}
	if err := json.Unmarshal(body, &tmpmap); err != nil {
		http.Error(w, "Error parsing JSON zones file: "+err.Error(), 400)
		return
	}

	zones.Lock()
	defer zones.Unlock()
	for key, value := range tmpmap {
		key = dns.Fqdn(key)
		if cdn, e := idna.ToASCII(key); e == nil {
			key = cdn
		}
		zones.store[key] = make(map[dns.RR_Header][]dns.RR)
		for _, r := range value {
			if cdn, e := idna.ToASCII(r.Name); e == nil {
				r.Name = cdn
			}
			rr, err := dns.NewRR(dns.Fqdn(r.Name) + " " + r.Class + " " + r.Type + " " + r.Data)
			if err == nil {
				rr.Header().Ttl = r.Ttl
				key2 := dns.RR_Header{Name: dns.Fqdn(rr.Header().Name), Rrtype: rr.Header().Rrtype, Class: rr.Header().Class}
				zones.store[key][key2] = append(zones.store[key][key2], rr)
			} else {
				log.Printf("Skipping problematic record: %+v\nError: %+v\n", r, err)
			}
		}
	}
	fmt.Fprintf(w, "Loaded %d zone(s) in cache\n", len(tmpmap))
	fmt.Fprintln(w, "ok")
	log.Printf("Loaded %d zone(s) in cache\n", len(tmpmap))
}

// GET /hits
func HTTPHitsHandler(w http.ResponseWriter, r *http.Request) {
	zs := zones
	if r.Method != "GET" {
		http.Error(w, "Method not allowed", 405)
	}
	if r.FormValue("key") != apiKey {
		http.Error(w, "Auth failed: incorrect key", 403)
		return
	}

	zs.RLock()
	defer zs.RUnlock()
	w.Header().Set("Content-Type", "application/json")
	json, _ := json.MarshalIndent(zs.hits, "", `   `)
	fmt.Fprintf(w, "%s", json)
}

// GET /zones
func HTTPZonesHandler(w http.ResponseWriter, r *http.Request) {
	if r.Method != "GET" {
		http.Error(w, "Method not allowed", 405)
	}
	if r.FormValue("key") != apiKey {
		http.Error(w, "Auth failed: incorrect key", 403)
		return
	}

	zones.RLock()
	defer zones.RUnlock()
	w.Header().Set("Content-Type", "application/json")
	json, err := json.MarshalIndent(zones.store, "", `   `)
	if err == nil {
		fmt.Fprintf(w, "%s", json)
	} else {
		http.Error(w, err.Error(), 500)
	}
}

func StartHTTP(c *cli.Context) {
	handlers := http.NewServeMux()
	handlers.HandleFunc("/notify", HTTPNotifyHandler)
	handlers.HandleFunc("/notify/zones", HTTPNotifyZonesHandler)
	handlers.HandleFunc("/hits", HTTPHitsHandler)
	handlers.HandleFunc("/zones", HTTPZonesHandler)

	log.Println("Starting HTTP notification listener on", c.String("http-listen"))
	log.Fatal(http.ListenAndServeTLS(c.String("http-listen"),
		c.String("ssl-cert"), c.String("ssl-key"), handlers))

}
<commit_msg>Fix IDNA URL<commit_after>package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"

	"github.com/codegangsta/cli"
	"github.com/miekg/dns"
	"golang.org/x/net/idna"
)

// POST||GET /notify
func HTTPNotifyHandler(w http.ResponseWriter, r *http.Request) {
	if r.FormValue("key") != apiKey {
		http.Error(w, "Auth failed: incorrect key", 403)
		return
	}
	log.Println("Got HTTP prefetch notification, reloading zones...")
	prefetch(zones, false)
	fmt.Fprintln(w, "ok")
}

// POST /notify/zones
func HTTPNotifyZonesHandler(w http.ResponseWriter, r *http.Request) {
	if r.Method != "POST" {
		http.Error(w, "Method not allowed", 405)
	}
	if r.FormValue("key") != apiKey {
		http.Error(w, "Auth failed: incorrect key", 403)
		return
	}
	log.Println("Got HTTP zone push notification, updating cache...")
	tmpmap := make(map[string][]Record)
	body, err := ioutil.ReadAll(r.Body)
	if err != nil {
		http.Error(w, "Error reading response body: "+err.Error(), 400)
		return
	}
	if err := json.Unmarshal(body, &tmpmap); err != nil {
		http.Error(w, "Error parsing JSON zones file: "+err.Error(), 400)
		return
	}

	zones.Lock()
	defer zones.Unlock()
	for key, value := range tmpmap {
		key = dns.Fqdn(key)
		if cdn, e := idna.ToASCII(key); e == nil {
			key = cdn
		}
		zones.store[key] = make(map[dns.RR_Header][]dns.RR)
		for _, r := range value {
			if cdn, e := idna.ToASCII(r.Name); e == nil {
				r.Name = cdn
			}
			rr, err := dns.NewRR(dns.Fqdn(r.Name) + " " + r.Class + " " + r.Type + " " + r.Data)
			if err == nil {
				rr.Header().Ttl = r.Ttl
				key2 := dns.RR_Header{Name: dns.Fqdn(rr.Header().Name), Rrtype: rr.Header().Rrtype, Class: rr.Header().Class}
				zones.store[key][key2] = append(zones.store[key][key2], rr)
			} else {
				log.Printf("Skipping problematic record: %+v\nError: %+v\n", r, err)
			}
		}
	}
	fmt.Fprintf(w, "Loaded %d zone(s) in cache\n", len(tmpmap))
	fmt.Fprintln(w, "ok")
	log.Printf("Loaded %d zone(s) in cache\n", len(tmpmap))
}

// GET /hits
func HTTPHitsHandler(w http.ResponseWriter, r *http.Request) {
	zs := zones
	if r.Method != "GET" {
		http.Error(w, "Method not allowed", 405)
	}
	if r.FormValue("key") != apiKey {
		http.Error(w, "Auth failed: incorrect key", 403)
		return
	}

	zs.RLock()
	defer zs.RUnlock()
	w.Header().Set("Content-Type", "application/json")
	json, _ := json.MarshalIndent(zs.hits, "", `   `)
	fmt.Fprintf(w, "%s", json)
}

// GET /zones
func HTTPZonesHandler(w http.ResponseWriter, r *http.Request) {
	if r.Method != "GET" {
		http.Error(w, "Method not allowed", 405)
	}
	if r.FormValue("key") != apiKey {
		http.Error(w, "Auth failed: incorrect key", 403)
		return
	}

	zones.RLock()
	defer zones.RUnlock()
	w.Header().Set("Content-Type", "application/json")
	json, err := json.MarshalIndent(zones.store, "", `   `)
	if err == nil {
		fmt.Fprintf(w, "%s", json)
	} else {
		http.Error(w, err.Error(), 500)
	}
}

func StartHTTP(c *cli.Context) {
	handlers := http.NewServeMux()
	handlers.HandleFunc("/notify", HTTPNotifyHandler)
	handlers.HandleFunc("/notify/zones", HTTPNotifyZonesHandler)
	handlers.HandleFunc("/hits", HTTPHitsHandler)
	handlers.HandleFunc("/zones", HTTPZonesHandler)

	log.Println("Starting HTTP notification listener on", c.String("http-listen"))
	log.Fatal(http.ListenAndServeTLS(c.String("http-listen"),
		c.String("ssl-cert"), c.String("ssl-key"), handlers))

}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"net"
	"net/http"
	"strconv"
	"strings"
	"time"

	"github.com/litl/galaxy/utils"
	log "github.com/mailgun/gotools-log"
	"github.com/mailgun/vulcan"
	"github.com/mailgun/vulcan/endpoint"
	"github.com/mailgun/vulcan/loadbalance/roundrobin"
	"github.com/mailgun/vulcan/location/httploc"
	"github.com/mailgun/vulcan/request"
	"github.com/mailgun/vulcan/route"
	"github.com/mailgun/vulcan/route/hostroute"
)

var (
	httpRouter *HTTPRouter
)

type RequestLogger struct{}

type HTTPRouter struct {
	router    *hostroute.HostRouter
	balancers map[string]*roundrobin.RoundRobin
}

func (r *RequestLogger) ObserveRequest(req request.Request) {}

func (r *RequestLogger) ObserveResponse(req request.Request, a request.Attempt) {
	err := ""
	statusCode := ""
	if a.GetError() != nil {
		err = " err=" + a.GetError().Error()
	}

	if a.GetResponse() != nil {
		statusCode = " status=" + strconv.FormatInt(int64(a.GetResponse().StatusCode), 10)
	}

	log.Infof("id=%d method=%s clientIp=%s url=%s backend=%s%s duration=%s%s",
		req.GetId(),
		req.GetHttpRequest().Method,
		req.GetHttpRequest().RemoteAddr,
		req.GetHttpRequest().Host+req.GetHttpRequest().RequestURI,
		a.GetEndpoint(),
		statusCode, a.GetDuration(), err)
}

func NewHTTPRouter() *HTTPRouter {
	return &HTTPRouter{
		balancers: make(map[string]*roundrobin.RoundRobin),
	}
}

func (s *HTTPRouter) AddBackend(name, vhost, url string) error {

	var err error
	balancer := s.balancers[vhost]

	if balancer == nil {
		// Create a round robin load balancer with some endpoints
		balancer, err = roundrobin.NewRoundRobin()
		if err != nil {
			return err
		}

		// Create a http location with the load balancer we've just added
		loc, err := httploc.NewLocationWithOptions(name, balancer,
			httploc.Options{
				TrustForwardHeader: true,
			})
		if err != nil {
			return err
		}
		loc.GetObserverChain().Add("logger", &RequestLogger{})

		s.router.SetRouter(vhost, &route.ConstRouter{Location: loc})
		log.Infof("Creating balancer for %s", vhost)
		s.balancers[vhost] = balancer
	}

	// Already registered?
	if balancer.FindEndpointByUrl(url) != nil {
		return nil
	}
	endpoint := endpoint.MustParseUrl(url)
	log.Infof("Adding %s endpoint %s", vhost, endpoint.GetUrl())
	err = balancer.AddEndpoint(endpoint)
	if err != nil {
		return err
	}
	return nil
}

func (s *HTTPRouter) RemoveBackend(vhost, url string) error {
	balancer := s.balancers[vhost]
	if balancer == nil {
		return nil
	}

	endpoint := balancer.FindEndpointByUrl(url)
	if endpoint == nil {
		return nil
	}
	log.Infof("Removing %s endpoint %s", vhost, endpoint.GetUrl())
	balancer.RemoveEndpoint(endpoint)

	endpoints := balancer.GetEndpoints()
	if len(endpoints) == 0 {
		s.RemoveRouter(vhost)
	}
	return nil
}

// Remove all backends for vhost that are not listed in addrs
func (s *HTTPRouter) RemoveBackends(vhost string, addrs []string) {
	// Remove backends that are no longer registered

	balancer := s.balancers[vhost]
	if balancer == nil {
		return
	}

	endpoints := balancer.GetEndpoints()
	for _, endpoint := range endpoints {
		if !utils.StringInSlice(endpoint.GetUrl().String(), addrs) {
			s.RemoveBackend(vhost, endpoint.GetUrl().String())
		}
	}
}

// Removes a virtual host router
func (s *HTTPRouter) RemoveRouter(vhost string) {
	log.Infof("Removing balancer for %s", vhost)
	delete(s.balancers, vhost)
	s.router.RemoveRouter(vhost)
}

func (s *HTTPRouter) adminHandler(w http.ResponseWriter, r *http.Request) {
	if len(s.balancers) == 0 {
		w.WriteHeader(503)
		return
	}
	for k, _ := range s.balancers {
		balancer := s.balancers[k]
		endpoints := balancer.GetEndpoints()
		fmt.Fprintf(w, "%s\n", k)
		for _, endpoint := range endpoints {
			fmt.Fprintf(w, "  %s\t%d\t%d\t%0.2f\n", endpoint.GetUrl(), endpoint.GetOriginalWeight(), endpoint.GetEffectiveWeight(), endpoint.GetMeter().GetRate())
		}
	}
}

func (s *HTTPRouter) statusHandler(h http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		var err error
		host := r.Host
		if strings.Contains(host, ":") {
			host, _, err = net.SplitHostPort(r.Host)
			if err != nil {
				log.Warningf("%s", err)
				h.ServeHTTP(w, r)
				return
			}
		}

		if _, exists := s.balancers[host]; !exists {
			s.adminHandler(w, r)
			return
		}
		h.ServeHTTP(w, r)
	})
}

func (s *HTTPRouter) Start() {
	// init the logging package
	log.Init([]*log.LogConfig{
		&log.LogConfig{Name: "console"},
	})

	log.Infof("Listening at %s", listenAddr)

	s.router = hostroute.NewHostRouter()

	proxy, err := vulcan.NewProxy(s.router)
	if err != nil {
		log.Fatalf("Error: %s", err)
	}

	// Proxy acts as http handler:
	server := &http.Server{
		Addr:           listenAddr,
		Handler:        s.statusHandler(proxy),
		ReadTimeout:    60 * time.Second,
		WriteTimeout:   60 * time.Second,
		MaxHeaderBytes: 1 << 20,
	}

	log.Errorf("%s", server.ListenAndServe())
}

func startHTTPServer() {
	defer wg.Done()
	httpRouter = NewHTTPRouter()
	httpRouter.Start()
}
<commit_msg>Use galaxy logger<commit_after>package main

import (
	"fmt"
	"net"
	"net/http"
	"strconv"
	"strings"
	"time"

	"github.com/litl/galaxy/utils"

	"github.com/litl/galaxy/log"
	gotoolslog "github.com/mailgun/gotools-log"
	"github.com/mailgun/vulcan"
	"github.com/mailgun/vulcan/endpoint"
	"github.com/mailgun/vulcan/loadbalance/roundrobin"
	"github.com/mailgun/vulcan/location/httploc"
	"github.com/mailgun/vulcan/request"
	"github.com/mailgun/vulcan/route"
	"github.com/mailgun/vulcan/route/hostroute"
)

var (
	httpRouter *HTTPRouter
)

type RequestLogger struct{}

type HTTPRouter struct {
	router    *hostroute.HostRouter
	balancers map[string]*roundrobin.RoundRobin
}

func (r *RequestLogger) ObserveRequest(req request.Request) {}

func (r *RequestLogger) ObserveResponse(req request.Request, a request.Attempt) {
	err := ""
	statusCode := ""
	if a.GetError() != nil {
		err = " err=" + a.GetError().Error()
	}

	if a.GetResponse() != nil {
		statusCode = " status=" + strconv.FormatInt(int64(a.GetResponse().StatusCode), 10)
	}

	log.Printf("id=%d method=%s clientIp=%s url=%s backend=%s%s duration=%s%s",
		req.GetId(),
		req.GetHttpRequest().Method,
		req.GetHttpRequest().RemoteAddr,
		req.GetHttpRequest().Host+req.GetHttpRequest().RequestURI,
		a.GetEndpoint(),
		statusCode, a.GetDuration(), err)
}

func NewHTTPRouter() *HTTPRouter {
	return &HTTPRouter{
		balancers: make(map[string]*roundrobin.RoundRobin),
	}
}

func (s *HTTPRouter) AddBackend(name, vhost, url string) error {

	var err error
	balancer := s.balancers[vhost]

	if balancer == nil {
		// Create a round robin load balancer with some endpoints
		balancer, err = roundrobin.NewRoundRobin()
		if err != nil {
			return err
		}

		// Create a http location with the load balancer we've just added
		loc, err := httploc.NewLocationWithOptions(name, balancer,
			httploc.Options{
				TrustForwardHeader: true,
			})
		if err != nil {
			return err
		}
		loc.GetObserverChain().Add("logger", &RequestLogger{})

		s.router.SetRouter(vhost, &route.ConstRouter{Location: loc})
		log.Printf("Creating balancer for %s", vhost)
		s.balancers[vhost] = balancer
	}

	// Already registered?
	if balancer.FindEndpointByUrl(url) != nil {
		return nil
	}
	endpoint := endpoint.MustParseUrl(url)
	log.Printf("Adding %s endpoint %s", vhost, endpoint.GetUrl())
	err = balancer.AddEndpoint(endpoint)
	if err != nil {
		return err
	}
	return nil
}

func (s *HTTPRouter) RemoveBackend(vhost, url string) error {
	balancer := s.balancers[vhost]
	if balancer == nil {
		return nil
	}

	endpoint := balancer.FindEndpointByUrl(url)
	if endpoint == nil {
		return nil
	}
	log.Printf("Removing %s endpoint %s", vhost, endpoint.GetUrl())
	balancer.RemoveEndpoint(endpoint)

	endpoints := balancer.GetEndpoints()
	if len(endpoints) == 0 {
		s.RemoveRouter(vhost)
	}
	return nil
}

// Remove all backends for vhost that are not listed in addrs
func (s *HTTPRouter) RemoveBackends(vhost string, addrs []string) {
	// Remove backends that are no longer registered

	balancer := s.balancers[vhost]
	if balancer == nil {
		return
	}

	endpoints := balancer.GetEndpoints()
	for _, endpoint := range endpoints {
		if !utils.StringInSlice(endpoint.GetUrl().String(), addrs) {
			s.RemoveBackend(vhost, endpoint.GetUrl().String())
		}
	}
}

// Removes a virtual host router
func (s *HTTPRouter) RemoveRouter(vhost string) {
	log.Printf("Removing balancer for %s", vhost)
	delete(s.balancers, vhost)
	s.router.RemoveRouter(vhost)
}

func (s *HTTPRouter) adminHandler(w http.ResponseWriter, r *http.Request) {
	if len(s.balancers) == 0 {
		w.WriteHeader(503)
		return
	}
	for k, _ := range s.balancers {
		balancer := s.balancers[k]
		endpoints := balancer.GetEndpoints()
		fmt.Fprintf(w, "%s\n", k)
		for _, endpoint := range endpoints {
			fmt.Fprintf(w, "  %s\t%d\t%d\t%0.2f\n", endpoint.GetUrl(), endpoint.GetOriginalWeight(), endpoint.GetEffectiveWeight(), endpoint.GetMeter().GetRate())
		}
	}
}

func (s *HTTPRouter) statusHandler(h http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		var err error
		host := r.Host
		if strings.Contains(host, ":") {
			host, _, err = net.SplitHostPort(r.Host)
			if err != nil {
				log.Warnf("%s", err)
				h.ServeHTTP(w, r)
				return
			}
		}

		if _, exists := s.balancers[host]; !exists {
			s.adminHandler(w, r)
			return
		}
		h.ServeHTTP(w, r)
	})
}

func (s *HTTPRouter) Start() {

	// init the vulcan logging
	gotoolslog.Init([]*gotoolslog.LogConfig{
		&gotoolslog.LogConfig{Name: "console"},
	})

	log.Printf("Listening at %s", listenAddr)

	s.router = hostroute.NewHostRouter()

	proxy, err := vulcan.NewProxy(s.router)
	if err != nil {
		log.Fatalf("Error: %s", err)
	}

	// Proxy acts as http handler:
	server := &http.Server{
		Addr:           listenAddr,
		Handler:        s.statusHandler(proxy),
		ReadTimeout:    60 * time.Second,
		WriteTimeout:   60 * time.Second,
		MaxHeaderBytes: 1 << 20,
	}

	log.Errorf("%s", server.ListenAndServe())
}

func startHTTPServer() {
	defer wg.Done()
	httpRouter = NewHTTPRouter()
	httpRouter.Start()
}
<|endoftext|>


<commit_before>package wxpay

import (
	"bytes"
	"crypto/tls"
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
)

// AppTrans is abstact of Transaction handler. With AppTrans, we can get prepay id
type AppTrans struct {
	Config *WxConfig
}

// Initialized the AppTrans with specific config
func NewAppTrans(cfg *WxConfig) (*AppTrans, error) {
	if cfg.AppId == "" ||
		cfg.MchId == "" ||
		cfg.AppKey == "" ||
		cfg.NotifyUrl == "" ||
		cfg.QueryOrderUrl == "" ||
		cfg.PlaceOrderUrl == "" ||
		cfg.TradeType == "" {
		return &AppTrans{Config: cfg}, errors.New("config field canot empty string")
	}

	return &AppTrans{Config: cfg}, nil
}

// Submit the order to weixin pay and return the prepay id if success,
// Prepay id is used for app to start a payment
// If fail, error is not nil, check error for more information
func (this *AppTrans) Submit(orderId string, amount float64, desc string, clientIp string, trade_type, productid, openid string) (string, string, error) {

	odrInXml := this.signedOrderRequestXmlString(orderId, fmt.Sprintf("%.0f", amount), desc, clientIp, trade_type, productid, openid)

	resp, err := doHttpPost(this.Config.PlaceOrderUrl, []byte(odrInXml))
	if err != nil {
		return "", "", err
	}

	placeOrderResult, err := ParsePlaceOrderResult(resp)
	if err != nil {
		return "", "", err
	}

	//Verify the sign of response
	resultInMap := placeOrderResult.ToMap()
	wantSign := Sign(resultInMap, this.Config.AppKey)
	gotSign := resultInMap["sign"]
	if wantSign != gotSign {
		return "", "", fmt.Errorf("sign not match, want:%s, got:%s", wantSign, gotSign)
	}

	if placeOrderResult.ReturnCode != "SUCCESS" {
		return "", "", fmt.Errorf("return code:%s, return desc:%s", placeOrderResult.ReturnCode, placeOrderResult.ReturnMsg)
	}

	if placeOrderResult.ResultCode != "SUCCESS" {
		return "", "", fmt.Errorf("resutl code:%s, result desc:%s", placeOrderResult.ErrCode, placeOrderResult.ErrCodeDesc)
	}

	return placeOrderResult.PrepayId, placeOrderResult.CodeUrl, nil
}

func (this *AppTrans) newQueryXml(transId string) string {
	param := make(map[string]string)
	param["appid"] = this.Config.AppId
	param["mch_id"] = this.Config.MchId
	param["transaction_id"] = transId
	param["nonce_str"] = NewNonceString()

	sign := Sign(param, this.Config.AppKey)
	param["sign"] = sign

	return ToXmlString(param)
}

// Query the order from weixin pay server by transaction id of weixin pay
func (this *AppTrans) Query(transId string) (QueryOrderResult, error) {
	queryOrderResult := QueryOrderResult{}

	queryXml := this.newQueryXml(transId)
	// fmt.Println(queryXml)
	resp, err := doHttpPost(this.Config.QueryOrderUrl, []byte(queryXml))
	if err != nil {
		return queryOrderResult, nil
	}

	queryOrderResult, err = ParseQueryOrderResult(resp)
	if err != nil {
		return queryOrderResult, err
	}

	//verity sign of response
	resultInMap := queryOrderResult.ToMap()
	wantSign := Sign(resultInMap, this.Config.AppKey)
	gotSign := resultInMap["sign"]
	if wantSign != gotSign {
		return queryOrderResult, fmt.Errorf("sign not match, want:%s, got:%s", wantSign, gotSign)
	}

	return queryOrderResult, nil
}

// NewPaymentRequest build the payment request structure for app to start a payment.
// Return stuct of PaymentRequest, please refer to http://pay.weixin.qq.com/wiki/doc/api/app.php?chapter=9_12&index=2
func (this *AppTrans) NewPaymentRequest(prepayId, codeURl, trade_type string) map[string]string {
	param := make(map[string]string)
	if trade_type == "APP" {
		param["appid"] = this.Config.AppId
		param["package"] = "Sign=WXPay"
		param["partnerid"] = this.Config.MchId
		param["prepayid"] = prepayId
		param["noncestr"] = NewNonceString()
		param["timestamp"] = NewTimestampString()
	} else {
		param["appId"] = this.Config.AppId
		param["timeStamp"] = NewTimestampString()
		param["nonceStr"] = NewNonceString()
		param["package"] = "prepay_id=" + prepayId
		param["signType"] = "MD5"
	}

	sign := Sign(param, this.Config.AppKey)
	param["sign"] = sign
	param["codeUrl"] = codeURl
	return param
}

func (this *AppTrans) newOrderRequest(orderId, amount, desc, clientIp, trade_type, productid, openid string) map[string]string {
	param := make(map[string]string)
	param["appid"] = this.Config.AppId
	param["attach"] = "透传字段" //optional
	param["body"] = desc
	param["mch_id"] = this.Config.MchId
	param["nonce_str"] = NewNonceString()
	param["notify_url"] = this.Config.NotifyUrl
	param["out_trade_no"] = orderId
	param["spbill_create_ip"] = clientIp
	param["total_fee"] = amount
	if trade_type == "APP" {
		param["trade_type"] = "APP"
	} else if trade_type == "NATIVE" {
		param["trade_type"] = "NATIVE"
		param["product_id"] = productid
	} else {
		param["trade_type"] = "JSAPI"
		param["openid"] = openid
	}

	return param
}

func (this *AppTrans) signedOrderRequestXmlString(orderId, amount, desc, clientIp, trade_type, productid, openid string) string {
	order := this.newOrderRequest(orderId, amount, desc, clientIp, trade_type, productid, openid)
	sign := Sign(order, this.Config.AppKey)
	// fmt.Println(sign)

	order["sign"] = sign

	return ToXmlString(order)
}

// doRequest post the order in xml format with a sign
func doHttpPost(targetUrl string, body []byte) ([]byte, error) {
	req, err := http.NewRequest("POST", targetUrl, bytes.NewBuffer([]byte(body)))
	if err != nil {
		return []byte(""), err
	}
	req.Header.Add("Content-type", "application/x-www-form-urlencoded;charset=UTF-8")

	tr := &http.Transport{
		TLSClientConfig: &tls.Config{InsecureSkipVerify: false},
	}
	client := &http.Client{Transport: tr}

	resp, err := client.Do(req)
	if err != nil {
		return []byte(""), err
	}

	defer resp.Body.Close()
	respData, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return []byte(""), err
	}

	return respData, nil
}

// get请求
func doHttpGet(targetUrl string, params map[string]string) ([]byte, error) {
	u, err := url.Parse(targetUrl)

	if err != nil {
		return []byte(""), err
	}

	q := u.Query()

	for k, v := range params {
		q.Set(k, v)
	}
	u.RawQuery = q.Encode()

	resp, err := http.Get(u.String())
	if err != nil {
		return []byte(""), err
	}

	result, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return []byte(""), err
	}

	resp.Body.Close()

	return result, nil
}

// 根据授权code获取openid  jsapi用
func (this *AppTrans) GetOpenID(param map[string]string) (string, error) {

	res, err := doHttpGet("https://api.weixin.qq.com/sns/oauth2/access_token", param)
	if err != nil {
		return "", err
	}

	// json string to map
	m, err := JsonStrToMap(string(res))

	if err != nil {
		return "", err
	}

	// get openid
	openid := ""
	if s, ok := m["openid"].(string); ok {
		openid = s
	}
	return openid, nil

}

// json转map
func JsonStrToMap(str string) (map[string]interface{}, error) {
	m := make(map[string]interface{})

	err := json.Unmarshal([]byte(str), &m)

	if err != nil {
		return nil, err
	}
	return m, nil
}
<commit_msg>Modify qrcode<commit_after>package wxpay

import (
	"bytes"
	"crypto/tls"
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
)

// AppTrans is abstact of Transaction handler. With AppTrans, we can get prepay id
type AppTrans struct {
	Config *WxConfig
}

// Initialized the AppTrans with specific config
func NewAppTrans(cfg *WxConfig) (*AppTrans, error) {
	if cfg.AppId == "" ||
		cfg.MchId == "" ||
		cfg.AppKey == "" ||
		cfg.NotifyUrl == "" ||
		cfg.QueryOrderUrl == "" ||
		cfg.PlaceOrderUrl == "" ||
		cfg.TradeType == "" {
		return &AppTrans{Config: cfg}, errors.New("config field canot empty string")
	}

	return &AppTrans{Config: cfg}, nil
}

// Submit the order to weixin pay and return the prepay id if success,
// Prepay id is used for app to start a payment
// If fail, error is not nil, check error for more information
func (this *AppTrans) Submit(orderId string, amount float64, desc string, clientIp string, trade_type, productid, openid string) (string, string, error) {
	odrInXml := this.signedOrderRequestXmlString(orderId, fmt.Sprintf("%.0f", amount), desc, clientIp, trade_type, productid, openid)

	resp, err := doHttpPost(this.Config.PlaceOrderUrl, []byte(odrInXml))
	if err != nil {
		return "", "", err
	}

	placeOrderResult, err := ParsePlaceOrderResult(resp)
	if err != nil {
		return "", "", err
	}

	//Verify the sign of response
	resultInMap := placeOrderResult.ToMap()
	wantSign := Sign(resultInMap, this.Config.AppKey)
	gotSign := resultInMap["sign"]
	if wantSign != gotSign {
		return "", "", fmt.Errorf("sign not match, want:%s, got:%s", wantSign, gotSign)
	}

	if placeOrderResult.ReturnCode != "SUCCESS" {
		return "", "", fmt.Errorf("return code:%s, return desc:%s", placeOrderResult.ReturnCode, placeOrderResult.ReturnMsg)
	}

	if placeOrderResult.ResultCode != "SUCCESS" {
		return "", "", fmt.Errorf("resutl code:%s, result desc:%s", placeOrderResult.ErrCode, placeOrderResult.ErrCodeDesc)
	}

	return placeOrderResult.PrepayId, placeOrderResult.CodeUrl, nil
}

func (this *AppTrans) newQueryXml(transId string) string {
	param := make(map[string]string)
	param["appid"] = this.Config.AppId
	param["mch_id"] = this.Config.MchId
	param["transaction_id"] = transId
	param["nonce_str"] = NewNonceString()

	sign := Sign(param, this.Config.AppKey)
	param["sign"] = sign

	return ToXmlString(param)
}

// Query the order from weixin pay server by transaction id of weixin pay
func (this *AppTrans) Query(transId string) (QueryOrderResult, error) {
	queryOrderResult := QueryOrderResult{}

	queryXml := this.newQueryXml(transId)
	// fmt.Println(queryXml)
	resp, err := doHttpPost(this.Config.QueryOrderUrl, []byte(queryXml))
	if err != nil {
		return queryOrderResult, nil
	}

	queryOrderResult, err = ParseQueryOrderResult(resp)
	if err != nil {
		return queryOrderResult, err
	}

	//verity sign of response
	resultInMap := queryOrderResult.ToMap()
	wantSign := Sign(resultInMap, this.Config.AppKey)
	gotSign := resultInMap["sign"]
	if wantSign != gotSign {
		return queryOrderResult, fmt.Errorf("sign not match, want:%s, got:%s", wantSign, gotSign)
	}

	return queryOrderResult, nil
}

// NewPaymentRequest build the payment request structure for app to start a payment.
// Return stuct of PaymentRequest, please refer to http://pay.weixin.qq.com/wiki/doc/api/app.php?chapter=9_12&index=2
func (this *AppTrans) NewPaymentRequest(prepayId, codeURl, trade_type string) map[string]string {
	param := make(map[string]string)
	if trade_type == "APP" {
		param["appid"] = this.Config.AppId
		param["package"] = "Sign=WXPay"
		param["partnerid"] = this.Config.MchId
		param["prepayid"] = prepayId
		param["noncestr"] = NewNonceString()
		param["timestamp"] = NewTimestampString()
	} else {
		param["appId"] = this.Config.AppId
		param["timeStamp"] = NewTimestampString()
		param["nonceStr"] = NewNonceString()
		param["package"] = "prepay_id=" + prepayId
		param["signType"] = "MD5"
	}

	sign := Sign(param, this.Config.AppKey)
	param["sign"] = sign
	param["codeUrl"] = codeURl
	return param
}

func (this *AppTrans) newOrderRequest(orderId, amount, desc, clientIp, trade_type, productid, openid string) map[string]string {
	param := make(map[string]string)
	param["appid"] = this.Config.AppId
	param["attach"] = "透传字段" //optional
	param["body"] = desc
	param["mch_id"] = this.Config.MchId
	param["nonce_str"] = NewNonceString()
	param["notify_url"] = this.Config.NotifyUrl
	param["out_trade_no"] = orderId
	param["spbill_create_ip"] = clientIp
	param["total_fee"] = amount
	if trade_type == "APP" {
		param["trade_type"] = "APP"
	} else if trade_type == "NATIVE" {
		param["trade_type"] = "NATIVE"
		param["product_id"] = productid
	} else {
		param["trade_type"] = "JSAPI"
		param["openid"] = openid
	}

	return param
}

func (this *AppTrans) signedOrderRequestXmlString(orderId, amount, desc, clientIp, trade_type, productid, openid string) string {
	order := this.newOrderRequest(orderId, amount, desc, clientIp, trade_type, productid, openid)
	sign := Sign(order, this.Config.AppKey)
	// fmt.Println(sign)

	order["sign"] = sign

	return ToXmlString(order)
}

// doRequest post the order in xml format with a sign
func doHttpPost(targetUrl string, body []byte) ([]byte, error) {
	req, err := http.NewRequest("POST", targetUrl, bytes.NewBuffer([]byte(body)))
	if err != nil {
		return []byte(""), err
	}
	req.Header.Add("Content-type", "application/x-www-form-urlencoded;charset=UTF-8")

	tr := &http.Transport{
		TLSClientConfig: &tls.Config{InsecureSkipVerify: false},
	}
	client := &http.Client{Transport: tr}

	resp, err := client.Do(req)
	if err != nil {
		return []byte(""), err
	}

	defer resp.Body.Close()
	respData, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return []byte(""), err
	}

	return respData, nil
}

// get请求
func doHttpGet(targetUrl string, params map[string]string) ([]byte, error) {
	u, err := url.Parse(targetUrl)

	if err != nil {
		return []byte(""), err
	}

	q := u.Query()

	for k, v := range params {
		q.Set(k, v)
	}
	u.RawQuery = q.Encode()

	resp, err := http.Get(u.String())
	if err != nil {
		return []byte(""), err
	}

	result, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return []byte(""), err
	}

	resp.Body.Close()

	return result, nil
}

// 根据授权code获取openid  jsapi用
func (this *AppTrans) GetOpenID(param map[string]string) (string, error) {

	res, err := doHttpGet("https://api.weixin.qq.com/sns/oauth2/access_token", param)
	if err != nil {
		return "", err
	}

	// json string to map
	m, err := JsonStrToMap(string(res))

	if err != nil {
		return "", err
	}

	// get openid
	openid := ""
	if s, ok := m["openid"].(string); ok {
		openid = s
	}
	return openid, nil

}

// json转map
func JsonStrToMap(str string) (map[string]interface{}, error) {
	m := make(map[string]interface{})

	err := json.Unmarshal([]byte(str), &m)

	if err != nil {
		return nil, err
	}
	return m, nil
}
<|endoftext|>


<commit_before>package main

import (
	"errors"
	"fmt"
	"log"
	"net/http"
	"net/url"
	"strings"
)

func doHTTPStatus(w http.ResponseWriter, code int) {
	w.WriteHeader(code)
	w.Write([]byte(http.StatusText(code)))
}

func doHTTP404(w http.ResponseWriter) {
	doHTTPStatus(w, http.StatusNotFound)
}

func doHTTPRouter(w http.ResponseWriter, r *http.Request) {
	switch r.Method {
	case "POST":
		doHTTPPost(w, r)
	default:
		doHTTPGet(w, r)
	}
}

func raidLeave(w http.ResponseWriter, channel, raid, username, cmd string) {
	if err := raidDb.leave(channel, raid, username); err != nil {
		fmt.Fprint(w, err.Error())
	} else {
		fmt.Fprint(w, fmt.Sprintf(
			"OK. You're no longer signed up for \"%s\" on #%s",
			raid,
			channel))
		slack.toChannel(channel, fmt.Sprintf(
			"@%s is no longer signed up for \"%s\".\nUse \"%s join %s\" to take their place!",
			username, raid, cmd, raid))
	}
}

func raidJoin(w http.ResponseWriter, user, channel, raid, cmd string) error {
	if err := raidDb.join(channel, raid, user); err != nil {
		fmt.Fprint(w, err.Error())
		return err
	}
	fmt.Fprint(w, fmt.Sprintf("OK. You're signed up for \"%s\" on #%s", raid, channel))
	slack.toChannel(channel, fmt.Sprintf(
		"@%s has signed up for \"%s\".\nUse \"%s join %s\" to join them!",
		user, raid, cmd, raid))
	return nil
}

func doHTTPGet(w http.ResponseWriter, r *http.Request) {
	var q = r.URL.Query()
	switch q.Get("a") {
	case "rj": // Raid Join
		channel := q.Get("c")
		uuid := q.Get("r")
		user := q.Get("u")
		sig := q.Get("h")
		if channel == "" || user == "" || uuid == "" || sig == "" {
			w.WriteHeader(http.StatusBadRequest)
			return
		}
		for _, r := range raidDb.list(channel) {
			if r.UUID == uuid {
				if r.validateHmacForUser(user, sig) == nil {
					raidJoin(w, user, channel, r.Name, q.Get("cmd"))
					return
				}
			}
		}
	case "rp": // Raid Part
		channel := q.Get("c")
		uuid := q.Get("r")
		user := q.Get("u")
		sig := q.Get("h")
		if channel == "" || user == "" || uuid == "" || sig == "" {
			w.WriteHeader(http.StatusBadRequest)
			return
		}
		for _, r := range raidDb.list(channel) {
			if r.UUID == uuid {
				if r.validateHmacForUser(user, sig) == nil {
					raidLeave(w, channel, r.Name, user, q.Get("cmd"))
					return
				}
			}
		}
	}
	w.WriteHeader(http.StatusBadRequest)
}

func doHTTPPost(w http.ResponseWriter, r *http.Request) {
	r.ParseForm()
	command := strings.Split(r.Form.Get("text"), " ")
	subcommand := strings.Join(command[1:], " ")
	channel := r.Form.Get("channel_name")
	username := r.Form.Get("user_name")

	log.Printf("@%s on #%s -- %s %s", username, channel, r.Form.Get("command"), r.Form.Get("text"))

	switch r.Form.Get("command") {
	case "/xline":
		if r.Form.Get("token") != slack.xlineKey {
			doHTTPStatus(w, http.StatusUnauthorized)
			log.Printf("Unauthorized Request: %#v -- %#v", http.StatusUnauthorized, r.Form)
			return
		}
		switch channel {
		case "privategroup":
			fmt.Fprint(w, "Please use the "+r.Form.Get("command")+" command in a public channel")
			return
		case "directmessage":
			fmt.Fprint(w, "Please use the "+r.Form.Get("command")+" command in a channel")
			return
		}
		if len(command) < 1 || command[0] == "help" || command[0] == "" {
			for _, v := range admins {
				if v != username {
					continue
				}
				fmt.Fprint(w, r.Form.Get("command")+" add <sticker> <url> <description>\n")
				fmt.Fprint(w, r.Form.Get("command")+" remove <sticker>\n")
				break
			}
			fmt.Fprint(w, r.Form.Get("command")+" search <term>\n")
			fmt.Fprint(w, r.Form.Get("command")+" <sticker>\n")
			return
		}
		switch strings.ToLower(command[0]) {
		case "add":
			var isAdmin = false
			for _, v := range admins {
				if v != username {
					continue
				}
				isAdmin = true
			}
			if !isAdmin {
				fmt.Fprint(w, "Only an admin may use this feature")
				return
			}
			var err error
			switch len(command) {
			case 0, 1, 2:
				err = errors.New("You must provide a name and a url at least")
			case 3:
				err = xlineDB.add(strings.ToLower(command[1]), command[2], "")
			default:
				err = xlineDB.add(strings.ToLower(command[1]), command[2], strings.ToLower(strings.Join(command[3:], " ")))
			}
			if err != nil {
				fmt.Fprint(w, err.Error())
			} else {
				fmt.Fprint(w, "added")
			}
		case "remove":
			var isAdmin = false
			for _, v := range admins {
				if v != username {
					continue
				}
				isAdmin = true
			}
			if !isAdmin {
				fmt.Fprint(w, "Only an admin may use this feature")
				return
			}
			fmt.Fprint(w, "remove")
			if err := xlineDB.remove(strings.ToLower(command[1])); err != nil {
				fmt.Fprint(w, err.Error())
			} else {
				fmt.Fprint(w, "removed \""+command[1]+"\"")
			}
		case "search":
			found := xlineDB.search(strings.ToLower(subcommand))
			if len(found) > 0 {
				fmt.Fprintf(w, "found %d stickers:\n\t%s", len(found), strings.Join(found, "\n\t"))
			} else {
				fmt.Fprintf(w, "found 0 stickers")
			}
		default:
			if out, err := xlineDB.get(strings.ToLower(command[0])); err != nil {
				found := xlineDB.search(strings.ToLower(strings.Join(command, " ")))
				switch len(found) {
				case 0:
					fmt.Fprint(w, err.Error())
				case 1:
					out, _ = xlineDB.get(strings.Split(found[0], " ")[0])
					slack.toChannel(
						channel,
						fmt.Sprintf("%s: %s %s\n%s", username, r.Form.Get("command"), r.Form.Get("text"), out),
						"stickybot")
				default:
					fmt.Fprintf(w, "did you mean one of these?\n\t%s", strings.Join(found, "\n\t"))
				}
			} else {
				slack.toChannel(
					channel,
					fmt.Sprintf("%s: %s %s\n%s", username, r.Form.Get("command"), r.Form.Get("text"), out),
					"stickybot")
			}
		}
	case "/needs":
		if r.Form.Get("token") != slack.needKey {
			doHTTPStatus(w, http.StatusUnauthorized)
			log.Printf("Unauthorized Request: %#v -- %#v", http.StatusUnauthorized, r.Form)
			return
		}
	case "/raid", "/raidtest":
		if r.Form.Get("token") != slack.raidKey {
			doHTTPStatus(w, http.StatusUnauthorized)
			log.Printf("Unauthorized Request: %#v -- %#v", http.StatusUnauthorized, r.Form)
			return
		}
		switch channel {
		case "privategroup":
			fmt.Fprint(w, "Please use the "+r.Form.Get("command")+" command in a public channel")
			return
		case "directmessage":
			fmt.Fprint(w, "Please use the "+r.Form.Get("command")+" command in a channel")
			return
		}
		if len(command) < 1 || command[0] == "help" || command[0] == "" {
			fmt.Fprint(w, r.Form.Get("command")+" [command]\n\nWhere command is one of:\n\n")
			fmt.Fprint(w, "\t• list\n")
			fmt.Fprint(w, "\t• host [name of a raid to create]\n")
			fmt.Fprint(w, "\t• join [name of a raid to sign up for]\n")
			fmt.Fprint(w, "\t• leave [name of a raid to remove yourself from]\n")
			fmt.Fprint(w, "\t• finish [name of a raid to remove]\n")
			fmt.Fprint(w, "\t• ping [name of a raid to ping people for]\n\n")
			fmt.Fprint(w, "This will only be for raids in #"+channel+". ")
			fmt.Fprint(w, "To find and use raids in other channels you'll want to ")
			fmt.Fprint(w, "use the "+r.Form.Get("command")+" command from those channels")
			fmt.Fprint(w, "\n\nFor an introduction, please watch https://www.youtube.com/watch?v=T4g_3Tv5xJU")
			return
		}

		switch strings.ToLower(command[0]) {
		case "list":
			list := raidDb.list(channel)
			if len(list) == 0 {
				fmt.Fprintf(
					w,
					"There are no raids being hosted on #%s yet. Perhaps "+
						"you would like to \""+r.Form.Get("command")+" host\" one?",
					channel)
			} else {
				fmt.Fprintf(w, "The following raids are being hosted on #%s:\n", channel)
				for _, v := range raidDb.list(channel) {
					var isMember = false
					for _, m := range v.Members {
						if m == username {
							isMember = true
							break
						}
					}
					var link = ""
					if isMember == false {
						link = fmt.Sprintf(
							"<http://%s%s?a=rj&u=%s&c=%s&r=%s&h=%s&cmd=%s|join>",
							r.Host,
							r.RequestURI,
							url.QueryEscape(username),
							url.QueryEscape(channel),
							url.QueryEscape(v.UUID),
							url.QueryEscape(v.hmacForUser(username)),
							url.QueryEscape(r.Form.Get("command")),
						)
					} else {
						link = fmt.Sprintf(
							"<http://%s%s?a=rp&u=%s&c=%s&r=%s&h=%s&cmd=%s|leave>",
							r.Host,
							r.RequestURI,
							url.QueryEscape(username),
							url.QueryEscape(channel),
							url.QueryEscape(v.UUID),
							url.QueryEscape(v.hmacForUser(username)),
							url.QueryEscape(r.Form.Get("command")),
						)
					}
					fmt.Fprintf(
						w,
						"• \"%s\" with: _%s_ %s\n",
						v.Name,
						strings.Join(v.Members, "_, _"),
						link,
					)
				}
			}
		case "host":
			if len(subcommand) < 3 {
				fmt.Fprintf(
					w,
					"Sorry you must give me a little bit more to work with than \"%s\"",
					subcommand)
				return
			}

			if err := raidDb.register(channel, subcommand, username); err != nil {
				fmt.Fprint(w, err.Error())
			} else {
				fmt.Fprint(w, fmt.Sprintf(
					"OK. \"%s\" has been registered on #%s for you",
					subcommand,
					channel))
				slack.toChannel(channel, fmt.Sprintf(
					"@%s is hosting a new raid: \"%s\".\nUse \""+r.Form.Get("command")+" join %s\" to sign up!",
					username, subcommand, subcommand))
			}
		case "join":
			raidJoin(w, username, channel, subcommand, r.Form.Get("command"))
		case "leave":
			raidLeave(w, channel, subcommand, username, r.Form.Get("command"))
		case "finish":
			if err := raidDb.finish(channel, subcommand, username); err != nil {
				fmt.Fprint(w, err.Error())
			} else {
				fmt.Fprint(w, fmt.Sprintf(
					"OK. \"%s\" has been removed from the raid list for #%s",
					subcommand,
					channel))
				slack.toChannel(channel, fmt.Sprintf(
					"@%s has closed out \"%s\"",
					username, subcommand))
			}
		case "ping":
			if list, err := raidDb.members(channel, subcommand); err != nil {
				fmt.Fprint(w, err.Error())
			} else {
				slack.toChannel(channel, fmt.Sprintf(
					"pinging @%s about \"%s\" for @%s",
					strings.Join(list, ", @"), subcommand, username))
			}
		default:
			fmt.Fprint(w, "I'm afraid I don't know how to '"+command[0]+"'. ")
			fmt.Fprint(w, "Try '/"+r.Form.Get("command")+" help' to get a list of things I can do for you")
			return
		}
	default:
		doHTTPStatus(w, http.StatusNotImplemented)
		return
	}
}
<commit_msg>show age<commit_after>package main

import (
	"errors"
	"fmt"
	"log"
	"math"
	"net/http"
	"net/url"
	"strings"
	"time"
)

func doHTTPStatus(w http.ResponseWriter, code int) {
	w.WriteHeader(code)
	w.Write([]byte(http.StatusText(code)))
}

func doHTTP404(w http.ResponseWriter) {
	doHTTPStatus(w, http.StatusNotFound)
}

func doHTTPRouter(w http.ResponseWriter, r *http.Request) {
	switch r.Method {
	case "POST":
		doHTTPPost(w, r)
	default:
		doHTTPGet(w, r)
	}
}

func raidLeave(w http.ResponseWriter, channel, raid, username, cmd string) {
	if err := raidDb.leave(channel, raid, username); err != nil {
		fmt.Fprint(w, err.Error())
	} else {
		fmt.Fprint(w, fmt.Sprintf(
			"OK. You're no longer signed up for \"%s\" on #%s",
			raid,
			channel))
		slack.toChannel(channel, fmt.Sprintf(
			"@%s is no longer signed up for \"%s\".\nUse \"%s join %s\" to take their place!",
			username, raid, cmd, raid))
	}
}

func raidJoin(w http.ResponseWriter, user, channel, raid, cmd string) error {
	if err := raidDb.join(channel, raid, user); err != nil {
		fmt.Fprint(w, err.Error())
		return err
	}
	fmt.Fprint(w, fmt.Sprintf("OK. You're signed up for \"%s\" on #%s", raid, channel))
	slack.toChannel(channel, fmt.Sprintf(
		"@%s has signed up for \"%s\".\nUse \"%s join %s\" to join them!",
		user, raid, cmd, raid))
	return nil
}

func doHTTPGet(w http.ResponseWriter, r *http.Request) {
	var q = r.URL.Query()
	switch q.Get("a") {
	case "rj": // Raid Join
		channel := q.Get("c")
		uuid := q.Get("r")
		user := q.Get("u")
		sig := q.Get("h")
		if channel == "" || user == "" || uuid == "" || sig == "" {
			w.WriteHeader(http.StatusBadRequest)
			return
		}
		for _, r := range raidDb.list(channel) {
			if r.UUID == uuid {
				if r.validateHmacForUser(user, sig) == nil {
					raidJoin(w, user, channel, r.Name, q.Get("cmd"))
					return
				}
			}
		}
	case "rp": // Raid Part
		channel := q.Get("c")
		uuid := q.Get("r")
		user := q.Get("u")
		sig := q.Get("h")
		if channel == "" || user == "" || uuid == "" || sig == "" {
			w.WriteHeader(http.StatusBadRequest)
			return
		}
		for _, r := range raidDb.list(channel) {
			if r.UUID == uuid {
				if r.validateHmacForUser(user, sig) == nil {
					raidLeave(w, channel, r.Name, user, q.Get("cmd"))
					return
				}
			}
		}
	}
	w.WriteHeader(http.StatusBadRequest)
}

func doHTTPPost(w http.ResponseWriter, r *http.Request) {
	r.ParseForm()
	command := strings.Split(r.Form.Get("text"), " ")
	subcommand := strings.Join(command[1:], " ")
	channel := r.Form.Get("channel_name")
	username := r.Form.Get("user_name")

	log.Printf("@%s on #%s -- %s %s", username, channel, r.Form.Get("command"), r.Form.Get("text"))

	switch r.Form.Get("command") {
	case "/xline":
		if r.Form.Get("token") != slack.xlineKey {
			doHTTPStatus(w, http.StatusUnauthorized)
			log.Printf("Unauthorized Request: %#v -- %#v", http.StatusUnauthorized, r.Form)
			return
		}
		switch channel {
		case "privategroup":
			fmt.Fprint(w, "Please use the "+r.Form.Get("command")+" command in a public channel")
			return
		case "directmessage":
			fmt.Fprint(w, "Please use the "+r.Form.Get("command")+" command in a channel")
			return
		}
		if len(command) < 1 || command[0] == "help" || command[0] == "" {
			for _, v := range admins {
				if v != username {
					continue
				}
				fmt.Fprint(w, r.Form.Get("command")+" add <sticker> <url> <description>\n")
				fmt.Fprint(w, r.Form.Get("command")+" remove <sticker>\n")
				break
			}
			fmt.Fprint(w, r.Form.Get("command")+" search <term>\n")
			fmt.Fprint(w, r.Form.Get("command")+" <sticker>\n")
			return
		}
		switch strings.ToLower(command[0]) {
		case "add":
			var isAdmin = false
			for _, v := range admins {
				if v != username {
					continue
				}
				isAdmin = true
			}
			if !isAdmin {
				fmt.Fprint(w, "Only an admin may use this feature")
				return
			}
			var err error
			switch len(command) {
			case 0, 1, 2:
				err = errors.New("You must provide a name and a url at least")
			case 3:
				err = xlineDB.add(strings.ToLower(command[1]), command[2], "")
			default:
				err = xlineDB.add(strings.ToLower(command[1]), command[2], strings.ToLower(strings.Join(command[3:], " ")))
			}
			if err != nil {
				fmt.Fprint(w, err.Error())
			} else {
				fmt.Fprint(w, "added")
			}
		case "remove":
			var isAdmin = false
			for _, v := range admins {
				if v != username {
					continue
				}
				isAdmin = true
			}
			if !isAdmin {
				fmt.Fprint(w, "Only an admin may use this feature")
				return
			}
			fmt.Fprint(w, "remove")
			if err := xlineDB.remove(strings.ToLower(command[1])); err != nil {
				fmt.Fprint(w, err.Error())
			} else {
				fmt.Fprint(w, "removed \""+command[1]+"\"")
			}
		case "search":
			found := xlineDB.search(strings.ToLower(subcommand))
			if len(found) > 0 {
				fmt.Fprintf(w, "found %d stickers:\n\t%s", len(found), strings.Join(found, "\n\t"))
			} else {
				fmt.Fprintf(w, "found 0 stickers")
			}
		default:
			if out, err := xlineDB.get(strings.ToLower(command[0])); err != nil {
				found := xlineDB.search(strings.ToLower(strings.Join(command, " ")))
				switch len(found) {
				case 0:
					fmt.Fprint(w, err.Error())
				case 1:
					out, _ = xlineDB.get(strings.Split(found[0], " ")[0])
					slack.toChannel(
						channel,
						fmt.Sprintf("%s: %s %s\n%s", username, r.Form.Get("command"), r.Form.Get("text"), out),
						"stickybot")
				default:
					fmt.Fprintf(w, "did you mean one of these?\n\t%s", strings.Join(found, "\n\t"))
				}
			} else {
				slack.toChannel(
					channel,
					fmt.Sprintf("%s: %s %s\n%s", username, r.Form.Get("command"), r.Form.Get("text"), out),
					"stickybot")
			}
		}
	case "/needs":
		if r.Form.Get("token") != slack.needKey {
			doHTTPStatus(w, http.StatusUnauthorized)
			log.Printf("Unauthorized Request: %#v -- %#v", http.StatusUnauthorized, r.Form)
			return
		}
	case "/raid", "/raidtest":
		if r.Form.Get("token") != slack.raidKey {
			doHTTPStatus(w, http.StatusUnauthorized)
			log.Printf("Unauthorized Request: %#v -- %#v", http.StatusUnauthorized, r.Form)
			return
		}
		switch channel {
		case "privategroup":
			fmt.Fprint(w, "Please use the "+r.Form.Get("command")+" command in a public channel")
			return
		case "directmessage":
			fmt.Fprint(w, "Please use the "+r.Form.Get("command")+" command in a channel")
			return
		}
		if len(command) < 1 || command[0] == "help" || command[0] == "" {
			fmt.Fprint(w, r.Form.Get("command")+" [command]\n\nWhere command is one of:\n\n")
			fmt.Fprint(w, "\t• list\n")
			fmt.Fprint(w, "\t• host [name of a raid to create]\n")
			fmt.Fprint(w, "\t• join [name of a raid to sign up for]\n")
			fmt.Fprint(w, "\t• leave [name of a raid to remove yourself from]\n")
			fmt.Fprint(w, "\t• finish [name of a raid to remove]\n")
			fmt.Fprint(w, "\t• ping [name of a raid to ping people for]\n\n")
			fmt.Fprint(w, "This will only be for raids in #"+channel+". ")
			fmt.Fprint(w, "To find and use raids in other channels you'll want to ")
			fmt.Fprint(w, "use the "+r.Form.Get("command")+" command from those channels")
			fmt.Fprint(w, "\n\nFor an introduction, please watch https://www.youtube.com/watch?v=T4g_3Tv5xJU")
			return
		}

		switch strings.ToLower(command[0]) {
		case "list":
			list := raidDb.list(channel)
			if len(list) == 0 {
				fmt.Fprintf(
					w,
					"There are no raids being hosted on #%s yet. Perhaps "+
						"you would like to \""+r.Form.Get("command")+" host\" one?",
					channel)
			} else {
				fmt.Fprintf(w, "The following raids are being hosted on #%s:\n", channel)
				for _, v := range raidDb.list(channel) {
					var isMember = false
					for _, m := range v.Members {
						if m == username {
							isMember = true
							break
						}
					}
					var link = ""
					if isMember == false {
						link = fmt.Sprintf(
							"<http://%s%s?a=rj&u=%s&c=%s&r=%s&h=%s&cmd=%s|join>",
							r.Host,
							r.RequestURI,
							url.QueryEscape(username),
							url.QueryEscape(channel),
							url.QueryEscape(v.UUID),
							url.QueryEscape(v.hmacForUser(username)),
							url.QueryEscape(r.Form.Get("command")),
						)
					} else {
						link = fmt.Sprintf(
							"<http://%s%s?a=rp&u=%s&c=%s&r=%s&h=%s&cmd=%s|leave>",
							r.Host,
							r.RequestURI,
							url.QueryEscape(username),
							url.QueryEscape(channel),
							url.QueryEscape(v.UUID),
							url.QueryEscape(v.hmacForUser(username)),
							url.QueryEscape(r.Form.Get("command")),
						)
					}
					fmt.Fprintf(
						w,
						"• \"%s\" (%.1f days ago) with: _%s_ %s\n",
						v.Name,
						math.Floor((time.Now().Sub(v.CreatedAt).Hours()/24)*10)/10,
						strings.Join(v.Members, "_, _"),
						link,
					)
				}
			}
		case "host":
			if len(subcommand) < 3 {
				fmt.Fprintf(
					w,
					"Sorry you must give me a little bit more to work with than \"%s\"",
					subcommand)
				return
			}

			if err := raidDb.register(channel, subcommand, username); err != nil {
				fmt.Fprint(w, err.Error())
			} else {
				fmt.Fprint(w, fmt.Sprintf(
					"OK. \"%s\" has been registered on #%s for you",
					subcommand,
					channel))
				slack.toChannel(channel, fmt.Sprintf(
					"@%s is hosting a new raid: \"%s\".\nUse \""+r.Form.Get("command")+" join %s\" to sign up!",
					username, subcommand, subcommand))
			}
		case "join":
			raidJoin(w, username, channel, subcommand, r.Form.Get("command"))
		case "leave":
			raidLeave(w, channel, subcommand, username, r.Form.Get("command"))
		case "finish":
			if err := raidDb.finish(channel, subcommand, username); err != nil {
				fmt.Fprint(w, err.Error())
			} else {
				fmt.Fprint(w, fmt.Sprintf(
					"OK. \"%s\" has been removed from the raid list for #%s",
					subcommand,
					channel))
				slack.toChannel(channel, fmt.Sprintf(
					"@%s has closed out \"%s\"",
					username, subcommand))
			}
		case "ping":
			if list, err := raidDb.members(channel, subcommand); err != nil {
				fmt.Fprint(w, err.Error())
			} else {
				slack.toChannel(channel, fmt.Sprintf(
					"pinging @%s about \"%s\" for @%s",
					strings.Join(list, ", @"), subcommand, username))
			}
		default:
			fmt.Fprint(w, "I'm afraid I don't know how to '"+command[0]+"'. ")
			fmt.Fprint(w, "Try '/"+r.Form.Get("command")+" help' to get a list of things I can do for you")
			return
		}
	default:
		doHTTPStatus(w, http.StatusNotImplemented)
		return
	}
}
<|endoftext|>