<commit_before>package main

import (
	"encoding/gob"
	"fmt"
	"log"
	"net"
	"os"
	"time"

	"github.com/udhos/fugo/future"
	"github.com/udhos/fugo/msg"
)

type world struct {
	playerTab      []*player
	playerAdd      chan *player
	playerDel      chan *player
	input          chan inputMsg
	updateInterval time.Duration
	missileList    []*msg.Missile
}

type inputMsg struct {
	player *player
	msg    interface{}
}

type player struct {
	conn         net.Conn
	output       chan msg.Update
	fuelStart    time.Time
	cannonStart  time.Time
	cannonSpeed  float32
	cannonCoordX float32
	team         int
}

func main() {
	addr := ":8080"
	if len(os.Args) > 1 {
		addr = os.Args[1]
	}
	w := world{
		playerTab:      []*player{},
		playerAdd:      make(chan *player),
		playerDel:      make(chan *player),
		updateInterval: 1000 * time.Millisecond,
		input:          make(chan inputMsg),
	}
	if errListen := listenAndServe(&w, addr); errListen != nil {
		log.Printf("main: %v", errListen)
		return
	}

	ticker := time.NewTicker(w.updateInterval)

	team := 0

	log.Printf("main: entering service loop")
SERVICE:
	for {
		select {
		case p := <-w.playerAdd:
			log.Printf("player add: %v team=%d", p, team)
			w.playerTab = append(w.playerTab, p)

			playerFuelSet(p, time.Now(), 5) // reset fuel to 50%
			p.cannonStart = p.fuelStart
			p.cannonSpeed = float32(.1 / 1.0) // 10% every 1 second
			p.cannonCoordX = .8               // 80%
			p.team = team
			team = (team + 1) % 2 // switch next team
		case p := <-w.playerDel:
			log.Printf("player del: %v", p)
			for i, pl := range w.playerTab {
				if pl == p {
					w.playerTab = append(w.playerTab[:i], w.playerTab[i+1:]...)
					log.Printf("player removed: %v", p)
					continue SERVICE
				}
			}
			log.Printf("player not found: %v", p)
		case i := <-w.input:
			//log.Printf("input: %v", i)

			switch i.msg.(type) {
			case msg.Fire:
				//log.Printf("input fire: %v", m)

				fuel := playerFuel(i.player)
				if fuel < 1 {
					continue SERVICE // not enough fuel
				}

				if fuel >= 10 {
					playerFuelSet(i.player, time.Now(), 9)
				} else {
					playerFuelSet(i.player, time.Now(), fuel-1)
				}

				missileSpeed := float32(.5 / 1.0) // 50% every 1 second
				now := time.Now()
				miss1 := &msg.Missile{
					CoordX: i.player.cannonCoordX,
					Speed:  missileSpeed,
					Team:   i.player.team,
					Start:  now,
				}
				w.missileList = append(w.missileList, miss1)

				log.Printf("input fire - fuel was=%v is=%v missiles=%d", fuel, playerFuel(i.player), len(w.missileList))

				updateWorld(&w)
			}

		case <-ticker.C:
			//log.Printf("tick: %v", t)

			updateWorld(&w)
		}
	}
}

func updateWorld(w *world) {
	now := time.Now()
	for _, p := range w.playerTab {
		p.cannonCoordX, p.cannonSpeed = future.CannonX(p.cannonCoordX, p.cannonSpeed, time.Since(p.cannonStart))
		p.cannonStart = now
	}

	size := len(w.missileList)
	for i := 0; i < size; i++ {
		m := w.missileList[i]
		y := m.CoordY
		m.CoordY = future.MissileY(0, m.Speed, time.Since(m.Start))
		if m.CoordY >= 1 {
			log.Printf("kill missile y=%v was=%v size=%d", m.CoordY, y, size)
			size--
			if i >= size {
				// last element
				break
			}
			w.missileList[i] = w.missileList[size]
			i--
		}
	}
	w.missileList = w.missileList[:size]

	for _, p := range w.playerTab {
		sendUpdatesToPlayer(w, p)
	}
}

func playerFuel(p *player) float32 {
	return future.Fuel(0, time.Since(p.fuelStart))
}

func playerFuelSet(p *player, now time.Time, fuel float32) {
	p.fuelStart = now.Add(-time.Duration(float32(time.Second) * fuel / future.FuelRechargeRate))
}

func sendUpdatesToPlayer(w *world, p *player) {
	update := msg.Update{
		Fuel:          playerFuel(p),
		Interval:      w.updateInterval,
		WorldMissiles: w.missileList,
		Team:          p.team,
	}

	for _, p := range w.playerTab {
		cannon := msg.Cannon{
			Start:  p.cannonStart,
			CoordX: p.cannonCoordX,
			Speed:  p.cannonSpeed,
			Team:   p.team,
		}
		update.Cannons = append(update.Cannons, &cannon)
	}

	//log.Printf("sending updates to player %v", p)

	p.output <- update
}

func listenAndServe(w *world, addr string) error {

	log.Printf("serving on TCP %s", addr)

	listener, errListen := net.Listen("tcp", addr)
	if errListen != nil {
		return fmt.Errorf("listenAndServe: %s: %v", addr, errListen)
	}

	gob.Register(msg.Update{})
	gob.Register(msg.Fire{})

	go func() {
		for {
			conn, err := listener.Accept()
			if err != nil {
				log.Printf("accept on TCP %s: %s", addr, err)
				continue
			}
			go connHandler(w, conn)
		}
	}()

	return nil
}

func connHandler(w *world, conn net.Conn) {
	log.Printf("handler for connection %v", conn)

	defer conn.Close()

	p := &player{
		conn:   conn,
		output: make(chan msg.Update),
	}

	w.playerAdd <- p // register player
	quitWriter := make(chan struct{})

	go func() {
		// copy from socket into input channel
		dec := gob.NewDecoder(conn)
		for {
			var m msg.Fire
			if err := dec.Decode(&m); err != nil {
				log.Printf("handler: Decode: %v", err)
				break
			}
			w.input <- inputMsg{player: p, msg: m}
		}
		close(quitWriter) // send quit request to output goroutine
		log.Printf("handler: reader goroutine exiting")
	}()

	// copy from output channel into socket
	enc := gob.NewEncoder(conn)
LOOP:
	for {
		select {
		case <-quitWriter:
			log.Printf("handler: quit request")
			break LOOP
		case m := <-p.output:
			if err := enc.Encode(&m); err != nil {
				log.Printf("handler: Encode: %v", err)
				break LOOP
			}
		}
	}
	w.playerDel <- p // deregister player
	log.Printf("handler: writer goroutine exiting")
}
<commit_msg>Clean-up.<commit_after>package main

import (
	"encoding/gob"
	"fmt"
	"log"
	"net"
	"os"
	"time"

	"github.com/udhos/fugo/future"
	"github.com/udhos/fugo/msg"
)

type world struct {
	playerTab      []*player
	playerAdd      chan *player
	playerDel      chan *player
	input          chan inputMsg
	updateInterval time.Duration
	missileList    []*msg.Missile
}

type inputMsg struct {
	player *player
	msg    interface{}
}

type player struct {
	conn         net.Conn
	output       chan msg.Update
	fuelStart    time.Time
	cannonStart  time.Time
	cannonSpeed  float32
	cannonCoordX float32
	team         int
}

func main() {
	addr := ":8080"
	if len(os.Args) > 1 {
		addr = os.Args[1]
	}
	w := world{
		playerTab:      []*player{},
		playerAdd:      make(chan *player),
		playerDel:      make(chan *player),
		updateInterval: 1000 * time.Millisecond,
		input:          make(chan inputMsg),
	}
	if errListen := listenAndServe(&w, addr); errListen != nil {
		log.Printf("main: %v", errListen)
		return
	}

	ticker := time.NewTicker(w.updateInterval)

	team := 0

	log.Printf("main: entering service loop")
SERVICE:
	for {
		select {
		case p := <-w.playerAdd:
			log.Printf("player add: %v team=%d", p, team)
			w.playerTab = append(w.playerTab, p)

			playerFuelSet(p, time.Now(), 5) // reset fuel to 50%
			p.cannonStart = p.fuelStart
			p.cannonSpeed = float32(.1 / 1.0) // 10% every 1 second
			p.cannonCoordX = .8               // 80%
			p.team = team
			team = (team + 1) % 2 // switch next team
		case p := <-w.playerDel:
			log.Printf("player del: %v", p)
			for i, pl := range w.playerTab {
				if pl == p {
					w.playerTab = append(w.playerTab[:i], w.playerTab[i+1:]...)
					log.Printf("player removed: %v", p)
					continue SERVICE
				}
			}
			log.Printf("player not found: %v", p)
		case i := <-w.input:
			//log.Printf("input: %v", i)

			switch i.msg.(type) {
			case msg.Fire:
				//log.Printf("input fire: %v", m)

				fuel := playerFuel(i.player)
				if fuel < 1 {
					continue SERVICE // not enough fuel
				}

				if fuel >= 10 {
					playerFuelSet(i.player, time.Now(), 9)
				} else {
					playerFuelSet(i.player, time.Now(), fuel-1)
				}

				missileSpeed := float32(.5 / 1.0) // 50% every 1 second
				now := time.Now()
				miss1 := &msg.Missile{
					CoordX: i.player.cannonCoordX,
					Speed:  missileSpeed,
					Team:   i.player.team,
					Start:  now,
				}
				w.missileList = append(w.missileList, miss1)

				log.Printf("input fire - fuel was=%v is=%v missiles=%d", fuel, playerFuel(i.player), len(w.missileList))

				updateWorld(&w)
			}

		case <-ticker.C:
			//log.Printf("tick: %v", t)

			updateWorld(&w)
		}
	}
}

func updateWorld(w *world) {
	now := time.Now()
	for _, p := range w.playerTab {
		p.cannonCoordX, p.cannonSpeed = future.CannonX(p.cannonCoordX, p.cannonSpeed, time.Since(p.cannonStart))
		p.cannonStart = now
	}

	size := len(w.missileList)
	for i := 0; i < size; i++ {
		m := w.missileList[i]
		m.CoordY = future.MissileY(0, m.Speed, time.Since(m.Start))
		if m.CoordY >= 1 {
			size--
			if i >= size {
				// last element
				break
			}
			w.missileList[i] = w.missileList[size]
			i--
		}
	}
	w.missileList = w.missileList[:size]

	for _, p := range w.playerTab {
		sendUpdatesToPlayer(w, p)
	}
}

func playerFuel(p *player) float32 {
	return future.Fuel(0, time.Since(p.fuelStart))
}

func playerFuelSet(p *player, now time.Time, fuel float32) {
	p.fuelStart = now.Add(-time.Duration(float32(time.Second) * fuel / future.FuelRechargeRate))
}

func sendUpdatesToPlayer(w *world, p *player) {
	update := msg.Update{
		Fuel:          playerFuel(p),
		Interval:      w.updateInterval,
		WorldMissiles: w.missileList,
		Team:          p.team,
	}

	for _, p := range w.playerTab {
		cannon := msg.Cannon{
			Start:  p.cannonStart,
			CoordX: p.cannonCoordX,
			Speed:  p.cannonSpeed,
			Team:   p.team,
		}
		update.Cannons = append(update.Cannons, &cannon)
	}

	//log.Printf("sending updates to player %v", p)

	p.output <- update
}

func listenAndServe(w *world, addr string) error {

	log.Printf("serving on TCP %s", addr)

	listener, errListen := net.Listen("tcp", addr)
	if errListen != nil {
		return fmt.Errorf("listenAndServe: %s: %v", addr, errListen)
	}

	gob.Register(msg.Update{})
	gob.Register(msg.Fire{})

	go func() {
		for {
			conn, err := listener.Accept()
			if err != nil {
				log.Printf("accept on TCP %s: %s", addr, err)
				continue
			}
			go connHandler(w, conn)
		}
	}()

	return nil
}

func connHandler(w *world, conn net.Conn) {
	log.Printf("handler for connection %v", conn)

	defer conn.Close()

	p := &player{
		conn:   conn,
		output: make(chan msg.Update),
	}

	w.playerAdd <- p // register player
	quitWriter := make(chan struct{})

	go func() {
		// copy from socket into input channel
		dec := gob.NewDecoder(conn)
		for {
			var m msg.Fire
			if err := dec.Decode(&m); err != nil {
				log.Printf("handler: Decode: %v", err)
				break
			}
			w.input <- inputMsg{player: p, msg: m}
		}
		close(quitWriter) // send quit request to output goroutine
		log.Printf("handler: reader goroutine exiting")
	}()

	// copy from output channel into socket
	enc := gob.NewEncoder(conn)
LOOP:
	for {
		select {
		case <-quitWriter:
			log.Printf("handler: quit request")
			break LOOP
		case m := <-p.output:
			if err := enc.Encode(&m); err != nil {
				log.Printf("handler: Encode: %v", err)
				break LOOP
			}
		}
	}
	w.playerDel <- p // deregister player
	log.Printf("handler: writer goroutine exiting")
}
<|endoftext|>


<commit_before>package parse

import (
	"bufio"
	"fmt"
	"io"
	"unicode"
)

// Pos is a position in source text.
type Pos struct {
	Name   string
	Offset int
	Line   int
	Col    int
}

func (p *Pos) Copy() *Pos {
	var p2 Pos
	p2 = *p
	return &p2
}

func (p *Pos) String() string {
	return fmt.Sprintf("%s:%d:%d", p.Name, p.Line, p.Col)
}

func (p *Pos) FormatError(tag string, msg string) error {
	return fmt.Errorf("%s error at %s: %s", tag, p, msg)
}

// A token is a single lexeme produced by the scanner.
type token struct {
	typ tokType
	pos *Pos
	val string
}

func (t token) AsError() error {
	if t.typ != tokError {
		panic("AsError called on non-error token")
	}
	return t.pos.FormatError("lex", t.val)
}

