<commit_before>package goose

import (
	"database/sql"
	"errors"
	"fmt"
	"os"
	"path/filepath"

	"github.com/kylelemons/go-gypsy/yaml"
	"github.com/lib/pq"
)

// DBDriver encapsulates the info needed to work with
// a specific database driver
type DBDriver struct {
	Name    string
	OpenStr string
	Import  string
	Dialect SqlDialect
}

type DBConf struct {
	MigrationsDir string
	Env           string
	Driver        DBDriver
	PgSchema      string
}

// extract configuration details from the given file
func NewDBConf(p, env string, pgschema string) (*DBConf, error) {

	cfgFile := filepath.Join(p, "dbconf.yml")

	f, err := yaml.ReadFile(cfgFile)
	if err != nil {
		return nil, err

	}

	drv, err := f.Get(fmt.Sprintf("%s.driver", env))
	if err != nil {
		return nil, err
	}
	drv = os.ExpandEnv(drv)

	open, err := f.Get(fmt.Sprintf("%s.open", env))
	if err != nil {
		return nil, err
	}
	open = os.ExpandEnv(open)

	// Automatically parse postgres urls
	if drv == "postgres" {

		// Assumption: If we can parse the URL, we should
		if parsedURL, err := pq.ParseURL(open); err == nil && parsedURL != "" {
			open = parsedURL
		}
	}

	d := newDBDriver(drv, open)

	// allow the configuration to override the Import for this driver
	if imprt, err := f.Get(fmt.Sprintf("%s.import", env)); err == nil {
		d.Import = imprt
	}

	// allow the configuration to override the Dialect for this driver
	if dialect, err := f.Get(fmt.Sprintf("%s.dialect", env)); err == nil {
		d.Dialect = dialectByName(dialect)
	}

	if !d.IsValid() {
		return nil, errors.New(fmt.Sprintf("Invalid DBConf: %v", d))
	}

	return &DBConf{
		MigrationsDir: filepath.Join(p, "migrations"),
		Env:           env,
		Driver:        d,
		PgSchema:      pgschema,
	}, nil
}

// Create a new DBDriver and populate driver specific
// fields for drivers that we know about.
// Further customization may be done in NewDBConf
func newDBDriver(name, open string) DBDriver {

	d := DBDriver{
		Name:    name,
		OpenStr: open,
	}

	switch name {
	case "postgres":
		d.Import = "github.com/lib/pq"
		d.Dialect = &PostgresDialect{}

	case "mymysql":
		d.Import = "github.com/ziutek/mymysql/godrv"
		d.Dialect = &MySqlDialect{}

	case "mysql":
		d.Import = "github.com/go-sql-driver/mysql"
		d.Dialect = &MySqlDialect{}
	}

	return d
}

// ensure we have enough info about this driver
func (drv *DBDriver) IsValid() bool {
	return len(drv.Import) > 0 && drv.Dialect != nil
}

// OpenDBFromDBConf wraps database/sql.DB.Open() and configures
// the newly opened DB based on the given DBConf.
//
// Callers must Close() the returned DB.
func OpenDBFromDBConf(conf *DBConf) (*sql.DB, error) {
	db, err := sql.Open(conf.Driver.Name, conf.Driver.OpenStr)
	if err != nil {
		return nil, err
	}

	// if a postgres schema has been specified, apply it
	if conf.Driver.Name == "postgres" && conf.PgSchema != "" {
		if _, err := db.Exec("SET search_path TO " + conf.PgSchema); err != nil {
			return nil, err
		}
	}

	return db, nil
}
<commit_msg>Whitespace<commit_after>package goose

import (
	"database/sql"
	"errors"
	"fmt"
	"os"
	"path/filepath"

	"github.com/kylelemons/go-gypsy/yaml"
	"github.com/lib/pq"
)

// DBDriver encapsulates the info needed to work with
// a specific database driver
type DBDriver struct {
	Name    string
	OpenStr string
	Import  string
	Dialect SqlDialect
}

type DBConf struct {
	MigrationsDir string
	Env           string
	Driver        DBDriver
	PgSchema      string
}

// extract configuration details from the given file
func NewDBConf(p, env string, pgschema string) (*DBConf, error) {

	cfgFile := filepath.Join(p, "dbconf.yml")

	f, err := yaml.ReadFile(cfgFile)
	if err != nil {
		return nil, err
	}

	drv, err := f.Get(fmt.Sprintf("%s.driver", env))
	if err != nil {
		return nil, err
	}
	drv = os.ExpandEnv(drv)

	open, err := f.Get(fmt.Sprintf("%s.open", env))
	if err != nil {
		return nil, err
	}
	open = os.ExpandEnv(open)

	// Automatically parse postgres urls
	if drv == "postgres" {

		// Assumption: If we can parse the URL, we should
		if parsedURL, err := pq.ParseURL(open); err == nil && parsedURL != "" {
			open = parsedURL
		}
	}

	d := newDBDriver(drv, open)

	// allow the configuration to override the Import for this driver
	if imprt, err := f.Get(fmt.Sprintf("%s.import", env)); err == nil {
		d.Import = imprt
	}

	// allow the configuration to override the Dialect for this driver
	if dialect, err := f.Get(fmt.Sprintf("%s.dialect", env)); err == nil {
		d.Dialect = dialectByName(dialect)
	}

	if !d.IsValid() {
		return nil, errors.New(fmt.Sprintf("Invalid DBConf: %v", d))
	}

	return &DBConf{
		MigrationsDir: filepath.Join(p, "migrations"),
		Env:           env,
		Driver:        d,
		PgSchema:      pgschema,
	}, nil
}

// Create a new DBDriver and populate driver specific
// fields for drivers that we know about.
// Further customization may be done in NewDBConf
func newDBDriver(name, open string) DBDriver {

	d := DBDriver{
		Name:    name,
		OpenStr: open,
	}

	switch name {
	case "postgres":
		d.Import = "github.com/lib/pq"
		d.Dialect = &PostgresDialect{}

	case "mymysql":
		d.Import = "github.com/ziutek/mymysql/godrv"
		d.Dialect = &MySqlDialect{}

	case "mysql":
		d.Import = "github.com/go-sql-driver/mysql"
		d.Dialect = &MySqlDialect{}
	}

	return d
}

// ensure we have enough info about this driver
func (drv *DBDriver) IsValid() bool {
	return len(drv.Import) > 0 && drv.Dialect != nil
}

// OpenDBFromDBConf wraps database/sql.DB.Open() and configures
// the newly opened DB based on the given DBConf.
//
// Callers must Close() the returned DB.
func OpenDBFromDBConf(conf *DBConf) (*sql.DB, error) {
	db, err := sql.Open(conf.Driver.Name, conf.Driver.OpenStr)
	if err != nil {
		return nil, err
	}

	// if a postgres schema has been specified, apply it
	if conf.Driver.Name == "postgres" && conf.PgSchema != "" {
		if _, err := db.Exec("SET search_path TO " + conf.PgSchema); err != nil {
			return nil, err
		}
	}

	return db, nil
}
<|endoftext|>


<commit_before>package main

import "strconv"
import "strings"
import "github.com/gedex/inflector"
import "fmt"
import "math"

func (ic *Compiler) expression() string {
	var token = ic.Scan(0)

	for token == "\n" {
		token = ic.Scan(0)
	}

	switch token {
		case "true":
			ic.ExpressionType = Number
			return "1"
		case "false":
			ic.ExpressionType = Number
			return "0"
		case "error":
			ic.ExpressionType = Number
			return "ERROR"
	}

	if token == "{" {
		var t = ic.Scan(0)
		if t == "}" {
			ic.ExpressionType = Something
		} else {
			ic.ExpressionType = ic.DefinedInterfaces[t].GetType()
			ic.Scan('}')
		}
		var tmp = ic.Tmp("something")
		ic.Assembly("ARRAY ", tmp)
		ic.Assembly("PUT 0")
		return tmp
	}

	//Text.
	if token[0] == '"' || token[0] == '`' {
		ic.ExpressionType = Text
		return ic.ParseString(token)
	}

	//Letters.
	if token[0] == "'"[0] {
		if s, err := strconv.Unquote(token); err == nil {
			ic.ExpressionType = Letter
			return strconv.Itoa(int([]byte(s)[0]))
		} else {
			ic.RaiseError(err)
		}
	}

	//Hexadecimal.
	if len(token) > 2 && token[0] == '0' && token[1] == 'x' {
		ic.ExpressionType = Number
		return token
	}

	//Arrays.
	if token == "$" && ic.Peek() == "[" {
		return ic.ScanArray()
	}

	//Arrays.
	if token == "[" {
		return ic.ScanArray()
	}

	//Pipes.
	if token == "|" {
		var name = "open"
		if ic.Peek() != "|" {
			var arg = ic.ScanExpression()
			name += "_m_"+ic.ExpressionType.Name
			if f, ok := ic.DefinedFunctions[name]; ok {
				var tmp = ic.Tmp("open")
				ic.Assembly(ic.ExpressionType.Push, " ", arg)
				ic.Assembly(ic.RunFunction(name))
				ic.Assembly(f.Returns[0].Pop, " ", tmp)
				ic.ExpressionType = f.Returns[0]
				ic.Scan('|')
				return tmp
			} else {
				ic.RaiseError("Cannot create a pipe out of a ", ic.ExpressionType.Name)
			}
		} else {
			ic.Scan('|')
			var tmp = ic.Tmp("pipe")
			ic.Assembly("PIPE ", tmp)
			ic.ExpressionType = Pipe
			return tmp
			//ic.RaiseError("Blank pipe!")
		}
	}

	//Subexpessions.
	if token == "(" {
		defer func() {
			ic.Scan(')')
		}()
		return ic.ScanExpression()
	}

	//Is it a literal number? Then just return it.
	if _, err := strconv.Atoi(token); err == nil{
		ic.ExpressionType = Number
		return token
	}

	//Decimal numbers.
	if strings.Contains(token, ".") {
		parts := strings.Split(token, ".")
		a, _ := strconv.Atoi(parts[0])
		b, _ := strconv.Atoi(parts[1])
		ic.ExpressionType = Decimal
		return fmt.Sprint(a*1000000+b*int(math.Pow(10, 6-float64(len(parts[1])))))
	}

	//Minus.
	if token == "-" {
		ic.NextToken = token
		ic.ExpressionType = Number
		return ic.Shunt("0")
	}

	if t := ic.GetVariable(token); t != Undefined {
		ic.ExpressionType = t
		ic.SetVariable(token+"_use", Used)
		if tok := ic.Scan(0); tok == "[" || tok == "." {
			ic.NextToken = tok
			return ic.Shunt(token)
		} else {
			ic.NextToken = tok
		}
		return token
	}

	//Scope methods with multiple arguments inside the method.
	//eg. method Package.dosomething(22)
	// in a Package method, dosomething(22) should be local.
	if ic.GetFlag(InMethod) {
		if f, ok := ic.DefinedFunctions[token+"_m_"+ic.LastDefinedType.Name]; ok && len(f.Args) > 0 {
			if ic.LastDefinedType.Detail == nil || len(ic.LastDefinedType.Detail.Elements) > 0 {
				ic.Assembly("%v %v", ic.LastDefinedType.Push, ic.LastDefinedType.Name)
			}
			ic.ExpressionType = InFunction
			return token+"_m_"+ic.LastDefinedType.Name
		}
	}

	if t, ok := ic.DefinedTypes[token]; ok {
		ic.ExpressionType = t

		if ic.Peek() == "(" || ic.NextToken == "(" {
			ic.Scan('(')
			ic.Scan(')')

			var array = ic.Tmp("user")
			ic.Assembly("ARRAY ", array)
			for range ic.DefinedTypes[token].Detail.Elements {
				ic.Assembly("PUT 0")
			}
			return array

		//This is a type literal.
		} else if ic.Peek() == "{" {
			ic.NextToken = token
			variable := ic.ScanTypeLiteral()
				//TODO better gc protection.
			ic.SetVariable(variable, t)
			ic.SetVariable(variable+"_use", Used)
			return variable

		} else if ic.GetFlag(InMethod) && ic.LastDefinedType.Super == token {
			ic.ExpressionType = ic.DefinedTypes[ic.LastDefinedType.Super]
			return ic.LastDefinedType.Name

		} else if len(t.Detail.Elements) == 0 && ic.Peek() == "." {
			ic.Scan('.')
			ic.ExpressionType = InFunction
			var name = ic.Scan(Name)
			return name+"_m_"+token

		} else {
			ic.RaiseError()
		}

	}

	if token == "new" {
		var sort = ic.expression()
		if _, ok := ic.DefinedFunctions["new_m_"+ic.ExpressionType.Name]; !ok {
			ic.RaiseError("no new method found for ", ic.ExpressionType.Name)
		}
		var r = ic.Tmp("new")
		ic.Assembly(ic.ExpressionType.Push, " ", sort)
		ic.Assembly("RUN new_m_"+ic.ExpressionType.Name)
		ic.Assembly("GRAB ", r)
		return r
	}

	if _, ok := ic.DefinedFunctions[token]; ok {
		if ic.Peek() == "@" {
			ic.Scan('@')
			var variable = ic.expression()
			ic.Assembly("%v %v", ic.ExpressionType.Push, variable)
			var name = ic.ExpressionType.Name
			ic.ExpressionType = InFunction
			return token+"_m_"+name
		}

		if ic.Peek() != "(" {
			ic.ExpressionType = Func
			var id = ic.Tmp("func")
			ic.Assembly("SCOPE ", token)
			ic.Assembly("TAKE ", id)

			return id
		} else {
			ic.ExpressionType = InFunction
			return token
		}
	}

	if ic.Translation && !ic.Translated {
		ic.Translated = true
		defer func() {
			ic.Translated = false
		}()
		var err error
		ic.NextToken, err = getTranslation(ic.Language, "en", token)
		if strings.Contains(ic.NextToken, "\n") {
			ic.NextToken = strings.Split(ic.NextToken, "\n")[0]
		}
		println(ic.NextToken, ic.Language)
		if err != nil {
			ic.RaiseError(err)
		}
		return ic.expression()
	}

	token = inflector.Singularize(token)
	if t, ok := ic.DefinedTypes[token]; ok {
		ic.Scan('(')
		ic.Scan(')')
		return ic.NewListOf(t)
	}
	if t, ok := ic.DefinedInterfaces[token]; ok {
		ic.Scan('(')
		ic.Scan(')')
		return ic.NewListOf(t.GetType())
	}

	ic.NextToken = token
	ic.ExpressionType = Undefined
	//ic.RaiseError()
	return ""
}