type tokType int

const (
	tokEOF tokType = iota

	tokApostrophe   // '
	tokAtSign       // @
	tokBacktick     // `
	tokBool         // true, false
	tokCharLiteral  // \c, \newline, etc
	tokCircumflex   // ^
	tokComment      // ; foobar
	tokDispatch     // any dispatch macro token: #{, #(, #_, etc. Does not include tags.
	tokKeyword      // :foo
	tokLeftBrace    // {
	tokLeftBracket  // [
	tokLeftParen    // (
	tokNil          // nil
	tokNumber       // any numeric literal; may be invalid (parser will determine)
	tokOctothorpe   // # (only used for tags; dispatch tokens are separate)
	tokRightBrace   // }
	tokRightBracket // ]
	tokRightParen   // )
	tokString       // string literal (java escapes)
	tokSymbol       // foo, also lambda args (%, %N)
	tokTilde        // ~
	tokNewline

	tokError // error; val is the error text
)

var tokTypeToName = map[tokType]string{
	tokApostrophe:   "apostrophe",
	tokAtSign:       "at-sign",
	tokBacktick:     "backtick",
	tokBool:         "bool",
	tokCharLiteral:  "char-literal",
	tokCircumflex:   "circumflex",
	tokComment:      "comment",
	tokDispatch:     "dispatch",
	tokEOF:          "eof",
	tokError:        "error",
	tokKeyword:      "keyword",
	tokLeftBrace:    "left-brace",
	tokLeftBracket:  "left-bracket",
	tokLeftParen:    "left-paren",
	tokNil:          "nil",
	tokNumber:       "number",
	tokOctothorpe:   "octothorpe",
	tokRightBrace:   "right-brace",
	tokRightBracket: "right-bracket",
	tokRightParen:   "right-paren",
	tokString:       "string",
	tokSymbol:       "symbol",
	tokTilde:        "tilde",
	tokNewline:      "newline",
}

func (t tokType) String() string {
	name, ok := tokTypeToName[t]
	if !ok {
		panic("bad token type")
	}
	return name
}

func (t token) String() string {
	switch t.typ {
	case tokError, tokBool, tokCharLiteral, tokComment, tokKeyword, tokNumber, tokDispatch, tokString, tokSymbol:
		return fmt.Sprintf("<%s@%s>(%q)", t.typ, t.pos, t.val)
	}
	return fmt.Sprintf("<%s@%s>", t.typ, t.pos)
}

// lexer holds the state of the scanner. A single rune of backup is supported.
type lexer struct {
	name    string // the name of the input source
	input   *bufio.Reader
	pos     *Pos // the current position in the input
	start   *Pos // the start position of the token being scanned
	lastPos *Pos // the position before the most recent next() call
	tokens  chan token
	val     []rune // the literal contents of the token
}

func lex(name string, input *bufio.Reader) *lexer {
	l := &lexer{
		name:   name,
		input:  input,
		pos:    &Pos{Name: name, Line: 1, Col: 1},
		start:  &Pos{Name: name, Line: 1, Col: 1},
		tokens: make(chan token),
	}
	go l.run()
	return l
}

type inputReadErr struct {
	err error
}

func (l *lexer) next() (r rune, eof bool) {
	r, w, err := l.input.ReadRune()
	if err != nil {
		if err == io.EOF {
			return 0, true
		}
		panic(inputReadErr{err})
	}
	l.lastPos = l.pos.Copy()
	l.pos.Offset += w
	l.pos.Col += w
	if r == '\n' {
		l.pos.Line++
		l.pos.Col = 1
	}
	l.val = append(l.val, r)
	return r, false
}

func (l *lexer) back() {
	if l.lastPos == nil {
		panic("back() call not preceded by a next()")
	}
	if err := l.input.UnreadRune(); err != nil {
		panic("should not happen")
	}
	l.pos = l.lastPos
	l.val = l.val[:len(l.val)-1]
	l.lastPos = nil
}

// scanWhile scans while f(current rune) is true. It does not include the first value for which the predicate
// returns false.
func (l *lexer) scanWhile(f func(r rune) bool) {
	for {
		r, eof := l.next()
		if eof {
			return
		}
		if !f(r) {
			l.back()
			return
		}
	}
}

// scanUntil scans until a rune in set is reached (or EOF). It does not consume the discovered rune.
func (l *lexer) scanUntil(set string) {
	runes := []rune(set)
	for {
		r, eof := l.next()
		if eof {
			return
		}
		for _, r2 := range runes {
			if r == r2 {
				l.back()
				return
			}
		}
	}
}

func (l *lexer) emit(typ tokType) {
	l.tokens <- token{typ, l.start, string(l.val)}
	l.skip()
}

func (l *lexer) skip() {
	l.start = l.pos.Copy()
	l.val = l.val[:0]
}

func (l *lexer) synth(typ tokType, val string) {
	l.tokens <- token{typ, l.start, val}
}

func (l *lexer) nextToken() token {
	return <-l.tokens
}

func (l *lexer) errorf(format string, args ...interface{}) stateFn {
	l.tokens <- token{tokError, l.start, fmt.Sprintf(format, args...)}
	return nil
}

func (l *lexer) scanError(err error) stateFn {
	l.tokens <- token{tokError, l.start, fmt.Sprintf("error while scanning: %s", err)}
	return nil
}

func (l *lexer) eof() stateFn {
	l.emit(tokEOF)
	return nil
}

// stateFn represents a single state in the scanner.
type stateFn func(*lexer) stateFn

func (l *lexer) run() {
	defer func() {
		if e := recover(); e != nil {
			if e2, ok := e.(inputReadErr); ok {
				l.scanError(e2.err)
				return
			}
			panic(e)
		}
	}()

	for state := lexOuter; state != nil; state = state(l) {
	}
	close(l.tokens)
}

func lexOuter(l *lexer) stateFn {
	r, eof := l.next()
	if eof {
		return l.eof()
	}

	switch r {
	case ';':
		return lexComment
	case '"':
		return lexString
	case '\\':
		return lexCharLiteral
	case ':':
		return lexKeyword
	case '%':
		// Symbols can only begin with %; not allowed in the middle.
		return lexSymbol
	case '#':
		return lexDispatch
	case '+', '-':
		r2, eof := l.next()
		if eof {
			l.emit(tokSymbol)
			return l.eof()
		}
		l.back()
		if r2 >= '0' && r2 <= '9' {
			return lexNumber
		}
		return lexSymbol
	}

	// Recognize single-char tokens
	switch r {
	case '\'':
		l.emit(tokApostrophe)
	case '@':
		l.emit(tokAtSign)
	case '`':
		l.emit(tokBacktick)
	case '^':
		l.emit(tokCircumflex)
	case '{':
		l.emit(tokLeftBrace)
	case '[':
		l.emit(tokLeftBracket)
	case '(':
		l.emit(tokLeftParen)
	case '}':
		l.emit(tokRightBrace)
	case ']':
		l.emit(tokRightBracket)
	case ')':
		l.emit(tokRightParen)
	case '~':
		l.emit(tokTilde)
	case '\n':
		l.emit(tokNewline)
	default:
		goto afterSingles
	}
	return lexOuter
afterSingles:

	switch {
	case isWhitespace(r):
		return lexWhitespace
	case r >= '0' && r <= '9':
		return lexNumber
	case isSymbolChar(r):
		return lexSymbol
	}
	return l.errorf("unrecognized token starting with %c", r)
}

func lexWhitespace(l *lexer) stateFn {
	l.scanWhile(isWhitespace)
	l.skip()
	return lexOuter
}

func lexComment(l *lexer) stateFn {
	l.scanUntil("\n")
	l.emit(tokComment)
	return lexOuter
}

func lexString(l *lexer) stateFn {
	escaped := false
	for {
		r, eof := l.next()
		if eof {
			return l.errorf("reached EOF before string closing quote")
		}
		switch r {
		case '"':
			if !escaped {
				l.emit(tokString)
				return lexOuter
			}
			escaped = false
		case '\\':
			escaped = !escaped
		default:
			escaped = false
		}
	}
}

func lexCharLiteral(l *lexer) stateFn {
	_, eof := l.next()
	if eof {
		return l.errorf("invalid character literal")
	}
	l.scanWhile(isSymbolChar)
	l.emit(tokCharLiteral)
	return lexOuter
}

func lexKeyword(l *lexer) stateFn {
	l.scanWhile(isSymbolChar)
	l.emit(tokKeyword)
	return lexOuter
}

func lexDispatch(l *lexer) stateFn {
	// Dispatch is tricky. '#foo" and '# foo' are both interpeted as the tag 'foo'. However, '# _' is not
	// interpreted as the ignore macro -- it is the tag '_'. (So the whitespace matters when tokenizing a
	// dispatch macro.) We'll work around this by cheating slightly: if it's a tag, we'll emit an octothorpe
	// token and move on (the subsequent symbol is the tag value). If it's another use of #, the dispatch token
	// we emit will have two chars. The second char will be repeated in the following token. (for instance,
	// "#{1}" will be tokenized as "#{", "{", "1", "}".
	r, eof := l.next()
	if eof {
		l.emit(tokOctothorpe)
		return nil
	}
	val := string(l.val)
	l.back()
	l.skip()
	switch r {
	case '{', '(', '\'', '"', '_':
		l.synth(tokDispatch, val)
		return lexOuter
	default:
		l.emit(tokOctothorpe)
	}
	return lexOuter
}

func lexNumber(l *lexer) stateFn {
	// There are many different chars that can appear in a number, but it is a subset of symbol chars. Tokenize
	// this way to match the behavior of the clojure compiler. For example: '(+ 3foo)' produces the invalid
	// number '3foo' rather than parsing the same way as '(+ 3 foo)'.
	l.scanWhile(isSymbolChar)
	l.emit(tokNumber)
	return lexOuter
}

func lexSymbol(l *lexer) stateFn {
	l.scanWhile(isSymbolChar)
	l.emit(tokSymbol)
	return lexOuter
}

func isWhitespace(r rune) bool {
	return (unicode.IsSpace(r) && r != '\n') || r == ','
}

// Decent approximation for now
func isSymbolChar(r rune) bool {
	if unicode.IsLetter(r) || unicode.IsDigit(r) {
		return true
	}
	switch r {
	case '*', '+', '!', '-', '_', '?', '/', '.', ':', '$', '=', '>', '<', '&':
		return true
	}
	return false
}

//func isNumberChar(r rune) bool {
//// There are many ways to write number literals in Clojure. Read http://clojure.org/reader and
//// http://docs.oracle.com/javase/tutorial/java/nutsandbolts/datatypes.html for more information.
////
//// Notes: the following are supported in Java but not Clojure:
//// - Binary literals (0b101)
//// - f/F/d/D suffixes
//// - Underscores inside numbers

//if r >= '0' && r <= '9' {
//return true
//}
//switch r {
//case '.':
//return true
//case '+', '-': // signs as a prefix or on the exponent
//return true
//case 'e', 'E': // exponent
//return true
//case 'r': // specify radix
//return true
//case 'M', 'N': // suffixes for BigDecimal or BigInt
//return true
//case '/': // ratios
//return true
//case 'x': // hex
//return true
//}
//return false
//}
<commit_msg>'#' is an allowed char inside symbols<commit_after>package parse

import (
	"bufio"
	"fmt"
	"io"
	"unicode"
)

// Pos is a position in source text.
type Pos struct {
	Name   string
	Offset int
	Line   int
	Col    int
}

func (p *Pos) Copy() *Pos {
	var p2 Pos
	p2 = *p
	return &p2
}

func (p *Pos) String() string {
	return fmt.Sprintf("%s:%d:%d", p.Name, p.Line, p.Col)
}

func (p *Pos) FormatError(tag string, msg string) error {
	return fmt.Errorf("%s error at %s: %s", tag, p, msg)
}

// A token is a single lexeme produced by the scanner.
type token struct {
	typ tokType
	pos *Pos
	val string
}

func (t token) AsError() error {
	if t.typ != tokError {
		panic("AsError called on non-error token")
	}
	return t.pos.FormatError("lex", t.val)
}

type tokType int

const (
	tokEOF tokType = iota

	tokApostrophe   // '
	tokAtSign       // @
	tokBacktick     // `
	tokBool         // true, false
	tokCharLiteral  // \c, \newline, etc
	tokCircumflex   // ^
	tokComment      // ; foobar
	tokDispatch     // any dispatch macro token: #{, #(, #_, etc. Does not include tags.
	tokKeyword      // :foo
	tokLeftBrace    // {
	tokLeftBracket  // [
	tokLeftParen    // (
	tokNil          // nil
	tokNumber       // any numeric literal; may be invalid (parser will determine)
	tokOctothorpe   // # (only used for tags; dispatch tokens are separate)
	tokRightBrace   // }
	tokRightBracket // ]
	tokRightParen   // )
	tokString       // string literal (java escapes)
	tokSymbol       // foo, also lambda args (%, %N)
	tokTilde        // ~
	tokNewline

	tokError // error; val is the error text
)

var tokTypeToName = map[tokType]string{
	tokApostrophe:   "apostrophe",
	tokAtSign:       "at-sign",
	tokBacktick:     "backtick",
	tokBool:         "bool",
	tokCharLiteral:  "char-literal",
	tokCircumflex:   "circumflex",
	tokComment:      "comment",
	tokDispatch:     "dispatch",
	tokEOF:          "eof",
	tokError:        "error",
	tokKeyword:      "keyword",
	tokLeftBrace:    "left-brace",
	tokLeftBracket:  "left-bracket",
	tokLeftParen:    "left-paren",
	tokNil:          "nil",
	tokNumber:       "number",
	tokOctothorpe:   "octothorpe",
	tokRightBrace:   "right-brace",
	tokRightBracket: "right-bracket",
	tokRightParen:   "right-paren",
	tokString:       "string",
	tokSymbol:       "symbol",
	tokTilde:        "tilde",
	tokNewline:      "newline",
}

func (t tokType) String() string {
	name, ok := tokTypeToName[t]
	if !ok {
		panic("bad token type")
	}
	return name
}

func (t token) String() string {
	switch t.typ {
	case tokError, tokBool, tokCharLiteral, tokComment, tokKeyword, tokNumber, tokDispatch, tokString, tokSymbol:
		return fmt.Sprintf("<%s@%s>(%q)", t.typ, t.pos, t.val)
	}
	return fmt.Sprintf("<%s@%s>", t.typ, t.pos)
}

// lexer holds the state of the scanner. A single rune of backup is supported.
type lexer struct {
	name    string // the name of the input source
	input   *bufio.Reader
	pos     *Pos // the current position in the input
	start   *Pos // the start position of the token being scanned
	lastPos *Pos // the position before the most recent next() call
	tokens  chan token
	val     []rune // the literal contents of the token
}

func lex(name string, input *bufio.Reader) *lexer {
	l := &lexer{
		name:   name,
		input:  input,
		pos:    &Pos{Name: name, Line: 1, Col: 1},
		start:  &Pos{Name: name, Line: 1, Col: 1},
		tokens: make(chan token),
	}
	go l.run()
	return l
}

type inputReadErr struct {
	err error
}

func (l *lexer) next() (r rune, eof bool) {
	r, w, err := l.input.ReadRune()
	if err != nil {
		if err == io.EOF {
			return 0, true
		}
		panic(inputReadErr{err})
	}
	l.lastPos = l.pos.Copy()
	l.pos.Offset += w
	l.pos.Col += w
	if r == '\n' {
		l.pos.Line++
		l.pos.Col = 1
	}
	l.val = append(l.val, r)
	return r, false
}

func (l *lexer) back() {
	if l.lastPos == nil {
		panic("back() call not preceded by a next()")
	}
	if err := l.input.UnreadRune(); err != nil {
		panic("should not happen")
	}
	l.pos = l.lastPos
	l.val = l.val[:len(l.val)-1]
	l.lastPos = nil
}

// scanWhile scans while f(current rune) is true. It does not include the first value for which the predicate
// returns false.
func (l *lexer) scanWhile(f func(r rune) bool) {
	for {
		r, eof := l.next()
		if eof {
			return
		}
		if !f(r) {
			l.back()
			return
		}
	}
}

// scanUntil scans until a rune in set is reached (or EOF). It does not consume the discovered rune.
func (l *lexer) scanUntil(set string) {
	runes := []rune(set)
	for {
		r, eof := l.next()
		if eof {
			return
		}
		for _, r2 := range runes {
			if r == r2 {
				l.back()
				return
			}
		}
	}
}

func (l *lexer) emit(typ tokType) {
	l.tokens <- token{typ, l.start, string(l.val)}
	l.skip()
}

func (l *lexer) skip() {
	l.start = l.pos.Copy()
	l.val = l.val[:0]
}

func (l *lexer) synth(typ tokType, val string) {
	l.tokens <- token{typ, l.start, val}
}

func (l *lexer) nextToken() token {
	return <-l.tokens
}

func (l *lexer) errorf(format string, args ...interface{}) stateFn {
	l.tokens <- token{tokError, l.start, fmt.Sprintf(format, args...)}
	return nil
}

func (l *lexer) scanError(err error) stateFn {
	l.tokens <- token{tokError, l.start, fmt.Sprintf("error while scanning: %s", err)}
	return nil
}

func (l *lexer) eof() stateFn {
	l.emit(tokEOF)
	return nil
}

// stateFn represents a single state in the scanner.
type stateFn func(*lexer) stateFn

func (l *lexer) run() {
	defer func() {
		if e := recover(); e != nil {
			if e2, ok := e.(inputReadErr); ok {
				l.scanError(e2.err)
				return
			}
			panic(e)
		}
	}()

	for state := lexOuter; state != nil; state = state(l) {
	}
	close(l.tokens)
}

func lexOuter(l *lexer) stateFn {
	r, eof := l.next()
	if eof {
		return l.eof()
	}

	switch r {
	case ';':
		return lexComment
	case '"':
		return lexString
	case '\\':
		return lexCharLiteral
	case ':':
		return lexKeyword
	case '%':
		// Symbols can only begin with %; not allowed in the middle.
		return lexSymbol
	case '#':
		return lexDispatch
	case '+', '-':
		r2, eof := l.next()
		if eof {
			l.emit(tokSymbol)
			return l.eof()
		}
		l.back()
		if r2 >= '0' && r2 <= '9' {
			return lexNumber
		}
		return lexSymbol
	}

	// Recognize single-char tokens
	switch r {
	case '\'':
		l.emit(tokApostrophe)
	case '@':
		l.emit(tokAtSign)
	case '`':
		l.emit(tokBacktick)
	case '^':
		l.emit(tokCircumflex)
	case '{':
		l.emit(tokLeftBrace)
	case '[':
		l.emit(tokLeftBracket)
	case '(':
		l.emit(tokLeftParen)
	case '}':
		l.emit(tokRightBrace)
	case ']':
		l.emit(tokRightBracket)
	case ')':
		l.emit(tokRightParen)
	case '~':
		l.emit(tokTilde)
	case '\n':
		l.emit(tokNewline)
	default:
		goto afterSingles
	}
	return lexOuter
afterSingles:

	switch {
	case isWhitespace(r):
		return lexWhitespace
	case r >= '0' && r <= '9':
		return lexNumber
	case isSymbolChar(r):
		return lexSymbol
	}
	return l.errorf("unrecognized token starting with %c", r)
}

func lexWhitespace(l *lexer) stateFn {
	l.scanWhile(isWhitespace)
	l.skip()
	return lexOuter
}

func lexComment(l *lexer) stateFn {
	l.scanUntil("\n")
	l.emit(tokComment)
	return lexOuter
}

func lexString(l *lexer) stateFn {
	escaped := false
	for {
		r, eof := l.next()
		if eof {
			return l.errorf("reached EOF before string closing quote")
		}
		switch r {
		case '"':
			if !escaped {
				l.emit(tokString)
				return lexOuter
			}
			escaped = false
		case '\\':
			escaped = !escaped
		default:
			escaped = false
		}
	}
}

func lexCharLiteral(l *lexer) stateFn {
	_, eof := l.next()
	if eof {
		return l.errorf("invalid character literal")
	}
	l.scanWhile(isSymbolChar)
	l.emit(tokCharLiteral)
	return lexOuter
}

func lexKeyword(l *lexer) stateFn {
	l.scanWhile(isSymbolChar)
	l.emit(tokKeyword)
	return lexOuter
}

func lexDispatch(l *lexer) stateFn {
	// Dispatch is tricky. '#foo" and '# foo' are both interpeted as the tag 'foo'. However, '# _' is not
	// interpreted as the ignore macro -- it is the tag '_'. (So the whitespace matters when tokenizing a
	// dispatch macro.) We'll work around this by cheating slightly: if it's a tag, we'll emit an octothorpe
	// token and move on (the subsequent symbol is the tag value). If it's another use of #, the dispatch token
	// we emit will have two chars. The second char will be repeated in the following token. (for instance,
	// "#{1}" will be tokenized as "#{", "{", "1", "}".
	r, eof := l.next()
	if eof {
		l.emit(tokOctothorpe)
		return nil
	}
	val := string(l.val)
	l.back()
	l.skip()
	switch r {
	case '{', '(', '\'', '"', '_':
		l.synth(tokDispatch, val)
		return lexOuter
	default:
		l.emit(tokOctothorpe)
	}
	return lexOuter
}

func lexNumber(l *lexer) stateFn {
	// There are many different chars that can appear in a number, but it is a subset of symbol chars. Tokenize
	// this way to match the behavior of the clojure compiler. For example: '(+ 3foo)' produces the invalid
	// number '3foo' rather than parsing the same way as '(+ 3 foo)'.
	l.scanWhile(isSymbolChar)
	l.emit(tokNumber)
	return lexOuter
}

func lexSymbol(l *lexer) stateFn {
	l.scanWhile(isSymbolChar)
	l.emit(tokSymbol)
	return lexOuter
}

func isWhitespace(r rune) bool {
	return (unicode.IsSpace(r) && r != '\n') || r == ','
}

// Decent approximation for now
func isSymbolChar(r rune) bool {
	if unicode.IsLetter(r) || unicode.IsDigit(r) {
		return true
	}
	switch r {
	case '*', '+', '!', '-', '_', '?', '/', '.', ':', '$', '=', '>', '<', '&', '#':
		return true
	}
	return false
}

//func isNumberChar(r rune) bool {
//// There are many ways to write number literals in Clojure. Read http://clojure.org/reader and
//// http://docs.oracle.com/javase/tutorial/java/nutsandbolts/datatypes.html for more information.
////
//// Notes: the following are supported in Java but not Clojure:
//// - Binary literals (0b101)
//// - f/F/d/D suffixes
//// - Underscores inside numbers

//if r >= '0' && r <= '9' {
//return true
//}
//switch r {
//case '.':
//return true
//case '+', '-': // signs as a prefix or on the exponent
//return true
//case 'e', 'E': // exponent
//return true
//case 'r': // specify radix
//return true
//case 'M', 'N': // suffixes for BigDecimal or BigInt
//return true
//case '/': // ratios
//return true
//case 'x': // hex
//return true
//}
//return false
//}
<|endoftext|>


<commit_before>package parsemail

import (
	"bytes"
	"encoding/base64"
	"fmt"
	"io"
	"io/ioutil"
	"mime"
	"mime/multipart"
	"net/mail"
	"strings"
	"time"
)

const contentTypeMultipartMixed = "multipart/mixed"
const contentTypeMultipartAlternative = "multipart/alternative"
const contentTypeMultipartRelated = "multipart/related"
const contentTypeTextHtml = "text/html"
const contentTypeTextPlain = "text/plain"

// Parse an email message read from io.Reader into parsemail.Email struct
func Parse(r io.Reader) (email Email, err error) {
	msg, err := mail.ReadMessage(r)
	if err != nil {
		return
	}

	email, err = createEmailFromHeader(msg.Header)
	if err != nil {
		return
	}

	contentType, params, err := parseContentType(msg.Header.Get("Content-Type"))
	if err != nil {
		return
	}

	switch contentType {
	case contentTypeMultipartMixed:
		email.TextBody, email.HTMLBody, email.Attachments, email.EmbeddedFiles, err = parseMultipartMixed(msg.Body, params["boundary"])
	case contentTypeMultipartAlternative:
		email.TextBody, email.HTMLBody, email.EmbeddedFiles, err = parseMultipartAlternative(msg.Body, params["boundary"])
	case contentTypeTextPlain:
		message, _ := ioutil.ReadAll(msg.Body)
		email.TextBody = strings.TrimSuffix(string(message[:]), "\n")
	case contentTypeTextHtml:
		message, _ := ioutil.ReadAll(msg.Body)
		email.HTMLBody = strings.TrimSuffix(string(message[:]), "\n")
	default:
		err = fmt.Errorf("Unknown top level mime type: %s", contentType)
	}

	return
}

func createEmailFromHeader(header mail.Header) (email Email, err error) {
	hp := headerParser{header: &header}

	email.Subject = decodeMimeSentence(header.Get("Subject"))
	email.From = hp.parseAddressList(header.Get("From"))
	email.Sender = hp.parseAddress(header.Get("Sender"))
	email.ReplyTo = hp.parseAddressList(header.Get("Reply-To"))
	email.To = hp.parseAddressList(header.Get("To"))
	email.Cc = hp.parseAddressList(header.Get("Cc"))
	email.Bcc = hp.parseAddressList(header.Get("Bcc"))
	email.Date = hp.parseTime(header.Get("Date"))
	email.ResentFrom = hp.parseAddressList(header.Get("Resent-From"))
	email.ResentSender = hp.parseAddress(header.Get("Resent-Sender"))
	email.ResentTo = hp.parseAddressList(header.Get("Resent-To"))
	email.ResentCc = hp.parseAddressList(header.Get("Resent-Cc"))
	email.ResentBcc = hp.parseAddressList(header.Get("Resent-Bcc"))
	email.ResentMessageID = hp.parseMessageId(header.Get("Resent-Message-ID"))
	email.MessageID = hp.parseMessageId(header.Get("Message-ID"))
	email.InReplyTo = hp.parseMessageIdList(header.Get("In-Reply-To"))
	email.References = hp.parseMessageIdList(header.Get("References"))
	email.ResentDate = hp.parseTime(header.Get("Resent-Date"))

	if hp.err != nil {
		err = hp.err
		return
	}

	//decode whole header for easier access to extra fields
	//todo: should we decode? aren't only standard fields mime encoded?
	email.Header, err = decodeHeaderMime(header)
	if err != nil {
		return
	}

	return
}

func parseContentType(contentTypeHeader string) (contentType string, params map[string]string, err error) {
	if contentTypeHeader == "" {
		contentType = contentTypeTextPlain
		return
	}

	return mime.ParseMediaType(contentTypeHeader)
}