func (ic *Compiler) ScanExpression() string {
	return ic.Shunt(ic.expression())
}
<commit_msg>Add something function.<commit_after>package main

import "strconv"
import "strings"
import "github.com/gedex/inflector"
import "fmt"
import "math"

func (ic *Compiler) expression() string {
	var token = ic.Scan(0)

	for token == "\n" {
		token = ic.Scan(0)
	}

	switch token {
		case "true":
			ic.ExpressionType = Number
			return "1"
		case "false":
			ic.ExpressionType = Number
			return "0"
		case "error":
			ic.ExpressionType = Number
			return "ERROR"
	}

	if token == "{" {
		var t = ic.Scan(0)
		if t == "}" {
			ic.ExpressionType = Something
		} else {
			ic.ExpressionType = ic.DefinedInterfaces[t].GetType()
			ic.Scan('}')
		}
		var tmp = ic.Tmp("something")
		ic.Assembly("ARRAY ", tmp)
		ic.Assembly("PUT 0")
		return tmp
	}

	if token == "something" {
		ic.Scan('(')
		ic.Scan(')')
		var tmp = ic.Tmp("something")
		ic.Assembly("ARRAY ", tmp)
		ic.Assembly("PUT 0")
		return tmp
	}

	//Text.
	if token[0] == '"' || token[0] == '`' {
		ic.ExpressionType = Text
		return ic.ParseString(token)
	}

	//Letters.
	if token[0] == "'"[0] {
		if s, err := strconv.Unquote(token); err == nil {
			ic.ExpressionType = Letter
			return strconv.Itoa(int([]byte(s)[0]))
		} else {
			ic.RaiseError(err)
		}
	}

	//Hexadecimal.
	if len(token) > 2 && token[0] == '0' && token[1] == 'x' {
		ic.ExpressionType = Number
		return token
	}

	//Arrays.
	if token == "$" && ic.Peek() == "[" {
		return ic.ScanArray()
	}

	//Arrays.
	if token == "[" {
		return ic.ScanArray()
	}

	//Pipes.
	if token == "|" {
		var name = "open"
		if ic.Peek() != "|" {
			var arg = ic.ScanExpression()
			name += "_m_"+ic.ExpressionType.Name
			if f, ok := ic.DefinedFunctions[name]; ok {
				var tmp = ic.Tmp("open")
				ic.Assembly(ic.ExpressionType.Push, " ", arg)
				ic.Assembly(ic.RunFunction(name))
				ic.Assembly(f.Returns[0].Pop, " ", tmp)
				ic.ExpressionType = f.Returns[0]
				ic.Scan('|')
				return tmp
			} else {
				ic.RaiseError("Cannot create a pipe out of a ", ic.ExpressionType.Name)
			}
		} else {
			ic.Scan('|')
			var tmp = ic.Tmp("pipe")
			ic.Assembly("PIPE ", tmp)
			ic.ExpressionType = Pipe
			return tmp
			//ic.RaiseError("Blank pipe!")
		}
	}

	//Subexpessions.
	if token == "(" {
		defer func() {
			ic.Scan(')')
		}()
		return ic.ScanExpression()
	}

	//Is it a literal number? Then just return it.
	if _, err := strconv.Atoi(token); err == nil{
		ic.ExpressionType = Number
		return token
	}

	//Decimal numbers.
	if strings.Contains(token, ".") {
		parts := strings.Split(token, ".")
		a, _ := strconv.Atoi(parts[0])
		b, _ := strconv.Atoi(parts[1])
		ic.ExpressionType = Decimal
		return fmt.Sprint(a*1000000+b*int(math.Pow(10, 6-float64(len(parts[1])))))
	}

	//Minus.
	if token == "-" {
		ic.NextToken = token
		ic.ExpressionType = Number
		return ic.Shunt("0")
	}

	if t := ic.GetVariable(token); t != Undefined {
		ic.ExpressionType = t
		ic.SetVariable(token+"_use", Used)
		if tok := ic.Scan(0); tok == "[" || tok == "." {
			ic.NextToken = tok
			return ic.Shunt(token)
		} else {
			ic.NextToken = tok
		}
		return token
	}

	//Scope methods with multiple arguments inside the method.
	//eg. method Package.dosomething(22)
	// in a Package method, dosomething(22) should be local.
	if ic.GetFlag(InMethod) {
		if f, ok := ic.DefinedFunctions[token+"_m_"+ic.LastDefinedType.Name]; ok && len(f.Args) > 0 {
			if ic.LastDefinedType.Detail == nil || len(ic.LastDefinedType.Detail.Elements) > 0 {
				ic.Assembly("%v %v", ic.LastDefinedType.Push, ic.LastDefinedType.Name)
			}
			ic.ExpressionType = InFunction
			return token+"_m_"+ic.LastDefinedType.Name
		}
	}

	if t, ok := ic.DefinedTypes[token]; ok {
		ic.ExpressionType = t

		if ic.Peek() == "(" || ic.NextToken == "(" {
			ic.Scan('(')
			ic.Scan(')')

			var array = ic.Tmp("user")
			ic.Assembly("ARRAY ", array)
			for range ic.DefinedTypes[token].Detail.Elements {
				ic.Assembly("PUT 0")
			}
			return array

		//This is a type literal.
		} else if ic.Peek() == "{" {
			ic.NextToken = token
			variable := ic.ScanTypeLiteral()
				//TODO better gc protection.
			ic.SetVariable(variable, t)
			ic.SetVariable(variable+"_use", Used)
			return variable

		} else if ic.GetFlag(InMethod) && ic.LastDefinedType.Super == token {
			ic.ExpressionType = ic.DefinedTypes[ic.LastDefinedType.Super]
			return ic.LastDefinedType.Name

		} else if len(t.Detail.Elements) == 0 && ic.Peek() == "." {
			ic.Scan('.')
			ic.ExpressionType = InFunction
			var name = ic.Scan(Name)
			return name+"_m_"+token

		} else {
			ic.RaiseError()
		}

	}

	if token == "new" {
		var sort = ic.expression()
		if _, ok := ic.DefinedFunctions["new_m_"+ic.ExpressionType.Name]; !ok {
			ic.RaiseError("no new method found for ", ic.ExpressionType.Name)
		}
		var r = ic.Tmp("new")
		ic.Assembly(ic.ExpressionType.Push, " ", sort)
		ic.Assembly("RUN new_m_"+ic.ExpressionType.Name)
		ic.Assembly("GRAB ", r)
		return r
	}

	if _, ok := ic.DefinedFunctions[token]; ok {
		if ic.Peek() == "@" {
			ic.Scan('@')
			var variable = ic.expression()
			ic.Assembly("%v %v", ic.ExpressionType.Push, variable)
			var name = ic.ExpressionType.Name
			ic.ExpressionType = InFunction
			return token+"_m_"+name
		}

		if ic.Peek() != "(" {
			ic.ExpressionType = Func
			var id = ic.Tmp("func")
			ic.Assembly("SCOPE ", token)
			ic.Assembly("TAKE ", id)

			return id
		} else {
			ic.ExpressionType = InFunction
			return token
		}
	}

	if ic.Translation && !ic.Translated {
		ic.Translated = true
		defer func() {
			ic.Translated = false
		}()
		var err error
		ic.NextToken, err = getTranslation(ic.Language, "en", token)
		if strings.Contains(ic.NextToken, "\n") {
			ic.NextToken = strings.Split(ic.NextToken, "\n")[0]
		}
		println(ic.NextToken, ic.Language)
		if err != nil {
			ic.RaiseError(err)
		}
		return ic.expression()
	}

	token = inflector.Singularize(token)
	if t, ok := ic.DefinedTypes[token]; ok {
		ic.Scan('(')
		ic.Scan(')')
		return ic.NewListOf(t)
	}
	if t, ok := ic.DefinedInterfaces[token]; ok {
		ic.Scan('(')
		ic.Scan(')')
		return ic.NewListOf(t.GetType())
	}

	ic.NextToken = token
	ic.ExpressionType = Undefined
	//ic.RaiseError()
	return ""
}

func (ic *Compiler) ScanExpression() string {
	return ic.Shunt(ic.expression())
}
<|endoftext|>


<commit_before>package sdl

// #cgo pkg-config: sdl2
// #cgo LDFLAGS: -lSDL2_image
//
// #include "SDL.h"
import "C"

import (
	"fmt"
	"unsafe"
)

//
// The only way to keep this file sane is with vim folds. `:help fold` for more info
//

// START EventType {{{1

// TODO put all these consts in 1 block and assign them to their C #define equivalents.
// And suffix with Event instead of Ev
type EventType uint16

const (
	FirstEvent EventType = 0
)

/* Application events */
const (
	QuitEv EventType = 0x100 + iota
	/* These application events have special meaning on iOS, see README-ios.txt for details */
	AppTerminatingEv
	AppLowMemoryEv
	AppWillEnterBackgroundEv
	AppDidEnterBackgroundEv
	AppWillEnterForegroundEv
	AppDidEnterForegroundEv
)

/* Window events */
const (
	WindowEv EventType = 0x200 + iota
	SysWmEv
)

/* Keyboard events */
const (
	KeyDownEv EventType = 0x300 + iota
	KeyUpEv
	TextEditingEv
	TextInputEv
)

/* Mouse events */
const (
	MouseMotionEv EventType = 0x400 + iota
	MouseButtonDownEv
	MouseButtonUpEv
	MouseWheelEv
)

/* Joystick events */
const (
	JoyAxisMotionEv EventType = 0x600 + iota
	JoyBallMotionEv
	JoyHatMotionEv
	JoyButtonDownEv
	JoyButtonUpEv
	JoyDeviceAddedEv
	JoyDeviceRemovedEv
)

/* Game controller events */
const (
	ControllerAxisMotionEv EventType = 0x650 + iota
	ControllerButtonDownEv
	ControllerButtonUpEv
	ControllerDeviceAddedEv
	ControllerDeviceRemovedEv
	ControllerDeviceRemappedEv
)

/* Touch events */
const (
	FingerDownEv EventType = 0x700 + iota
	FingerUpEv
	FingerMotionEv
)

/* Gesture events */
const (
	DollarGestureEv EventType = 0x800 + iota
	DollarRecordEv
	MultiGestureEv
)

/* Clipboard events */
const (
	ClipboardUpdateEv EventType = 0x900
)

/* Drag and drop events */
const (
	DropFileEv EventType = 0x1000
)

/*
Events ::USEREVENT through ::LASTEVENT are for your use
and should be allocated with RegisterEvents()
*/
const (
	UserEv EventType = 0x8000
	// This last event is only for bounding internal arrays
	LastEv EventType = 0xFFFF
)

// END EventType }}}1

// Event is implemented by all SDL events.
type Event interface {
	// Type returns the event's type.
	Type() EventType

	// Timestamp returns the number of milliseconds since the SDL library initialization.
	Timestamp() uint32
}

// PollEvent returns the next available event, or nil if there is no event pending.
func PollEvent() Event {
	var cEvent C.SDL_Event
	if C.SDL_PollEvent(&cEvent) == 0 {
		return nil
	}
	return convertEvent(unsafe.Pointer(&cEvent))
}

// HasEvent returns whether there is a pending event available.
func HasEvent() bool {
	return C.SDL_PollEvent(nil) != 0
}

func convertEvent(cEvent unsafe.Pointer) Event {
	common := (*C.SDL_CommonEvent)(cEvent)
	switch EventType(common._type) {
	case QuitEv:
		return QuitEvent{(*C.SDL_QuitEvent)(cEvent)}
	case KeyDownEv, KeyUpEv:
		return KeyboardEvent{(*C.SDL_KeyboardEvent)(cEvent)}
	default:
		fmt.Println("Unhandled event with int:", int(common._type))
		return commonEvent{common}
	}
}

// {{{1 Event Structs

// {{{2 CommonEvent

// commonEvent holds fields common to all events.  It is not exported because it
// doesn't provide anything useful outside of the Event interface.
type commonEvent struct {
	ev *C.SDL_CommonEvent
}

func (e commonEvent) Type() EventType {
	return EventType(e.ev._type)
}

func (e commonEvent) Timestamp() uint32 {
	return uint32(e.ev.timestamp)
}

// }}}2 CommonEvent

// {{{2 WindowEvent
// TODO make this implement Event
type WindowEvent struct {
	ev C.SDL_WindowEvent
}

// }}}2 WindowEvent

// {{{2 KeyboardEvent
type KeyboardEvent struct {
	e *C.SDL_KeyboardEvent
}

func (e KeyboardEvent) Timestamp() uint32 {
	return uint32(e.e.timestamp)
}

func (e KeyboardEvent) Type() EventType {
	return EventType(e.e._type)
}

func (e KeyboardEvent) WindowID() uint32 {
	return uint32(e.e.windowID)
}

func (e KeyboardEvent) State() uint8 {
	return uint8(e.e.state)
}

func (e KeyboardEvent) Repeat() uint8 {
	return uint8(e.e.repeat)
}

// TODO this
/*
func (e KeyboardEvent) Keysym() Keysym {
}
*/
// }}}2 KeyboardEvent

// {{{2 TextEditingEvent
// TODO make this implement Event
type TextEditingEvent struct {
	ev C.SDL_TextEditingEvent
}

// }}}2 TextEditingEvent

// {{{2 TextInputEvent
// TODO make this implement Event
type TextInputEvent struct {
	ev C.SDL_TextInputEvent
}

// }}}2 TextInputEvent

// {{{2 MouseMotionEvent
// TODO make this implement Event
type MouseMotionEvent struct {
	ev C.SDL_MouseMotionEvent
}

// }}}2 MouseMotionEvent

// {{{2 MouseButtonEvent
// TODO make this implement Event
type MouseButtonEvent struct {
	ev C.SDL_MouseButtonEvent
}

// }}}2 MouseButtonEvent

// {{{2 MouseWheelEvent
// TODO make this implement Event
type MouseWheelEvent struct {
	ev C.SDL_MouseWheelEvent
}

// }}}2 MouseWheelEvent

// {{{2 JoyAxisEvent
// TODO make this implement Event
type JoyAxisEvent struct {
	ev C.SDL_JoyAxisEvent
}