func parseMultipartRelated(msg io.Reader, boundary string) (textBody, htmlBody string, embeddedFiles []EmbeddedFile, err error) {
	pmr := multipart.NewReader(msg, boundary)
	for {
		part, err := pmr.NextPart()

		if err == io.EOF {
			break
		} else if err != nil {
			return textBody, htmlBody, embeddedFiles, err
		}

		contentType, params, err := mime.ParseMediaType(part.Header.Get("Content-Type"))
		if err != nil {
			return textBody, htmlBody, embeddedFiles, err
		}

		switch contentType {
		case contentTypeTextPlain:
			ppContent, err := ioutil.ReadAll(part)
			if err != nil {
				return textBody, htmlBody, embeddedFiles, err
			}

			textBody += strings.TrimSuffix(string(ppContent[:]), "\n")
		case contentTypeTextHtml:
			ppContent, err := ioutil.ReadAll(part)
			if err != nil {
				return textBody, htmlBody, embeddedFiles, err
			}

			htmlBody += strings.TrimSuffix(string(ppContent[:]), "\n")
		case contentTypeMultipartAlternative:
			tb, hb, ef, err := parseMultipartAlternative(part, params["boundary"])
			if err != nil {
				return textBody, htmlBody, embeddedFiles, err
			}

			htmlBody += hb
			textBody += tb
			embeddedFiles = append(embeddedFiles, ef...)
		default:
			if isEmbeddedFile(part) {
				ef, err := decodeEmbeddedFile(part)
				if err != nil {
					return textBody, htmlBody, embeddedFiles, err
				}

				embeddedFiles = append(embeddedFiles, ef)
			} else {
				return textBody, htmlBody, embeddedFiles, fmt.Errorf("Can't process multipart/related inner mime type: %s", contentType)
			}
		}
	}

	return textBody, htmlBody, embeddedFiles, err
}

func parseMultipartAlternative(msg io.Reader, boundary string) (textBody, htmlBody string, embeddedFiles []EmbeddedFile, err error) {
	pmr := multipart.NewReader(msg, boundary)
	for {
		part, err := pmr.NextPart()

		if err == io.EOF {
			break
		} else if err != nil {
			return textBody, htmlBody, embeddedFiles, err
		}

		contentType, params, err := mime.ParseMediaType(part.Header.Get("Content-Type"))
		if err != nil {
			return textBody, htmlBody, embeddedFiles, err
		}

		switch contentType {
		case contentTypeTextPlain:
			ppContent, err := ioutil.ReadAll(part)
			if err != nil {
				return textBody, htmlBody, embeddedFiles, err
			}

			textBody += strings.TrimSuffix(string(ppContent[:]), "\n")
		case contentTypeTextHtml:
			ppContent, err := ioutil.ReadAll(part)
			if err != nil {
				return textBody, htmlBody, embeddedFiles, err
			}

			htmlBody += strings.TrimSuffix(string(ppContent[:]), "\n")
		case contentTypeMultipartRelated:
			tb, hb, ef, err := parseMultipartRelated(part, params["boundary"])
			if err != nil {
				return textBody, htmlBody, embeddedFiles, err
			}

			htmlBody += hb
			textBody += tb
			embeddedFiles = append(embeddedFiles, ef...)
		default:
			if isEmbeddedFile(part) {
				ef, err := decodeEmbeddedFile(part)
				if err != nil {
					return textBody, htmlBody, embeddedFiles, err
				}

				embeddedFiles = append(embeddedFiles, ef)
			} else {
				return textBody, htmlBody, embeddedFiles, fmt.Errorf("Can't process multipart/alternative inner mime type: %s", contentType)
			}
		}
	}

	return textBody, htmlBody, embeddedFiles, err
}

func parseMultipartMixed(msg io.Reader, boundary string) (textBody, htmlBody string, attachments []Attachment, embeddedFiles []EmbeddedFile, err error) {
	mr := multipart.NewReader(msg, boundary)
	for {
		part, err := mr.NextPart()
		if err == io.EOF {
			break
		} else if err != nil {
			return textBody, htmlBody, attachments, embeddedFiles, err
		}

		contentType, params, err := mime.ParseMediaType(part.Header.Get("Content-Type"))
		if err != nil {
			return textBody, htmlBody, attachments, embeddedFiles, err
		}

		if contentType == contentTypeMultipartAlternative {
			textBody, htmlBody, embeddedFiles, err = parseMultipartAlternative(part, params["boundary"])
			if err != nil {
				return textBody, htmlBody, attachments, embeddedFiles, err
			}
		} else if contentType == contentTypeMultipartRelated {
			textBody, htmlBody, embeddedFiles, err = parseMultipartRelated(part, params["boundary"])
			if err != nil {
				return textBody, htmlBody, attachments, embeddedFiles, err
			}
		} else if isAttachment(part) {
			at, err := decodeAttachment(part)
			if err != nil {
				return textBody, htmlBody, attachments, embeddedFiles, err
			}

			attachments = append(attachments, at)
		} else {
			return textBody, htmlBody, attachments, embeddedFiles, fmt.Errorf("Unknown multipart/mixed nested mime type: %s", contentType)
		}
	}

	return textBody, htmlBody, attachments, embeddedFiles, err
}

func decodeMimeSentence(s string) string {
	result := []string{}
	ss := strings.Split(s, " ")

	for _, word := range ss {
		dec := new(mime.WordDecoder)
		w, err := dec.Decode(word)
		if err != nil {
			if len(result) == 0 {
				w = word
			} else {
				w = " " + word
			}
		}

		result = append(result, w)
	}

	return strings.Join(result, "")
}

func decodeHeaderMime(header mail.Header) (mail.Header, error) {
	parsedHeader := map[string][]string{}

	for headerName, headerData := range header {

		parsedHeaderData := []string{}
		for _, headerValue := range headerData {
			parsedHeaderData = append(parsedHeaderData, decodeMimeSentence(headerValue))
		}

		parsedHeader[headerName] = parsedHeaderData
	}

	return mail.Header(parsedHeader), nil
}

func decodePartData(part *multipart.Part) (io.Reader, error) {
	encoding := part.Header.Get("Content-Transfer-Encoding")

	if strings.EqualFold(encoding, "base64") {
		dr := base64.NewDecoder(base64.StdEncoding, part)
		dd, err := ioutil.ReadAll(dr)
		if err != nil {
			return nil, err
		}

		return bytes.NewReader(dd), nil
	}

	return nil, fmt.Errorf("Unknown encoding: %s", encoding)
}

func isEmbeddedFile(part *multipart.Part) bool {
	return part.Header.Get("Content-Transfer-Encoding") != ""
}

func decodeEmbeddedFile(part *multipart.Part) (ef EmbeddedFile, err error) {
	cid := decodeMimeSentence(part.Header.Get("Content-Id"))
	decoded, err := decodePartData(part)
	if err != nil {
		return
	}

	ef.CID = strings.Trim(cid, "<>")
	ef.Data = decoded
	ef.ContentType = part.Header.Get("Content-Type")

	return
}

func isAttachment(part *multipart.Part) bool {
	return part.FileName() != ""
}

func decodeAttachment(part *multipart.Part) (at Attachment, err error) {
	filename := decodeMimeSentence(part.FileName())
	decoded, err := decodePartData(part)
	if err != nil {
		return
	}

	at.Filename = filename
	at.Data = decoded
	at.ContentType = strings.Split(part.Header.Get("Content-Type"), ";")[0]

	return
}

type headerParser struct {
	header *mail.Header
	err    error
}

func (hp headerParser) parseAddress(s string) (ma *mail.Address) {
	if hp.err != nil {
		return nil
	}

	if strings.Trim(s, " \n") != "" {
		ma, hp.err = mail.ParseAddress(s)

		return ma
	}

	return nil
}

func (hp headerParser) parseAddressList(s string) (ma []*mail.Address) {
	if hp.err != nil {
		return
	}

	if strings.Trim(s, " \n") != "" {
		ma, hp.err = mail.ParseAddressList(s)
		return
	}

	return
}

func (hp headerParser) parseTime(s string) (t time.Time) {
	if hp.err != nil || s == "" {
		return
	}

	t, hp.err = time.Parse(time.RFC1123Z, s)
	if hp.err == nil {
		return t
	}

	t, hp.err = time.Parse("Mon, 2 Jan 2006 15:04:05 -0700", s)

	return
}

func (hp headerParser) parseMessageId(s string) string {
	if hp.err != nil {
		return ""
	}

	return strings.Trim(s, "<> ")
}

func (hp headerParser) parseMessageIdList(s string) (result []string) {
	if hp.err != nil {
		return
	}

	for _, p := range strings.Split(s, " ") {
		if strings.Trim(p, " \n") != "" {
			result = append(result, hp.parseMessageId(p))
		}
	}

	return
}

// Attachment with filename, content type and data (as a io.Reader)
type Attachment struct {
	Filename    string
	ContentType string
	Data        io.Reader
}

// EmbeddedFile with content id, content type and data (as a io.Reader)
type EmbeddedFile struct {
	CID         string
	ContentType string
	Data        io.Reader
}

// Email with fields for all the headers defined in RFC5322 with it's attachments and
type Email struct {
	Header mail.Header

	Subject    string
	Sender     *mail.Address
	From       []*mail.Address
	ReplyTo    []*mail.Address
	To         []*mail.Address
	Cc         []*mail.Address
	Bcc        []*mail.Address
	Date       time.Time
	MessageID  string
	InReplyTo  []string
	References []string

	ResentFrom      []*mail.Address
	ResentSender    *mail.Address
	ResentTo        []*mail.Address
	ResentDate      time.Time
	ResentCc        []*mail.Address
	ResentBcc       []*mail.Address
	ResentMessageID string

	HTMLBody string
	TextBody string

	Attachments   []Attachment
	EmbeddedFiles []EmbeddedFile
}
<commit_msg>Handle text-plain parts in multipart/mixed email<commit_after>package parsemail

import (
	"bytes"
	"encoding/base64"
	"fmt"
	"io"
	"io/ioutil"
	"mime"
	"mime/multipart"
	"net/mail"
	"strings"
	"time"
)

const contentTypeMultipartMixed = "multipart/mixed"
const contentTypeMultipartAlternative = "multipart/alternative"
const contentTypeMultipartRelated = "multipart/related"
const contentTypeTextHtml = "text/html"
const contentTypeTextPlain = "text/plain"

// Parse an email message read from io.Reader into parsemail.Email struct
func Parse(r io.Reader) (email Email, err error) {
	msg, err := mail.ReadMessage(r)
	if err != nil {
		return
	}

	email, err = createEmailFromHeader(msg.Header)
	if err != nil {
		return
	}

	contentType, params, err := parseContentType(msg.Header.Get("Content-Type"))
	if err != nil {
		return
	}

	switch contentType {
	case contentTypeMultipartMixed:
		email.TextBody, email.HTMLBody, email.Attachments, email.EmbeddedFiles, err = parseMultipartMixed(msg.Body, params["boundary"])
	case contentTypeMultipartAlternative:
		email.TextBody, email.HTMLBody, email.EmbeddedFiles, err = parseMultipartAlternative(msg.Body, params["boundary"])
	case contentTypeTextPlain:
		message, _ := ioutil.ReadAll(msg.Body)
		email.TextBody = strings.TrimSuffix(string(message[:]), "\n")
	case contentTypeTextHtml:
		message, _ := ioutil.ReadAll(msg.Body)
		email.HTMLBody = strings.TrimSuffix(string(message[:]), "\n")
	default:
		err = fmt.Errorf("Unknown top level mime type: %s", contentType)
	}

	return
}

func createEmailFromHeader(header mail.Header) (email Email, err error) {
	hp := headerParser{header: &header}

	email.Subject = decodeMimeSentence(header.Get("Subject"))
	email.From = hp.parseAddressList(header.Get("From"))
	email.Sender = hp.parseAddress(header.Get("Sender"))
	email.ReplyTo = hp.parseAddressList(header.Get("Reply-To"))
	email.To = hp.parseAddressList(header.Get("To"))
	email.Cc = hp.parseAddressList(header.Get("Cc"))
	email.Bcc = hp.parseAddressList(header.Get("Bcc"))
	email.Date = hp.parseTime(header.Get("Date"))
	email.ResentFrom = hp.parseAddressList(header.Get("Resent-From"))
	email.ResentSender = hp.parseAddress(header.Get("Resent-Sender"))
	email.ResentTo = hp.parseAddressList(header.Get("Resent-To"))
	email.ResentCc = hp.parseAddressList(header.Get("Resent-Cc"))
	email.ResentBcc = hp.parseAddressList(header.Get("Resent-Bcc"))
	email.ResentMessageID = hp.parseMessageId(header.Get("Resent-Message-ID"))
	email.MessageID = hp.parseMessageId(header.Get("Message-ID"))
	email.InReplyTo = hp.parseMessageIdList(header.Get("In-Reply-To"))
	email.References = hp.parseMessageIdList(header.Get("References"))
	email.ResentDate = hp.parseTime(header.Get("Resent-Date"))

	if hp.err != nil {
		err = hp.err
		return
	}

	//decode whole header for easier access to extra fields
	//todo: should we decode? aren't only standard fields mime encoded?
	email.Header, err = decodeHeaderMime(header)
	if err != nil {
		return
	}

	return
}

func parseContentType(contentTypeHeader string) (contentType string, params map[string]string, err error) {
	if contentTypeHeader == "" {
		contentType = contentTypeTextPlain
		return
	}

	return mime.ParseMediaType(contentTypeHeader)
}

func parseMultipartRelated(msg io.Reader, boundary string) (textBody, htmlBody string, embeddedFiles []EmbeddedFile, err error) {
	pmr := multipart.NewReader(msg, boundary)
	for {
		part, err := pmr.NextPart()

		if err == io.EOF {
			break
		} else if err != nil {
			return textBody, htmlBody, embeddedFiles, err
		}

		contentType, params, err := mime.ParseMediaType(part.Header.Get("Content-Type"))
		if err != nil {
			return textBody, htmlBody, embeddedFiles, err
		}

		switch contentType {
		case contentTypeTextPlain:
			ppContent, err := ioutil.ReadAll(part)
			if err != nil {
				return textBody, htmlBody, embeddedFiles, err
			}

			textBody += strings.TrimSuffix(string(ppContent[:]), "\n")
		case contentTypeTextHtml:
			ppContent, err := ioutil.ReadAll(part)
			if err != nil {
				return textBody, htmlBody, embeddedFiles, err
			}

			htmlBody += strings.TrimSuffix(string(ppContent[:]), "\n")
		case contentTypeMultipartAlternative:
			tb, hb, ef, err := parseMultipartAlternative(part, params["boundary"])
			if err != nil {
				return textBody, htmlBody, embeddedFiles, err
			}

			htmlBody += hb
			textBody += tb
			embeddedFiles = append(embeddedFiles, ef...)
		default:
			if isEmbeddedFile(part) {
				ef, err := decodeEmbeddedFile(part)
				if err != nil {
					return textBody, htmlBody, embeddedFiles, err
				}

				embeddedFiles = append(embeddedFiles, ef)
			} else {
				return textBody, htmlBody, embeddedFiles, fmt.Errorf("Can't process multipart/related inner mime type: %s", contentType)
			}
		}
	}

	return textBody, htmlBody, embeddedFiles, err
}