// }}}2 JoyAxisEvent

// {{{2 JoyBallEvent
// TODO make this implement Event
type JoyBallEvent struct {
	ev C.SDL_JoyBallEvent
}

// }}}2 JoyBallEvent

// {{{2 JoyHatEvent
// TODO make this implement Event
type JoyHatEvent struct {
	ev C.SDL_JoyHatEvent
}

// }}}2 JoyHatEvent

// {{{2 JoyButtonEvent
// TODO make this implement Event
type JoyButtonEvent struct {
	ev C.SDL_JoyButtonEvent
}

// }}}2 JoyButtonEvent

// {{{2 JoyDeviceEvent
// TODO make this implement Event
type JoyDeviceEvent struct {
	ev C.SDL_JoyDeviceEvent
}

// }}}2 JoyDeviceEvent

// {{{2 ControllerAxisEvent
// TODO make this implement Event
type ControllerAxisEvent struct {
	ev C.SDL_ControllerAxisEvent
}

// }}}2 ControllerAxisEvent

// {{{2 ControllerButtonEvent
// TODO make this implement Event
type ControllerButtonEvent struct {
	ev C.SDL_ControllerButtonEvent
}

// }}}2 ControllerButtonEvent

// {{{2 ControllerDeviceEvent
// TODO make this implement Event
type ControllerDeviceEvent struct {
	ev C.SDL_ControllerDeviceEvent
}

// }}}2 ControllerDeviceEvent

// {{{2 QuitEvent
type QuitEvent struct {
	e *C.SDL_QuitEvent
}

func (e QuitEvent) Timestamp() uint32 {
	return uint32(e.e.timestamp)
}

func (e QuitEvent) Type() EventType {
	return QuitEv
}

// }}}2 QuitEvent

// {{{2 UserEvent
// TODO make this implement Event
type UserEvent struct {
	ev C.SDL_UserEvent
}

// }}}2 UserEvent

// {{{2 SysWMEvent
// TODO make this implement Event
type SysWMEvent struct {
	ev C.SDL_SysWMEvent
}

// }}}2 SysWMEvent

// {{{2 TouchFingerEvent
// TODO make this implement Event
type TouchFingerEvent struct {
	ev C.SDL_TouchFingerEvent
}

// }}}2 TouchFingerEvent

// {{{2 MultiGestureEvent
// TODO make this implement Event
type MultiGestureEvent struct {
	ev C.SDL_MultiGestureEvent
}

// }}}2 MultiGestureEvent

// {{{2 DollarGestureEvent
// TODO make this implement Event
type DollarGestureEvent struct {
	ev C.SDL_DollarGestureEvent
}

// }}}2 DollarGestureEvent

// {{{2 DropEvent
// TODO make this implement Event
type DropEvent struct {
	ev C.SDL_DropEvent
}

// }}}2 DropEvent

// }}}1 Event Structs
<commit_msg>Use commonEvent for quit events<commit_after>package sdl

// #cgo pkg-config: sdl2
// #cgo LDFLAGS: -lSDL2_image
//
// #include "SDL.h"
import "C"

import (
	"fmt"
	"unsafe"
)

//
// The only way to keep this file sane is with vim folds. `:help fold` for more info
//

// START EventType {{{1

// TODO put all these consts in 1 block and assign them to their C #define equivalents.
// And suffix with Event instead of Ev
type EventType uint16

const (
	FirstEvent EventType = 0
)

/* Application events */
const (
	QuitEv EventType = 0x100 + iota
	/* These application events have special meaning on iOS, see README-ios.txt for details */
	AppTerminatingEv
	AppLowMemoryEv
	AppWillEnterBackgroundEv
	AppDidEnterBackgroundEv
	AppWillEnterForegroundEv
	AppDidEnterForegroundEv
)

/* Window events */
const (
	WindowEv EventType = 0x200 + iota
	SysWmEv
)

/* Keyboard events */
const (
	KeyDownEv EventType = 0x300 + iota
	KeyUpEv
	TextEditingEv
	TextInputEv
)

/* Mouse events */
const (
	MouseMotionEv EventType = 0x400 + iota
	MouseButtonDownEv
	MouseButtonUpEv
	MouseWheelEv
)

/* Joystick events */
const (
	JoyAxisMotionEv EventType = 0x600 + iota
	JoyBallMotionEv
	JoyHatMotionEv
	JoyButtonDownEv
	JoyButtonUpEv
	JoyDeviceAddedEv
	JoyDeviceRemovedEv
)

/* Game controller events */
const (
	ControllerAxisMotionEv EventType = 0x650 + iota
	ControllerButtonDownEv
	ControllerButtonUpEv
	ControllerDeviceAddedEv
	ControllerDeviceRemovedEv
	ControllerDeviceRemappedEv
)

/* Touch events */
const (
	FingerDownEv EventType = 0x700 + iota
	FingerUpEv
	FingerMotionEv
)

/* Gesture events */
const (
	DollarGestureEv EventType = 0x800 + iota
	DollarRecordEv
	MultiGestureEv
)

/* Clipboard events */
const (
	ClipboardUpdateEv EventType = 0x900
)

/* Drag and drop events */
const (
	DropFileEv EventType = 0x1000
)

/*
Events ::USEREVENT through ::LASTEVENT are for your use
and should be allocated with RegisterEvents()
*/
const (
	UserEv EventType = 0x8000
	// This last event is only for bounding internal arrays
	LastEv EventType = 0xFFFF
)

// END EventType }}}1

// Event is implemented by all SDL events.
type Event interface {
	// Type returns the event's type.
	Type() EventType

	// Timestamp returns the number of milliseconds since the SDL library initialization.
	Timestamp() uint32
}

// PollEvent returns the next available event, or nil if there is no event pending.
func PollEvent() Event {
	var cEvent C.SDL_Event
	if C.SDL_PollEvent(&cEvent) == 0 {
		return nil
	}
	return convertEvent(unsafe.Pointer(&cEvent))
}

// HasEvent returns whether there is a pending event available.
func HasEvent() bool {
	return C.SDL_PollEvent(nil) != 0
}

func convertEvent(cEvent unsafe.Pointer) Event {
	common := (*C.SDL_CommonEvent)(cEvent)
	switch EventType(common._type) {
	case QuitEv:
		// Quit events don't hold any data beyond the common events.
		return commonEvent{common}
	case KeyDownEv, KeyUpEv:
		return KeyboardEvent{(*C.SDL_KeyboardEvent)(cEvent)}
	default:
		fmt.Println("Unhandled event with int:", int(common._type))
		return commonEvent{common}
	}
}

// {{{1 Event Structs

// {{{2 CommonEvent

// commonEvent holds fields common to all events.  It is not exported because it
// doesn't provide anything useful outside of the Event interface.
type commonEvent struct {
	ev *C.SDL_CommonEvent
}

func (e commonEvent) Type() EventType {
	return EventType(e.ev._type)
}

func (e commonEvent) Timestamp() uint32 {
	return uint32(e.ev.timestamp)
}

// }}}2 CommonEvent

// {{{2 WindowEvent
// TODO make this implement Event
type WindowEvent struct {
	ev C.SDL_WindowEvent
}

// }}}2 WindowEvent

// {{{2 KeyboardEvent
type KeyboardEvent struct {
	e *C.SDL_KeyboardEvent
}

func (e KeyboardEvent) Timestamp() uint32 {
	return uint32(e.e.timestamp)
}

func (e KeyboardEvent) Type() EventType {
	return EventType(e.e._type)
}

func (e KeyboardEvent) WindowID() uint32 {
	return uint32(e.e.windowID)
}

func (e KeyboardEvent) State() uint8 {
	return uint8(e.e.state)
}

func (e KeyboardEvent) Repeat() uint8 {
	return uint8(e.e.repeat)
}

// TODO this
/*
func (e KeyboardEvent) Keysym() Keysym {
}
*/
// }}}2 KeyboardEvent

// {{{2 TextEditingEvent
// TODO make this implement Event
type TextEditingEvent struct {
	ev C.SDL_TextEditingEvent
}

// }}}2 TextEditingEvent

// {{{2 TextInputEvent
// TODO make this implement Event
type TextInputEvent struct {
	ev C.SDL_TextInputEvent
}

// }}}2 TextInputEvent

// {{{2 MouseMotionEvent
// TODO make this implement Event
type MouseMotionEvent struct {
	ev C.SDL_MouseMotionEvent
}

// }}}2 MouseMotionEvent

// {{{2 MouseButtonEvent
// TODO make this implement Event
type MouseButtonEvent struct {
	ev C.SDL_MouseButtonEvent
}

// }}}2 MouseButtonEvent

// {{{2 MouseWheelEvent
// TODO make this implement Event
type MouseWheelEvent struct {
	ev C.SDL_MouseWheelEvent
}

// }}}2 MouseWheelEvent

// {{{2 JoyAxisEvent
// TODO make this implement Event
type JoyAxisEvent struct {
	ev C.SDL_JoyAxisEvent
}

// }}}2 JoyAxisEvent

// {{{2 JoyBallEvent
// TODO make this implement Event
type JoyBallEvent struct {
	ev C.SDL_JoyBallEvent
}

// }}}2 JoyBallEvent

// {{{2 JoyHatEvent
// TODO make this implement Event
type JoyHatEvent struct {
	ev C.SDL_JoyHatEvent
}

// }}}2 JoyHatEvent

// {{{2 JoyButtonEvent
// TODO make this implement Event
type JoyButtonEvent struct {
	ev C.SDL_JoyButtonEvent
}

// }}}2 JoyButtonEvent

// {{{2 JoyDeviceEvent
// TODO make this implement Event
type JoyDeviceEvent struct {
	ev C.SDL_JoyDeviceEvent
}

// }}}2 JoyDeviceEvent

// {{{2 ControllerAxisEvent
// TODO make this implement Event
type ControllerAxisEvent struct {
	ev C.SDL_ControllerAxisEvent
}

// }}}2 ControllerAxisEvent

// {{{2 ControllerButtonEvent
// TODO make this implement Event
type ControllerButtonEvent struct {
	ev C.SDL_ControllerButtonEvent
}

// }}}2 ControllerButtonEvent

// {{{2 ControllerDeviceEvent
// TODO make this implement Event
type ControllerDeviceEvent struct {
	ev C.SDL_ControllerDeviceEvent
}

// }}}2 ControllerDeviceEvent

// {{{2 UserEvent
// TODO make this implement Event
type UserEvent struct {
	ev C.SDL_UserEvent
}

// }}}2 UserEvent

// {{{2 SysWMEvent
// TODO make this implement Event
type SysWMEvent struct {
	ev C.SDL_SysWMEvent
}

// }}}2 SysWMEvent

// {{{2 TouchFingerEvent
// TODO make this implement Event
type TouchFingerEvent struct {
	ev C.SDL_TouchFingerEvent
}

// }}}2 TouchFingerEvent

// {{{2 MultiGestureEvent
// TODO make this implement Event
type MultiGestureEvent struct {
	ev C.SDL_MultiGestureEvent
}

// }}}2 MultiGestureEvent

// {{{2 DollarGestureEvent
// TODO make this implement Event
type DollarGestureEvent struct {
	ev C.SDL_DollarGestureEvent
}

// }}}2 DollarGestureEvent

// {{{2 DropEvent
// TODO make this implement Event
type DropEvent struct {
	ev C.SDL_DropEvent
}

// }}}2 DropEvent

// }}}1 Event Structs
<|endoftext|>


<commit_before>package send

import (
	"bytes"
	"crypto/tls"
	"encoding/base64"
	"errors"
	"fmt"
	"io"
	"log"
	"net/mail"
	"net/smtp"
	"os"
	"strings"
	"sync"

	"github.com/mongodb/grip/message"
)

type smtpLogger struct {
	opts *SMTPOptions
	*Base
}

// NewSMTPLogger constructs a Sender implementation that delivers mail
// for every loggable message. The configuration of the outgoing SMTP
// server, and the formatting of the log message is handled by the
// SMTPOptions structure, which you must use to configure this sender.
func NewSMTPLogger(opts *SMTPOptions, l LevelInfo) (Sender, error) {
	s, err := MakeSMTPLogger(opts)
	if err != nil {
		return nil, err
	}

	if err = s.SetLevel(l); err != nil {
		return nil, err
	}

	return s, nil
}

// MakeSMTPLogger constructs an unconfigured (e.g. without level information)
// SMTP Sender implementation that delivers mail for every loggable message.
// The configuration of the outgoing SMTP server, and the formatting of the
// log message is handled by the SMTPOptions structure, which you must use
// to configure this sender.
func MakeSMTPLogger(opts *SMTPOptions) (Sender, error) {
	if err := opts.Validate(); err != nil {
		return nil, err
	}

	if err := opts.client.Create(opts); err != nil {
		return nil, err
	}

	s := &smtpLogger{
		Base: NewBase(opts.Name),
		opts: opts,
	}

	fallback := log.New(os.Stdout, "", log.LstdFlags)
	if err := s.SetErrorHandler(ErrorHandlerFromLogger(fallback)); err != nil {
		return nil, err
	}

	s.reset = func() {
		fallback.SetPrefix(fmt.Sprintf("[%s] ", s.Name()))
	}

	s.SetName(opts.Name)

	return s, nil
}

func (s *smtpLogger) Send(m message.Composer) {
	if s.Level().ShouldLog(m) {
		if err := s.opts.sendMail(m); err != nil {
			s.ErrorHandler(err, m)
		}
	}
}

///////////////////////////////////////////////////////////////////////////
//
// Implemntation of the Configuration Object
//
///////////////////////////////////////////////////////////////////////////

// The configuration object handles the (internal) implementation of
// generating and sending email.

// SMTPOptions configures the behavior of the SMTP logger. There is no
// explicit constructor; however, the Validate method (which is called
// by the Sender's constructor) checks for incomplete configuration
// and sets default options if they are unset.
//
// In additional to constructing this object with the necessary
// options. You must also set at least one recipient address using the
// AddRecipient or AddRecipients functions. You can add or reset the
// recipients after configuring the options or the sender.
type SMTPOptions struct { // nolint
	// Name controls both the name of the logger, and the name on
	// the from header field.
	Name string
	// From specifies the address specified.
	From string
	// Server, Port, and UseSSL control how we connect to the SMTP
	// server. If unspecified, these options default to
	// "localhost", port, and false.
	Server string
	Port   int
	UseSSL bool
	// Username and password define how the client authenticates
	// to the SMTP server. If no Username is specified, the client
	// will not authenticate to the server.
	Username string
	Password string

	// These options control the output behavior. You must specify
	// a subject for the emails, *or* one of the bool options that
	// specify how to generate the subject (e.g. NameAsSubject,
	// MessageAsSubject) you can specify a non-zero value to
	// TruncatedMessageSubjectLength to use a fragment of the
	// message as the subject. The use of these options is
	// depended on the implementation of the GetContents function.
	//
	// The GetContents function returns a pair of strings (subject,
	// body), and defaults to an implementation that generates a
	// plain text email according to the (Subject,
	// TruncatedMessageSubjectLength, NameAsSubject, and
	// MessageAsSubject). If you use the default GetContents
	// implementation, PlainTextContents is set to false.
	//
	// You can define your own implementation of the GetContents
	// function, to use a template, or to generate an HTML email
	// as needed, you must also set PlainTextContents as needed.
	GetContents                   func(*SMTPOptions, message.Composer) (string, string)
	Subject                       string
	TruncatedMessageSubjectLength int
	NameAsSubject                 bool
	MessageAsSubject              bool
	PlainTextContents             bool

	client   smtpClient
	fromAddr *mail.Address
	toAddrs  []*mail.Address
	mutex    sync.Mutex
}

// ResetRecipients removes all recipients from the configuration
// object. You can reset the recipients at any time, but you must have
// at least one recipient configured when you use this options object to
// configure a sender *or* attempt to send a message.
func (o *SMTPOptions) ResetRecipients() {
	o.mutex.Lock()
	defer o.mutex.Unlock()

	o.toAddrs = []*mail.Address{}
}

// AddRecipient takes a name and email address as an argument and
// attempts to parse a valid email address from this data, and if
// valid adds this email address.
func (o *SMTPOptions) AddRecipient(name, address string) error {
	o.mutex.Lock()
	defer o.mutex.Unlock()

	addr, err := mail.ParseAddress(fmt.Sprintf("%s <%s>", name, address))
	if err != nil {
		return err
	}

	o.toAddrs = append(o.toAddrs, addr)
	return nil
}

// AddRecipients accepts one or more string that can be, itself, comma
// separated lists of email addresses, which are then added to the
// recipients for the logger.
func (o *SMTPOptions) AddRecipients(addresses ...string) error {
	if len(addresses) == 0 {
		return errors.New("AddRecipients requires one or more strings that contain comma " +
			"separated email addresses")
	}

	o.mutex.Lock()
	defer o.mutex.Unlock()

	addrs, err := mail.ParseAddressList(strings.Join(addresses, ","))
	if err != nil {
		return err
	}

	o.toAddrs = append(o.toAddrs, addrs...)

	return nil
}

// Validate checks the contents of the SMTPOptions struct and sets
// default values in appropriate cases. Returns an error if the struct
// is not valid. The constructor for the SMTP sender calls this method
// to make sure that options struct is valid before initiating the sender.
func (o *SMTPOptions) Validate() error {
	if o == nil {
		return errors.New("must specify non-nil SMTP options")
	}

	o.mutex.Lock()
	defer o.mutex.Unlock()

	// setup defaults

	if o.Port == 0 {
		o.Port = 25
	}

	if o.Server == "" {
		o.Server = "localhost"
	}

	if o.client == nil {
		o.client = &smtpClientImpl{}
	}

	if o.GetContents == nil {
		o.PlainTextContents = true
		o.GetContents = func(opts *SMTPOptions, m message.Composer) (string, string) {
			msg := m.String()

			// we can assume that it's valid because Validate has already run
			if o.MessageAsSubject {
				return m.String(), ""
			}

			if o.NameAsSubject {
				return opts.Name, msg
			}

			if o.TruncatedMessageSubjectLength > 0 {
				if len(msg) <= opts.TruncatedMessageSubjectLength {
					return msg, msg
				}

				return msg[:opts.TruncatedMessageSubjectLength-1], msg
			}

			if o.Subject != "" {
				return o.Subject, msg
			}

			return "", msg
		}
	}

	o.fromAddr = &mail.Address{
		Name:    o.Name,
		Address: o.From,
	}

	// validate user configuration options

	errs := []string{}
	if !o.MessageAsSubject && !o.NameAsSubject && o.TruncatedMessageSubjectLength == 0 && o.Subject == "" {
		errs = append(errs, "no subject policy defined in SMTP options")
	}

	if o.NameAsSubject && o.MessageAsSubject {
		errs = append(errs, "conflicting message subject policy defined")
	}

	if o.Name == "" {
		errs = append(errs, "no name specified")
	}

	if len(o.toAddrs) < 1 {
		errs = append(errs, "no recipient addresses defined.")
	}

	// put additional pre-flight checks above this line, as needed.

	if len(errs) > 0 {
		return errors.New(strings.Join(errs, "; "))
	}

	return nil
}

/* Connects an SMTP server (usually localhost:25 in prod) and uses that to
   send an email with the body encoded in base64. */
func (o *SMTPOptions) sendMail(m message.Composer) error {
	o.mutex.Lock()
	defer o.mutex.Unlock()

	var subject, body string
	toAddrs := o.toAddrs
	fromAddr := o.fromAddr
	isPlainText := o.PlainTextContents
	var headers map[string][]string

	if emailMsg, ok := m.Raw().(*message.Email); ok {
		var err error
		if len(emailMsg.From) != 0 {
			fromAddr, err = mail.ParseAddress(emailMsg.From)
			if err != nil {
				return fmt.Errorf("invalid from address: %+v", err)
			}
		}

		toAddrs = make([]*mail.Address, len(emailMsg.Recipients))

		for i := range emailMsg.Recipients {
			toAddrs[i], err = mail.ParseAddress(emailMsg.Recipients[i])
			if err != nil {
				return fmt.Errorf("invalid recipient: %+v", err)
			}
		}

		subject = emailMsg.Subject
		body = emailMsg.Body
		isPlainText = emailMsg.PlainTextContents
		headers = emailMsg.Headers
	}
	if fromAddr == nil {
		return fmt.Errorf("no from address specified, cannot send mail")
	}
	if len(toAddrs) == 0 {
		return fmt.Errorf("no recipients specified, cannot send mail")
	}

	if err := o.client.Mail(fromAddr.Address); err != nil {
		return fmt.Errorf("Error establishing mail sender (%s): %+v", fromAddr.String(), err)
	}

	var err error
	var errs []string
	var recipients []string

	// Set the recipients
	for _, target := range toAddrs {
		addr := target.String()
		if err = o.client.Rcpt(addr); err != nil {
			errs = append(errs,
				fmt.Sprintf("Error establishing mail recipient (%s): %+v", addr, err))
			continue
		}
		recipients = append(recipients, addr)
	}
	if len(errs) > 0 {
		return errors.New(strings.Join(errs, "; "))
	}

	// Send the email body.
	wc, err := o.client.Data()
	if err != nil {
		return err
	}
	defer wc.Close()

	if len(subject) == 0 && len(body) == 0 {
		subject, body = o.GetContents(o, m)
	}

	contents := []string{
		fmt.Sprintf("From: %s", fromAddr.String()),
		fmt.Sprintf("To: %s", strings.Join(recipients, ", ")),
		fmt.Sprintf("Subject: %s", subject),
		"MIME-Version: 1.0",
	}

	skipContentType := false
	for k, v := range headers {
		if k == "To" || k == "From" || k == "Subject" || k == "Content-Transfer-Encoding" {
			continue
		}
		if k == "Content-Type" {
			skipContentType = true
		}
		for i := range v {
			contents = append(contents, fmt.Sprintf("%s: %s", k, v[i]))
		}
	}

	if !skipContentType {
		if isPlainText {
			contents = append(contents, "Content-Type: text/plain; charset=\"utf-8\"")
		} else {
			contents = append(contents, "Content-Type: text/html; charset=\"utf-8\"")
		}
	}

	contents = append(contents,
		"Content-Transfer-Encoding: base64",
		base64.StdEncoding.EncodeToString([]byte(body)))

	// write the body
	_, err = bytes.NewBufferString(strings.Join(contents, "\r\n")).WriteTo(wc)
	return err
}

////////////////////////////////////////////////////////////////////////
//
// interface mock for testability
//
////////////////////////////////////////////////////////////////////////

type smtpClient interface {
	Create(*SMTPOptions) error
	Mail(string) error
	Rcpt(string) error
	Data() (io.WriteCloser, error)
}

type smtpClientImpl struct {
	*smtp.Client
}