func parseMultipartAlternative(msg io.Reader, boundary string) (textBody, htmlBody string, embeddedFiles []EmbeddedFile, err error) {
	pmr := multipart.NewReader(msg, boundary)
	for {
		part, err := pmr.NextPart()

		if err == io.EOF {
			break
		} else if err != nil {
			return textBody, htmlBody, embeddedFiles, err
		}

		contentType, params, err := mime.ParseMediaType(part.Header.Get("Content-Type"))
		if err != nil {
			return textBody, htmlBody, embeddedFiles, err
		}

		switch contentType {
		case contentTypeTextPlain:
			ppContent, err := ioutil.ReadAll(part)
			if err != nil {
				return textBody, htmlBody, embeddedFiles, err
			}

			textBody += strings.TrimSuffix(string(ppContent[:]), "\n")
		case contentTypeTextHtml:
			ppContent, err := ioutil.ReadAll(part)
			if err != nil {
				return textBody, htmlBody, embeddedFiles, err
			}

			htmlBody += strings.TrimSuffix(string(ppContent[:]), "\n")
		case contentTypeMultipartRelated:
			tb, hb, ef, err := parseMultipartRelated(part, params["boundary"])
			if err != nil {
				return textBody, htmlBody, embeddedFiles, err
			}

			htmlBody += hb
			textBody += tb
			embeddedFiles = append(embeddedFiles, ef...)
		default:
			if isEmbeddedFile(part) {
				ef, err := decodeEmbeddedFile(part)
				if err != nil {
					return textBody, htmlBody, embeddedFiles, err
				}

				embeddedFiles = append(embeddedFiles, ef)
			} else {
				return textBody, htmlBody, embeddedFiles, fmt.Errorf("Can't process multipart/alternative inner mime type: %s", contentType)
			}
		}
	}

	return textBody, htmlBody, embeddedFiles, err
}

func parseMultipartMixed(msg io.Reader, boundary string) (textBody, htmlBody string, attachments []Attachment, embeddedFiles []EmbeddedFile, err error) {
	mr := multipart.NewReader(msg, boundary)
	for {
		part, err := mr.NextPart()
		if err == io.EOF {
			break
		} else if err != nil {
			return textBody, htmlBody, attachments, embeddedFiles, err
		}

		contentType, params, err := mime.ParseMediaType(part.Header.Get("Content-Type"))
		if err != nil {
			return textBody, htmlBody, attachments, embeddedFiles, err
		}

		if contentType == contentTypeMultipartAlternative {
			textBody, htmlBody, embeddedFiles, err = parseMultipartAlternative(part, params["boundary"])
			if err != nil {
				return textBody, htmlBody, attachments, embeddedFiles, err
			}
		} else if contentType == contentTypeMultipartRelated {
			textBody, htmlBody, embeddedFiles, err = parseMultipartRelated(part, params["boundary"])
			if err != nil {
				return textBody, htmlBody, attachments, embeddedFiles, err
			}
		} else if contentType == contentTypeTextPlain {
			ppContent, err := ioutil.ReadAll(part)
			if err != nil {
				return textBody, htmlBody, attachments, embeddedFiles, err
			}

			textBody += strings.TrimSuffix(string(ppContent[:]), "\n")
		} else if isAttachment(part) {
			at, err := decodeAttachment(part)
			if err != nil {
				return textBody, htmlBody, attachments, embeddedFiles, err
			}

			attachments = append(attachments, at)
		} else {
			return textBody, htmlBody, attachments, embeddedFiles, fmt.Errorf("Unknown multipart/mixed nested mime type: %s", contentType)
		}
	}

	return textBody, htmlBody, attachments, embeddedFiles, err
}

func decodeMimeSentence(s string) string {
	result := []string{}
	ss := strings.Split(s, " ")

	for _, word := range ss {
		dec := new(mime.WordDecoder)
		w, err := dec.Decode(word)
		if err != nil {
			if len(result) == 0 {
				w = word
			} else {
				w = " " + word
			}
		}

		result = append(result, w)
	}

	return strings.Join(result, "")
}

func decodeHeaderMime(header mail.Header) (mail.Header, error) {
	parsedHeader := map[string][]string{}

	for headerName, headerData := range header {

		parsedHeaderData := []string{}
		for _, headerValue := range headerData {
			parsedHeaderData = append(parsedHeaderData, decodeMimeSentence(headerValue))
		}

		parsedHeader[headerName] = parsedHeaderData
	}

	return mail.Header(parsedHeader), nil
}

func decodePartData(part *multipart.Part) (io.Reader, error) {
	encoding := part.Header.Get("Content-Transfer-Encoding")

	if strings.EqualFold(encoding, "base64") {
		dr := base64.NewDecoder(base64.StdEncoding, part)
		dd, err := ioutil.ReadAll(dr)
		if err != nil {
			return nil, err
		}

		return bytes.NewReader(dd), nil
	}

	return nil, fmt.Errorf("Unknown encoding: %s", encoding)
}

func isEmbeddedFile(part *multipart.Part) bool {
	return part.Header.Get("Content-Transfer-Encoding") != ""
}

func decodeEmbeddedFile(part *multipart.Part) (ef EmbeddedFile, err error) {
	cid := decodeMimeSentence(part.Header.Get("Content-Id"))
	decoded, err := decodePartData(part)
	if err != nil {
		return
	}

	ef.CID = strings.Trim(cid, "<>")
	ef.Data = decoded
	ef.ContentType = part.Header.Get("Content-Type")

	return
}

func isAttachment(part *multipart.Part) bool {
	return part.FileName() != ""
}

func decodeAttachment(part *multipart.Part) (at Attachment, err error) {
	filename := decodeMimeSentence(part.FileName())
	decoded, err := decodePartData(part)
	if err != nil {
		return
	}

	at.Filename = filename
	at.Data = decoded
	at.ContentType = strings.Split(part.Header.Get("Content-Type"), ";")[0]

	return
}

type headerParser struct {
	header *mail.Header
	err    error
}

func (hp headerParser) parseAddress(s string) (ma *mail.Address) {
	if hp.err != nil {
		return nil
	}

	if strings.Trim(s, " \n") != "" {
		ma, hp.err = mail.ParseAddress(s)

		return ma
	}

	return nil
}

func (hp headerParser) parseAddressList(s string) (ma []*mail.Address) {
	if hp.err != nil {
		return
	}

	if strings.Trim(s, " \n") != "" {
		ma, hp.err = mail.ParseAddressList(s)
		return
	}

	return
}

func (hp headerParser) parseTime(s string) (t time.Time) {
	if hp.err != nil || s == "" {
		return
	}

	t, hp.err = time.Parse(time.RFC1123Z, s)
	if hp.err == nil {
		return t
	}

	t, hp.err = time.Parse("Mon, 2 Jan 2006 15:04:05 -0700", s)

	return
}

func (hp headerParser) parseMessageId(s string) string {
	if hp.err != nil {
		return ""
	}

	return strings.Trim(s, "<> ")
}

func (hp headerParser) parseMessageIdList(s string) (result []string) {
	if hp.err != nil {
		return
	}

	for _, p := range strings.Split(s, " ") {
		if strings.Trim(p, " \n") != "" {
			result = append(result, hp.parseMessageId(p))
		}
	}

	return
}

// Attachment with filename, content type and data (as a io.Reader)
type Attachment struct {
	Filename    string
	ContentType string
	Data        io.Reader
}

// EmbeddedFile with content id, content type and data (as a io.Reader)
type EmbeddedFile struct {
	CID         string
	ContentType string
	Data        io.Reader
}

// Email with fields for all the headers defined in RFC5322 with it's attachments and
type Email struct {
	Header mail.Header

	Subject    string
	Sender     *mail.Address
	From       []*mail.Address
	ReplyTo    []*mail.Address
	To         []*mail.Address
	Cc         []*mail.Address
	Bcc        []*mail.Address
	Date       time.Time
	MessageID  string
	InReplyTo  []string
	References []string

	ResentFrom      []*mail.Address
	ResentSender    *mail.Address
	ResentTo        []*mail.Address
	ResentDate      time.Time
	ResentCc        []*mail.Address
	ResentBcc       []*mail.Address
	ResentMessageID string

	HTMLBody string
	TextBody string

	Attachments   []Attachment
	EmbeddedFiles []EmbeddedFile
}
<|endoftext|>


<commit_before>package rpc

import (
	"errors"
	"io"
	"net"
	"time"

	"github.com/Sirupsen/logrus"
)

var (
	opReadTimeout   = 15 * time.Second // client read
	opWriteTimeout  = 15 * time.Second // client write
	errReadTimeout  = errors.New("read timeout")
	errWriteTimeout = errors.New("write timeout")
)

type Client struct {
	end       chan struct{}
	requests  chan *Message
	send      chan *Message
	responses chan *Message
	seq       uint32
	messages  map[uint32]*Message
	wire      *Wire
	err       error
}

func NewClient(conn net.Conn) *Client {
	c := &Client{
		wire:      NewWire(conn),
		end:       make(chan struct{}, 1024),
		requests:  make(chan *Message, 1024),
		send:      make(chan *Message, 1024),
		responses: make(chan *Message, 1024),
		messages:  map[uint32]*Message{},
	}
	go c.loop()
	go c.write()
	go c.read()
	return c
}

func (c *Client) WriteAt(buf []byte, offset int64) (int, error) {
	return c.operation(TypeWrite, buf, offset)
}

func (c *Client) SetError(err error) {
	c.responses <- &Message{
		transportErr: err,
	}
}

func (c *Client) ReadAt(buf []byte, offset int64) (int, error) {
	return c.operation(TypeRead, buf, offset)
}

func (c *Client) operation(op uint32, buf []byte, offset int64) (int, error) {
	msg := Message{
		Complete: make(chan struct{}, 1),
		Type:     op,
		Offset:   offset,
		Data:     buf,
	}

	timeout := func(op uint32) <-chan time.Time {
		switch op {
		case TypeRead:
			return time.After(opReadTimeout)
		}
		return time.After(opWriteTimeout)
	}(msg.Type)

	c.requests <- &msg

	select {
	case <-msg.Complete:
	case <-timeout:
		switch msg.Type {
		case TypeRead:
			c.SetError(errReadTimeout)
			logrus.Errorln("Read timeout: seq=", msg.Seq, "size=", len(msg.Data)/1024, "(kB)")

		case TypeWrite:
			c.SetError(errWriteTimeout)
			logrus.Errorln("Write timeout: seq=", msg.Seq, "size=", len(msg.Data)/1024, "(kB)")
		}
		// stats.Print()//flush automaticalyupon timeout
	}

	if msg.Type == TypeError {
		return 0, errors.New(string(msg.Data))
	}

	if msg.Type == TypeEOF {
		return len(msg.Data), io.EOF
	}

	return len(msg.Data), nil
}

func (c *Client) Close() {
	c.wire.Close()
	c.end <- struct{}{}
}

func (c *Client) loop() {
	defer close(c.send)

	for {
		select {
		case <-c.end:
			return
		case req := <-c.requests:
			c.handleRequest(req)
		case resp := <-c.responses:
			c.handleResponse(resp)
		}
	}
}

func (c *Client) nextSeq() uint32 {
	c.seq++
	return c.seq
}

func (c *Client) replyError(req *Message) {
	delete(c.messages, req.Seq)
	req.Type = TypeError
	req.Data = []byte(c.err.Error())
	req.Complete <- struct{}{}
}

func (c *Client) handleRequest(req *Message) {
	if c.err != nil {
		c.replyError(req)
		return
	}

	req.Seq = c.nextSeq()
	c.messages[req.Seq] = req
	c.send <- req
}

func (c *Client) handleResponse(resp *Message) {
	if resp.transportErr != nil {
		c.err = resp.transportErr
		// Terminate all in flight
		for _, msg := range c.messages {
			c.replyError(msg)
		}
		return
	}

	if req, ok := c.messages[resp.Seq]; ok {
		if c.err != nil {
			c.replyError(req)
			return
		}

		delete(c.messages, resp.Seq)
		// can probably optimize away this copy
		if len(resp.Data) > 0 {
			copy(req.Data, resp.Data)
		}
		req.Type = resp.Type
		req.Complete <- struct{}{}
	}
}

func (c *Client) write() {
	for msg := range c.send {
		if err := c.wire.Write(msg); err != nil {
			c.responses <- &Message{
				transportErr: err,
			}
		}
	}
}