func (c *smtpClientImpl) Create(opts *SMTPOptions) error {
	var err error

	if opts.UseSSL {
		var tlsCon *tls.Conn
		tlsCon, err = tls.Dial("tcp", fmt.Sprintf("%v:%v", opts.Server, opts.Port), &tls.Config{})
		if err != nil {
			return err
		}
		c.Client, err = smtp.NewClient(tlsCon, opts.Server)
	} else {
		c.Client, err = smtp.Dial(fmt.Sprintf("%v:%v", opts.Server, opts.Port))
	}

	if err != nil {
		return err
	}

	if opts.Username != "" {
		if err = c.Client.Auth(smtp.PlainAuth("", opts.Username, opts.Password, opts.Server)); err != nil {
			return err
		}
	}

	return nil
}
<commit_msg>MAKE-397 Grip SMTP sender not sending correctly formatted RCPT command/not handling STARTTLS command (#29)<commit_after>package send

import (
	"bytes"
	"crypto/tls"
	"encoding/base64"
	"errors"
	"fmt"
	"io"
	"log"
	"net/mail"
	"net/smtp"
	"os"
	"strings"
	"sync"

	"github.com/mongodb/grip/message"
)

type smtpLogger struct {
	opts *SMTPOptions
	*Base
}

// NewSMTPLogger constructs a Sender implementation that delivers mail
// for every loggable message. The configuration of the outgoing SMTP
// server, and the formatting of the log message is handled by the
// SMTPOptions structure, which you must use to configure this sender.
func NewSMTPLogger(opts *SMTPOptions, l LevelInfo) (Sender, error) {
	s, err := MakeSMTPLogger(opts)
	if err != nil {
		return nil, err
	}

	if err = s.SetLevel(l); err != nil {
		return nil, err
	}

	return s, nil
}

// MakeSMTPLogger constructs an unconfigured (e.g. without level information)
// SMTP Sender implementation that delivers mail for every loggable message.
// The configuration of the outgoing SMTP server, and the formatting of the
// log message is handled by the SMTPOptions structure, which you must use
// to configure this sender.
func MakeSMTPLogger(opts *SMTPOptions) (Sender, error) {
	if err := opts.Validate(); err != nil {
		return nil, err
	}

	if err := opts.client.Create(opts); err != nil {
		return nil, err
	}

	s := &smtpLogger{
		Base: NewBase(opts.Name),
		opts: opts,
	}

	fallback := log.New(os.Stdout, "", log.LstdFlags)
	if err := s.SetErrorHandler(ErrorHandlerFromLogger(fallback)); err != nil {
		return nil, err
	}

	s.reset = func() {
		fallback.SetPrefix(fmt.Sprintf("[%s] ", s.Name()))
	}

	s.SetName(opts.Name)

	return s, nil
}

func (s *smtpLogger) Send(m message.Composer) {
	if s.Level().ShouldLog(m) {
		if err := s.opts.sendMail(m); err != nil {
			s.ErrorHandler(err, m)
		}
	}
}

///////////////////////////////////////////////////////////////////////////
//
// Implemntation of the Configuration Object
//
///////////////////////////////////////////////////////////////////////////

// The configuration object handles the (internal) implementation of
// generating and sending email.

// SMTPOptions configures the behavior of the SMTP logger. There is no
// explicit constructor; however, the Validate method (which is called
// by the Sender's constructor) checks for incomplete configuration
// and sets default options if they are unset.
//
// In additional to constructing this object with the necessary
// options. You must also set at least one recipient address using the
// AddRecipient or AddRecipients functions. You can add or reset the
// recipients after configuring the options or the sender.
type SMTPOptions struct { // nolint
	// Name controls both the name of the logger, and the name on
	// the from header field.
	Name string
	// From specifies the address specified.
	From string
	// Server, Port, and UseSSL control how we connect to the SMTP
	// server. If unspecified, these options default to
	// "localhost", port, and false.
	Server string
	Port   int
	UseSSL bool
	// Username and password define how the client authenticates
	// to the SMTP server. If no Username is specified, the client
	// will not authenticate to the server.
	Username string
	Password string

	// These options control the output behavior. You must specify
	// a subject for the emails, *or* one of the bool options that
	// specify how to generate the subject (e.g. NameAsSubject,
	// MessageAsSubject) you can specify a non-zero value to
	// TruncatedMessageSubjectLength to use a fragment of the
	// message as the subject. The use of these options is
	// depended on the implementation of the GetContents function.
	//
	// The GetContents function returns a pair of strings (subject,
	// body), and defaults to an implementation that generates a
	// plain text email according to the (Subject,
	// TruncatedMessageSubjectLength, NameAsSubject, and
	// MessageAsSubject). If you use the default GetContents
	// implementation, PlainTextContents is set to false.
	//
	// You can define your own implementation of the GetContents
	// function, to use a template, or to generate an HTML email
	// as needed, you must also set PlainTextContents as needed.
	GetContents                   func(*SMTPOptions, message.Composer) (string, string)
	Subject                       string
	TruncatedMessageSubjectLength int
	NameAsSubject                 bool
	MessageAsSubject              bool
	PlainTextContents             bool

	client   smtpClient
	fromAddr *mail.Address
	toAddrs  []*mail.Address
	mutex    sync.Mutex
}

// ResetRecipients removes all recipients from the configuration
// object. You can reset the recipients at any time, but you must have
// at least one recipient configured when you use this options object to
// configure a sender *or* attempt to send a message.
func (o *SMTPOptions) ResetRecipients() {
	o.mutex.Lock()
	defer o.mutex.Unlock()

	o.toAddrs = []*mail.Address{}
}

// AddRecipient takes a name and email address as an argument and
// attempts to parse a valid email address from this data, and if
// valid adds this email address.
func (o *SMTPOptions) AddRecipient(name, address string) error {
	o.mutex.Lock()
	defer o.mutex.Unlock()

	addr, err := mail.ParseAddress(fmt.Sprintf("%s <%s>", name, address))
	if err != nil {
		return err
	}

	o.toAddrs = append(o.toAddrs, addr)
	return nil
}

// AddRecipients accepts one or more string that can be, itself, comma
// separated lists of email addresses, which are then added to the
// recipients for the logger.
func (o *SMTPOptions) AddRecipients(addresses ...string) error {
	if len(addresses) == 0 {
		return errors.New("AddRecipients requires one or more strings that contain comma " +
			"separated email addresses")
	}

	o.mutex.Lock()
	defer o.mutex.Unlock()

	addrs, err := mail.ParseAddressList(strings.Join(addresses, ","))
	if err != nil {
		return err
	}

	o.toAddrs = append(o.toAddrs, addrs...)

	return nil
}

// Validate checks the contents of the SMTPOptions struct and sets
// default values in appropriate cases. Returns an error if the struct
// is not valid. The constructor for the SMTP sender calls this method
// to make sure that options struct is valid before initiating the sender.
func (o *SMTPOptions) Validate() error {
	if o == nil {
		return errors.New("must specify non-nil SMTP options")
	}

	o.mutex.Lock()
	defer o.mutex.Unlock()

	// setup defaults

	if o.Port == 0 {
		o.Port = 25
	}

	if o.Server == "" {
		o.Server = "localhost"
	}

	if o.client == nil {
		o.client = &smtpClientImpl{}
	}

	if o.GetContents == nil {
		o.PlainTextContents = true
		o.GetContents = func(opts *SMTPOptions, m message.Composer) (string, string) {
			msg := m.String()

			// we can assume that it's valid because Validate has already run
			if o.MessageAsSubject {
				return m.String(), ""
			}

			if o.NameAsSubject {
				return opts.Name, msg
			}

			if o.TruncatedMessageSubjectLength > 0 {
				if len(msg) <= opts.TruncatedMessageSubjectLength {
					return msg, msg
				}

				return msg[:opts.TruncatedMessageSubjectLength-1], msg
			}

			if o.Subject != "" {
				return o.Subject, msg
			}

			return "", msg
		}
	}

	o.fromAddr = &mail.Address{
		Name:    o.Name,
		Address: o.From,
	}

	// validate user configuration options

	errs := []string{}
	if !o.MessageAsSubject && !o.NameAsSubject && o.TruncatedMessageSubjectLength == 0 && o.Subject == "" {
		errs = append(errs, "no subject policy defined in SMTP options")
	}

	if o.NameAsSubject && o.MessageAsSubject {
		errs = append(errs, "conflicting message subject policy defined")
	}

	if o.Name == "" {
		errs = append(errs, "no name specified")
	}

	if len(o.toAddrs) < 1 {
		errs = append(errs, "no recipient addresses defined.")
	}

	// put additional pre-flight checks above this line, as needed.

	if len(errs) > 0 {
		return errors.New(strings.Join(errs, "; "))
	}

	return nil
}

/* Connects an SMTP server (usually localhost:25 in prod) and uses that to
   send an email with the body encoded in base64. */
func (o *SMTPOptions) sendMail(m message.Composer) error {
	o.mutex.Lock()
	defer o.mutex.Unlock()

	var subject, body string
	toAddrs := o.toAddrs
	fromAddr := o.fromAddr
	isPlainText := o.PlainTextContents
	var headers map[string][]string

	if emailMsg, ok := m.Raw().(*message.Email); ok {
		var err error
		if len(emailMsg.From) != 0 {
			fromAddr, err = mail.ParseAddress(emailMsg.From)
			if err != nil {
				return fmt.Errorf("invalid from address: %+v", err)
			}
		}

		toAddrs = make([]*mail.Address, len(emailMsg.Recipients))

		for i := range emailMsg.Recipients {
			toAddrs[i], err = mail.ParseAddress(emailMsg.Recipients[i])
			if err != nil {
				return fmt.Errorf("invalid recipient: %+v", err)
			}
		}

		subject = emailMsg.Subject
		body = emailMsg.Body
		isPlainText = emailMsg.PlainTextContents
		headers = emailMsg.Headers
	}
	if fromAddr == nil {
		return fmt.Errorf("no from address specified, cannot send mail")
	}
	if len(toAddrs) == 0 {
		return fmt.Errorf("no recipients specified, cannot send mail")
	}

	if err := o.client.Mail(fromAddr.Address); err != nil {
		return fmt.Errorf("Error establishing mail sender (%s): %+v", fromAddr, err)
	}

	var err error
	var errs []string
	var recipients []string

	// Set the recipients
	for _, target := range toAddrs {
		if err = o.client.Rcpt(target.Address); err != nil {
			errs = append(errs,
				fmt.Sprintf("Error establishing mail recipient (%s): %+v", target.String(), err))
			continue
		}
		recipients = append(recipients, target.String())
	}
	if len(errs) > 0 {
		return errors.New(strings.Join(errs, "; "))
	}

	// Send the email body.
	wc, err := o.client.Data()
	if err != nil {
		return err
	}
	defer wc.Close()

	if len(subject) == 0 && len(body) == 0 {
		subject, body = o.GetContents(o, m)
	}

	contents := []string{
		fmt.Sprintf("From: %s", fromAddr.String()),
		fmt.Sprintf("To: %s", strings.Join(recipients, ", ")),
		fmt.Sprintf("Subject: %s", subject),
		"MIME-Version: 1.0",
	}

	skipContentType := false
	for k, v := range headers {
		if k == "To" || k == "From" || k == "Subject" || k == "Content-Transfer-Encoding" {
			continue
		}
		if k == "Content-Type" {
			skipContentType = true
		}
		for i := range v {
			contents = append(contents, fmt.Sprintf("%s: %s", k, v[i]))
		}
	}

	if !skipContentType {
		if isPlainText {
			contents = append(contents, "Content-Type: text/plain; charset=\"utf-8\"")
		} else {
			contents = append(contents, "Content-Type: text/html; charset=\"utf-8\"")
		}
	}

	contents = append(contents,
		"Content-Transfer-Encoding: base64",
		base64.StdEncoding.EncodeToString([]byte(body)))

	// write the body
	_, err = bytes.NewBufferString(strings.Join(contents, "\r\n")).WriteTo(wc)
	return err
}

////////////////////////////////////////////////////////////////////////
//
// interface mock for testability
//
////////////////////////////////////////////////////////////////////////

type smtpClient interface {
	Create(*SMTPOptions) error
	Mail(string) error
	Rcpt(string) error
	Data() (io.WriteCloser, error)
}

type smtpClientImpl struct {
	*smtp.Client
}

func (c *smtpClientImpl) Create(opts *SMTPOptions) error {
	var err error
	c.Client, err = smtp.Dial(fmt.Sprintf("%v:%v", opts.Server, opts.Port))
	if err != nil {
		return err
	}

	if opts.UseSSL {
		config := &tls.Config{ServerName: opts.Server}
		err = c.Client.StartTLS(config)

	} else {
		var hostname string
		hostname, err = os.Hostname()
		if err != nil {
			return err
		}

		err = c.Hello(hostname)
	}
	if err != nil {
		return err
	}

	if opts.Username != "" {
		if err = c.Client.Auth(smtp.PlainAuth("", opts.Username, opts.Password, opts.Server)); err != nil {
			return err
		}
	}

	return nil
}
<|endoftext|>


<commit_before>package send

type SenderType int

const (
	Custom SenderType = iota
	Systemd
	Native
	JsonConsole
	JsonFile
	Syslog
	Internal
	File
	Slack
	Xmpp
	Bootstrap
)

func (t SenderType) String() string {
	switch t {
	case Systemd:
		return "systemd"
	case Native:
		return "native"
	case Syslog:
		return "syslog"
	case Internal:
		return "internal"
	case File:
		return "file"
	case Bootstrap:
		return "bootstrap"
	case Custom:
		return "custom"
	case Slack:
		return "slack"
	case Xmpp:
		return "xmpp"
	case JsonConsole:
		return "json-console"
	case JsonFile:
		return "json-console"
	default:
		return "native"
	}
}
<commit_msg>clarify odd sender type information<commit_after>package send

type SenderType int

const (
	Custom SenderType = iota
	Systemd
	Native
	JsonConsole
	JsonFile
	Syslog
	Internal
	File
	Slack
	Xmpp
	Stream
	Bootstrap
)

func (t SenderType) String() string {
	switch t {
	case Systemd:
		return "systemd"
	case Native:
		return "native"
	case Syslog:
		return "syslog"
	case Internal:
		return "internal"
	case File:
		return "file"
	case Bootstrap:
		return "bootstrap"
	case Custom:
		return "custom"
	case Slack:
		return "slack"
	case Xmpp:
		return "xmpp"
	case JsonConsole:
		return "json-console"
	case JsonFile:
		return "json-file"
	case Stream:
		return "stream"
	default:
		return "native"
	}
}
<|endoftext|>


<commit_before>// Use of this source code is governed by a BSD-style license that can be found
// in the LICENSE file.

package token

import (
	"errors"
	"fmt"
)

type IDMap struct {
	byName map[string]ID
	byKey  []string
}

func (m *IDMap) insert(name string) (ID, error) {
	if name == "" {
		return 0, nil
	}
	if id, ok := builtInsByName[name]; ok {
		return id, nil
	}
	if m.byName == nil {
		m.byName = map[string]ID{}
	}
	if id, ok := m.byName[name]; ok {
		return id, nil
	}

	key := nBuiltInKeys + Key(len(m.byKey))
	if key > maxKey {
		return 0, errors.New("token: too many distinct tokens")
	}
	flags := FlagsImplicitSemicolon
	if numeric(name[0]) {
		flags |= FlagsLiteral
	} else {
		flags |= FlagsIdent
	}
	id := ID(key<<idShift) | ID(flags)
	m.byName[name] = id
	m.byKey = append(m.byKey, name)
	return id, nil
}

func (m *IDMap) ByName(name string) ID {
	if m.byName != nil {
		return m.byName[name]
	}
	return 0
}

func (m *IDMap) ByKey(k Key) string {
	if k < nBuiltInKeys {
		return builtInsByKey[k].name
	}
	k -= nBuiltInKeys
	if uint(k) < uint(len(m.byKey)) {
		return m.byKey[k]
	}
	return ""
}

func alpha(c byte) bool {
	return ('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z') || (c == '_')
}

func alphaNumeric(c byte) bool {
	return ('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z') || (c == '_') || ('0' <= c && c <= '9')
}

func numeric(c byte) bool {
	return ('0' <= c && c <= '9')
}

func hasPrefix(a []byte, s string) bool {
	if len(s) == 0 {
		return true
	}
	if len(a) < len(s) {
		return false
	}
	a = a[:len(s)]
	for i := range a {
		if a[i] != s[i] {
			return false
		}
	}
	return true
}

func Tokenize(src []byte, m *IDMap, filename string) (tokens []Token, retErr error) {
	line := uint32(1)
loop:
	for i := 0; i < len(src); {
		c := src[i]

		if c <= ' ' {
			if c == '\n' {
				if len(tokens) > 0 && tokens[len(tokens)-1].IsImplicitSemicolon() {
					tokens = append(tokens, Token{IDSemicolon, line})
				}
				if line == 1<<32-1 {
					return nil, fmt.Errorf("token: too many lines in %q", filename)
				}
				line++
			}
			i++
			continue
		}

		if alpha(c) {
			j := i + 1
			for ; j < len(src) && alphaNumeric(src[j]); j++ {
			}
			id, err := m.insert(string(src[i:j]))
			if err != nil {
				return nil, err
			}
			tokens = append(tokens, Token{id, line})
			i = j
			continue
		}

		if numeric(c) {
			// TODO: 0x12 hex and 0b11 binary numbers.
			//
			// TODO: allow underscores like 0b1000_0000_1111?
			//
			// TODO: save numbers in a separate map, not the IDMap??
			j := i + 1
			for ; j < len(src) && numeric(src[j]); j++ {
			}
			id, err := m.insert(string(src[i:j]))
			if err != nil {
				return nil, err
			}
			tokens = append(tokens, Token{id, line})
			i = j
			continue
		}

		if c == '/' && i+1 < len(src) && src[i+1] == '/' {
			// TODO: save comments, dont' just skip them. We'll need to render
			// them for puffsfmt.
			i += 2
			for ; i < len(src) && src[i] != '\n'; i++ {
			}
			continue
		}

		if id := squiggles[c]; id != 0 {
			i++
			if id.Flags() != 0 {
				tokens = append(tokens, Token{id, line})
				continue
			}
			for _, x := range lexers[c] {
				if hasPrefix(src[i:], x.suffix) {
					i += len(x.suffix)
					tokens = append(tokens, Token{x.id, line})
					continue loop
				}
			}
		}

		msg := ""
		if c <= 0x7F {
			msg = fmt.Sprintf("byte '\\x%02X' (%q)", c, c)
		} else {
			msg = fmt.Sprintf("non-ASCII byte '\\x%02X'", c)
		}
		return nil, fmt.Errorf("token: unrecognized %s at %s:%d", msg, filename, line)
	}
	return tokens, nil
}
<commit_msg>Enforce a maximum token size.<commit_after>// Use of this source code is governed by a BSD-style license that can be found
// in the LICENSE file.

package token

import (
	"errors"
	"fmt"
)

const maxTokenSize = 1023

type IDMap struct {
	byName map[string]ID
	byKey  []string
}

func (m *IDMap) insert(name string) (ID, error) {
	if name == "" {
		return 0, nil
	}
	if id, ok := builtInsByName[name]; ok {
		return id, nil
	}
	if m.byName == nil {
		m.byName = map[string]ID{}
	}
	if id, ok := m.byName[name]; ok {
		return id, nil
	}

	key := nBuiltInKeys + Key(len(m.byKey))
	if key > maxKey {
		return 0, errors.New("token: too many distinct tokens")
	}
	flags := FlagsImplicitSemicolon
	if numeric(name[0]) {
		flags |= FlagsLiteral
	} else {
		flags |= FlagsIdent
	}
	id := ID(key<<idShift) | ID(flags)
	m.byName[name] = id
	m.byKey = append(m.byKey, name)
	return id, nil
}

func (m *IDMap) ByName(name string) ID {
	if m.byName != nil {
		return m.byName[name]
	}
	return 0
}

func (m *IDMap) ByKey(k Key) string {
	if k < nBuiltInKeys {
		return builtInsByKey[k].name
	}
	k -= nBuiltInKeys
	if uint(k) < uint(len(m.byKey)) {
		return m.byKey[k]
	}
	return ""
}

func alpha(c byte) bool {
	return ('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z') || (c == '_')
}

func alphaNumeric(c byte) bool {
	return ('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z') || (c == '_') || ('0' <= c && c <= '9')
}

func numeric(c byte) bool {
	return ('0' <= c && c <= '9')
}

func hasPrefix(a []byte, s string) bool {
	if len(s) == 0 {
		return true
	}
	if len(a) < len(s) {
		return false
	}
	a = a[:len(s)]
	for i := range a {
		if a[i] != s[i] {
			return false
		}
	}
	return true
}

func Tokenize(src []byte, m *IDMap, filename string) (tokens []Token, retErr error) {
	line := uint32(1)
loop:
	for i := 0; i < len(src); {
		c := src[i]

		if c <= ' ' {
			if c == '\n' {
				if len(tokens) > 0 && tokens[len(tokens)-1].IsImplicitSemicolon() {
					tokens = append(tokens, Token{IDSemicolon, line})
				}
				if line == 1<<32-1 {
					return nil, fmt.Errorf("token: too many lines in %q", filename)
				}
				line++
			}
			i++
			continue
		}

		if alpha(c) {
			j := i + 1
			for j < len(src) && alphaNumeric(src[j]) {
				j++
				if j-i > maxTokenSize {
					return nil, fmt.Errorf("token: identifier too long at %s:%d", filename, line)
				}
			}
			id, err := m.insert(string(src[i:j]))
			if err != nil {
				return nil, err
			}
			tokens = append(tokens, Token{id, line})
			i = j
			continue
		}

		if numeric(c) {
			// TODO: 0x12 hex and 0b11 binary numbers.
			//
			// TODO: allow underscores like 0b1000_0000_1111?
			//
			// TODO: save numbers in a separate map, not the IDMap??
			j := i + 1
			for j < len(src) && numeric(src[j]) {
				j++
				if j-i > maxTokenSize {
					return nil, fmt.Errorf("token: constant too long at %s:%d", filename, line)
				}
			}
			id, err := m.insert(string(src[i:j]))
			if err != nil {
				return nil, err
			}
			tokens = append(tokens, Token{id, line})
			i = j
			continue
		}

		if c == '/' && i+1 < len(src) && src[i+1] == '/' {
			// TODO: save comments, dont' just skip them. We'll need to render
			// them for puffsfmt.
			i += 2
			for ; i < len(src) && src[i] != '\n'; i++ {
			}
			continue
		}

		if id := squiggles[c]; id != 0 {
			i++
			if id.Flags() != 0 {
				tokens = append(tokens, Token{id, line})
				continue
			}
			for _, x := range lexers[c] {
				if hasPrefix(src[i:], x.suffix) {
					i += len(x.suffix)
					tokens = append(tokens, Token{x.id, line})
					continue loop
				}
			}
		}

		msg := ""
		if c <= 0x7F {
			msg = fmt.Sprintf("byte '\\x%02X' (%q)", c, c)
		} else {
			msg = fmt.Sprintf("non-ASCII byte '\\x%02X'", c)
		}
		return nil, fmt.Errorf("token: unrecognized %s at %s:%d", msg, filename, line)
	}
	return tokens, nil
}
<|endoftext|>


<commit_before>package watch

import (
	"crypto/tls"
	"crypto/x509"
	"errors"
	"fmt"
	"net"
	"net/http"
	"net/http/cookiejar"
	"strings"
	"time"

	"log"

	"reflect"

	"github.com/foomo/petze/config"
)

var typeDNSConfigErr = reflect.TypeOf(&net.DNSConfigError{})
var typeDNSErr = reflect.TypeOf(&net.DNSError{})
var typeOpErr = reflect.TypeOf(&net.OpError{})
var typeX509CertificateInvalidError = reflect.TypeOf(x509.CertificateInvalidError{})
var typeX509HostnameError = reflect.TypeOf(x509.HostnameError{})
var typeX509SystemRootsError = reflect.TypeOf(x509.SystemRootsError{})
var typeX509UnknownAuthorityError = reflect.TypeOf(x509.UnknownAuthorityError{})

type ErrorType string

const (
	ErrorInvalidEndpoint           ErrorType = "endpointInvalid"
	ErrorTypeServerTooSlow                   = "serverTooSlow"
	ErrorTypeNotImplemented                  = "notImplemented"
	ErrorTypeUnknownError                    = "unknownError"
	ErrorTypeClientError                     = "clientError"
	ErrorTypeDNS                             = "dns"
	ErrorTypeDNSConfig                       = "dnsConfig"
	ErrorTypeTLSCertificateInvalid           = "tlsCertificateInvalid"
	ErrorTypeTLSHostNameError                = "tlsHostNameError"
	ErrorTypeTLSSystemRootsError             = "tlsSystemRootsError"
	ErrorTypeTLSUnknownAuthority             = "tlsUnknownAutority"
	ErrorTypeWrongHTTPStatusCode             = "wrongHTTPStatus"
	ErrorTypeCertificateIsExpiring           = "certificateIsExpiring"
	ErrorTypeUnexpectedContentType           = "unexpectedContentType"
	ErrorTypeSessionFail                     = "sessionFail"
	ErrorTypeGoQueryMismatch                 = "goqueryMismatch"
	ErrorTypeDataMismatch                    = "dataMismatch"
)

type WarningType string

const (
	WarningTypeCertificateWillExpire WarningType = "certificateWillExpire"
)

type Error struct {
	Error   string    `json:"error"`
	Type    ErrorType `json:"type"`
	Comment string    `json:"comment,omitempty"`
}

type Warning struct {
	Warning string      `json:"warning"`
	Type    WarningType `json:"type"`
}

type Result struct {
	ID        string    `json:"id"`
	Errors    []Error   `json:"errors"`
	Warnings  []Warning `json:"warnings"`
	Timeout   bool      `json:"timeout"`
	Timestamp time.Time `json:"timestamp"`
}

func NewResult(id string) *Result {
	return &Result{
		ID:        id,
		Errors:    []Error{},
		Timestamp: time.Now(),
	}
}

func (r *Result) addError(e error, t ErrorType, comment string) {
	r.Errors = addError(r.Errors, e, t, comment)
}

func addError(errors []Error, err error, t ErrorType, comment string) []Error {
	return append(errors, Error{
		Error:   err.Error(),
		Type:    t,
		Comment: comment,
	})
}

func addWarning(warnings []Warning, warning string, t WarningType) []Warning {
	return append(warnings, Warning{
		Warning: warning,
		Type:    t,
	})
}

type dialerErrRecorder struct {
	warnings                   []Warning
	errors                     []Error
	unknownErr                 error
	err                        net.Error
	dnsError                   net.Error
	dnsConfigError             net.Error
	tlsCertificateInvalidError *x509.CertificateInvalidError
	tlsHostnameError           *x509.HostnameError
	tlsSystemRootsError        *x509.SystemRootsError
	tlsUnknownAuthorityError   *x509.UnknownAuthorityError
}

type Watcher struct {
	active  bool
	service *config.Service
}

// Watch create a watcher and start watching
func Watch(service *config.Service, chanResult chan Result) *Watcher {
	w := &Watcher{
		active:  true,
		service: service,
	}
	go w.watchLoop(chanResult)
	return w
}

// Stop watching - beware this is async
func (w *Watcher) Stop() {
	w.active = false
}

func (w *Watcher) watchLoop(chanResult chan Result) {
	for w.active {
		r := watch(w.service)
		if w.active {
			chanResult <- *r
			time.Sleep(time.Second * time.Duration(w.service.Interval))
		}
	}
}

func getClientAndDialErrRecorder() (client *http.Client, errRecorder *dialerErrRecorder) {
	errRecorder = &dialerErrRecorder{
		warnings: []Warning{},
		errors:   []Error{},
	}
	tlsConfig := &tls.Config{}
	dialer := &net.Dialer{
		Timeout:   10 * time.Second,
		KeepAlive: 0 * time.Second,
	}
	dialTLS := func(network, address string) (conn net.Conn, err error) {
		tlsConn, tlsErr := tls.DialWithDialer(dialer, network, address, tlsConfig)
		if tlsErr == nil {
			//conn = tlsConn.(net.Conn)
			connectionState := tlsConn.ConnectionState()
			for _, cert := range connectionState.PeerCertificates {
				durationUntilExpiry := cert.NotAfter.Sub(time.Now())
				if durationUntilExpiry < time.Hour*24 {
					// well in less than 24 h is worth an error
					errRecorder.errors = addError(errRecorder.errors, errors.New(fmt.Sprint("cert CN=\"", cert.Subject.CommonName, "\" is expiring in less than 24h: ", cert.NotAfter, ", left: ", durationUntilExpiry)), ErrorTypeCertificateIsExpiring, "")
				} else if durationUntilExpiry < time.Hour*24*14 {
					// we will start issuing warnings two weeks before a cert expires
					errRecorder.warnings = addWarning(errRecorder.warnings, fmt.Sprint("cert CN=\"", cert.Subject.CommonName, "\" expires on: ", cert.NotAfter, ", left: ", durationUntilExpiry), WarningTypeCertificateWillExpire)
				}
			}
			conn = tlsConn
		} else {

			switch reflect.TypeOf(tlsErr) {
			case typeX509UnknownAuthorityError:
				unknownAuthorityError := tlsErr.(x509.UnknownAuthorityError)
				errRecorder.tlsUnknownAuthorityError = &unknownAuthorityError
			case typeX509HostnameError:
				hostnameErr := tlsErr.(x509.HostnameError)
				errRecorder.tlsHostnameError = &hostnameErr
			case typeX509CertificateInvalidError:
				tlsCertificateInvalidError := tlsErr.(x509.CertificateInvalidError)
				errRecorder.tlsCertificateInvalidError = &tlsCertificateInvalidError
			case typeX509SystemRootsError:
				systemRootsError := tlsErr.(x509.SystemRootsError)
				errRecorder.tlsSystemRootsError = &systemRootsError
			default:
				log.Println("unknown tls error", reflect.TypeOf(tlsErr), tlsErr)
			}
		}
		return conn, tlsErr
	}
	dial := func(network, address string) (conn net.Conn, err error) {
		conn, err = dialer.Dial(network, address)
		if err != nil {
			switch reflect.TypeOf(err) {
			case typeOpErr:
				opError := reflect.ValueOf(err).Elem().Interface().(net.OpError)
				switch reflect.TypeOf(opError.Err) {
				case typeDNSConfigErr:
					log.Println("dns config error")
					errRecorder.dnsConfigError = opError.Err.(net.Error)
				case typeDNSErr:
					log.Println("dns error")
					errRecorder.dnsError = opError.Err.(net.Error)
				default:
					errRecorder.unknownErr = opError.Err
				}
			default:
				log.Println("again some general bullshit", err)
				errRecorder.err = err.(net.Error)
			}
		}
		return
	}
	client = &http.Client{
		Transport: &http.Transport{
			Proxy:               http.ProxyFromEnvironment,
			Dial:                dial,
			DialTLS:             dialTLS,
			TLSHandshakeTimeout: 10 * time.Second,
			TLSClientConfig:     tlsConfig,
		},
	}
	return
}

// actual watch
func watch(service *config.Service) (r *Result) {
	r = NewResult(service.ID)
	// parsing, the endpoint
	request, err := http.NewRequest("GET", service.Endpoint, nil)
	if err != nil {
		r.addError(err, ErrorInvalidEndpoint, "")
		return r
	}
	// my personal dns error check
	if len(request.Host) > 0 {
		host := request.Host
		parts := strings.Split(host, ":")
		if len(parts) > 1 {
			host, _, err = net.SplitHostPort(request.Host)
			if err != nil {
				r.addError(err, ErrorInvalidEndpoint, "")
				return
			}
		}
		_, lookupErr := net.LookupIP(host)
		if lookupErr != nil {
			r.addError(lookupErr, ErrorTypeDNS, "")
			return
		}
	}

	// i am explicitly not calling http.Get, because it does 30x handling and i do not want that
	client, errRecorder := getClientAndDialErrRecorder()
	response, err := client.Do(request)
	r.Errors = append(r.Errors, errRecorder.errors...)
	r.Warnings = append(r.Warnings, errRecorder.warnings...)
	//r.RunTime = time.Since(r.Timestamp)

	if response != nil && response.Body != nil {
		// always close the body
		response.Body.Close()
	}

	if err != nil {
		// sth. went wrong
		r.addError(err, ErrorTypeClientError, "")
		var netErr net.Error
		switch true {
		case errRecorder.tlsHostnameError != nil:
			r.addError(errRecorder.tlsHostnameError, ErrorTypeTLSHostNameError, "")
		case errRecorder.tlsSystemRootsError != nil:
			r.addError(errRecorder.tlsSystemRootsError, ErrorTypeTLSSystemRootsError, "")
		case errRecorder.tlsUnknownAuthorityError != nil:
			r.addError(errRecorder.tlsUnknownAuthorityError, ErrorTypeTLSUnknownAuthority, "")
		case errRecorder.tlsCertificateInvalidError != nil:
			r.addError(errRecorder.tlsCertificateInvalidError, ErrorTypeTLSCertificateInvalid, "")
		case errRecorder.unknownErr != nil:
			r.addError(errRecorder.unknownErr, ErrorTypeUnknownError, "")
		case errRecorder.dnsConfigError != nil:
			netErr = errRecorder.dnsConfigError
			r.addError(errRecorder.dnsConfigError, ErrorTypeDNSConfig, "")
		case errRecorder.dnsError != nil:
			netErr = errRecorder.dnsError
			r.addError(errRecorder.dnsError, ErrorTypeDNS, "")
		case errRecorder.err != nil:
			netErr = errRecorder.err
			r.addError(errRecorder.err, ErrorTypeUnknownError, "")
		}
		//log.Println("service", service.ID, err, "errRecorder dns:", errRecorder.dnsError, ", dnsConfig", errRecorder.dnsConfigError, ", err:", errRecorder.err, ", tls cert invalid:", errRecorder.tlsCertificateInvalidError)
		if netErr != nil {
			r.Timeout = netErr.Timeout()
			//r.ErrorIsTemporary = netErr.Temporary()
		}
		return
	}

	cookieJar, _ := cookiejar.New(nil)
	client.Jar = cookieJar
	errSession := runSession(service, r, client)
	if errSession != nil {
		log.Println("session error", errSession)
		r.addError(errSession, ErrorTypeSessionFail, "")
	}

	// r.addError(errors.New(fmt.Sprint("response time too slow:", runTimeMilliseconds, ", should not be more than:", maxRuntime)), ErrorTypeServerTooSlow)
	//r.StatusCode = response.StatusCode
	if response.StatusCode != http.StatusOK {
		r.addError(errors.New(fmt.Sprint("unexpected status code: ", response.StatusCode)), ErrorTypeWrongHTTPStatusCode, "")
	}
	return
}
<commit_msg>Change sleep interval method signature<commit_after>package watch

import (
	"crypto/tls"
	"crypto/x509"
	"errors"
	"fmt"
	"net"
	"net/http"
	"net/http/cookiejar"
	"strings"
	"time"

	"log"

	"reflect"

	"github.com/foomo/petze/config"
)

var typeDNSConfigErr = reflect.TypeOf(&net.DNSConfigError{})
var typeDNSErr = reflect.TypeOf(&net.DNSError{})
var typeOpErr = reflect.TypeOf(&net.OpError{})
var typeX509CertificateInvalidError = reflect.TypeOf(x509.CertificateInvalidError{})
var typeX509HostnameError = reflect.TypeOf(x509.HostnameError{})
var typeX509SystemRootsError = reflect.TypeOf(x509.SystemRootsError{})
var typeX509UnknownAuthorityError = reflect.TypeOf(x509.UnknownAuthorityError{})

type ErrorType string

const (
	ErrorInvalidEndpoint           ErrorType = "endpointInvalid"
	ErrorTypeServerTooSlow                   = "serverTooSlow"
	ErrorTypeNotImplemented                  = "notImplemented"
	ErrorTypeUnknownError                    = "unknownError"
	ErrorTypeClientError                     = "clientError"
	ErrorTypeDNS                             = "dns"
	ErrorTypeDNSConfig                       = "dnsConfig"
	ErrorTypeTLSCertificateInvalid           = "tlsCertificateInvalid"
	ErrorTypeTLSHostNameError                = "tlsHostNameError"
	ErrorTypeTLSSystemRootsError             = "tlsSystemRootsError"
	ErrorTypeTLSUnknownAuthority             = "tlsUnknownAutority"
	ErrorTypeWrongHTTPStatusCode             = "wrongHTTPStatus"
	ErrorTypeCertificateIsExpiring           = "certificateIsExpiring"
	ErrorTypeUnexpectedContentType           = "unexpectedContentType"
	ErrorTypeSessionFail                     = "sessionFail"
	ErrorTypeGoQueryMismatch                 = "goqueryMismatch"
	ErrorTypeDataMismatch                    = "dataMismatch"
)

type WarningType string

const (
	WarningTypeCertificateWillExpire WarningType = "certificateWillExpire"
)

type Error struct {
	Error   string    `json:"error"`
	Type    ErrorType `json:"type"`
	Comment string    `json:"comment,omitempty"`
}

type Warning struct {
	Warning string      `json:"warning"`
	Type    WarningType `json:"type"`
}

type Result struct {
	ID        string    `json:"id"`
	Errors    []Error   `json:"errors"`
	Warnings  []Warning `json:"warnings"`
	Timeout   bool      `json:"timeout"`
	Timestamp time.Time `json:"timestamp"`
}

func NewResult(id string) *Result {
	return &Result{
		ID:        id,
		Errors:    []Error{},
		Timestamp: time.Now(),
	}
}

func (r *Result) addError(e error, t ErrorType, comment string) {
	r.Errors = addError(r.Errors, e, t, comment)
}

func addError(errors []Error, err error, t ErrorType, comment string) []Error {
	return append(errors, Error{
		Error:   err.Error(),
		Type:    t,
		Comment: comment,
	})
}

func addWarning(warnings []Warning, warning string, t WarningType) []Warning {
	return append(warnings, Warning{
		Warning: warning,
		Type:    t,
	})
}

type dialerErrRecorder struct {
	warnings                   []Warning
	errors                     []Error
	unknownErr                 error
	err                        net.Error
	dnsError                   net.Error
	dnsConfigError             net.Error
	tlsCertificateInvalidError *x509.CertificateInvalidError
	tlsHostnameError           *x509.HostnameError
	tlsSystemRootsError        *x509.SystemRootsError
	tlsUnknownAuthorityError   *x509.UnknownAuthorityError
}

type Watcher struct {
	active  bool
	service *config.Service
}

// Watch create a watcher and start watching
func Watch(service *config.Service, chanResult chan Result) *Watcher {
	w := &Watcher{
		active:  true,
		service: service,
	}
	go w.watchLoop(chanResult)
	return w
}

// Stop watching - beware this is async
func (w *Watcher) Stop() {
	w.active = false
}

func (w *Watcher) watchLoop(chanResult chan Result) {
	for w.active {
		r := watch(w.service)
		if w.active {
			chanResult <- *r
			time.Sleep(w.service.Interval)
		}
	}
}

func getClientAndDialErrRecorder() (client *http.Client, errRecorder *dialerErrRecorder) {
	errRecorder = &dialerErrRecorder{
		warnings: []Warning{},
		errors:   []Error{},
	}
	tlsConfig := &tls.Config{}
	dialer := &net.Dialer{
		Timeout:   10 * time.Second,
		KeepAlive: 0 * time.Second,
	}
	dialTLS := func(network, address string) (conn net.Conn, err error) {
		tlsConn, tlsErr := tls.DialWithDialer(dialer, network, address, tlsConfig)
		if tlsErr == nil {
			//conn = tlsConn.(net.Conn)
			connectionState := tlsConn.ConnectionState()
			for _, cert := range connectionState.PeerCertificates {
				durationUntilExpiry := cert.NotAfter.Sub(time.Now())
				if durationUntilExpiry < time.Hour*24 {
					// well in less than 24 h is worth an error
					errRecorder.errors = addError(errRecorder.errors, errors.New(fmt.Sprint("cert CN=\"", cert.Subject.CommonName, "\" is expiring in less than 24h: ", cert.NotAfter, ", left: ", durationUntilExpiry)), ErrorTypeCertificateIsExpiring, "")
				} else if durationUntilExpiry < time.Hour*24*14 {
					// we will start issuing warnings two weeks before a cert expires
					errRecorder.warnings = addWarning(errRecorder.warnings, fmt.Sprint("cert CN=\"", cert.Subject.CommonName, "\" expires on: ", cert.NotAfter, ", left: ", durationUntilExpiry), WarningTypeCertificateWillExpire)
				}
			}
			conn = tlsConn
		} else {

			switch reflect.TypeOf(tlsErr) {
			case typeX509UnknownAuthorityError:
				unknownAuthorityError := tlsErr.(x509.UnknownAuthorityError)
				errRecorder.tlsUnknownAuthorityError = &unknownAuthorityError
			case typeX509HostnameError:
				hostnameErr := tlsErr.(x509.HostnameError)
				errRecorder.tlsHostnameError = &hostnameErr
			case typeX509CertificateInvalidError:
				tlsCertificateInvalidError := tlsErr.(x509.CertificateInvalidError)
				errRecorder.tlsCertificateInvalidError = &tlsCertificateInvalidError
			case typeX509SystemRootsError:
				systemRootsError := tlsErr.(x509.SystemRootsError)
				errRecorder.tlsSystemRootsError = &systemRootsError
			default:
				log.Println("unknown tls error", reflect.TypeOf(tlsErr), tlsErr)
			}
		}
		return conn, tlsErr
	}
	dial := func(network, address string) (conn net.Conn, err error) {
		conn, err = dialer.Dial(network, address)
		if err != nil {
			switch reflect.TypeOf(err) {
			case typeOpErr:
				opError := reflect.ValueOf(err).Elem().Interface().(net.OpError)
				switch reflect.TypeOf(opError.Err) {
				case typeDNSConfigErr:
					log.Println("dns config error")
					errRecorder.dnsConfigError = opError.Err.(net.Error)
				case typeDNSErr:
					log.Println("dns error")
					errRecorder.dnsError = opError.Err.(net.Error)
				default:
					errRecorder.unknownErr = opError.Err
				}
			default:
				log.Println("again some general bullshit", err)
				errRecorder.err = err.(net.Error)
			}
		}
		return
	}
	client = &http.Client{
		Transport: &http.Transport{
			Proxy:               http.ProxyFromEnvironment,
			Dial:                dial,
			DialTLS:             dialTLS,
			TLSHandshakeTimeout: 10 * time.Second,
			TLSClientConfig:     tlsConfig,
		},
	}
	return
}

// actual watch
func watch(service *config.Service) (r *Result) {
	r = NewResult(service.ID)
	// parsing, the endpoint
	request, err := http.NewRequest("GET", service.Endpoint, nil)
	if err != nil {
		r.addError(err, ErrorInvalidEndpoint, "")
		return r
	}
	// my personal dns error check
	if len(request.Host) > 0 {
		host := request.Host
		parts := strings.Split(host, ":")
		if len(parts) > 1 {
			host, _, err = net.SplitHostPort(request.Host)
			if err != nil {
				r.addError(err, ErrorInvalidEndpoint, "")
				return
			}
		}
		_, lookupErr := net.LookupIP(host)
		if lookupErr != nil {
			r.addError(lookupErr, ErrorTypeDNS, "")
			return
		}
	}

	// i am explicitly not calling http.Get, because it does 30x handling and i do not want that
	client, errRecorder := getClientAndDialErrRecorder()
	response, err := client.Do(request)
	r.Errors = append(r.Errors, errRecorder.errors...)
	r.Warnings = append(r.Warnings, errRecorder.warnings...)
	//r.RunTime = time.Since(r.Timestamp)

	if response != nil && response.Body != nil {
		// always close the body
		response.Body.Close()
	}

	if err != nil {
		// sth. went wrong
		r.addError(err, ErrorTypeClientError, "")
		var netErr net.Error
		switch true {
		case errRecorder.tlsHostnameError != nil:
			r.addError(errRecorder.tlsHostnameError, ErrorTypeTLSHostNameError, "")
		case errRecorder.tlsSystemRootsError != nil:
			r.addError(errRecorder.tlsSystemRootsError, ErrorTypeTLSSystemRootsError, "")
		case errRecorder.tlsUnknownAuthorityError != nil:
			r.addError(errRecorder.tlsUnknownAuthorityError, ErrorTypeTLSUnknownAuthority, "")
		case errRecorder.tlsCertificateInvalidError != nil:
			r.addError(errRecorder.tlsCertificateInvalidError, ErrorTypeTLSCertificateInvalid, "")
		case errRecorder.unknownErr != nil:
			r.addError(errRecorder.unknownErr, ErrorTypeUnknownError, "")
		case errRecorder.dnsConfigError != nil:
			netErr = errRecorder.dnsConfigError
			r.addError(errRecorder.dnsConfigError, ErrorTypeDNSConfig, "")
		case errRecorder.dnsError != nil:
			netErr = errRecorder.dnsError
			r.addError(errRecorder.dnsError, ErrorTypeDNS, "")
		case errRecorder.err != nil:
			netErr = errRecorder.err
			r.addError(errRecorder.err, ErrorTypeUnknownError, "")
		}
		//log.Println("service", service.ID, err, "errRecorder dns:", errRecorder.dnsError, ", dnsConfig", errRecorder.dnsConfigError, ", err:", errRecorder.err, ", tls cert invalid:", errRecorder.tlsCertificateInvalidError)
		if netErr != nil {
			r.Timeout = netErr.Timeout()
			//r.ErrorIsTemporary = netErr.Temporary()
		}
		return
	}

	cookieJar, _ := cookiejar.New(nil)
	client.Jar = cookieJar
	errSession := runSession(service, r, client)
	if errSession != nil {
		log.Println("session error", errSession)
		r.addError(errSession, ErrorTypeSessionFail, "")
	}

	// r.addError(errors.New(fmt.Sprint("response time too slow:", runTimeMilliseconds, ", should not be more than:", maxRuntime)), ErrorTypeServerTooSlow)
	//r.StatusCode = response.StatusCode
	if response.StatusCode != http.StatusOK {
		r.addError(errors.New(fmt.Sprint("unexpected status code: ", response.StatusCode)), ErrorTypeWrongHTTPStatusCode, "")
	}
	return
}
<|endoftext|>


<commit_before>package nhc

import (
	"net"
	"reflect"
	"testing"
)

func TestDiscover(t *testing.T) {
	tests := []struct {
		name string
		want *net.TCPAddr
	}{
		{"no nhc on LAN", nil},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := Discover(); !reflect.DeepEqual(got, tt.want) {
				t.Errorf("Discover() = %v, want %v", got, tt.want)
			}
		})
	}
}
<commit_msg>change type of want in unit test<commit_after>package nhc