func (c *Client) read() {
	for {
		msg, err := c.wire.Read()
		if err != nil {
			logrus.Errorf("Error reading from wire: %v", err)
			c.responses <- &Message{
				transportErr: err,
			}
			break
		}
		c.responses <- msg
	}
}
<commit_msg>RW timeout retry (#128)<commit_after>package rpc

import (
	"errors"
	"io"
	"net"
	"time"

	"github.com/Sirupsen/logrus"
)

var (
	//ErrRWTimeout r/w operation timeout
	ErrRWTimeout = errors.New("r/w timeout")

	opRetries      = 4
	opReadTimeout  = 15 * time.Second // client read
	opWriteTimeout = 15 * time.Second // client write
)

//Client replica client
type Client struct {
	end       chan struct{}
	requests  chan *Message
	send      chan *Message
	responses chan *Message
	seq       uint32
	messages  map[uint32]*Message
	wire      *Wire
	err       error
}

//NewClient replica client
func NewClient(conn net.Conn) *Client {
	c := &Client{
		wire:      NewWire(conn),
		end:       make(chan struct{}, 1024),
		requests:  make(chan *Message, 1024),
		send:      make(chan *Message, 1024),
		responses: make(chan *Message, 1024),
		messages:  map[uint32]*Message{},
	}
	go c.loop()
	go c.write()
	go c.read()
	return c
}

//WriteAt replica client
func (c *Client) WriteAt(buf []byte, offset int64) (int, error) {
	return c.operation(TypeWrite, buf, offset)
}

//SetError replica client transport error
func (c *Client) SetError(err error) {
	c.responses <- &Message{
		transportErr: err,
	}
}

//ReadAt replica client
func (c *Client) ReadAt(buf []byte, offset int64) (int, error) {
	return c.operation(TypeRead, buf, offset)
}

func (c *Client) operation(op uint32, buf []byte, offset int64) (int, error) {
	retry := 0
	for {
		msg := Message{
			Complete: make(chan struct{}, 1),
			Type:     op,
			Offset:   offset,
			Data:     buf,
		}

		timeout := func(op uint32) <-chan time.Time {
			switch op {
			case TypeRead:
				return time.After(opReadTimeout)
			}
			return time.After(opWriteTimeout)
		}(msg.Type)

		c.requests <- &msg

		select {
		case <-msg.Complete:
			if msg.Type == TypeError {
				return 0, errors.New(string(msg.Data))
			}
			if msg.Type == TypeEOF {
				return len(msg.Data), io.EOF
			}
			return len(msg.Data), nil
		case <-timeout:
			switch msg.Type {
			case TypeRead:
				logrus.Errorln("Read timeout on replcia", c.wire.conn.RemoteAddr().String(), "seq=", msg.Seq, "size=", len(msg.Data)/1024, "(kB)")
			case TypeWrite:
				logrus.Errorln("Write timeout on replica", c.wire.conn.RemoteAddr().String(), "seq=", msg.Seq, "size=", len(msg.Data)/1024, "(kB)")
			}
			if retry < opRetries {
				retry++
				logrus.Errorln("Retry ", retry, "on replica", c.wire.conn.RemoteAddr().String(), "seq=", msg.Seq, "size=", len(msg.Data)/1024, "(kB)")
			} else {
				c.SetError(ErrRWTimeout)
				return 0, ErrRWTimeout
			}
		}
	}
}

//Close replica client
func (c *Client) Close() {
	c.wire.Close()
	c.end <- struct{}{}
}

func (c *Client) loop() {
	defer close(c.send)

	for {
		select {
		case <-c.end:
			return
		case req := <-c.requests:
			c.handleRequest(req)
		case resp := <-c.responses:
			c.handleResponse(resp)
		}
	}
}

func (c *Client) nextSeq() uint32 {
	c.seq++
	return c.seq
}

func (c *Client) replyError(req *Message) {
	delete(c.messages, req.Seq)
	req.Type = TypeError
	req.Data = []byte(c.err.Error())
	req.Complete <- struct{}{}
}

func (c *Client) handleRequest(req *Message) {
	if c.err != nil {
		c.replyError(req)
		return
	}

	req.Seq = c.nextSeq()
	c.messages[req.Seq] = req
	c.send <- req
}

func (c *Client) handleResponse(resp *Message) {
	if resp.transportErr != nil {
		c.err = resp.transportErr
		// Terminate all in flight
		for _, msg := range c.messages {
			c.replyError(msg)
		}
		return
	}

	if req, ok := c.messages[resp.Seq]; ok {
		if c.err != nil {
			c.replyError(req)
			return
		}

		delete(c.messages, resp.Seq)
		// can probably optimize away this copy
		if len(resp.Data) > 0 {
			copy(req.Data, resp.Data)
		}
		req.Type = resp.Type
		req.Complete <- struct{}{}
	}
}

func (c *Client) write() {
	for msg := range c.send {
		if err := c.wire.Write(msg); err != nil {
			c.responses <- &Message{
				transportErr: err,
			}
		}
	}
}

func (c *Client) read() {
	for {
		msg, err := c.wire.Read()
		if err != nil {
			logrus.Errorf("Error reading from wire: %v", err)
			c.responses <- &Message{
				transportErr: err,
			}
			break
		}
		c.responses <- msg
	}
}
<|endoftext|>


<commit_before>package rpc

import (
	"context"
	"net"
	"strings"
	"sync"

	"github.com/appleboy/gorush/config"
	"github.com/appleboy/gorush/core"
	"github.com/appleboy/gorush/logx"
	"github.com/appleboy/gorush/notify"
	"github.com/appleboy/gorush/rpc/proto"

	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/reflection"
	"google.golang.org/grpc/status"
)

// Server is used to implement gorush grpc server.
type Server struct {
	cfg *config.ConfYaml
	mu  sync.Mutex
	// statusMap stores the serving status of the services this Server monitors.
	statusMap map[string]proto.HealthCheckResponse_ServingStatus
}

// NewServer returns a new Server.
func NewServer(cfg *config.ConfYaml) *Server {
	return &Server{
		cfg:       cfg,
		statusMap: make(map[string]proto.HealthCheckResponse_ServingStatus),
	}
}

// Check implements `service Health`.
func (s *Server) Check(ctx context.Context, in *proto.HealthCheckRequest) (*proto.HealthCheckResponse, error) {
	s.mu.Lock()
	defer s.mu.Unlock()
	if in.Service == "" {
		// check the server overall health status.
		return &proto.HealthCheckResponse{
			Status: proto.HealthCheckResponse_SERVING,
		}, nil
	}
	if status, ok := s.statusMap[in.Service]; ok {
		return &proto.HealthCheckResponse{
			Status: status,
		}, nil
	}
	return nil, status.Error(codes.NotFound, "unknown service")
}

// Send implements helloworld.GreeterServer
func (s *Server) Send(ctx context.Context, in *proto.NotificationRequest) (*proto.NotificationReply, error) {
	badge := int(in.Badge)
	notification := notify.PushNotification{
		ID:               in.ID,
		Platform:         int(in.Platform),
		Tokens:           in.Tokens,
		Message:          in.Message,
		Title:            in.Title,
		Topic:            in.Topic,
		APIKey:           in.Key,
		Category:         in.Category,
		Sound:            in.Sound,
		ContentAvailable: in.ContentAvailable,
		ThreadID:         in.ThreadID,
		MutableContent:   in.MutableContent,
		Image:            in.Image,
		Priority:         strings.ToLower(in.GetPriority().String()),
	}

	if badge > 0 {
		notification.Badge = &badge
	}

	if in.Topic != "" && in.Platform == core.PlatFormAndroid {
		notification.To = in.Topic
	}

	if in.Alert != nil {
		notification.Alert = notify.Alert{
			Title:        in.Alert.Title,
			Body:         in.Alert.Body,
			Subtitle:     in.Alert.Subtitle,
			Action:       in.Alert.Action,
			ActionLocKey: in.Alert.Action,
			LaunchImage:  in.Alert.LaunchImage,
			LocArgs:      in.Alert.LocArgs,
			LocKey:       in.Alert.LocKey,
			TitleLocArgs: in.Alert.TitleLocArgs,
			TitleLocKey:  in.Alert.TitleLocKey,
		}
	}

	if in.Data != nil {
		notification.Data = map[string]interface{}{}
		for k, v := range in.Data.Fields {
			notification.Data[k] = v
		}
	}

	go func() {
		_, err := notify.SendNotification(&notification, s.cfg)
		if err != nil {
			logx.LogError.Error(err)
		}
	}()

	return &proto.NotificationReply{
		Success: true,
		Counts:  int32(len(notification.Tokens)),
	}, nil
}

// RunGRPCServer run gorush grpc server
func RunGRPCServer(ctx context.Context, cfg *config.ConfYaml) error {
	if !cfg.GRPC.Enabled {
		logx.LogAccess.Info("gRPC server is disabled.")
		return nil
	}

	s := grpc.NewServer()
	rpcSrv := NewServer(cfg)
	proto.RegisterGorushServer(s, rpcSrv)
	proto.RegisterHealthServer(s, rpcSrv)

	// Register reflection service on gRPC server.
	reflection.Register(s)

	lis, err := net.Listen("tcp", ":"+cfg.GRPC.Port)
	if err != nil {
		logx.LogError.Fatalln(err)
		return err
	}
	logx.LogAccess.Info("gRPC server is running on " + cfg.GRPC.Port + " port.")
	go func() {
		<-ctx.Done()
		s.GracefulStop() // graceful shutdown
		logx.LogAccess.Info("shutdown the gRPC server")
	}()
	if err = s.Serve(lis); err != nil {
		logx.LogError.Fatalln(err)
	}
	return err
}
<commit_msg>fix #418: use struct.asMap() for data field (#638)<commit_after>package rpc

import (
	"context"
	"fmt"
	"net"
	"strings"
	"sync"

	"github.com/appleboy/gorush/config"
	"github.com/appleboy/gorush/core"
	"github.com/appleboy/gorush/logx"
	"github.com/appleboy/gorush/notify"
	"github.com/appleboy/gorush/rpc/proto"

	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/reflection"
	"google.golang.org/grpc/status"
)

// Server is used to implement gorush grpc server.
type Server struct {
	cfg *config.ConfYaml
	mu  sync.Mutex
	// statusMap stores the serving status of the services this Server monitors.
	statusMap map[string]proto.HealthCheckResponse_ServingStatus
}

// NewServer returns a new Server.
func NewServer(cfg *config.ConfYaml) *Server {
	return &Server{
		cfg:       cfg,
		statusMap: make(map[string]proto.HealthCheckResponse_ServingStatus),
	}
}

// Check implements `service Health`.
func (s *Server) Check(ctx context.Context, in *proto.HealthCheckRequest) (*proto.HealthCheckResponse, error) {
	s.mu.Lock()
	defer s.mu.Unlock()
	if in.Service == "" {
		// check the server overall health status.
		return &proto.HealthCheckResponse{
			Status: proto.HealthCheckResponse_SERVING,
		}, nil
	}
	if status, ok := s.statusMap[in.Service]; ok {
		return &proto.HealthCheckResponse{
			Status: status,
		}, nil
	}
	return nil, status.Error(codes.NotFound, "unknown service")
}

// Send implements helloworld.GreeterServer
func (s *Server) Send(ctx context.Context, in *proto.NotificationRequest) (*proto.NotificationReply, error) {
	badge := int(in.Badge)
	notification := notify.PushNotification{
		ID:               in.ID,
		Platform:         int(in.Platform),
		Tokens:           in.Tokens,
		Message:          in.Message,
		Title:            in.Title,
		Topic:            in.Topic,
		APIKey:           in.Key,
		Category:         in.Category,
		Sound:            in.Sound,
		ContentAvailable: in.ContentAvailable,
		ThreadID:         in.ThreadID,
		MutableContent:   in.MutableContent,
		Image:            in.Image,
		Priority:         strings.ToLower(in.GetPriority().String()),
	}

	if badge > 0 {
		notification.Badge = &badge
	}

	if in.Topic != "" && in.Platform == core.PlatFormAndroid {
		notification.To = in.Topic
	}

	if in.Alert != nil {
		notification.Alert = notify.Alert{
			Title:        in.Alert.Title,
			Body:         in.Alert.Body,
			Subtitle:     in.Alert.Subtitle,
			Action:       in.Alert.Action,
			ActionLocKey: in.Alert.Action,
			LaunchImage:  in.Alert.LaunchImage,
			LocArgs:      in.Alert.LocArgs,
			LocKey:       in.Alert.LocKey,
			TitleLocArgs: in.Alert.TitleLocArgs,
			TitleLocKey:  in.Alert.TitleLocKey,
		}
	}

	if in.Data != nil {
		notification.Data = map[string]interface{}{}
		for k, v := range in.Data.AsMap() {
			notification.Data[k] = fmt.Sprintf("%v", v)
		}
	}

	go func() {
		_, err := notify.SendNotification(&notification, s.cfg)
		if err != nil {
			logx.LogError.Error(err)
		}
	}()

	return &proto.NotificationReply{
		Success: true,
		Counts:  int32(len(notification.Tokens)),
	}, nil
}

// RunGRPCServer run gorush grpc server
func RunGRPCServer(ctx context.Context, cfg *config.ConfYaml) error {
	if !cfg.GRPC.Enabled {
		logx.LogAccess.Info("gRPC server is disabled.")
		return nil
	}

	s := grpc.NewServer()
	rpcSrv := NewServer(cfg)
	proto.RegisterGorushServer(s, rpcSrv)
	proto.RegisterHealthServer(s, rpcSrv)

	// Register reflection service on gRPC server.
	reflection.Register(s)

	lis, err := net.Listen("tcp", ":"+cfg.GRPC.Port)
	if err != nil {
		logx.LogError.Fatalln(err)
		return err
	}
	logx.LogAccess.Info("gRPC server is running on " + cfg.GRPC.Port + " port.")
	go func() {
		<-ctx.Done()
		s.GracefulStop() // graceful shutdown
		logx.LogAccess.Info("shutdown the gRPC server")
	}()
	if err = s.Serve(lis); err != nil {
		logx.LogError.Fatalln(err)
	}
	return err
}
<|endoftext|>


<commit_before>// Copyright 2016 The Upspin Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package rpc

import (
	"crypto/rand"
	"encoding/binary"
	"fmt"
	"io/ioutil"
	"math/big"
	"net/http"
	"strings"
	"time"

	pb "github.com/golang/protobuf/proto"
	gContext "golang.org/x/net/context"

	"upspin.io/errors"
	"upspin.io/factotum"
	"upspin.io/log"
	"upspin.io/upspin"
	"upspin.io/upspin/proto"
	"upspin.io/valid"
)

// Errors returned in case of various authentication failure scenarios.
var (
	errUnauthenticated  = errors.Str("user not authenticated")
	errExpired          = errors.Str("auth token expired")
	errMissingSignature = errors.Str("missing or invalid signature")

	authTokenDuration = 20 * time.Hour // Max duration an auth token lasts.
)

const (
	// authTokenHeader is the key in the context's metadata for the auth token.
	authTokenHeader = "Upspin-Auth-Token"

	// authRequestHeader is the key for inline user authentication.
	authRequestHeader = "Upspin-Auth-Request"

	// authErrorHeader is the key for inline user authentication errors.
	authErrorHeader = "Upspin-Auth-Error"

	// proxyRequestHeader key is for inline proxy configuration requests.
	proxyRequestHeader = "Upspin-Proxy-Request"

	// authTokenEntropyLen is the size of random bytes in an auth token.
	authTokenEntropyLen = 16

	// clientAuthMagic is a string used in validating the client's user name.
	clientAuthMagic = " Authenticate "

	// serverAuthMagic is a string used in validating the server's user name.
	serverAuthMagic = " AuthenticateServer "
)

// Service describes an RPC service.
type Service struct {
	// The name of the service, which forms the first path component of any
	// HTTP request.
	Name string

	// The RPC methods to serve.
	Methods map[string]Method

	// The streaming RPC methods to serve.
	Streams map[string]Stream
}

// Method describes an RPC method.
type Method func(s Session, reqBytes []byte) (pb.Message, error)

// Stream describes a streaming RPC method.
type Stream func(s Session, reqBytes []byte, done <-chan struct{}) (<-chan pb.Message, error)

// ServerConfig holds the configuration for instantiating a Server.
type ServerConfig struct {
	// Lookup looks up user keys.
	// If nil, PublicUserKeyService will be used.
	Lookup func(userName upspin.UserName) (upspin.PublicKey, error)

	// Service provides the service to serve by HTTP.
	Service Service
}

// NewServer returns a new Server that uses the given config.
// If a nil config is provided the defaults are used.
func NewServer(cfg upspin.Config, scfg *ServerConfig) http.Handler {
	return &serverImpl{
		config:       cfg,
		serverconfig: scfg,
	}
}

type serverImpl struct {
	config       upspin.Config
	serverconfig *ServerConfig
}

func (s *serverImpl) lookup(u upspin.UserName) (upspin.PublicKey, error) {
	if s.serverconfig == nil || s.serverconfig.Lookup == nil {
		return PublicUserKeyService(s.config)(u)
	}
	return s.serverconfig.Lookup(u)
}

func (s *serverImpl) service() *Service {
	if s.config == nil {
		return nil
	}
	return &s.serverconfig.Service
}

func generateRandomToken() (string, error) {
	var buf [authTokenEntropyLen]byte
	n, err := rand.Read(buf[:])
	if err != nil {
		return "", err
	}
	if n != len(buf) {
		return "", errors.Str("random bytes too short")
	}
	return fmt.Sprintf("%X", buf), nil
}

// ServeHTTP exposes the configured Service as an HTTP API.
func (s *serverImpl) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	d := s.service()
	if d == nil {
		http.NotFound(w, r)
		return
	}

	prefix := "/api/" + d.Name + "/"
	if !strings.HasPrefix(r.URL.Path, prefix) {
		http.NotFound(w, r)
		return
	}
	name := strings.TrimPrefix(r.URL.Path, prefix)

	method := d.Methods[name]
	stream := d.Streams[name]
	if method == nil && stream == nil {
		http.NotFound(w, r)
		return
	}

	session, err := s.SessionForRequest(w, r)
	if err != nil {
		http.Error(w, err.Error(), http.StatusUnauthorized)
		return
	}

	body, err := ioutil.ReadAll(r.Body)
	r.Body.Close()
	if err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}

	if method != nil {
		serveMethod(method, session, w, body)
		return
	}
	serveStream(stream, session, w, body)
}