import (
	"net"
	"reflect"
	"testing"
)

func TestDiscover(t *testing.T) {
	tests := []struct {
		name string
		want *net.IP
	}{
		{"no nhc on LAN", nil},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := Discover(); !reflect.DeepEqual(got, tt.want) {
				t.Errorf("Discover() = %v, want %v", got, tt.want)
			}
		})
	}
}
<|endoftext|>


<commit_before>package turnpike

import (
	"encoding/base64"
	"encoding/json"
	"fmt"
	"reflect"
	"strings"

	"github.com/ugorji/go/codec"
)

// Serialization indicates the data serialization format used in a WAMP session
type Serialization int

const (
	// Use JSON-encoded strings as a payload.
	JSON Serialization = iota
	// Use msgpack-encoded strings as a payload.
	MSGPACK
)

// applies a list of values from a WAMP message to a message type
func apply(msgType MessageType, arr []interface{}) (Message, error) {
	msg := msgType.New()
	if msg == nil {
		return nil, fmt.Errorf("Unsupported message type")
	}
	val := reflect.ValueOf(msg)
	if val.Kind() == reflect.Ptr {
		val = val.Elem()
	}
	for i := 0; i < val.NumField() && i < len(arr)-1; i++ {
		f := val.Field(i)
		if arr[i+1] == nil {
			continue
		}
		arg := reflect.ValueOf(arr[i+1])
		if arg.Kind() == reflect.Ptr {
			arg = arg.Elem()
		}
		if arg.Type().AssignableTo(f.Type()) {
			f.Set(arg)
		} else if arg.Type().ConvertibleTo(f.Type()) {
			f.Set(arg.Convert(f.Type()))
		} else if f.Type().Kind() != arg.Type().Kind() {
			return nil, fmt.Errorf("Message format error: %dth field not recognizable, got %s, expected %s", i+1, arg.Type(), f.Type())
		} else if f.Type().Kind() == reflect.Map {
			if err := applyMap(f, arg); err != nil {
				return nil, err
			}
		} else if f.Type().Kind() == reflect.Slice {
			if err := applySlice(f, arg); err != nil {
				return nil, err
			}
		} else {
			return nil, fmt.Errorf("Message format error: %dth field not recognizable", i+1)
		}
	}
	return msg, nil
}

// attempts to convert a value to another; is a no-op if it's already assignable to the type
func convert(val reflect.Value, typ reflect.Type) (reflect.Value, error) {
	valType := val.Type()
	if !valType.AssignableTo(typ) {
		if valType.ConvertibleTo(typ) {
			return val.Convert(typ), nil
		} else {
			return val, fmt.Errorf("type %s not convertible to %s", valType.Kind(), typ.Kind())
		}
	}
	return val, nil
}

// re-initializes dst and moves all key/value pairs into dst, converting types as necessary
func applyMap(dst reflect.Value, src reflect.Value) error {
	dstKeyType := dst.Type().Key()
	dstValType := dst.Type().Elem()

	dst.Set(reflect.MakeMap(dst.Type()))
	for _, k := range src.MapKeys() {
		if k.Type().Kind() == reflect.Interface {
			k = k.Elem()
		}
		var err error
		if k, err = convert(k, dstKeyType); err != nil {
			return fmt.Errorf("key '%v' invalid type: %s", k.Interface(), err)
		}

		v := src.MapIndex(k)
		if v, err = convert(v, dstValType); err != nil {
			return fmt.Errorf("value for key '%v' invalid type: %s", k.Interface(), err)
		}
		dst.SetMapIndex(k, v)
	}
	return nil
}