func serveMethod(m Method, sess Session, w http.ResponseWriter, body []byte) {
	resp, err := m(sess, body)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	payload, err := pb.Marshal(resp)
	if err != nil {
		log.Error.Printf("error encoding response: %v", err)
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	w.Write(payload)
}

func serveStream(s Stream, sess Session, w http.ResponseWriter, body []byte) {
	done := make(chan struct{})
	msgs, err := s(sess, body, done)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	go func() {
		<-w.(http.CloseNotifier).CloseNotify()
		close(done)
	}()

	// Write the headers, beginning the stream.
	w.Write([]byte("OK"))
	w.(http.Flusher).Flush()

	var lenBytes [4]byte // stores a uint32, the length of each output message
	for {
		select {
		case msg, ok := <-msgs:
			if !ok {
				return
			}
			if done == nil {
				// Drop this message as there's nobody to deliver to.
				continue
			}

			b, err := pb.Marshal(msg)
			if err != nil {
				log.Error.Printf("rpc/auth: error encoding proto in stream: %v", err)
				return
			}

			binary.BigEndian.PutUint32(lenBytes[:], uint32(len(b)))
			if _, err := w.Write(lenBytes[:]); err != nil {
				return
			}
			if _, err := w.Write(b); err != nil {
				return
			}
			w.(http.Flusher).Flush()

		case <-done:
			done = nil
		}
	}
}

func (s *serverImpl) SessionForRequest(w http.ResponseWriter, r *http.Request) (session Session, err error) {
	const op = "rpc.SessionForRequest"
	defer func() {
		if err == nil {
			return
		}
		// Capture session setup errors and
		// send them to the client in the HTTP response Header.
		w.Header().Set(authErrorHeader, err.Error())
		// Attach the op to the error here, because the client doesn't
		// care that this error originated in this function.
		err = errors.E(op, err)
	}()
	if tok, ok := r.Header[authTokenHeader]; ok && len(tok) == 1 {
		return s.validateToken(tok[0])
	}
	proxyRequest, ok := r.Header[proxyRequestHeader]
	if ok && len(proxyRequest) != 1 {
		return nil, errors.E(errors.Invalid, errors.Str("invalid proxy request in header"))
	}
	authRequest, ok := r.Header[authRequestHeader]
	if ok && len(authRequest) != 4 {
		return nil, errors.E(errors.Invalid, errors.Str("invalid auth request in header"))
	}
	if authRequest == nil {
		log.Printf("%#v", r.Header)
		return nil, errors.E(errors.Invalid, errors.Str("no auth token or request in header"))
	}
	return s.handleSessionRequest(w, authRequest, proxyRequest)
}

// Ping implements Pinger.
func (s *serverImpl) Ping(gContext gContext.Context, req *proto.PingRequest) (*proto.PingResponse, error) {
	return &proto.PingResponse{PingSequence: req.PingSequence}, nil
}

func (s *serverImpl) validateToken(authToken string) (Session, error) {
	if len(authToken) < authTokenEntropyLen {
		return nil, errors.E(errors.Invalid, errors.Str("invalid auth token"))
	}

	// Get the session for this authToken
	session := GetSession(authToken)
	if session == nil {
		// We don't know this client or have forgotten about it. We must authenticate.
		// Log it so we can track how often this happens. Maybe we need to increase the session cache size.
		log.Debug.Printf("Got token from user but there's no session for it.")
		return nil, errors.E(errors.Permission, errUnauthenticated)
	}

	// If session has expired, forcibly remove it from the cache and return an error.
	if session.Expires().Before(time.Now()) {
		ClearSession(authToken)
		return nil, errors.E(errors.Permission, errExpired)
	}

	return session, nil
}

func (s *serverImpl) handleSessionRequest(w http.ResponseWriter, authRequest []string, proxyRequest []string) (Session, error) {
	// Validate the username.
	user := upspin.UserName(authRequest[0])
	if err := valid.UserName(user); err != nil {
		return nil, errors.E(user, err)
	}

	// Get user's public key.
	key, err := s.lookup(user)
	if err != nil {
		return nil, errors.E(user, err)
	}

	now := time.Now()

	// Validate signature.
	if err := verifyUser(key, authRequest, clientAuthMagic, now); err != nil {
		return nil, errors.E(errors.Permission, user, errors.Errorf("invalid signature: %v", err))
	}

	// Generate an auth token and bind it to a session for the client.
	expiration := now.Add(authTokenDuration)
	authToken, err := generateRandomToken()
	if err != nil {
		return nil, err
	}
	w.Header().Set(authTokenHeader, authToken)

	// If there is a proxy request, remember the proxy's endpoint and authenticate server to client.
	ep := &upspin.Endpoint{}
	if len(proxyRequest) == 1 {
		ep, err = upspin.ParseEndpoint(proxyRequest[0])
		if err != nil {
			return nil, errors.E(errors.Invalid, errors.Errorf("invalid proxy endpoint: %v", err))
		}
		// Authenticate the server to the user.
		authMsg, err := signUser(s.config, serverAuthMagic)
		if err != nil {
			return nil, errors.E(errors.Permission, err)
		}
		w.Header()[authRequestHeader] = authMsg
	}

	return NewSession(user, expiration, authToken, ep, nil), nil
}

// verifyUser authenticates the remote user.
//
// The message is a slice of strings of the form: user, time, sig.R, sig.S
func verifyUser(key upspin.PublicKey, msg []string, magic string, now time.Time) error {
	if len(msg) != 4 {
		return errors.Str("bad header")
	}

	// Make sure the challenge time is sane.
	msgNow, err := time.Parse(time.ANSIC, msg[1])
	if err != nil {
		return err
	}
	// Currently just print a message if the time is too far off.
	// TODO(p): we have to do better than this.
	if msgNow.After(now.Add(30*time.Second)) || msgNow.Before(now.Add(-45*time.Second)) {
		log.Info.Printf("verifying %s: timestamp is far wrong (%v); proceeding anyway", msg[0], now.Sub(msgNow))
	}

	// Parse signature
	var rs, ss big.Int
	if _, ok := rs.SetString(msg[2], 10); !ok {
		return errMissingSignature
	}
	if _, ok := ss.SetString(msg[3], 10); !ok {
		return errMissingSignature
	}

	// Validate signature.
	hash := []byte(msg[0] + magic + msg[1])
	err = factotum.Verify(hash, upspin.Signature{R: &rs, S: &ss}, key)
	if err != nil {
		errors.Errorf("signature fails to validate using the provided key: %s", err)
	}
	return nil
}

// signUser creates a header authenticating the local user.
func signUser(cfg upspin.Config, magic string) ([]string, error) {
	if cfg == nil {
		return nil, errors.Str("nil config")
	}
	f := cfg.Factotum()
	if f == nil {
		return nil, errors.Str("no factotum available")
	}

	// Discourage replay attacks.
	now := time.Now().UTC().Format(time.ANSIC)
	userString := string(cfg.UserName())
	sig, err := f.Sign([]byte(userString + magic + now))
	if err != nil {
		log.Error.Printf("proxyRequest signing server user: %v", err)
		return nil, err
	}
	return []string{
		userString,
		now,
		sig.R.String(),
		sig.S.String(),
	}, nil
}
<commit_msg>rpc/server: actually return the error<commit_after>// Copyright 2016 The Upspin Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package rpc

import (
	"crypto/rand"
	"encoding/binary"
	"fmt"
	"io/ioutil"
	"math/big"
	"net/http"
	"strings"
	"time"

	pb "github.com/golang/protobuf/proto"
	gContext "golang.org/x/net/context"

	"upspin.io/errors"
	"upspin.io/factotum"
	"upspin.io/log"
	"upspin.io/upspin"
	"upspin.io/upspin/proto"
	"upspin.io/valid"
)

// Errors returned in case of various authentication failure scenarios.
var (
	errUnauthenticated  = errors.Str("user not authenticated")
	errExpired          = errors.Str("auth token expired")
	errMissingSignature = errors.Str("missing or invalid signature")

	authTokenDuration = 20 * time.Hour // Max duration an auth token lasts.
)

const (
	// authTokenHeader is the key in the context's metadata for the auth token.
	authTokenHeader = "Upspin-Auth-Token"

	// authRequestHeader is the key for inline user authentication.
	authRequestHeader = "Upspin-Auth-Request"

	// authErrorHeader is the key for inline user authentication errors.
	authErrorHeader = "Upspin-Auth-Error"

	// proxyRequestHeader key is for inline proxy configuration requests.
	proxyRequestHeader = "Upspin-Proxy-Request"

	// authTokenEntropyLen is the size of random bytes in an auth token.
	authTokenEntropyLen = 16

	// clientAuthMagic is a string used in validating the client's user name.
	clientAuthMagic = " Authenticate "

	// serverAuthMagic is a string used in validating the server's user name.
	serverAuthMagic = " AuthenticateServer "
)

// Service describes an RPC service.
type Service struct {
	// The name of the service, which forms the first path component of any
	// HTTP request.
	Name string

	// The RPC methods to serve.
	Methods map[string]Method

	// The streaming RPC methods to serve.
	Streams map[string]Stream
}

// Method describes an RPC method.
type Method func(s Session, reqBytes []byte) (pb.Message, error)

// Stream describes a streaming RPC method.
type Stream func(s Session, reqBytes []byte, done <-chan struct{}) (<-chan pb.Message, error)

// ServerConfig holds the configuration for instantiating a Server.
type ServerConfig struct {
	// Lookup looks up user keys.
	// If nil, PublicUserKeyService will be used.
	Lookup func(userName upspin.UserName) (upspin.PublicKey, error)

	// Service provides the service to serve by HTTP.
	Service Service
}

// NewServer returns a new Server that uses the given config.
// If a nil config is provided the defaults are used.
func NewServer(cfg upspin.Config, scfg *ServerConfig) http.Handler {
	return &serverImpl{
		config:       cfg,
		serverconfig: scfg,
	}
}

type serverImpl struct {
	config       upspin.Config
	serverconfig *ServerConfig
}

func (s *serverImpl) lookup(u upspin.UserName) (upspin.PublicKey, error) {
	if s.serverconfig == nil || s.serverconfig.Lookup == nil {
		return PublicUserKeyService(s.config)(u)
	}
	return s.serverconfig.Lookup(u)
}

func (s *serverImpl) service() *Service {
	if s.config == nil {
		return nil
	}
	return &s.serverconfig.Service
}

func generateRandomToken() (string, error) {
	var buf [authTokenEntropyLen]byte
	n, err := rand.Read(buf[:])
	if err != nil {
		return "", err
	}
	if n != len(buf) {
		return "", errors.Str("random bytes too short")
	}
	return fmt.Sprintf("%X", buf), nil
}

// ServeHTTP exposes the configured Service as an HTTP API.
func (s *serverImpl) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	d := s.service()
	if d == nil {
		http.NotFound(w, r)
		return
	}

	prefix := "/api/" + d.Name + "/"
	if !strings.HasPrefix(r.URL.Path, prefix) {
		http.NotFound(w, r)
		return
	}
	name := strings.TrimPrefix(r.URL.Path, prefix)

	method := d.Methods[name]
	stream := d.Streams[name]
	if method == nil && stream == nil {
		http.NotFound(w, r)
		return
	}

	session, err := s.SessionForRequest(w, r)
	if err != nil {
		http.Error(w, err.Error(), http.StatusUnauthorized)
		return
	}

	body, err := ioutil.ReadAll(r.Body)
	r.Body.Close()
	if err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}

	if method != nil {
		serveMethod(method, session, w, body)
		return
	}
	serveStream(stream, session, w, body)
}