// re-initializes dst and moves all values from src to dst, converting types as necessary
func applySlice(dst reflect.Value, src reflect.Value) error {
	dst.Set(reflect.MakeSlice(dst.Type(), src.Len(), src.Len()))
	dstElemType := dst.Type().Elem()
	for i := 0; i < src.Len(); i++ {
		v, err := convert(src.Index(i), dstElemType)
		if err != nil {
			return fmt.Errorf("Invalid %dth value: %s", i, err)
		}
		dst.Index(i).Set(v)
	}
	return nil
}

// Serializer is the interface implemented by an object that can serialize and
// deserialize WAMP messages
type Serializer interface {
	Serialize(Message) ([]byte, error)
	Deserialize([]byte) (Message, error)
}

// convert the message into a list of values, omitting trailing empty values
func toList(msg Message) []interface{} {
	val := reflect.ValueOf(msg)
	if val.Kind() == reflect.Ptr {
		val = val.Elem()
	}

	// iterate backwards until a non-empty or non-"omitempty" field is found
	last := val.Type().NumField() - 1
	for ; last > 0; last-- {
		tag := val.Type().Field(last).Tag.Get("wamp")
		if !strings.Contains(tag, "omitempty") || val.Field(last).Len() > 0 {
			break
		}
	}

	ret := []interface{}{int(msg.MessageType())}
	for i := 0; i <= last; i++ {
		ret = append(ret, val.Field(i).Interface())
	}
	return ret
}

// MessagePackSerializer is an implementation of Serializer that handles
// serializing and deserializing msgpack encoded payloads.
type MessagePackSerializer struct {
}

// Serialize encodes a Message into a msgpack payload.
func (s *MessagePackSerializer) Serialize(msg Message) ([]byte, error) {
	var b []byte
	return b, codec.NewEncoderBytes(&b, new(codec.MsgpackHandle)).Encode(toList(msg))
}

// Deserialize decodes a msgpack payload into a Message.
func (s *MessagePackSerializer) Deserialize(data []byte) (Message, error) {
	var arr []interface{}
	if err := codec.NewDecoderBytes(data, new(codec.MsgpackHandle)).Decode(&arr); err != nil {
		return nil, err
	} else if len(arr) == 0 {
		return nil, fmt.Errorf("Invalid message")
	}

	var msgType MessageType
	if typ, ok := arr[0].(int64); ok {
		msgType = MessageType(typ)
	} else {
		return nil, fmt.Errorf("Unsupported message format")
	}

	log.Info("Deserialized msgType %d, arr %+v", msgType, arr)

	return apply(msgType, arr)
}

// JSONSerializer is an implementation of Serializer that handles serializing
// and deserializing JSON encoded payloads.
type JSONSerializer struct {
}

// Serialize marshals the payload into a message.
//
// This method does not handle binary data according to WAMP specifications automatically,
// but instead uses the default implementation in encoding/json.
// Use the BinaryData type in your structures if using binary data.
func (s *JSONSerializer) Serialize(msg Message) ([]byte, error) {
	return json.Marshal(toList(msg))
}

// Deserialize unmarshals the payload into a message.
//
// This method does not handle binary data according to WAMP specifications automatically,
// but instead uses the default implementation in encoding/json.
// Use the BinaryData type in your structures if using binary data.
func (s *JSONSerializer) Deserialize(data []byte) (Message, error) {
	var arr []interface{}
	if err := json.Unmarshal(data, &arr); err != nil {
		return nil, err
	} else if len(arr) == 0 {
		return nil, fmt.Errorf("Invalid message")
	}

	var msgType MessageType
	if typ, ok := arr[0].(float64); ok {
		msgType = MessageType(typ)
	} else {
		return nil, fmt.Errorf("Unsupported message format")
	}
	return apply(msgType, arr)
}

// BindaryData is a byte array that can be marshalled and unmarshalled according
// to WAMP specifications:
// https://github.com/tavendo/WAMP/blob/master/spec/basic.md#binary-conversion-of-json-strings
//
// This type *should* be used in types that will be marshalled as JSON.
type BinaryData []byte

func (b BinaryData) MarshalJSON() ([]byte, error) {
	s := base64.StdEncoding.EncodeToString([]byte(b))
	return json.Marshal("\x00" + s)
}

func (b *BinaryData) UnmarshalJSON(arr []byte) error {
	var s string
	err := json.Unmarshal(arr, &s)
	if err != nil {
		return nil
	}
	if s[0] != '\x00' {
		return fmt.Errorf("Not a binary string, doesn't start with a NUL: %v", arr)
	}
	*b, err = base64.StdEncoding.DecodeString(s[1:])
	return err
}
<commit_msg>Debug json deserialize, not msgpack<commit_after>package turnpike

import (
	"encoding/base64"
	"encoding/json"
	"fmt"
	"reflect"
	"strings"

	"github.com/ugorji/go/codec"
)

// Serialization indicates the data serialization format used in a WAMP session
type Serialization int

const (
	// Use JSON-encoded strings as a payload.
	JSON Serialization = iota
	// Use msgpack-encoded strings as a payload.
	MSGPACK
)

// applies a list of values from a WAMP message to a message type
func apply(msgType MessageType, arr []interface{}) (Message, error) {
	msg := msgType.New()
	if msg == nil {
		return nil, fmt.Errorf("Unsupported message type")
	}
	val := reflect.ValueOf(msg)
	if val.Kind() == reflect.Ptr {
		val = val.Elem()
	}
	for i := 0; i < val.NumField() && i < len(arr)-1; i++ {
		f := val.Field(i)
		if arr[i+1] == nil {
			continue
		}
		arg := reflect.ValueOf(arr[i+1])
		if arg.Kind() == reflect.Ptr {
			arg = arg.Elem()
		}
		if arg.Type().AssignableTo(f.Type()) {
			f.Set(arg)
		} else if arg.Type().ConvertibleTo(f.Type()) {
			f.Set(arg.Convert(f.Type()))
		} else if f.Type().Kind() != arg.Type().Kind() {
			return nil, fmt.Errorf("Message format error: %dth field not recognizable, got %s, expected %s", i+1, arg.Type(), f.Type())
		} else if f.Type().Kind() == reflect.Map {
			if err := applyMap(f, arg); err != nil {
				return nil, err
			}
		} else if f.Type().Kind() == reflect.Slice {
			if err := applySlice(f, arg); err != nil {
				return nil, err
			}
		} else {
			return nil, fmt.Errorf("Message format error: %dth field not recognizable", i+1)
		}
	}
	return msg, nil
}

// attempts to convert a value to another; is a no-op if it's already assignable to the type
func convert(val reflect.Value, typ reflect.Type) (reflect.Value, error) {
	valType := val.Type()
	if !valType.AssignableTo(typ) {
		if valType.ConvertibleTo(typ) {
			return val.Convert(typ), nil
		} else {
			return val, fmt.Errorf("type %s not convertible to %s", valType.Kind(), typ.Kind())
		}
	}
	return val, nil
}

// re-initializes dst and moves all key/value pairs into dst, converting types as necessary
func applyMap(dst reflect.Value, src reflect.Value) error {
	dstKeyType := dst.Type().Key()
	dstValType := dst.Type().Elem()

	dst.Set(reflect.MakeMap(dst.Type()))
	for _, k := range src.MapKeys() {
		if k.Type().Kind() == reflect.Interface {
			k = k.Elem()
		}
		var err error
		if k, err = convert(k, dstKeyType); err != nil {
			return fmt.Errorf("key '%v' invalid type: %s", k.Interface(), err)
		}

		v := src.MapIndex(k)
		if v, err = convert(v, dstValType); err != nil {
			return fmt.Errorf("value for key '%v' invalid type: %s", k.Interface(), err)
		}
		dst.SetMapIndex(k, v)
	}
	return nil
}

// re-initializes dst and moves all values from src to dst, converting types as necessary
func applySlice(dst reflect.Value, src reflect.Value) error {
	dst.Set(reflect.MakeSlice(dst.Type(), src.Len(), src.Len()))
	dstElemType := dst.Type().Elem()
	for i := 0; i < src.Len(); i++ {
		v, err := convert(src.Index(i), dstElemType)
		if err != nil {
			return fmt.Errorf("Invalid %dth value: %s", i, err)
		}
		dst.Index(i).Set(v)
	}
	return nil
}

// Serializer is the interface implemented by an object that can serialize and
// deserialize WAMP messages
type Serializer interface {
	Serialize(Message) ([]byte, error)
	Deserialize([]byte) (Message, error)
}

// convert the message into a list of values, omitting trailing empty values
func toList(msg Message) []interface{} {
	val := reflect.ValueOf(msg)
	if val.Kind() == reflect.Ptr {
		val = val.Elem()
	}

	// iterate backwards until a non-empty or non-"omitempty" field is found
	last := val.Type().NumField() - 1
	for ; last > 0; last-- {
		tag := val.Type().Field(last).Tag.Get("wamp")
		if !strings.Contains(tag, "omitempty") || val.Field(last).Len() > 0 {
			break
		}
	}

	ret := []interface{}{int(msg.MessageType())}
	for i := 0; i <= last; i++ {
		ret = append(ret, val.Field(i).Interface())
	}
	return ret
}

// MessagePackSerializer is an implementation of Serializer that handles
// serializing and deserializing msgpack encoded payloads.
type MessagePackSerializer struct {
}

// Serialize encodes a Message into a msgpack payload.
func (s *MessagePackSerializer) Serialize(msg Message) ([]byte, error) {
	var b []byte
	return b, codec.NewEncoderBytes(&b, new(codec.MsgpackHandle)).Encode(toList(msg))
}

// Deserialize decodes a msgpack payload into a Message.
func (s *MessagePackSerializer) Deserialize(data []byte) (Message, error) {
	var arr []interface{}
	if err := codec.NewDecoderBytes(data, new(codec.MsgpackHandle)).Decode(&arr); err != nil {
		return nil, err
	} else if len(arr) == 0 {
		return nil, fmt.Errorf("Invalid message")
	}

	var msgType MessageType
	if typ, ok := arr[0].(int64); ok {
		msgType = MessageType(typ)
	} else {
		return nil, fmt.Errorf("Unsupported message format")
	}

	return apply(msgType, arr)
}

// JSONSerializer is an implementation of Serializer that handles serializing
// and deserializing JSON encoded payloads.
type JSONSerializer struct {
}

// Serialize marshals the payload into a message.
//
// This method does not handle binary data according to WAMP specifications automatically,
// but instead uses the default implementation in encoding/json.
// Use the BinaryData type in your structures if using binary data.
func (s *JSONSerializer) Serialize(msg Message) ([]byte, error) {
	return json.Marshal(toList(msg))
}

// Deserialize unmarshals the payload into a message.
//
// This method does not handle binary data according to WAMP specifications automatically,
// but instead uses the default implementation in encoding/json.
// Use the BinaryData type in your structures if using binary data.
func (s *JSONSerializer) Deserialize(data []byte) (Message, error) {
	var arr []interface{}
	if err := json.Unmarshal(data, &arr); err != nil {
		return nil, err
	} else if len(arr) == 0 {
		return nil, fmt.Errorf("Invalid message")
	}

	var msgType MessageType
	if typ, ok := arr[0].(float64); ok {
		msgType = MessageType(typ)
	} else {
		return nil, fmt.Errorf("Unsupported message format")
	}

	log.Info("Deserialized msgType %d, arr %+v", msgType, arr)

	return apply(msgType, arr)
}

// BindaryData is a byte array that can be marshalled and unmarshalled according
// to WAMP specifications:
// https://github.com/tavendo/WAMP/blob/master/spec/basic.md#binary-conversion-of-json-strings
//
// This type *should* be used in types that will be marshalled as JSON.
type BinaryData []byte

func (b BinaryData) MarshalJSON() ([]byte, error) {
	s := base64.StdEncoding.EncodeToString([]byte(b))
	return json.Marshal("\x00" + s)
}

func (b *BinaryData) UnmarshalJSON(arr []byte) error {
	var s string
	err := json.Unmarshal(arr, &s)
	if err != nil {
		return nil
	}
	if s[0] != '\x00' {
		return fmt.Errorf("Not a binary string, doesn't start with a NUL: %v", arr)
	}
	*b, err = base64.StdEncoding.DecodeString(s[1:])
	return err
}
<|endoftext|>