func serveMethod(m Method, sess Session, w http.ResponseWriter, body []byte) {
	resp, err := m(sess, body)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	payload, err := pb.Marshal(resp)
	if err != nil {
		log.Error.Printf("error encoding response: %v", err)
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	w.Write(payload)
}

func serveStream(s Stream, sess Session, w http.ResponseWriter, body []byte) {
	done := make(chan struct{})
	msgs, err := s(sess, body, done)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	go func() {
		<-w.(http.CloseNotifier).CloseNotify()
		close(done)
	}()

	// Write the headers, beginning the stream.
	w.Write([]byte("OK"))
	w.(http.Flusher).Flush()

	var lenBytes [4]byte // stores a uint32, the length of each output message
	for {
		select {
		case msg, ok := <-msgs:
			if !ok {
				return
			}
			if done == nil {
				// Drop this message as there's nobody to deliver to.
				continue
			}

			b, err := pb.Marshal(msg)
			if err != nil {
				log.Error.Printf("rpc/auth: error encoding proto in stream: %v", err)
				return
			}

			binary.BigEndian.PutUint32(lenBytes[:], uint32(len(b)))
			if _, err := w.Write(lenBytes[:]); err != nil {
				return
			}
			if _, err := w.Write(b); err != nil {
				return
			}
			w.(http.Flusher).Flush()

		case <-done:
			done = nil
		}
	}
}

func (s *serverImpl) SessionForRequest(w http.ResponseWriter, r *http.Request) (session Session, err error) {
	const op = "rpc.SessionForRequest"
	defer func() {
		if err == nil {
			return
		}
		// Capture session setup errors and
		// send them to the client in the HTTP response Header.
		w.Header().Set(authErrorHeader, err.Error())
		// Attach the op to the error here, because the client doesn't
		// care that this error originated in this function.
		err = errors.E(op, err)
	}()
	if tok, ok := r.Header[authTokenHeader]; ok && len(tok) == 1 {
		return s.validateToken(tok[0])
	}
	proxyRequest, ok := r.Header[proxyRequestHeader]
	if ok && len(proxyRequest) != 1 {
		return nil, errors.E(errors.Invalid, errors.Str("invalid proxy request in header"))
	}
	authRequest, ok := r.Header[authRequestHeader]
	if ok && len(authRequest) != 4 {
		return nil, errors.E(errors.Invalid, errors.Str("invalid auth request in header"))
	}
	if authRequest == nil {
		log.Printf("%#v", r.Header)
		return nil, errors.E(errors.Invalid, errors.Str("no auth token or request in header"))
	}
	return s.handleSessionRequest(w, authRequest, proxyRequest)
}

// Ping implements Pinger.
func (s *serverImpl) Ping(gContext gContext.Context, req *proto.PingRequest) (*proto.PingResponse, error) {
	return &proto.PingResponse{PingSequence: req.PingSequence}, nil
}

func (s *serverImpl) validateToken(authToken string) (Session, error) {
	if len(authToken) < authTokenEntropyLen {
		return nil, errors.E(errors.Invalid, errors.Str("invalid auth token"))
	}

	// Get the session for this authToken
	session := GetSession(authToken)
	if session == nil {
		// We don't know this client or have forgotten about it. We must authenticate.
		// Log it so we can track how often this happens. Maybe we need to increase the session cache size.
		log.Debug.Printf("Got token from user but there's no session for it.")
		return nil, errors.E(errors.Permission, errUnauthenticated)
	}

	// If session has expired, forcibly remove it from the cache and return an error.
	if session.Expires().Before(time.Now()) {
		ClearSession(authToken)
		return nil, errors.E(errors.Permission, errExpired)
	}

	return session, nil
}

func (s *serverImpl) handleSessionRequest(w http.ResponseWriter, authRequest []string, proxyRequest []string) (Session, error) {
	// Validate the username.
	user := upspin.UserName(authRequest[0])
	if err := valid.UserName(user); err != nil {
		return nil, errors.E(user, err)
	}

	// Get user's public key.
	key, err := s.lookup(user)
	if err != nil {
		return nil, errors.E(user, err)
	}

	now := time.Now()

	// Validate signature.
	if err := verifyUser(key, authRequest, clientAuthMagic, now); err != nil {
		return nil, errors.E(errors.Permission, user, errors.Errorf("invalid signature: %v", err))
	}

	// Generate an auth token and bind it to a session for the client.
	expiration := now.Add(authTokenDuration)
	authToken, err := generateRandomToken()
	if err != nil {
		return nil, err
	}
	w.Header().Set(authTokenHeader, authToken)

	// If there is a proxy request, remember the proxy's endpoint and authenticate server to client.
	ep := &upspin.Endpoint{}
	if len(proxyRequest) == 1 {
		ep, err = upspin.ParseEndpoint(proxyRequest[0])
		if err != nil {
			return nil, errors.E(errors.Invalid, errors.Errorf("invalid proxy endpoint: %v", err))
		}
		// Authenticate the server to the user.
		authMsg, err := signUser(s.config, serverAuthMagic)
		if err != nil {
			return nil, errors.E(errors.Permission, err)
		}
		w.Header()[authRequestHeader] = authMsg
	}

	return NewSession(user, expiration, authToken, ep, nil), nil
}

// verifyUser authenticates the remote user.
//
// The message is a slice of strings of the form: user, time, sig.R, sig.S
func verifyUser(key upspin.PublicKey, msg []string, magic string, now time.Time) error {
	if len(msg) != 4 {
		return errors.Str("bad header")
	}

	// Make sure the challenge time is sane.
	msgNow, err := time.Parse(time.ANSIC, msg[1])
	if err != nil {
		return err
	}
	// Currently just print a message if the time is too far off.
	// TODO(p): we have to do better than this.
	if msgNow.After(now.Add(30*time.Second)) || msgNow.Before(now.Add(-45*time.Second)) {
		log.Info.Printf("verifying %s: timestamp is far wrong (%v); proceeding anyway", msg[0], now.Sub(msgNow))
	}

	// Parse signature
	var rs, ss big.Int
	if _, ok := rs.SetString(msg[2], 10); !ok {
		return errMissingSignature
	}
	if _, ok := ss.SetString(msg[3], 10); !ok {
		return errMissingSignature
	}

	// Validate signature.
	// TODO: this really should be a hash.
	hash := []byte(msg[0] + magic + msg[1])
	err = factotum.Verify(hash, upspin.Signature{R: &rs, S: &ss}, key)
	if err != nil {
		err = errors.Errorf("signature fails to validate using the provided key: %s", err)
		log.Debug.Printf("rpc/server: verifyUser: %s", err)
		return err
	}
	return nil
}

// signUser creates a header authenticating the local user.
func signUser(cfg upspin.Config, magic string) ([]string, error) {
	if cfg == nil {
		return nil, errors.Str("nil config")
	}
	f := cfg.Factotum()
	if f == nil {
		return nil, errors.Str("no factotum available")
	}

	// Discourage replay attacks.
	now := time.Now().UTC().Format(time.ANSIC)
	userString := string(cfg.UserName())
	// TODO: this should be a hash, matching verifyUser.
	sig, err := f.Sign([]byte(userString + magic + now))
	if err != nil {
		log.Error.Printf("proxyRequest signing server user: %v", err)
		return nil, err
	}
	return []string{
		userString,
		now,
		sig.R.String(),
		sig.S.String(),
	}, nil
}
<|endoftext|>


<commit_before>package main

import (
	"log"
	"net/http"
	"os"
	"strconv"

	"github.com/andreandradecosta/rpimonitor/controllers/monitor"
	"github.com/codegangsta/negroni"
	"github.com/gorilla/mux"
	"github.com/unrolled/secure"
	"gopkg.in/unrolled/render.v1"
)

func main() {
	port := os.Getenv("PORT")
	if port == "" {
		port = "8080"
	}
	httpsPort := os.Getenv("HTTPS_PORT")
	if httpsPort == "" {
		httpsPort = "8443"
	}
	isDev, err := strconv.ParseBool(os.Getenv("IsDevelopment"))
	if err != nil {
		isDev = false
	}
	host := os.Getenv("HOST")
	if host == "" {
		host = "localhost"
	}
	secureOptions := secure.Options{
		AllowedHosts:          []string{host + ":" + port},
		SSLRedirect:           true,
		SSLHost:               host + ":" + httpsPort,
		STSSeconds:            315360000,
		STSIncludeSubdomains:  true,
		STSPreload:            true,
		FrameDeny:             true,
		ContentTypeNosniff:    true,
		BrowserXssFilter:      true,
		ContentSecurityPolicy: "default-src 'self'",
		PublicKey:             `pin-sha256="base64+primary=="; pin-sha256="base64+backup=="; max-age=5184000; includeSubdomains; report-uri="https://www.example.com/hpkp-report"`,
		IsDevelopment:         isDev,
	}
	secureMiddleware := secure.New(secureOptions)

	router := mux.NewRouter().StrictSlash(true)
	renderer := render.New(render.Options{
		IndentJSON: true,
	})

	c := monitor.New(renderer)
	c.Register(router)

	n := negroni.Classic()
	n.Use(negroni.HandlerFunc(secureMiddleware.HandlerFuncWithNext))
	n.UseHandler(router)

	addr := ":" + port
	httpsAddr := ":" + httpsPort
	l := log.New(os.Stdout, "[negroni] ", 0)
	l.Printf("secure options %v", secureOptions)
	l.Printf("listening on http://%s and https://%s", addr, httpsAddr)
	// HTTP
	go func() {
		log.Fatal(http.ListenAndServe(addr, n))
	}()
	l.Fatal(http.ListenAndServeTLS(httpsAddr, "cert.pem", "key.pem", n))
}
<commit_msg>No useful.<commit_after>package main

import (
	"log"
	"net/http"
	"os"
	"strconv"

	"github.com/andreandradecosta/rpimonitor/controllers/monitor"
	"github.com/codegangsta/negroni"
	"github.com/gorilla/mux"
	"github.com/unrolled/secure"
	"gopkg.in/unrolled/render.v1"
)

func main() {
	port := os.Getenv("PORT")
	if port == "" {
		port = "8080"
	}
	httpsPort := os.Getenv("HTTPS_PORT")
	if httpsPort == "" {
		httpsPort = "8443"
	}
	isDev, err := strconv.ParseBool(os.Getenv("IsDevelopment"))
	if err != nil {
		isDev = false
	}
	host := os.Getenv("HOST")
	if host == "" {
		host = "localhost"
	}
	secureOptions := secure.Options{
		AllowedHosts:          []string{host + ":" + port},
		SSLRedirect:           true,
		SSLHost:               host + ":" + httpsPort,
		STSSeconds:            315360000,
		STSIncludeSubdomains:  true,
		STSPreload:            true,
		FrameDeny:             true,
		ContentTypeNosniff:    true,
		BrowserXssFilter:      true,
		ContentSecurityPolicy: "default-src 'self'",
		PublicKey:             `pin-sha256="base64+primary=="; pin-sha256="base64+backup=="; max-age=5184000; includeSubdomains; report-uri="https://www.example.com/hpkp-report"`,
		IsDevelopment:         isDev,
	}
	secureMiddleware := secure.New(secureOptions)

	router := mux.NewRouter().StrictSlash(true)
	renderer := render.New(render.Options{
		IndentJSON: true,
	})

	c := monitor.New(renderer)
	c.Register(router)

	n := negroni.Classic()
	n.Use(negroni.HandlerFunc(secureMiddleware.HandlerFuncWithNext))
	n.UseHandler(router)

	addr := ":" + port
	httpsAddr := ":" + httpsPort
	l := log.New(os.Stdout, "[negroni] ", 0)
	l.Printf("listening on http://%s and https://%s", addr, httpsAddr)
	// HTTP
	go func() {
		log.Fatal(http.ListenAndServe(addr, n))
	}()
	l.Fatal(http.ListenAndServeTLS(httpsAddr, "cert.pem", "key.pem", n))
}
<|endoftext|>