<commit_before>package chunker

import (
	"crypto/rand"
	"encoding/binary"
	"errors"
	"fmt"
	"io"
	"strconv"
)

// Pol is a polynomial from F_2[X].
type Pol uint64

// Add returns x+y.
func (x Pol) Add(y Pol) Pol {
	r := Pol(uint64(x) ^ uint64(y))
	return r
}

// Mul returns x*y. When an overflow occurs, Mul panics.
func (x Pol) Mul(y Pol) Pol {
	switch {
	case x == 0 || y == 0:
		return 0
	case x == 1:
		return y
	case y == 1:
		return x
	}

	var res Pol
	for i := 0; i <= y.Deg(); i++ {
		if (y & (1 << uint(i))) > 0 {
			res = res.Add(x << uint(i))
		}
	}

	if res.Div(y) != x {
		panic("multiplication would overflow uint64")
	}

	return res
}

// Deg returns the degree of the polynomial x. If x is zero, -1 is returned.
func (x Pol) Deg() int {
	// the degree of 0 is -1
	if x == 0 {
		return -1
	}

	// see https://graphics.stanford.edu/~seander/bithacks.html#IntegerLog

	r := 0
	if uint64(x)&0xffffffff00000000 > 0 {
		x >>= 32
		r |= 32
	}

	if uint64(x)&0xffff0000 > 0 {
		x >>= 16
		r |= 16
	}

	if uint64(x)&0xff00 > 0 {
		x >>= 8
		r |= 8
	}

	if uint64(x)&0xf0 > 0 {
		x >>= 4
		r |= 4
	}

	if uint64(x)&0xc > 0 {
		x >>= 2
		r |= 2
	}

	if uint64(x)&0x2 > 0 {
		r |= 1
	}

	return r
}

// String returns the coefficients in hex.
func (x Pol) String() string {
	return "0x" + strconv.FormatUint(uint64(x), 16)
}

// Expand returns the string representation of the polynomial x.
func (x Pol) Expand() string {
	if x == 0 {
		return "0"
	}

	s := ""
	for i := x.Deg(); i > 1; i-- {
		if x&(1<<uint(i)) > 0 {
			s += fmt.Sprintf("+x^%d", i)
		}
	}

	if x&2 > 0 {
		s += "+x"
	}

	if x&1 > 0 {
		s += "+1"
	}

	return s[1:]
}

// DivMod returns x / d = q, and remainder r,
// see https://en.wikipedia.org/wiki/Division_algorithm
func (x Pol) DivMod(d Pol) (Pol, Pol) {
	if x == 0 {
		return 0, 0
	}

	if d == 0 {
		panic("division by zero")
	}

	D := d.Deg()
	diff := x.Deg() - D
	if diff < 0 {
		return 0, x
	}

	var q Pol
	for diff >= 0 {
		m := d << uint(diff)
		q |= (1 << uint(diff))
		x = x.Add(m)

		diff = x.Deg() - D
	}

	return q, x
}

// Div returns the integer division result x / d.
func (x Pol) Div(d Pol) Pol {
	q, _ := x.DivMod(d)
	return q
}

// Mod returns the remainder of x / d
func (x Pol) Mod(d Pol) Pol {
	_, r := x.DivMod(d)
	return r
}

// I really dislike having a function that does not terminate, so specify a
// really large upper bound for finding a new irreducible polynomial, and
// return an error when no irreducible polynomial has been found within
// randPolMaxTries.
const randPolMaxTries = 1e6

// RandomPolynomial returns a new random irreducible polynomial
// of degree 53 using the default System CSPRNG as source.
// It is equivalent to calling DerivePolynomial(rand.Reader).
func RandomPolynomial() (Pol, error) {
	return DerivePolynomial(rand.Reader)
}

// DerivePolynomial returns an irreducible polynomial of degree 53
// (largest prime number below 64-8) by reading bytes from source.
// There are (2^53-2/53) irreducible polynomials of degree 53 in
// F_2[X], c.f. Michael O. Rabin (1981): "Fingerprinting by Random
// Polynomials", page 4. If no polynomial could be found in one
// million tries, an error is returned.
func DerivePolynomial(source io.Reader) (Pol, error) {
	for i := 0; i < randPolMaxTries; i++ {
		var f Pol

		// choose polynomial at (pseudo)random
		err := binary.Read(source, binary.LittleEndian, &f)
		if err != nil {
			return 0, err
		}

		// mask away bits above bit 53
		f &= Pol((1 << 54) - 1)

		// set highest and lowest bit so that the degree is 53 and the
		// polynomial is not trivially reducible
		f |= (1 << 53) | 1

		// test if f is irreducible
		if f.Irreducible() {
			return f, nil
		}
	}

	// If this is reached, we haven't found an irreducible polynomial in
	// randPolMaxTries. This error is very unlikely to occur.
	return 0, errors.New("unable to find new random irreducible polynomial")
}

// GCD computes the Greatest Common Divisor x and f.
func (x Pol) GCD(f Pol) Pol {
	if f == 0 {
		return x
	}

	if x == 0 {
		return f
	}

	if x.Deg() < f.Deg() {
		x, f = f, x
	}

	return f.GCD(x.Mod(f))
}

// Irreducible returns true iff x is irreducible over F_2. This function
// uses Ben Or's reducibility test.
//
// For details see "Tests and Constructions of Irreducible Polynomials over
// Finite Fields".
func (x Pol) Irreducible() bool {
	for i := 1; i <= x.Deg()/2; i++ {
		if x.GCD(qp(uint(i), x)) != 1 {
			return false
		}
	}

	return true
}

// MulMod computes x*f mod g
func (x Pol) MulMod(f, g Pol) Pol {
	if x == 0 || f == 0 {
		return 0
	}

	var res Pol
	for i := 0; i <= f.Deg(); i++ {
		if (f & (1 << uint(i))) > 0 {
			a := x
			for j := 0; j < i; j++ {
				a = a.Mul(2).Mod(g)
			}
			res = res.Add(a).Mod(g)
		}
	}

	return res
}

// qp computes the polynomial (x^(2^p)-x) mod g. This is needed for the
// reducibility test.
func qp(p uint, g Pol) Pol {
	num := (1 << p)
	i := 1

	// start with x
	res := Pol(2)

	for i < num {
		// repeatedly square res
		res = res.MulMod(res, g)
		i *= 2
	}

	// add x
	return res.Add(2).Mod(g)
}

// MarshalJSON returns the JSON representation of the Pol.
func (x Pol) MarshalJSON() ([]byte, error) {
	buf := strconv.AppendUint([]byte{'"'}, uint64(x), 16)
	buf = append(buf, '"')
	return buf, nil
}

// UnmarshalJSON parses a Pol from the JSON data.
func (x *Pol) UnmarshalJSON(data []byte) error {
	if len(data) < 2 {
		return errors.New("invalid string for polynomial")
	}
	n, err := strconv.ParseUint(string(data[1:len(data)-1]), 16, 64)
	if err != nil {
		return err
	}
	*x = Pol(n)

	return nil
}
<commit_msg>Fast path for Pol.Mul(x, 2)<commit_after>package chunker

import (
	"crypto/rand"
	"encoding/binary"
	"errors"
	"fmt"
	"io"
	"strconv"
)

// Pol is a polynomial from F_2[X].
type Pol uint64

// Add returns x+y.
func (x Pol) Add(y Pol) Pol {
	r := Pol(uint64(x) ^ uint64(y))
	return r
}

// Mul returns x*y. When an overflow occurs, Mul panics.
func (x Pol) Mul(y Pol) Pol {
	switch {
	case x == 0 || y == 0:
		return 0
	case x == 1:
		return y
	case y == 1:
		return x
	case y == 2:
		return x.mul2()
	}

	var res Pol
	for i := 0; i <= y.Deg(); i++ {
		if (y & (1 << uint(i))) > 0 {
			res = res.Add(x << uint(i))
		}
	}

	if res.Div(y) != x {
		panic("multiplication would overflow uint64")
	}

	return res
}

// 2*x.
func (x Pol) mul2() Pol {
	if x&(1<<63) != 0 {
		panic("multiplication would overflow uint64")
	}
	return x << 1
}

// Deg returns the degree of the polynomial x. If x is zero, -1 is returned.
func (x Pol) Deg() int {
	// the degree of 0 is -1
	if x == 0 {
		return -1
	}

	// see https://graphics.stanford.edu/~seander/bithacks.html#IntegerLog

	r := 0
	if uint64(x)&0xffffffff00000000 > 0 {
		x >>= 32
		r |= 32
	}

	if uint64(x)&0xffff0000 > 0 {
		x >>= 16
		r |= 16
	}

	if uint64(x)&0xff00 > 0 {
		x >>= 8
		r |= 8
	}

	if uint64(x)&0xf0 > 0 {
		x >>= 4
		r |= 4
	}

	if uint64(x)&0xc > 0 {
		x >>= 2
		r |= 2
	}

	if uint64(x)&0x2 > 0 {
		r |= 1
	}

	return r
}

// String returns the coefficients in hex.
func (x Pol) String() string {
	return "0x" + strconv.FormatUint(uint64(x), 16)
}

// Expand returns the string representation of the polynomial x.
func (x Pol) Expand() string {
	if x == 0 {
		return "0"
	}

	s := ""
	for i := x.Deg(); i > 1; i-- {
		if x&(1<<uint(i)) > 0 {
			s += fmt.Sprintf("+x^%d", i)
		}
	}

	if x&2 > 0 {
		s += "+x"
	}

	if x&1 > 0 {
		s += "+1"
	}

	return s[1:]
}

// DivMod returns x / d = q, and remainder r,
// see https://en.wikipedia.org/wiki/Division_algorithm
func (x Pol) DivMod(d Pol) (Pol, Pol) {
	if x == 0 {
		return 0, 0
	}

	if d == 0 {
		panic("division by zero")
	}

	D := d.Deg()
	diff := x.Deg() - D
	if diff < 0 {
		return 0, x
	}

	var q Pol
	for diff >= 0 {
		m := d << uint(diff)
		q |= (1 << uint(diff))
		x = x.Add(m)

		diff = x.Deg() - D
	}

	return q, x
}

// Div returns the integer division result x / d.
func (x Pol) Div(d Pol) Pol {
	q, _ := x.DivMod(d)
	return q
}

// Mod returns the remainder of x / d
func (x Pol) Mod(d Pol) Pol {
	_, r := x.DivMod(d)
	return r
}

// I really dislike having a function that does not terminate, so specify a
// really large upper bound for finding a new irreducible polynomial, and
// return an error when no irreducible polynomial has been found within
// randPolMaxTries.
const randPolMaxTries = 1e6

// RandomPolynomial returns a new random irreducible polynomial
// of degree 53 using the default System CSPRNG as source.
// It is equivalent to calling DerivePolynomial(rand.Reader).
func RandomPolynomial() (Pol, error) {
	return DerivePolynomial(rand.Reader)
}

// DerivePolynomial returns an irreducible polynomial of degree 53
// (largest prime number below 64-8) by reading bytes from source.
// There are (2^53-2/53) irreducible polynomials of degree 53 in
// F_2[X], c.f. Michael O. Rabin (1981): "Fingerprinting by Random
// Polynomials", page 4. If no polynomial could be found in one
// million tries, an error is returned.
func DerivePolynomial(source io.Reader) (Pol, error) {
	for i := 0; i < randPolMaxTries; i++ {
		var f Pol

		// choose polynomial at (pseudo)random
		err := binary.Read(source, binary.LittleEndian, &f)
		if err != nil {
			return 0, err
		}

		// mask away bits above bit 53
		f &= Pol((1 << 54) - 1)

		// set highest and lowest bit so that the degree is 53 and the
		// polynomial is not trivially reducible
		f |= (1 << 53) | 1

		// test if f is irreducible
		if f.Irreducible() {
			return f, nil
		}
	}

	// If this is reached, we haven't found an irreducible polynomial in
	// randPolMaxTries. This error is very unlikely to occur.
	return 0, errors.New("unable to find new random irreducible polynomial")
}

// GCD computes the Greatest Common Divisor x and f.
func (x Pol) GCD(f Pol) Pol {
	if f == 0 {
		return x
	}

	if x == 0 {
		return f
	}

	if x.Deg() < f.Deg() {
		x, f = f, x
	}

	return f.GCD(x.Mod(f))
}

// Irreducible returns true iff x is irreducible over F_2. This function
// uses Ben Or's reducibility test.
//
// For details see "Tests and Constructions of Irreducible Polynomials over
// Finite Fields".
func (x Pol) Irreducible() bool {
	for i := 1; i <= x.Deg()/2; i++ {
		if x.GCD(qp(uint(i), x)) != 1 {
			return false
		}
	}

	return true
}

// MulMod computes x*f mod g
func (x Pol) MulMod(f, g Pol) Pol {
	if x == 0 || f == 0 {
		return 0
	}

	var res Pol
	for i := 0; i <= f.Deg(); i++ {
		if (f & (1 << uint(i))) > 0 {
			a := x
			for j := 0; j < i; j++ {
				a = a.Mul(2).Mod(g)
			}
			res = res.Add(a).Mod(g)
		}
	}

	return res
}

// qp computes the polynomial (x^(2^p)-x) mod g. This is needed for the
// reducibility test.
func qp(p uint, g Pol) Pol {
	num := (1 << p)
	i := 1

	// start with x
	res := Pol(2)

	for i < num {
		// repeatedly square res
		res = res.MulMod(res, g)
		i *= 2
	}

	// add x
	return res.Add(2).Mod(g)
}

// MarshalJSON returns the JSON representation of the Pol.
func (x Pol) MarshalJSON() ([]byte, error) {
	buf := strconv.AppendUint([]byte{'"'}, uint64(x), 16)
	buf = append(buf, '"')
	return buf, nil
}

// UnmarshalJSON parses a Pol from the JSON data.
func (x *Pol) UnmarshalJSON(data []byte) error {
	if len(data) < 2 {
		return errors.New("invalid string for polynomial")
	}
	n, err := strconv.ParseUint(string(data[1:len(data)-1]), 16, 64)
	if err != nil {
		return err
	}
	*x = Pol(n)

	return nil
}
<|endoftext|>


<commit_before>package connection

import (
	"fmt"
	"log"
	"reflect"
	"strconv"
	"time"
)

//following two structs represent cipherscan output

type CipherscanOutput struct {
	Target         string                  `json:"target"`
	IP             string                  `json:"ip"`
	Timestamp      string                  `json:"utctimestamp"`
	ServerSide     string                  `json:"serverside"`
	CurvesFallback string                  `json:"curves_fallback"`
	CipherSuites   []CipherscanCiphersuite `json:"ciphersuite"`
}

type CipherscanCiphersuite struct {
	Cipher       string   `json:"cipher"`
	Protocols    []string `json:"protocols"`
	PubKey       []string `json:"pubkey"`
	SigAlg       []string `json:"sigalg"`
	Trusted      string   `json:"trusted"`
	TicketHint   string   `json:"ticket_hint"`
	OCSPStapling string   `json:"ocsp_stapling"`
	PFS          string   `json:"pfs"`
	Curves       []string `json:"curves,omitempty"`
}

//the following structs represent the output we want to provide to DB.

type Stored struct {
	ScanTarget         string                       `json:"scanTarget"`
	ScanIP             string                       `json:"scanIP"`
	FirstSeenTimestamp string                       `json:"firstSeenTimestamp"`
	LastSeenTimestamp  string                       `json:"lastSeenTimestamp"`
	ServerSide         bool                         `json:"serverside"`
	CipherSuites       map[string]StoredCiphersuite `json:"cipherscanCiphersuite"`
	CurvesFallback     bool                         `json:"curvesFallback"`
	ObsoletedBy        string                       `json:"obsoletedBy,omitempty"`
}

type StoredCiphersuite struct {
	Cipher       string   `json:"cipher"`
	Protocols    []string `json:"protocols"`
	PubKey       float64  `json:"pubkey"`
	SigAlg       string   `json:"sigalg"`
	TicketHint   string   `json:"ticket_hint"`
	OCSPStapling bool     `json:"ocsp_stapling"`
	PFS          string   `json:"pfs"`
	Curves       []string `json:"curves,omitempty"`
}

func stringtoBool(s string) bool {
	if s == "True" {
		return true
	} else {
		return false
	}

}

func (c Stored) Equal(ci Stored) bool {

	if c.CurvesFallback != ci.CurvesFallback {
		return false
	}

	if c.ServerSide != ci.ServerSide {
		return false
	}

	for pos, suite := range c.CipherSuites {

		if !suite.equal(ci.CipherSuites[pos]) {
			return false
		}
	}

	return true
}

func (s StoredCiphersuite) equal(cs StoredCiphersuite) bool {

	if s.Cipher != cs.Cipher {
		return false
	}

	if s.OCSPStapling != cs.OCSPStapling {
		return false
	}

	if s.PFS != cs.PFS {
		return false
	}

	if s.PubKey != cs.PubKey {
		return false
	}

	if s.SigAlg != cs.SigAlg {
		return false
	}

	if !reflect.DeepEqual(s.Curves, cs.Curves) {
		return false
	}

	if !reflect.DeepEqual(s.Protocols, cs.Protocols) {
		return false
	}

	return true

}

func (s CipherscanOutput) convertTimestamp(t string) (time.Time, error) {

	layout := "2006-01-02T15:04:05.0Z"
	return time.Parse(layout, t)
}

func (s CipherscanOutput) Stored() (Stored, error) {

	c := Stored{}

	var err error

	t, err := s.convertTimestamp(s.Timestamp)

	if err != nil {
		return c, err
	}

	timestamp := fmt.Sprintf("%d-%02d-%02dT%02d:%02d:%02d", t.UTC().Year(), t.UTC().Month(), t.UTC().Day(), t.UTC().Hour(), t.UTC().Minute(), t.UTC().Second())

	c.FirstSeenTimestamp = timestamp
	c.LastSeenTimestamp = timestamp
	c.ServerSide = stringtoBool(s.ServerSide)
	c.CurvesFallback = stringtoBool(s.CurvesFallback)
	c.ScanTarget = s.Target
	c.ScanIP = s.IP

	c.CipherSuites = make(map[string]StoredCiphersuite)

	pos := 1

	for _, cipher := range s.CipherSuites {

		newcipher := StoredCiphersuite{}

		newcipher.Cipher = cipher.Cipher
		newcipher.OCSPStapling = stringtoBool(cipher.OCSPStapling)
		newcipher.PFS = cipher.PFS

		newcipher.Protocols = cipher.Protocols

		if len(cipher.PubKey) > 1 {
			log.Println("Multiple PubKeys for ", s.Target, " at cipher :", cipher.Cipher)
		}

		if len(cipher.PubKey) > 0 {
			newcipher.PubKey, err = strconv.ParseFloat(cipher.PubKey[0], 64)
		} else {
			return c, fmt.Errorf("No Public Keys found")
		}

		if len(cipher.SigAlg) > 1 {
			log.Println("Multiple SigAlgs for ", s.Target, " at cipher :", cipher.Cipher)
		}

		if len(cipher.SigAlg) > 0 {
			newcipher.SigAlg = cipher.SigAlg[0]
		} else {
			return c, fmt.Errorf("No Signature Algorithms found")
		}

		newcipher.TicketHint = cipher.TicketHint

		if err != nil {
			return c, err
		}

		newcipher.Curves = append(newcipher.Curves, cipher.Curves...)

		c.CipherSuites[strconv.Itoa(pos)] = newcipher
		pos++
	}

	return c, nil
}
<commit_msg>removed target from connection info as it is stored in scan table<commit_after>package connection

import (
	"fmt"
	"log"
	"reflect"
	"strconv"
	"time"
)

//following two structs represent cipherscan output

type CipherscanOutput struct {
	Target         string                  `json:"target"`
	IP             string                  `json:"ip"`
	Timestamp      string                  `json:"utctimestamp"`
	ServerSide     string                  `json:"serverside"`
	CurvesFallback string                  `json:"curves_fallback"`
	CipherSuites   []CipherscanCiphersuite `json:"ciphersuite"`
}

type CipherscanCiphersuite struct {
	Cipher       string   `json:"cipher"`
	Protocols    []string `json:"protocols"`
	PubKey       []string `json:"pubkey"`
	SigAlg       []string `json:"sigalg"`
	Trusted      string   `json:"trusted"`
	TicketHint   string   `json:"ticket_hint"`
	OCSPStapling string   `json:"ocsp_stapling"`
	PFS          string   `json:"pfs"`
	Curves       []string `json:"curves,omitempty"`
}

//the following structs represent the output we want to provide to DB.

type Stored struct {
	ScanIP             string                       `json:"scanIP"`
	FirstSeenTimestamp string                       `json:"firstSeenTimestamp"`
	LastSeenTimestamp  string                       `json:"lastSeenTimestamp"`
	ServerSide         bool                         `json:"serverside"`
	CipherSuites       map[string]StoredCiphersuite `json:"cipherscanCiphersuite"`
	CurvesFallback     bool                         `json:"curvesFallback"`
	ObsoletedBy        string                       `json:"obsoletedBy,omitempty"`
}

type StoredCiphersuite struct {
	Cipher       string   `json:"cipher"`
	Protocols    []string `json:"protocols"`
	PubKey       float64  `json:"pubkey"`
	SigAlg       string   `json:"sigalg"`
	TicketHint   string   `json:"ticket_hint"`
	OCSPStapling bool     `json:"ocsp_stapling"`
	PFS          string   `json:"pfs"`
	Curves       []string `json:"curves,omitempty"`
}

func stringtoBool(s string) bool {
	if s == "True" {
		return true
	} else {
		return false
	}

}

func (c Stored) Equal(ci Stored) bool {

	if c.CurvesFallback != ci.CurvesFallback {
		return false
	}

	if c.ServerSide != ci.ServerSide {
		return false
	}

	for pos, suite := range c.CipherSuites {

		if !suite.equal(ci.CipherSuites[pos]) {
			return false
		}
	}

	return true
}

func (s StoredCiphersuite) equal(cs StoredCiphersuite) bool {

	if s.Cipher != cs.Cipher {
		return false
	}

	if s.OCSPStapling != cs.OCSPStapling {
		return false
	}

	if s.PFS != cs.PFS {
		return false
	}

	if s.PubKey != cs.PubKey {
		return false
	}

	if s.SigAlg != cs.SigAlg {
		return false
	}

	if !reflect.DeepEqual(s.Curves, cs.Curves) {
		return false
	}

	if !reflect.DeepEqual(s.Protocols, cs.Protocols) {
		return false
	}

	return true

}

func (s CipherscanOutput) convertTimestamp(t string) (time.Time, error) {

	layout := "2006-01-02T15:04:05.0Z"
	return time.Parse(layout, t)
}

func (s CipherscanOutput) Stored() (Stored, error) {

	c := Stored{}

	var err error

	t, err := s.convertTimestamp(s.Timestamp)

	if err != nil {
		return c, err
	}

	timestamp := fmt.Sprintf("%d-%02d-%02dT%02d:%02d:%02d", t.UTC().Year(), t.UTC().Month(), t.UTC().Day(), t.UTC().Hour(), t.UTC().Minute(), t.UTC().Second())

	c.FirstSeenTimestamp = timestamp
	c.LastSeenTimestamp = timestamp
	c.ServerSide = stringtoBool(s.ServerSide)
	c.CurvesFallback = stringtoBool(s.CurvesFallback)
	c.ScanIP = s.IP

	c.CipherSuites = make(map[string]StoredCiphersuite)

	pos := 1

	for _, cipher := range s.CipherSuites {

		newcipher := StoredCiphersuite{}

		newcipher.Cipher = cipher.Cipher
		newcipher.OCSPStapling = stringtoBool(cipher.OCSPStapling)
		newcipher.PFS = cipher.PFS

		newcipher.Protocols = cipher.Protocols

		if len(cipher.PubKey) > 1 {
			log.Println("Multiple PubKeys for ", s.Target, " at cipher :", cipher.Cipher)
		}

		if len(cipher.PubKey) > 0 {
			newcipher.PubKey, err = strconv.ParseFloat(cipher.PubKey[0], 64)
		} else {
			return c, fmt.Errorf("No Public Keys found")
		}

		if len(cipher.SigAlg) > 1 {
			log.Println("Multiple SigAlgs for ", s.Target, " at cipher :", cipher.Cipher)
		}

		if len(cipher.SigAlg) > 0 {
			newcipher.SigAlg = cipher.SigAlg[0]
		} else {
			return c, fmt.Errorf("No Signature Algorithms found")
		}

		newcipher.TicketHint = cipher.TicketHint

		if err != nil {
			return c, err
		}

		newcipher.Curves = append(newcipher.Curves, cipher.Curves...)

		c.CipherSuites[strconv.Itoa(pos)] = newcipher
		pos++
	}

	return c, nil
}
<|endoftext|>


<commit_before>// Copyright 2016 Ross Light
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
	"net/http"
	"unicode"

	"golang.org/x/text/language"
	textsearch "golang.org/x/text/search"

	"zombiezen.com/go/sandpass/pkg/keepass"
)

func handleSearch(w http.ResponseWriter, r *http.Request) error {
	mu.Lock()
	defer mu.Unlock()
	db, err := sessions.dbFromRequest(w, r)
	if err != nil {
		return err
	}
	var data struct {
		Query   string
		Results []*keepass.Entry
	}
	data.Query = r.FormValue("q")
	pq := parseQuery(data.Query)
	if pq == nil {
		data.Query = ""
	} else {
		data.Results = search(db, pq)
	}
	return tmpl.ExecuteTemplate(w, "search.html", data)
}

func search(db *keepass.Database, q *parsedQuery) []*keepass.Entry {
	var results []*keepass.Entry
	for _, e := range db.Entries() {
		if q.matchesText(e.Title) {
			results = append(results, e)
		}
	}
	return results
}

type parsedQuery struct {
	pats []*textsearch.Pattern
}

func parseQuery(query string) *parsedQuery {
	if len(query) == 0 {
		return nil
	}
	var words []string
	start := -1
	for i, r := range query {
		space := unicode.IsSpace(r)
		if space && start != -1 {
			words = append(words, query[start:i])
			start = -1
		} else if !space && start == -1 {
			start = i
		}
	}
	if start != -1 {
		words = append(words, query[start:])
	}
	if len(words) == 0 {
		return nil
	}
	m := textsearch.New(language.Und, textsearch.Loose)
	if len(words) == 1 {
		return &parsedQuery{pats: []*textsearch.Pattern{m.CompileString(query)}}
	}
	pq := &parsedQuery{pats: make([]*textsearch.Pattern, len(words))}
	for i := range words {
		pq.pats[i] = m.CompileString(words[i])
	}
	return pq
}

func (pq *parsedQuery) matchesText(s string) bool {
	if pq == nil || len(pq.pats) == 0 {
		return false
	}
	for _, pat := range pq.pats {
		if start, _ := pat.IndexString(s); start == -1 {
			return false
		}
	}
	return true
}
<commit_msg>Search over notes<commit_after>// Copyright 2016 Ross Light
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
	"net/http"
	"unicode"

	"golang.org/x/text/language"
	textsearch "golang.org/x/text/search"

	"zombiezen.com/go/sandpass/pkg/keepass"
)

func handleSearch(w http.ResponseWriter, r *http.Request) error {
	mu.Lock()
	defer mu.Unlock()
	db, err := sessions.dbFromRequest(w, r)
	if err != nil {
		return err
	}
	var data struct {
		Query   string
		Results []*keepass.Entry
	}
	data.Query = r.FormValue("q")
	pq := parseQuery(data.Query)
	if pq == nil {
		data.Query = ""
	} else {
		data.Results = search(db, pq)
	}
	return tmpl.ExecuteTemplate(w, "search.html", data)
}

func search(db *keepass.Database, q *parsedQuery) []*keepass.Entry {
	var results []*keepass.Entry
	for _, e := range db.Entries() {
		if q.matchesText(e.Title) || q.matchesText(e.Notes) {
			results = append(results, e)
		}
	}
	return results
}

type parsedQuery struct {
	pats []*textsearch.Pattern
}

func parseQuery(query string) *parsedQuery {
	if len(query) == 0 {
		return nil
	}
	var words []string
	start := -1
	for i, r := range query {
		space := unicode.IsSpace(r)
		if space && start != -1 {
			words = append(words, query[start:i])
			start = -1
		} else if !space && start == -1 {
			start = i
		}
	}
	if start != -1 {
		words = append(words, query[start:])
	}
	if len(words) == 0 {
		return nil
	}
	m := textsearch.New(language.Und, textsearch.Loose)
	if len(words) == 1 {
		return &parsedQuery{pats: []*textsearch.Pattern{m.CompileString(query)}}
	}
	pq := &parsedQuery{pats: make([]*textsearch.Pattern, len(words))}
	for i := range words {
		pq.pats[i] = m.CompileString(words[i])
	}
	return pq
}

func (pq *parsedQuery) matchesText(s string) bool {
	if pq == nil || len(pq.pats) == 0 {
		return false
	}
	for _, pat := range pq.pats {
		if start, _ := pat.IndexString(s); start == -1 {
			return false
		}
	}
	return true
}
<|endoftext|>


<commit_before>package goinsta

type Search struct {
	inst *Instagram
}

type SearchResult struct {
	HasMore    bool   `json:"has_more"`
	Status     string `json:"status"`
	NumResults int    `json:"num_results"`
	RankToken  string `json:"rank_token"`

	// User search results
	Users []User `json:"users"`

	// Tag search results
	Tags []struct {
		Name       string `json:"name"`
		MediaCount int    `json:"media_count"`
		ID         int64  `json:"id"`
	} `json:"results"`

	// Location search result
	RequestID string `json:"request_id"`
	Venues    []struct {
		ExternalIDSource string  `json:"external_id_source"`
		ExternalID       string  `json:"external_id"`
		Lat              float64 `json:"lat"`
		Lng              float64 `json:"lng"`
		Address          string  `json:"address"`
		Name             string  `json:"name"`
	} `json:"venues"`

	// Facebook
	// TODO
}

// newSearch creates new Search structure
func newSearch(inst *Instagram) *Search {
	search := &Search{
		inst: inst,
	}
	return search
}

// User search by username
func (search *Search) User(user string) (*SearchResult, error) {
	insta := search.inst
	body, err := insta.sendRequest(
		&reqOptions{
			Endpoint: urlSearchUser,
			Query: map[string]string{
				"ig_sig_key_version": goInstaSigKeyVersion,
				"is_typeahead":       "true",
				"query":              user,
				"rank_token":         search.insta.rankToken,
			},
		},
	)
	if err != nil {
		return nil, err
	}

	res := &SearchResult{}
	err = json.Unmarshal(body, res)
	return res, err
}

// Tags search by tag
func (search *Search) Tags(tag string) (*SearchResult, error) {
	insta := search.inst
	body, err := insta.sendRequest(
		&reqOptions{
			Endpoint: urlSearchTag,
			Query: map[string]string{
				"is_typeahead": "true",
				"rank_token":   search.insta.rankToken,
				"q":            tag,
			},
		},
	)
	if err != nil {
		return nil, err
	}

	res := &SearchResult{}
	err = json.Unmarshal(body, res)
	return res, err
}

// Location search by location.
//
// Lat and Lng (Latitude & Longitude) cannot be ""
func (search *Search) Location(lat, lng, search string) (*SearchResult, error) {
	insta := search.inst
	q := map[string]string{
		"rank_token":     search.inst.rankToken,
		"latitude":       lat,
		"longitude":      lng,
		"ranked_content": "true",
	}

	if search != "" {
		q["search_query"] = search
	} else {
		q["timestamp"] = strconv.FormatInt(time.Now().Unix(), 10)
	}

	body, err := insta.sendRequest(
		&reqOptions{
			Endpoint: urlSearchLocation,
			Query:    q,
		},
	)
	if err != nil {
		return nil, err
	}

	res := &SearchResult{}
	err = json.Unmarshal(body, res)
	return res, err
}

func (search *Search) Facebook(user string) (*SearchResult, error) {
	// TODO
	return nil, nil
}
<commit_msg>Fixed more errors<commit_after>package goinsta

import (
	"encoding/json"
	"fmt"
)

type Search struct {
	inst *Instagram
}

type SearchResult struct {
	HasMore    bool   `json:"has_more"`
	Status     string `json:"status"`
	NumResults int    `json:"num_results"`
	RankToken  string `json:"rank_token"`

	// User search results
	Users []User `json:"users"`

	// Tag search results
	Tags []struct {
		Name       string `json:"name"`
		MediaCount int    `json:"media_count"`
		ID         int64  `json:"id"`
	} `json:"results"`

	// Location search result
	RequestID string `json:"request_id"`
	Venues    []struct {
		ExternalIDSource string  `json:"external_id_source"`
		ExternalID       string  `json:"external_id"`
		Lat              float64 `json:"lat"`
		Lng              float64 `json:"lng"`
		Address          string  `json:"address"`
		Name             string  `json:"name"`
	} `json:"venues"`

	// Facebook
	// TODO
}

// newSearch creates new Search structure
func newSearch(inst *Instagram) *Search {
	search := &Search{
		inst: inst,
	}
	return search
}

// User search by username
func (search *Search) User(user string) (*SearchResult, error) {
	insta := search.inst
	body, err := insta.sendRequest(
		&reqOptions{
			Endpoint: urlSearchUser,
			Query: map[string]string{
				"ig_sig_key_version": goInstaSigKeyVersion,
				"is_typeahead":       "true",
				"query":              user,
				"rank_token":         insta.rankToken,
			},
		},
	)
	if err != nil {
		return nil, err
	}

	res := &SearchResult{}
	err = json.Unmarshal(body, res)
	return res, err
}

// Tags search by tag
func (search *Search) Tags(tag string) (*SearchResult, error) {
	insta := search.inst
	body, err := insta.sendRequest(
		&reqOptions{
			Endpoint: urlSearchTag,
			Query: map[string]string{
				"is_typeahead": "true",
				"rank_token":   insta.rankToken,
				"q":            tag,
			},
		},
	)
	if err != nil {
		return nil, err
	}

	res := &SearchResult{}
	err = json.Unmarshal(body, res)
	return res, err
}

// Location search by location.
//
// Lat and Lng (Latitude & Longitude) cannot be ""
func (search *Search) Location(lat, lng, location string) (*SearchResult, error) {
	insta := search.inst
	q := map[string]string{
		"rank_token":     insta.rankToken,
		"latitude":       lat,
		"longitude":      lng,
		"ranked_content": "true",
	}

	if location != "" {
		q["search_query"] = location
	} else {
		q["timestamp"] = strconv.FormatInt(time.Now().Unix(), 10)
	}

	body, err := insta.sendRequest(
		&reqOptions{
			Endpoint: urlSearchLocation,
			Query:    q,
		},
	)
	if err != nil {
		return nil, err
	}

	res := &SearchResult{}
	err = json.Unmarshal(body, res)
	return res, err
}

func (search *Search) Facebook(user string) (*SearchResult, error) {
	// TODO
	return nil, nil
}
<|endoftext|>


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

package main

import (
	"flag"
	"fmt"
	"os"

	"github.com/globocom/tsuru/db/storage"
)

var (
	databaseAddr string
	databaseName string
	baseDir      string
	readHttp     string
	writeHttp    string
)

func init() {
	flag.StringVar(&databaseAddr, "mongodb", "127.0.0.1:27017", "Address of the database server")
	flag.StringVar(&databaseName, "dbname", "archives", "Name of the database to store information about archives")
	flag.StringVar(&baseDir, "dir", "/var/lib/archives/", "Base directory, where the server will create and serve the archives")
	flag.StringVar(&readHttp, "read-http", "", "Address to bind the API that serves archives. Omit to not start this API.")
	flag.StringVar(&writeHttp, "write-http", "", "Address to bind the API that creates archives. Omit to not start this API.")
}

func conn() (*storage.Storage, error) {
	return storage.Open(databaseAddr, databaseName)
}

func main() {
	flag.Parse()
	if readHttp == "" && writeHttp == "" {
		fmt.Println("You need to specify at-least one of -read-http and -write-http")
		os.Exit(1)
	}
}
<commit_msg>updated tsuru github repository.<commit_after>// Copyright 2014 Globo.com. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
	"flag"
	"fmt"
	"os"

	"github.com/tsuru/tsuru/db/storage"
)

var (
	databaseAddr string
	databaseName string
	baseDir      string
	readHttp     string
	writeHttp    string
)

func init() {
	flag.StringVar(&databaseAddr, "mongodb", "127.0.0.1:27017", "Address of the database server")
	flag.StringVar(&databaseName, "dbname", "archives", "Name of the database to store information about archives")
	flag.StringVar(&baseDir, "dir", "/var/lib/archives/", "Base directory, where the server will create and serve the archives")
	flag.StringVar(&readHttp, "read-http", "", "Address to bind the API that serves archives. Omit to not start this API.")
	flag.StringVar(&writeHttp, "write-http", "", "Address to bind the API that creates archives. Omit to not start this API.")
}

func conn() (*storage.Storage, error) {
	return storage.Open(databaseAddr, databaseName)
}

func main() {
	flag.Parse()
	if readHttp == "" && writeHttp == "" {
		fmt.Println("You need to specify at-least one of -read-http and -write-http")
		os.Exit(1)
	}
}
<|endoftext|>


<commit_before>package dnsforwarder

import (
	"errors"
	"github.com/d2g/dnsforwarder/cache"
	"github.com/d2g/dnsforwarder/hosts"
	"github.com/miekg/dns"
	"log"
	"net"
	"strings"
	"time"
)

type Server struct {
	Configuration *Configuration
	Cache         cache.Cache
	Hosts         hosts.Hosts
	Hijacker
}

// The Hijacker must write the message to the writer.
/*
 * Return: Bool (Was it hijacked?) if True then the client should have written to the DNS Response Writer
 * Return: Any Errors?
 */
type Hijacker func(dns.ResponseWriter, *dns.Msg) (bool, error)

func (this *Server) ListenAndServeTCP(address net.TCPAddr) error {

	tcpHandler := dns.NewServeMux()
	tcpHandler.HandleFunc(".", this.TCPRequest)

	tcpServer := &dns.Server{Addr: address.String(),
		Net:          "tcp",
		Handler:      tcpHandler,
		ReadTimeout:  (time.Duration(this.Configuration.ReadTimeout) * time.Second),
		WriteTimeout: (time.Duration(this.Configuration.WriteTimeout) * time.Second)}

	err := tcpServer.ListenAndServe()
	if err != nil {
		log.Fatal("TCP DNS Server Failed, Error:" + err.Error())
		return err
	}

	return nil
}

func (this *Server) ListenAndServeUDP(address net.UDPAddr) error {

	udpHandler := dns.NewServeMux()
	udpHandler.HandleFunc(".", this.UDPRequest)

	udpServer := &dns.Server{Addr: address.String(),
		Net:          "udp",
		Handler:      udpHandler,
		UDPSize:      65535,
		ReadTimeout:  (time.Duration(this.Configuration.ReadTimeout) * time.Second),
		WriteTimeout: (time.Duration(this.Configuration.WriteTimeout) * time.Second)}

	err := udpServer.ListenAndServe()
	if err != nil {
		log.Println("UDP DNS Server Failed, Error:" + err.Error())
		return err
	}

	return nil
}

func (this *Server) TCPRequest(response dns.ResponseWriter, message *dns.Msg) {
	this.Request("tcp", response, message)
}

func (this *Server) UDPRequest(response dns.ResponseWriter, message *dns.Msg) {
	this.Request("udp", response, message)
}

func (this *Server) Request(method string, response dns.ResponseWriter, message *dns.Msg) {
	//Hijack Request If Needed.
	if this.Hijacker != nil {
		wasHijacked, err := this.Hijacker(response, message)
		if err != nil {
			log.Printf("DNS Hijack Error:%v\n", err)
		}
		if wasHijacked {
			return
		}
	}

	//Lets check against the cache
	if this.Cache != nil {
		hitCache, cacheMessage, err := this.Cache.Get(message)
		if err == nil {
			//No Error Getting Cache
			if hitCache {
				cacheMessage.Id = message.Id
				err := response.WriteMsg(cacheMessage)
				if err != nil {
					log.Printf("Error Writing Cached Response:%v\n", err)
				}
				return
			}
		} else {
			log.Println("Warning Cache Error:" + err.Error())
		}
	}

	//Check Against Local Host File
	if this.Hosts != nil {
		isLocal, ip, err := this.Hosts.Get(strings.TrimSuffix(message.Question[0].Name, "."))

		if err != nil {
			log.Println("Error Looking At Local DNS:" + err.Error())

		} else {
			if isLocal {
				//Return the local device detail
				localResponse := new(dns.Msg)
				localResponse.SetReply(message)
				rr_header := dns.RR_Header{Name: message.Question[0].Name, Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: this.Configuration.TTL}

				a := &dns.A{rr_header, ip}
				localResponse.Answer = append(localResponse.Answer, a)
				err := response.WriteMsg(localResponse)
				if err != nil {
					log.Printf("Error Writing Local Response:%v\n", err)
				}

				return
			}
		}
	}

	//So we didn't find the DNS locally.
	result, err := this.remoteDNSLookup(method, message)
	if err != nil {
		dns.HandleFailed(response, message)
		return
	}

	err = response.WriteMsg(result)
	if err != nil {
		log.Printf("Error Writing Response:%v\n", err)
	}

	//Cache Response :D
	if this.Cache != nil {
		err = this.Cache.Add(result)
		if err != nil {
			log.Printf("Error Adding Response To Cache:%v\n", err)
		}
	}
}

func (this *Server) remoteDNSLookup(protocol string, request *dns.Msg) (*dns.Msg, error) {
	dnsClient := &dns.Client{
		Net:          protocol,
		ReadTimeout:  (time.Duration(this.Configuration.ReadTimeout) * time.Second),
		WriteTimeout: (time.Duration(this.Configuration.ReadTimeout) * time.Second),
	}

	var err error = nil

	for _, nameserver := range this.Configuration.NameServers {
		//result, returnTime, err := dnsClient.Exchange(request, nameserver)
		result, _, err := dnsClient.Exchange(request, nameserver.String())

		if err != nil {
			log.Printf("DNS Error:" + err.Error())
			continue
		}

		if result != nil && result.Rcode != dns.RcodeSuccess {
			//log.Printf("%s failed to get an valid answer on %s\n", request.Question[0].Name, nameserver)
			continue
		}

		//if dns.IsFqdn(request.Question[0].Name) {
		//	log.Printf("%s resolv on %s ttl: %d\n", request.Question[0].Name[:len(request.Question[0].Name)-1], nameserver, returnTime)
		//} else {
		//	log.Printf("%s resolv on %s ttl: %d\n", request.Question[0].Name, nameserver, returnTime)
		//}

		return result, nil
	}

	if err != nil {
		return nil, err
	} else {
		return nil, errors.New("Unknown DNS Error")
	}
}
<commit_msg>Updated Cache Error Message To Be More Meaningfull.<commit_after>package dnsforwarder

import (
	"errors"
	"github.com/d2g/dnsforwarder/cache"
	"github.com/d2g/dnsforwarder/hosts"
	"github.com/miekg/dns"
	"log"
	"net"
	"strings"
	"time"
)

type Server struct {
	Configuration *Configuration
	Cache         cache.Cache
	Hosts         hosts.Hosts
	Hijacker
}

// The Hijacker must write the message to the writer.
/*
 * Return: Bool (Was it hijacked?) if True then the client should have written to the DNS Response Writer
 * Return: Any Errors?
 */
type Hijacker func(dns.ResponseWriter, *dns.Msg) (bool, error)

func (this *Server) ListenAndServeTCP(address net.TCPAddr) error {

	tcpHandler := dns.NewServeMux()
	tcpHandler.HandleFunc(".", this.TCPRequest)

	tcpServer := &dns.Server{Addr: address.String(),
		Net:          "tcp",
		Handler:      tcpHandler,
		ReadTimeout:  (time.Duration(this.Configuration.ReadTimeout) * time.Second),
		WriteTimeout: (time.Duration(this.Configuration.WriteTimeout) * time.Second)}

	err := tcpServer.ListenAndServe()
	if err != nil {
		log.Fatal("TCP DNS Server Failed, Error:" + err.Error())
		return err
	}

	return nil
}

func (this *Server) ListenAndServeUDP(address net.UDPAddr) error {

	udpHandler := dns.NewServeMux()
	udpHandler.HandleFunc(".", this.UDPRequest)

	udpServer := &dns.Server{Addr: address.String(),
		Net:          "udp",
		Handler:      udpHandler,
		UDPSize:      65535,
		ReadTimeout:  (time.Duration(this.Configuration.ReadTimeout) * time.Second),
		WriteTimeout: (time.Duration(this.Configuration.WriteTimeout) * time.Second)}

	err := udpServer.ListenAndServe()
	if err != nil {
		log.Println("UDP DNS Server Failed, Error:" + err.Error())
		return err
	}

	return nil
}

func (this *Server) TCPRequest(response dns.ResponseWriter, message *dns.Msg) {
	this.Request("tcp", response, message)
}

func (this *Server) UDPRequest(response dns.ResponseWriter, message *dns.Msg) {
	this.Request("udp", response, message)
}

func (this *Server) Request(method string, response dns.ResponseWriter, message *dns.Msg) {
	//Hijack Request If Needed.
	if this.Hijacker != nil {
		wasHijacked, err := this.Hijacker(response, message)
		if err != nil {
			log.Printf("DNS Hijack Error:%v\n", err)
		}
		if wasHijacked {
			return
		}
	}

	//Lets check against the cache
	if this.Cache != nil {
		hitCache, cacheMessage, err := this.Cache.Get(message)
		if err == nil {
			//No Error Getting Cache
			if hitCache {
				cacheMessage.Id = message.Id
				err := response.WriteMsg(cacheMessage)
				if err != nil {
					log.Printf("Error Writing Cached Response:%v\n", err)
				}
				return
			}
		} else {
			log.Println("Warning: Cache Error \"" + err.Error() + "\" On " + strings.TrimSuffix(message.Question[0].Name, "."))
		}
	}

	//Check Against Local Host File
	if this.Hosts != nil {
		isLocal, ip, err := this.Hosts.Get(strings.TrimSuffix(message.Question[0].Name, "."))

		if err != nil {
			log.Println("Error Looking At Local DNS:" + err.Error())

		} else {
			if isLocal {
				//Return the local device detail
				localResponse := new(dns.Msg)
				localResponse.SetReply(message)
				rr_header := dns.RR_Header{Name: message.Question[0].Name, Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: this.Configuration.TTL}

				a := &dns.A{rr_header, ip}
				localResponse.Answer = append(localResponse.Answer, a)
				err := response.WriteMsg(localResponse)
				if err != nil {
					log.Printf("Error Writing Local Response:%v\n", err)
				}

				return
			}
		}
	}

	//So we didn't find the DNS locally.
	result, err := this.remoteDNSLookup(method, message)
	if err != nil {
		dns.HandleFailed(response, message)
		return
	}

	err = response.WriteMsg(result)
	if err != nil {
		log.Printf("Error Writing Response:%v\n", err)
	}

	//Cache Response :D
	if this.Cache != nil {
		err = this.Cache.Add(result)
		if err != nil {
			log.Printf("Error Adding Response To Cache:%v\n", err)
		}
	}
}

func (this *Server) remoteDNSLookup(protocol string, request *dns.Msg) (*dns.Msg, error) {
	dnsClient := &dns.Client{
		Net:          protocol,
		ReadTimeout:  (time.Duration(this.Configuration.ReadTimeout) * time.Second),
		WriteTimeout: (time.Duration(this.Configuration.ReadTimeout) * time.Second),
	}

	var err error = nil

	for _, nameserver := range this.Configuration.NameServers {
		//result, returnTime, err := dnsClient.Exchange(request, nameserver)
		result, _, err := dnsClient.Exchange(request, nameserver.String())

		if err != nil {
			log.Printf("DNS Error:" + err.Error())
			continue
		}

		if result != nil && result.Rcode != dns.RcodeSuccess {
			//log.Printf("%s failed to get an valid answer on %s\n", request.Question[0].Name, nameserver)
			continue
		}

		//if dns.IsFqdn(request.Question[0].Name) {
		//	log.Printf("%s resolv on %s ttl: %d\n", request.Question[0].Name[:len(request.Question[0].Name)-1], nameserver, returnTime)
		//} else {
		//	log.Printf("%s resolv on %s ttl: %d\n", request.Question[0].Name, nameserver, returnTime)
		//}

		return result, nil
	}

	if err != nil {
		return nil, err
	} else {
		return nil, errors.New("Unknown DNS Error")
	}
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"github.com/julienschmidt/httprouter"
	"gopkg.in/h2non/bimg.v0"
	"net/http"
	"strconv"
	"strings"
	"time"
)

type ServerOptions struct {
	Port             int
	Burst            int
	Concurrency      int
	HttpReadTimeout  int
	HttpWriteTimeout int
	CORS             bool
	Gzip             bool
	Address          string
	ApiKey           string
	CertFile         string
	KeyFile          string
	Placeholder      []byte
}

func Server(o ServerOptions) error {
	addr := o.Address + ":" + strconv.Itoa(o.Port)
	handler := NewServerMux(o)

	server := &http.Server{
		Addr:           addr,
		Handler:        handler,
		MaxHeaderBytes: 1 << 20,
		ReadTimeout:    time.Duration(o.HttpReadTimeout) * time.Second,
		WriteTimeout:   time.Duration(o.HttpWriteTimeout) * time.Second,
	}

	return listenAndServe(server, o)
}

func listenAndServe(s *http.Server, o ServerOptions) error {
	if o.CertFile != "" && o.KeyFile != "" {
		return s.ListenAndServeTLS(o.CertFile, o.KeyFile)
	}
	return s.ListenAndServe()
}

func NewServerMux(o ServerOptions) http.Handler {
	mux := httprouter.New()
	mux.GET("/", indexController)
	mux.GET("/:operation/:size/*url", resizeController(o))
	return mux
}

func resizeController(o ServerOptions) func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
	return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
		if r.Method != "GET" {
			badRequest(w, "method not allowed")
			return
		}

		width, height, err := parseDimensions(ps.ByName("size"))
		if err != nil {
			badRequest(w, "invalid width or height path expression")
			return
		}

		debug("resize to %dx%d", width, height)
		opts := Options{Width: width, Height: height}

		image, err := Fetch(ps.ByName("url")[1:])
		if err != nil {
			failed(w, opts, o, err.Error())
			return
		}

		image, err = Resize(image, opts)
		if err != nil {
			failed(w, opts, o, err.Error())
			return
		}

		mime := GetImageMimeType(bimg.DetermineImageType(image))
		w.Header().Set("Content-Type", mime)
		w.Write(image)
	}
}

func indexController(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
	if r.URL.Path != "/" {
		http.NotFound(w, r)
		return
	}

	body, _ := json.Marshal(CurrentVersions)
	w.Header().Set("Content-Type", "application/json")
	w.Write(body)
}

func parseDimensions(value string) (int, int, error) {
	size := strings.Split(value, "x")
	width, err := strconv.Atoi(size[0])
	if err != nil {
		return 0, 0, err
	}
	height, err := strconv.Atoi(size[1])
	return width, height, err
}

func failed(w http.ResponseWriter, opts Options, o ServerOptions, msg string) {
	image := placeholder
	if len(o.Placeholder) > 1 {
		image = o.Placeholder
	}

	opts.Force = true
	image, err := Resize(image, opts)
	if err != nil {
		badRequest(w, err.Error())
		return
	}

	w.Header().Set("Content-Type", GetImageMimeType(bimg.DetermineImageType(image)))
	w.Header().Set("Error", msg)
	w.WriteHeader(http.StatusBadRequest)
	w.Write(image)
}

func badRequest(w http.ResponseWriter, msg string) {
	w.Header().Set("Content-Type", "image/jpeg")
	w.Header().Set("Error", msg)
	w.WriteHeader(http.StatusBadRequest)
	w.Write(placeholder)
}
<commit_msg>fix(resize): pass operation<commit_after>package main

import (
	"encoding/json"
	"github.com/julienschmidt/httprouter"
	"gopkg.in/h2non/bimg.v0"
	"net/http"
	"strconv"
	"strings"
	"time"
)

type ServerOptions struct {
	Port             int
	Burst            int
	Concurrency      int
	HttpReadTimeout  int
	HttpWriteTimeout int
	CORS             bool
	Gzip             bool
	Address          string
	ApiKey           string
	CertFile         string
	KeyFile          string
	Placeholder      []byte
}

func Server(o ServerOptions) error {
	addr := o.Address + ":" + strconv.Itoa(o.Port)
	handler := NewServerMux(o)

	server := &http.Server{
		Addr:           addr,
		Handler:        handler,
		MaxHeaderBytes: 1 << 20,
		ReadTimeout:    time.Duration(o.HttpReadTimeout) * time.Second,
		WriteTimeout:   time.Duration(o.HttpWriteTimeout) * time.Second,
	}

	return listenAndServe(server, o)
}

func listenAndServe(s *http.Server, o ServerOptions) error {
	if o.CertFile != "" && o.KeyFile != "" {
		return s.ListenAndServeTLS(o.CertFile, o.KeyFile)
	}
	return s.ListenAndServe()
}

func NewServerMux(o ServerOptions) http.Handler {
	mux := httprouter.New()
	mux.GET("/", indexController)
	mux.GET("/:operation/:size/*url", resizeController(o))
	return mux
}

func resizeController(o ServerOptions) func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
	return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
		if r.Method != "GET" {
			badRequest(w, "method not allowed")
			return
		}

		width, height, err := parseDimensions(ps.ByName("size"))
		if err != nil {
			badRequest(w, "invalid width or height path expression")
			return
		}

		debug("resize to %dx%d", width, height)
		opts := Options{Width: width, Height: height, Operation: ps.ByName("operation")}

		image, err := Fetch(ps.ByName("url")[1:])
		if err != nil {
			failed(w, opts, o, err.Error())
			return
		}

		image, err = Resize(image, opts)
		if err != nil {
			failed(w, opts, o, err.Error())
			return
		}

		mime := GetImageMimeType(bimg.DetermineImageType(image))
		w.Header().Set("Content-Type", mime)
		w.Write(image)
	}
}

func indexController(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
	if r.URL.Path != "/" {
		http.NotFound(w, r)
		return
	}

	body, _ := json.Marshal(CurrentVersions)
	w.Header().Set("Content-Type", "application/json")
	w.Write(body)
}

func parseDimensions(value string) (int, int, error) {
	size := strings.Split(value, "x")
	width, err := strconv.Atoi(size[0])
	if err != nil {
		return 0, 0, err
	}
	height, err := strconv.Atoi(size[1])
	return width, height, err
}

func failed(w http.ResponseWriter, opts Options, o ServerOptions, msg string) {
	image := placeholder
	if len(o.Placeholder) > 1 {
		image = o.Placeholder
	}

	opts.Force = true
	image, err := Resize(image, opts)
	if err != nil {
		badRequest(w, err.Error())
		return
	}

	w.Header().Set("Content-Type", GetImageMimeType(bimg.DetermineImageType(image)))
	w.Header().Set("Error", msg)
	w.WriteHeader(http.StatusBadRequest)
	w.Write(image)
}

func badRequest(w http.ResponseWriter, msg string) {
	w.Header().Set("Content-Type", "image/jpeg")
	w.Header().Set("Error", msg)
	w.WriteHeader(http.StatusBadRequest)
	w.Write(placeholder)
}
<|endoftext|>


<commit_before>package webapp

import (
	"encoding/json"
	"net/http"
	"path/filepath"
	"strconv"
	"time"

	"github.com/tag1consulting/pipeviz/Godeps/_workspace/src/github.com/Sirupsen/logrus"
	"github.com/tag1consulting/pipeviz/Godeps/_workspace/src/github.com/gorilla/websocket"
	"github.com/tag1consulting/pipeviz/Godeps/_workspace/src/github.com/zenazn/goji/web"
	"github.com/tag1consulting/pipeviz/broker"
	"github.com/tag1consulting/pipeviz/journal"
	"github.com/tag1consulting/pipeviz/log"
	"github.com/tag1consulting/pipeviz/represent"
	"github.com/tag1consulting/pipeviz/represent/helpers"
	"github.com/tag1consulting/pipeviz/represent/types"
)

var (
	publicDir = filepath.Join(defaultBase("github.com/tag1consulting/pipeviz/webapp/"), "public")
)

var (
	// TODO crappily hardcoded, for now
	brokerListen broker.GraphReceiver
	latestGraph  types.CoreGraph
)

const (
	// Time allowed to write data to the client.
	writeWait = 10 * time.Second
	// Time allowed to read the next pong message from the client.
	pongWait = 60 * time.Second
	// Send pings to client with this period; less than pongWait.
	pingPeriod = (pongWait * 9) / 10
)

var (
	// gorilla websocket upgrader
	upgrader = websocket.Upgrader{ReadBufferSize: 256, WriteBufferSize: 8192}
)

func init() {
	// Subscribe to the master broker and store latest locally as it comes
	brokerListen = broker.Get().Subscribe()
	// FIXME spawning a goroutine in init() used to be crappy, is it still?
	go func() {
		for g := range brokerListen {
			latestGraph = g
		}
	}()

	// Initially set the latestGraph to a new, empty one to avoid nil pointer
	// TODO fix this now that we have journal persistence
	latestGraph = represent.NewGraph()
}

// Creates a Goji *web.Mux that can act as the http muxer for the frontend app.
func NewMux() *web.Mux {
	m := web.New()

	m.Use(log.NewHttpLogger("webapp"))
	m.Get("/sock", OpenSocket)
	m.Get("/message/:mid", GetMessage)
	m.Get("/*", http.StripPrefix("/", http.FileServer(http.Dir(publicDir))))

	return m
}

func graphToJson(g types.CoreGraph) ([]byte, error) {
	var vertices []interface{}
	for _, v := range g.VerticesWith(helpers.Qbv(types.VTypeNone)) {
		vertices = append(vertices, v.Flat())
	}

	// TODO use something that lets us write to a reusable byte buffer instead
	return json.Marshal(struct {
		Id       uint64        `json:"id"`
		Vertices []interface{} `json:"vertices"`
	}{
		Id:       g.MsgId(),
		Vertices: vertices,
	})
}

func GetMessage(c web.C, w http.ResponseWriter, r *http.Request) {
	id, err := strconv.ParseUint(c.URLParams["mid"], 10, 64)
	if err != nil {
		http.Error(w, http.StatusText(400), 400)
		return
	}

	var getter journal.RecordGetter
	var ok bool
	if fun, exists := c.Env["journalGet"]; !exists {
		http.Error(w, "Could not access log store", 500)
		return
	} else if getter, ok = fun.(journal.RecordGetter); !ok {
		http.Error(w, "Could not access log store", 500)
		return
	}

	rec, err := getter(id)
	if err != nil {
		// TODO Might be something other than not found, but oh well for now
		http.Error(w, http.StatusText(404), 404)
		return
	}

	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(200)
	w.Write(rec.Message)
}

func OpenSocket(w http.ResponseWriter, r *http.Request) {
	ws, err := upgrader.Upgrade(w, r, nil)
	if err != nil {
		entry := logrus.WithFields(logrus.Fields{
			"system": "webapp",
			"err":    err,
		})

		if _, ok := err.(websocket.HandshakeError); !ok {
			entry.Error("Error on attempting upgrade to websocket")
		} else {
			entry.Warn("Handshake error on websocket upgrade")
		}
		return
	}

	go wsWriter(ws)
	wsReader(ws)
}

func wsReader(ws *websocket.Conn) {
	defer ws.Close()
	ws.SetReadLimit(512)
	ws.SetReadDeadline(time.Now().Add(pongWait))
	ws.SetPongHandler(func(string) error { ws.SetReadDeadline(time.Now().Add(pongWait)); return nil })
	// for now, we ignore all messages from the frontend
	for {
		_, _, err := ws.ReadMessage()
		if err != nil {
			break
		}
	}
}

func wsWriter(ws *websocket.Conn) {
	graphIn := broker.Get().Subscribe()
	pingTicker := time.NewTicker(pingPeriod)
	defer func() {
		pingTicker.Stop()
		broker.Get().Unsubscribe(graphIn)
		ws.Close()
	}()

	// write the current graph state first, before entering loop
	graphToSock(ws, latestGraph)
	var g types.CoreGraph
	for {
		select {
		case <-pingTicker.C:
			// ensure client connection is healthy
			ws.SetWriteDeadline(time.Now().Add(writeWait))
			if err := ws.WriteMessage(websocket.PingMessage, []byte{}); err != nil {
				return
			}
		case g = <-graphIn:
			graphToSock(ws, g)
		}
	}
}

func graphToSock(ws *websocket.Conn, g types.CoreGraph) {
	j, err := graphToJson(g)
	if err != nil {
		logrus.WithFields(logrus.Fields{
			"system": "webapp",
			"err":    err,
		}).Error("Error while marshaling graph into JSON for transmission over websocket")
	}

	if j != nil {
		ws.SetWriteDeadline(time.Now().Add(writeWait))
		if err := ws.WriteMessage(websocket.TextMessage, j); err != nil {
			logrus.WithFields(logrus.Fields{
				"system": "webapp",
				"err":    err,
			}).Error("Error while writing graph data to websocket")
			return
		}
	}
}
<commit_msg>Move typing pkgs under one main dir<commit_after>package webapp

import (
	"encoding/json"
	"net/http"
	"path/filepath"
	"strconv"
	"time"

	"github.com/tag1consulting/pipeviz/Godeps/_workspace/src/github.com/Sirupsen/logrus"
	"github.com/tag1consulting/pipeviz/Godeps/_workspace/src/github.com/gorilla/websocket"
	"github.com/tag1consulting/pipeviz/Godeps/_workspace/src/github.com/zenazn/goji/web"
	"github.com/tag1consulting/pipeviz/broker"
	"github.com/tag1consulting/pipeviz/journal"
	"github.com/tag1consulting/pipeviz/log"
	"github.com/tag1consulting/pipeviz/represent"
	"github.com/tag1consulting/pipeviz/represent/q"
	"github.com/tag1consulting/pipeviz/types/system"
)

var (
	publicDir = filepath.Join(defaultBase("github.com/tag1consulting/pipeviz/webapp/"), "public")
)

var (
	// TODO crappily hardcoded, for now
	brokerListen broker.GraphReceiver
	latestGraph  system.CoreGraph
)

const (
	// Time allowed to write data to the client.
	writeWait = 10 * time.Second
	// Time allowed to read the next pong message from the client.
	pongWait = 60 * time.Second
	// Send pings to client with this period; less than pongWait.
	pingPeriod = (pongWait * 9) / 10
)

var (
	// gorilla websocket upgrader
	upgrader = websocket.Upgrader{ReadBufferSize: 256, WriteBufferSize: 8192}
)

func init() {
	// Subscribe to the master broker and store latest locally as it comes
	brokerListen = broker.Get().Subscribe()
	// FIXME spawning a goroutine in init() used to be crappy, is it still?
	go func() {
		for g := range brokerListen {
			latestGraph = g
		}
	}()

	// Initially set the latestGraph to a new, empty one to avoid nil pointer
	// TODO fix this now that we have journal persistence
	latestGraph = represent.NewGraph()
}

// Creates a Goji *web.Mux that can act as the http muxer for the frontend app.
func NewMux() *web.Mux {
	m := web.New()

	m.Use(log.NewHttpLogger("webapp"))
	m.Get("/sock", OpenSocket)
	m.Get("/message/:mid", GetMessage)
	m.Get("/*", http.StripPrefix("/", http.FileServer(http.Dir(publicDir))))

	return m
}

func graphToJson(g system.CoreGraph) ([]byte, error) {
	var vertices []interface{}
	for _, v := range g.VerticesWith(q.Qbv(system.VTypeNone)) {
		vertices = append(vertices, v.Flat())
	}

	// TODO use something that lets us write to a reusable byte buffer instead
	return json.Marshal(struct {
		Id       uint64        `json:"id"`
		Vertices []interface{} `json:"vertices"`
	}{
		Id:       g.MsgId(),
		Vertices: vertices,
	})
}

func GetMessage(c web.C, w http.ResponseWriter, r *http.Request) {
	id, err := strconv.ParseUint(c.URLParams["mid"], 10, 64)
	if err != nil {
		http.Error(w, http.StatusText(400), 400)
		return
	}

	var getter journal.RecordGetter
	var ok bool
	if fun, exists := c.Env["journalGet"]; !exists {
		http.Error(w, "Could not access log store", 500)
		return
	} else if getter, ok = fun.(journal.RecordGetter); !ok {
		http.Error(w, "Could not access log store", 500)
		return
	}

	rec, err := getter(id)
	if err != nil {
		// TODO Might be something other than not found, but oh well for now
		http.Error(w, http.StatusText(404), 404)
		return
	}

	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(200)
	w.Write(rec.Message)
}

func OpenSocket(w http.ResponseWriter, r *http.Request) {
	ws, err := upgrader.Upgrade(w, r, nil)
	if err != nil {
		entry := logrus.WithFields(logrus.Fields{
			"system": "webapp",
			"err":    err,
		})

		if _, ok := err.(websocket.HandshakeError); !ok {
			entry.Error("Error on attempting upgrade to websocket")
		} else {
			entry.Warn("Handshake error on websocket upgrade")
		}
		return
	}

	go wsWriter(ws)
	wsReader(ws)
}

func wsReader(ws *websocket.Conn) {
	defer ws.Close()
	ws.SetReadLimit(512)
	ws.SetReadDeadline(time.Now().Add(pongWait))
	ws.SetPongHandler(func(string) error { ws.SetReadDeadline(time.Now().Add(pongWait)); return nil })
	// for now, we ignore all messages from the frontend
	for {
		_, _, err := ws.ReadMessage()
		if err != nil {
			break
		}
	}
}

func wsWriter(ws *websocket.Conn) {
	graphIn := broker.Get().Subscribe()
	pingTicker := time.NewTicker(pingPeriod)
	defer func() {
		pingTicker.Stop()
		broker.Get().Unsubscribe(graphIn)
		ws.Close()
	}()

	// write the current graph state first, before entering loop
	graphToSock(ws, latestGraph)
	var g system.CoreGraph
	for {
		select {
		case <-pingTicker.C:
			// ensure client connection is healthy
			ws.SetWriteDeadline(time.Now().Add(writeWait))
			if err := ws.WriteMessage(websocket.PingMessage, []byte{}); err != nil {
				return
			}
		case g = <-graphIn:
			graphToSock(ws, g)
		}
	}
}

func graphToSock(ws *websocket.Conn, g system.CoreGraph) {
	j, err := graphToJson(g)
	if err != nil {
		logrus.WithFields(logrus.Fields{
			"system": "webapp",
			"err":    err,
		}).Error("Error while marshaling graph into JSON for transmission over websocket")
	}

	if j != nil {
		ws.SetWriteDeadline(time.Now().Add(writeWait))
		if err := ws.WriteMessage(websocket.TextMessage, j); err != nil {
			logrus.WithFields(logrus.Fields{
				"system": "webapp",
				"err":    err,
			}).Error("Error while writing graph data to websocket")
			return
		}
	}
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"github.com/emicklei/go-restful"
	"go/build"
	"log"
	"net/http"
	"path"
	"strconv"
	"strings"
	"time"
)

var gDB *DB
var gPendingCommands map[int64]CommandContext

type CommandContext struct {
	CommandId int64           `json: "commandid"`
	Arguments map[string]bool `json: "arguments"`
}

type CommandResponse struct {
	Name        string `json: "name"`
	Description string `json: "description"`
	Trigger     string `json: "trigger"`
}

func serveIndexHtml(w http.ResponseWriter, r *http.Request) {
	http.ServeFile(w, r, path.Join(gServerConfig.PackagePath, "static", "index.html"))
}

func ensureIsLoggedIn(request *restful.Request, response *restful.Response, chain *restful.FilterChain) {
	log.Println("is logged in?", IsLoggedIn(request.Request))
	if !IsLoggedIn(request.Request) {
		response.WriteError(http.StatusUnauthorized, nil)
		return
	}

	chain.ProcessFilter(request, response)
}

func getDeviceForRequest(request *restful.Request, response *restful.Response) *Device {
	id, err := strconv.ParseInt(request.PathParameter("device-id"), 10, 64)
	if err != nil {
		response.WriteError(http.StatusBadRequest, nil)
		return nil
	}

	device, err := gDB.GetDeviceById(id)
	if device != nil && device.User == GetLoginName(request.Request) {
		return device
	}

	response.WriteError(http.StatusNotFound, nil)
	return nil
}

func addDevice(request *restful.Request, response *restful.Response) {
	indevice := new(Device)
	request.ReadEntity(indevice)

	name := indevice.Name
	endpoint := indevice.Endpoint

	if name == "" || endpoint == "" {
		response.WriteError(http.StatusBadRequest, nil)
		return
	}

	device, err := gDB.AddDevice(GetLoginName(request.Request), name, endpoint)
	if err == nil {
		response.WriteEntity(*device)
	} else {
		response.WriteError(http.StatusInternalServerError, nil)
	}
}

func serveDevicesByUser(request *restful.Request, response *restful.Response) {
	devices, _ := gDB.ListDevicesForUser(GetLoginName(request.Request))

	urls := []string{}
	for _, d := range devices {
		urls = append(urls, fmt.Sprintf("/device/%d", d.Id))
	}

	response.WriteEntity(urls)
}

func serveDevice(request *restful.Request, response *restful.Response) {
	if device := getDeviceForRequest(request, response); device != nil {
		response.WriteEntity(*device)
	}
}

func toCommandResponse(device *Device, command *Command) CommandResponse {
	trigger := fmt.Sprintf("/device/%d/command/%d", device.Id, command.Id)
	return CommandResponse{command.Name, command.Description, trigger}
}

func serveCommandsByDevice(request *restful.Request, response *restful.Response) {
	device := getDeviceForRequest(request, response)
	if device == nil {
		response.WriteError(http.StatusBadRequest, nil)
		return
	}

	commands, err := gDB.ListCommandsForDevice(device)
	if err != nil {
		response.WriteError(http.StatusBadRequest, nil)
		return
	}

	responses := make([]CommandResponse, len(commands))
	for i, cmd := range commands {
		responses[i] = toCommandResponse(device, cmd)
	}

	response.WriteEntity(responses)
}

func updateCommandsByDevice(request *restful.Request, response *restful.Response) {
	device := getDeviceForRequest(request, response)
	if device == nil {
		return
	}

	commands := []int64{}
	if err := request.ReadEntity(&commands); err != nil {
		response.WriteError(http.StatusBadRequest, nil)
		return
	}

	if err := gDB.UpdateCommandsForDevice(device.Id, commands); err != nil {
		response.WriteError(http.StatusInternalServerError, nil)
		return
	}
}

func updateDeviceLocation(request *restful.Request, response *restful.Response) {
	device := getDeviceForRequest(request, response)
	if device == nil {
		return
	}

	latitude, err := strconv.ParseFloat(request.QueryParameter("latitude"), 64)
	if err != nil {
		response.WriteError(http.StatusBadRequest, nil)
	}

	longitude, err := strconv.ParseFloat(request.QueryParameter("longitude"), 64)
	if err != nil {
		response.WriteError(http.StatusBadRequest, nil)
	}

	err = gDB.UpdateDeviceLocation(device, latitude, longitude)
	if err != nil {
		response.WriteError(http.StatusInternalServerError, nil)
	}
}

func serveInvocation(request *restful.Request, response *restful.Response) {
	token, err := strconv.ParseInt(request.PathParameter("token"), 10, 64)
	if err != nil {
		response.WriteError(http.StatusBadRequest, nil)
		return
	}

	// TODO check whether invocation was actually intended for device? how?
	context, exists := gPendingCommands[token]
	if !exists {
		response.WriteError(http.StatusBadRequest, nil)
		return
	}

	delete(gPendingCommands, token)
	response.WriteEntity(context)
}

func triggerCommand(request *restful.Request, response *restful.Response) {
	device := getDeviceForRequest(request, response)
	if device == nil {
		return
	}

	cmdid, err := strconv.ParseInt(request.PathParameter("command-id"), 10, 64)
	if err != nil {
		response.WriteError(http.StatusBadRequest, nil)
		return
	}

	// Check whether the device actually implements the command
	implements := false

	commands, _ := gDB.ListCommandsForDevice(device)
	for _, cmd := range commands {
		if cmd.Id == cmdid {
			implements = true
			break
		}
	}

	if implements == false {
		response.WriteError(http.StatusBadRequest, nil)
		return
	}

	token := int64(time.Now().Unix())
	context := CommandContext{CommandId: cmdid}

	// Store pending arguments, if any
	if request.Request.ContentLength != 0 {
		if err = request.ReadEntity(&context.Arguments); err != nil {
			response.WriteError(http.StatusBadRequest, nil)
			return
		}
	}
	gPendingCommands[token] = context

	// Issue push notification to device
	body := fmt.Sprintf("version=%d", token)
	pushRequest, err := http.NewRequest("PUT", device.Endpoint, strings.NewReader(body))
	if err != nil {
		response.WriteError(http.StatusInternalServerError, nil)
		return
	}

	pushRequest.Header["Content-Type"] = []string{"application/x-www-form-urlencoded"}

	var client http.Client
	_, err = client.Do(pushRequest)
	if err != nil {
		response.WriteError(http.StatusInternalServerError, nil)
	}
}

func createDeviceWebService() *restful.WebService {
	ws := new(restful.WebService)

	ws.
		Filter(ensureIsLoggedIn).
		Path("/device").
		Consumes(restful.MIME_JSON).
		Produces(restful.MIME_JSON)

	ws.
		Route(ws.GET("/").To(serveDevicesByUser).
		Doc("Retrieve all devices owned by a user").
		Writes([]Device{}))

	ws.
		Route(ws.GET("/{device-id}").To(serveDevice).
		Doc("Retrieve a device based on its id").
		Param(ws.PathParameter("device-id", "The identifier for the device")).
		Writes(Device{}))

	ws.
		Route(ws.PUT("/").To(addDevice).
		Consumes("application/json").
		Doc("Add a device").
		Param(ws.QueryParameter("name", "The name for the device")).
		Param(ws.QueryParameter("endpoint", "The push endpoint for the device")).
		Writes(Device{}))

	ws.
		Route(ws.POST("/location/{device-id}").To(updateDeviceLocation).
		Consumes("application/x-www-form-urlencoded").
		Doc("Update a device's latitude and longitude").
		Param(ws.QueryParameter("latitude", "The latitude where the device was observed")).
		Param(ws.QueryParameter("longitude", "The longitude where the device was observed")))

	ws.
		Route(ws.GET("/{device-id}/command").To(serveCommandsByDevice).
		Doc("List the commands available for a device").
		Param(ws.PathParameter("device-id", "The identifier for the device")).
		Writes([]CommandResponse{}))

	ws.
		Route(ws.PUT("/{device-id}/command").To(updateCommandsByDevice).
		Consumes("application/json").
		Doc("Update the list of commands available for a device").
		Param(ws.PathParameter("device-id", "The identifier for the device")).
		Param(ws.QueryParameter("commands", "List of command ids supported by the device")))

	ws.
		Route(ws.POST("/{device-id}/command/{command-id}").To(triggerCommand).
		Consumes("application/json").
		Doc("Trigger a command").
		Param(ws.PathParameter("device-id", "The identifier for the device")).
		Param(ws.PathParameter("command-id", "The identifier for the command")).
		Param(ws.QueryParameter("parameters", "An object with values for parameters")))

	// FIXME should this be under /device?
	ws.
		Route(ws.GET("/invocation/{token}").To(serveInvocation).
		Doc("Get the invocation context of a command").
		Param(ws.PathParameter("token", "The invocation identifier")).
		Writes(CommandContext{}))

	return ws
}

func defaultBase(path string) string {
	p, err := build.Default.Import(path, "", build.FindOnly)
	log.Println(p)
	if err != nil {
		return "."
	}
	return p.Dir
}

func main() {
	var packagePath = defaultBase("github.com/dougt/whereismyfox")
	var configFile = flag.String("config", path.Join(packagePath, "config.json"), "Location of configuration file")
	var dbFile = flag.String("db", path.Join(packagePath, "db.sqlite"), "Location of database")
	flag.Parse()

	readConfig(*configFile)
	gServerConfig.PackagePath = packagePath

	db, err := OpenDB(*dbFile)
	if err != nil {
		panic(err)
	}

	gDB = db
	if err = populateCommandsDB(db, path.Join(packagePath, "commands.json")); err != nil {
		panic(err)
	}

	gPendingCommands = map[int64]CommandContext{}

	restful.Add(createDeviceWebService())

	// Persona handling
	http.HandleFunc("/auth/check", loginCheckHandler)
	http.HandleFunc("/auth/login", loginHandler)
	http.HandleFunc("/auth/applogin", appLoginHandler)
	http.HandleFunc("/auth/logout", logoutHandler)

	http.HandleFunc("/manifest.webapp", func(w http.ResponseWriter, r *http.Request) {
		filename := "./app/manifest.webapp"
		log.Println("serving manifest from " + filename)

		w.Header()["Content-Type"] = []string{"application/x-web-app-manifest+json"}
		http.ServeFile(w, r, filename)
	})

	http.HandleFunc("/", serveIndexHtml)
	http.HandleFunc("/index.html", serveIndexHtml)
	log.Println(path.Join(packagePath, "static"))
	http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir(path.Join(packagePath, "static")))))

	log.Println("Listening on", gServerConfig.Hostname+":"+gServerConfig.Port)

	if gServerConfig.UseTLS {
		err = http.ListenAndServeTLS(gServerConfig.Hostname+":"+gServerConfig.Port,
			gServerConfig.CertFilename,
			gServerConfig.KeyFilename,
			nil)
	} else {
		log.Println("This is a really unsafe way to run the server.  Really.  Don't do this in production.")
		err = http.ListenAndServe(gServerConfig.Hostname+":"+gServerConfig.Port, nil)
	}

	log.Println("Exiting... ", err)
	gDB.Close()
}
<commit_msg>Use response.WriteErrorString instead of deprecated WriteError.<commit_after>package main

import (
	"flag"
	"fmt"
	"github.com/emicklei/go-restful"
	"go/build"
	"log"
	"net/http"
	"path"
	"strconv"
	"strings"
	"time"
)

var gDB *DB
var gPendingCommands map[int64]CommandContext

type CommandContext struct {
	CommandId int64           `json: "commandid"`
	Arguments map[string]bool `json: "arguments"`
}

type CommandResponse struct {
	Name        string `json: "name"`
	Description string `json: "description"`
	Trigger     string `json: "trigger"`
}

func serveIndexHtml(w http.ResponseWriter, r *http.Request) {
	http.ServeFile(w, r, path.Join(gServerConfig.PackagePath, "static", "index.html"))
}

func ensureIsLoggedIn(request *restful.Request, response *restful.Response, chain *restful.FilterChain) {
	if !IsLoggedIn(request.Request) {
		response.WriteErrorString(http.StatusUnauthorized, "Not logged in")
		return
	}

	chain.ProcessFilter(request, response)
}

func getDeviceForRequest(request *restful.Request, response *restful.Response) *Device {
	id, err := strconv.ParseInt(request.PathParameter("device-id"), 10, 64)
	if err != nil {
		response.WriteErrorString(http.StatusBadRequest, "Failed to parse device")
		return nil
	}

	device, err := gDB.GetDeviceById(id)
	if device != nil && device.User == GetLoginName(request.Request) {
		return device
	}

	response.WriteErrorString(http.StatusNotFound, "Device not found")
	return nil
}

func addDevice(request *restful.Request, response *restful.Response) {
	indevice := new(Device)
	request.ReadEntity(indevice)

	name := indevice.Name
	endpoint := indevice.Endpoint

	if name == "" || endpoint == "" {
		response.WriteErrorString(http.StatusBadRequest, "No name or endpoint")
		return
	}

	device, err := gDB.AddDevice(GetLoginName(request.Request), name, endpoint)
	if err == nil {
		response.WriteEntity(*device)
	} else {
		response.WriteErrorString(http.StatusInternalServerError, "Failed to add device")
	}
}

func serveDevicesByUser(request *restful.Request, response *restful.Response) {
	devices, _ := gDB.ListDevicesForUser(GetLoginName(request.Request))

	urls := []string{}
	for _, d := range devices {
		urls = append(urls, fmt.Sprintf("/device/%d", d.Id))
	}

	response.WriteEntity(urls)
}

func serveDevice(request *restful.Request, response *restful.Response) {
	if device := getDeviceForRequest(request, response); device != nil {
		response.WriteEntity(*device)
	}
}

func toCommandResponse(device *Device, command *Command) CommandResponse {
	trigger := fmt.Sprintf("/device/%d/command/%d", device.Id, command.Id)
	return CommandResponse{command.Name, command.Description, trigger}
}

func serveCommandsByDevice(request *restful.Request, response *restful.Response) {
	device := getDeviceForRequest(request, response)
	if device == nil {
		response.WriteErrorString(http.StatusBadRequest, "No device in request")
		return
	}

	commands, err := gDB.ListCommandsForDevice(device)
	if err != nil {
		response.WriteErrorString(http.StatusBadRequest, "Failed to retrieve commands")
		return
	}

	responses := make([]CommandResponse, len(commands))
	for i, cmd := range commands {
		responses[i] = toCommandResponse(device, cmd)
	}

	response.WriteEntity(responses)
}

func updateCommandsByDevice(request *restful.Request, response *restful.Response) {
	device := getDeviceForRequest(request, response)
	if device == nil {
		return
	}

	commands := []int64{}
	if err := request.ReadEntity(&commands); err != nil {
		response.WriteErrorString(http.StatusBadRequest, "Failed to parse commands")
		return
	}

	if err := gDB.UpdateCommandsForDevice(device.Id, commands); err != nil {
		response.WriteErrorString(http.StatusInternalServerError, "Failed to update commands")
		return
	}
}

func updateDeviceLocation(request *restful.Request, response *restful.Response) {
	device := getDeviceForRequest(request, response)
	if device == nil {
		return
	}

	latitude, err := strconv.ParseFloat(request.QueryParameter("latitude"), 64)
	if err != nil {
		response.WriteErrorString(http.StatusBadRequest, "Failed to parse latitude")
	}

	longitude, err := strconv.ParseFloat(request.QueryParameter("longitude"), 64)
	if err != nil {
		response.WriteErrorString(http.StatusBadRequest, "Failed to parse longitude")
	}

	err = gDB.UpdateDeviceLocation(device, latitude, longitude)
	if err != nil {
		response.WriteErrorString(http.StatusInternalServerError, "Failed to update location")
	}
}

func serveInvocation(request *restful.Request, response *restful.Response) {
	token, err := strconv.ParseInt(request.PathParameter("token"), 10, 64)
	if err != nil {
		response.WriteErrorString(http.StatusBadRequest, "Failed to parse invocation")
		return
	}

	// TODO check whether invocation was actually intended for device? how?
	context, exists := gPendingCommands[token]
	if !exists {
		response.WriteErrorString(http.StatusBadRequest, "Failed to find invocation")
		return
	}

	delete(gPendingCommands, token)
	response.WriteEntity(context)
}

func triggerCommand(request *restful.Request, response *restful.Response) {
	device := getDeviceForRequest(request, response)
	if device == nil {
		return
	}

	cmdid, err := strconv.ParseInt(request.PathParameter("command-id"), 10, 64)
	if err != nil {
		response.WriteErrorString(http.StatusBadRequest, "Failed to parse command")
		return
	}

	// Check whether the device actually implements the command
	implements := false

	commands, _ := gDB.ListCommandsForDevice(device)
	for _, cmd := range commands {
		if cmd.Id == cmdid {
			implements = true
			break
		}
	}

	if implements == false {
		response.WriteErrorString(http.StatusBadRequest, "No such command for device")
		return
	}

	token := int64(time.Now().Unix())
	context := CommandContext{CommandId: cmdid}

	// Store pending arguments, if any
	if request.Request.ContentLength != 0 {
		if err = request.ReadEntity(&context.Arguments); err != nil {
			response.WriteErrorString(http.StatusBadRequest, "Failed to parse arguments")
			return
		}
	}
	gPendingCommands[token] = context

	// Issue push notification to device
	body := fmt.Sprintf("version=%d", token)
	pushRequest, err := http.NewRequest("PUT", device.Endpoint, strings.NewReader(body))
	if err != nil {
		response.WriteErrorString(http.StatusInternalServerError, "Failed to push command")
		return
	}

	pushRequest.Header["Content-Type"] = []string{"application/x-www-form-urlencoded"}

	var client http.Client
	_, err = client.Do(pushRequest)
	if err != nil {
		response.WriteErrorString(http.StatusInternalServerError, "Failed to push command")
	}
}

func createDeviceWebService() *restful.WebService {
	ws := new(restful.WebService)

	ws.
		Filter(ensureIsLoggedIn).
		Path("/device").
		Consumes(restful.MIME_JSON).
		Produces(restful.MIME_JSON)

	ws.
		Route(ws.GET("/").To(serveDevicesByUser).
		Doc("Retrieve all devices owned by a user").
		Writes([]Device{}))

	ws.
		Route(ws.GET("/{device-id}").To(serveDevice).
		Doc("Retrieve a device based on its id").
		Param(ws.PathParameter("device-id", "The identifier for the device")).
		Writes(Device{}))

	ws.
		Route(ws.PUT("/").To(addDevice).
		Consumes("application/json").
		Doc("Add a device").
		Param(ws.QueryParameter("name", "The name for the device")).
		Param(ws.QueryParameter("endpoint", "The push endpoint for the device")).
		Writes(Device{}))

	ws.
		Route(ws.POST("/location/{device-id}").To(updateDeviceLocation).
		Consumes("application/x-www-form-urlencoded").
		Doc("Update a device's latitude and longitude").
		Param(ws.QueryParameter("latitude", "The latitude where the device was observed")).
		Param(ws.QueryParameter("longitude", "The longitude where the device was observed")))

	ws.
		Route(ws.GET("/{device-id}/command").To(serveCommandsByDevice).
		Doc("List the commands available for a device").
		Param(ws.PathParameter("device-id", "The identifier for the device")).
		Writes([]CommandResponse{}))

	ws.
		Route(ws.PUT("/{device-id}/command").To(updateCommandsByDevice).
		Consumes("application/json").
		Doc("Update the list of commands available for a device").
		Param(ws.PathParameter("device-id", "The identifier for the device")).
		Param(ws.QueryParameter("commands", "List of command ids supported by the device")))

	ws.
		Route(ws.POST("/{device-id}/command/{command-id}").To(triggerCommand).
		Consumes("application/json").
		Doc("Trigger a command").
		Param(ws.PathParameter("device-id", "The identifier for the device")).
		Param(ws.PathParameter("command-id", "The identifier for the command")).
		Param(ws.QueryParameter("parameters", "An object with values for parameters")))

	// FIXME should this be under /device?
	ws.
		Route(ws.GET("/invocation/{token}").To(serveInvocation).
		Doc("Get the invocation context of a command").
		Param(ws.PathParameter("token", "The invocation identifier")).
		Writes(CommandContext{}))

	return ws
}

func defaultBase(path string) string {
	p, err := build.Default.Import(path, "", build.FindOnly)
	log.Println(p)
	if err != nil {
		return "."
	}
	return p.Dir
}

func main() {
	var packagePath = defaultBase("github.com/dougt/whereismyfox")
	var configFile = flag.String("config", path.Join(packagePath, "config.json"), "Location of configuration file")
	var dbFile = flag.String("db", path.Join(packagePath, "db.sqlite"), "Location of database")
	flag.Parse()

	readConfig(*configFile)
	gServerConfig.PackagePath = packagePath

	db, err := OpenDB(*dbFile)
	if err != nil {
		panic(err)
	}

	gDB = db
	if err = populateCommandsDB(db, path.Join(packagePath, "commands.json")); err != nil {
		panic(err)
	}

	gPendingCommands = map[int64]CommandContext{}

	restful.Add(createDeviceWebService())

	// Persona handling
	http.HandleFunc("/auth/check", loginCheckHandler)
	http.HandleFunc("/auth/login", loginHandler)
	http.HandleFunc("/auth/applogin", appLoginHandler)
	http.HandleFunc("/auth/logout", logoutHandler)

	http.HandleFunc("/manifest.webapp", func(w http.ResponseWriter, r *http.Request) {
		filename := "./app/manifest.webapp"
		log.Println("serving manifest from " + filename)

		w.Header()["Content-Type"] = []string{"application/x-web-app-manifest+json"}
		http.ServeFile(w, r, filename)
	})

	http.HandleFunc("/", serveIndexHtml)
	http.HandleFunc("/index.html", serveIndexHtml)
	log.Println(path.Join(packagePath, "static"))
	http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir(path.Join(packagePath, "static")))))

	log.Println("Listening on", gServerConfig.Hostname+":"+gServerConfig.Port)

	if gServerConfig.UseTLS {
		err = http.ListenAndServeTLS(gServerConfig.Hostname+":"+gServerConfig.Port,
			gServerConfig.CertFilename,
			gServerConfig.KeyFilename,
			nil)
	} else {
		log.Println("This is a really unsafe way to run the server.  Really.  Don't do this in production.")
		err = http.ListenAndServe(gServerConfig.Hostname+":"+gServerConfig.Port, nil)
	}

	log.Println("Exiting... ", err)
	gDB.Close()
}
<|endoftext|>


<commit_before>package server

import (
	"bufio"
	"crypto/tls"
	"net"
	"strconv"
	"strings"
)

func Version() string {
	return "0.2.1104"
}

// serverOpts contains parameters for server.NewServer()
type ServerOpts struct {
	// The factory that will be used to create a new FTPDriver instance for
	// each client connection. This is a mandatory option.
	Factory DriverFactory

	Auth Auth

	// Server Name, Default is Go Ftp Server
	Name string

	// The hostname that the FTP server should listen on. Optional, defaults to
	// "::", which means all hostnames on ipv4 and ipv6.
	Hostname string

	// The port that the FTP should listen on. Optional, defaults to 3000. In
	// a production environment you will probably want to change this to 21.
	Port int

	// use tls, default is false
	TLS bool

	// if tls used, cert file is required
	CertFile string

	// if tls used, key file is required
	KeyFile string

	WelcomeMessage string
}

// Server is the root of your FTP application. You should instantiate one
// of these and call ListenAndServe() to start accepting client connections.
//
// Always use the NewServer() method to create a new Server.
type Server struct {
	*ServerOpts
	name          string
	listenTo      string
	driverFactory DriverFactory
	logger        *Logger
}

// serverOptsWithDefaults copies an ServerOpts struct into a new struct,
// then adds any default values that are missing and returns the new data.
func serverOptsWithDefaults(opts *ServerOpts) *ServerOpts {
	var newOpts ServerOpts
	if opts == nil {
		opts = &ServerOpts{}
	}
	if opts.Hostname == "" {
		newOpts.Hostname = "::"
	} else {
		newOpts.Hostname = opts.Hostname
	}
	if opts.Port == 0 {
		newOpts.Port = 3000
	} else {
		newOpts.Port = opts.Port
	}
	newOpts.Factory = opts.Factory
	if opts.Name == "" {
		newOpts.Name = "Go FTP Server"
	} else {
		newOpts.Name = opts.Name
	}

	if opts.WelcomeMessage == "" {
		newOpts.WelcomeMessage = defaultWelcomeMessage
	} else {
		newOpts.WelcomeMessage = opts.WelcomeMessage
	}

	if opts.Auth != nil {
		newOpts.Auth = opts.Auth
	}

	newOpts.TLS = opts.TLS
	newOpts.KeyFile = opts.KeyFile
	newOpts.CertFile = opts.CertFile

	return &newOpts
}

// NewServer initialises a new FTP server. Configuration options are provided
// via an instance of ServerOpts. Calling this function in your code will
// probably look something like this:
//
//     factory := &MyDriverFactory{}
//     server  := server.NewServer(&server.ServerOpts{ Factory: factory })
//
// or:
//
//     factory := &MyDriverFactory{}
//     opts    := &server.ServerOpts{
//       Factory: factory,
//       Port: 2000,
//       Hostname: "127.0.0.1",
//     }
//     server  := server.NewServer(opts)
//
func NewServer(opts *ServerOpts) *Server {
	opts = serverOptsWithDefaults(opts)
	s := new(Server)
	s.ServerOpts = opts
	s.listenTo = buildTcpString(opts.Hostname, opts.Port)
	s.name = opts.Name
	s.driverFactory = opts.Factory
	s.logger = newLogger("")
	return s
}

// NewConn constructs a new object that will handle the FTP protocol over
// an active net.TCPConn. The TCP connection should already be open before
// it is handed to this functions. driver is an instance of FTPDriver that
// will handle all auth and persistence details.
func (server *Server) newConn(tcpConn net.Conn, driver Driver, auth Auth) *Conn {
	c := new(Conn)
	c.namePrefix = "/"
	c.conn = tcpConn
	c.controlReader = bufio.NewReader(tcpConn)
	c.controlWriter = bufio.NewWriter(tcpConn)
	c.driver = driver
	c.auth = auth
	c.server = server
	c.sessionId = newSessionId()
	c.logger = newLogger(c.sessionId)
	return c
}

func simpleTLSConfig(certFile, keyFile string) (*tls.Config, error) {
	config := &tls.Config{}
	if config.NextProtos == nil {
		config.NextProtos = []string{"ftp"}
	}

	var err error
	config.Certificates = make([]tls.Certificate, 1)
	config.Certificates[0], err = tls.LoadX509KeyPair(certFile, keyFile)
	if err != nil {
		return nil, err
	}
	return config, nil
}

// ListenAndServe asks a new Server to begin accepting client connections. It
// accepts no arguments - all configuration is provided via the NewServer
// function.
//
// If the server fails to start for any reason, an error will be returned. Common
// errors are trying to bind to a privileged port or something else is already
// listening on the same port.
//
func (Server *Server) ListenAndServe() error {
	/*laddr, err := net.ResolveTCPAddr("tcp", Server.listenTo)
	if err != nil {
		return err
	}*/

	var listener net.Listener
	var err error
	//fmt.Println("-------", *Server.ServerOpts)
	if Server.ServerOpts.TLS {
		//fmt.Println("use tls")
		config, err := simpleTLSConfig(Server.CertFile, Server.KeyFile)
		if err != nil {
			return err
		}

		listener, err = tls.Listen("tcp", Server.listenTo, config)
	} else {
		listener, err = net.Listen("tcp", Server.listenTo)
	}
	if err != nil {
		return err
	}

	Server.logger.Printf("%s listening on %d", Server.Name, Server.Port)

	for {
		tcpConn, err := listener.Accept()
		if err != nil {
			Server.logger.Print("listening error")
			break
		}
		driver, err := Server.driverFactory.NewDriver()
		if err != nil {
			Server.logger.Print("Error creating driver, aborting client connection")
			tcpConn.Close()
		} else {
			ftpConn := Server.newConn(tcpConn, driver, Server.Auth)
			go ftpConn.Serve()
		}
	}
	return nil
}

func buildTcpString(hostname string, port int) (result string) {
	if strings.Contains(hostname, ":") {
		// ipv6
		if port == 0 {
			result = "[" + hostname + "]"
		} else {
			result = "[" + hostname + "]:" + strconv.Itoa(port)
		}
	} else {
		// ipv4
		if port == 0 {
			result = hostname
		} else {
			result = hostname + ":" + strconv.Itoa(port)
		}
	}
	return
}
<commit_msg>add Shutdown method to server<commit_after>package server

import (
	"bufio"
	"crypto/tls"
	"net"
	"strconv"
	"strings"
)

func Version() string {
	return "0.2.1104"
}

// serverOpts contains parameters for server.NewServer()
type ServerOpts struct {
	// The factory that will be used to create a new FTPDriver instance for
	// each client connection. This is a mandatory option.
	Factory DriverFactory

	Auth Auth

	// Server Name, Default is Go Ftp Server
	Name string

	// The hostname that the FTP server should listen on. Optional, defaults to
	// "::", which means all hostnames on ipv4 and ipv6.
	Hostname string

	// The port that the FTP should listen on. Optional, defaults to 3000. In
	// a production environment you will probably want to change this to 21.
	Port int

	// use tls, default is false
	TLS bool

	// if tls used, cert file is required
	CertFile string

	// if tls used, key file is required
	KeyFile string

	WelcomeMessage string
}

// Server is the root of your FTP application. You should instantiate one
// of these and call ListenAndServe() to start accepting client connections.
//
// Always use the NewServer() method to create a new Server.
type Server struct {
	*ServerOpts
	name          string
	listenTo      string
	driverFactory DriverFactory
	logger        *Logger
	listener      net.Listener
}

// serverOptsWithDefaults copies an ServerOpts struct into a new struct,
// then adds any default values that are missing and returns the new data.
func serverOptsWithDefaults(opts *ServerOpts) *ServerOpts {
	var newOpts ServerOpts
	if opts == nil {
		opts = &ServerOpts{}
	}
	if opts.Hostname == "" {
		newOpts.Hostname = "::"
	} else {
		newOpts.Hostname = opts.Hostname
	}
	if opts.Port == 0 {
		newOpts.Port = 3000
	} else {
		newOpts.Port = opts.Port
	}
	newOpts.Factory = opts.Factory
	if opts.Name == "" {
		newOpts.Name = "Go FTP Server"
	} else {
		newOpts.Name = opts.Name
	}

	if opts.WelcomeMessage == "" {
		newOpts.WelcomeMessage = defaultWelcomeMessage
	} else {
		newOpts.WelcomeMessage = opts.WelcomeMessage
	}

	if opts.Auth != nil {
		newOpts.Auth = opts.Auth
	}

	newOpts.TLS = opts.TLS
	newOpts.KeyFile = opts.KeyFile
	newOpts.CertFile = opts.CertFile

	return &newOpts
}

// NewServer initialises a new FTP server. Configuration options are provided
// via an instance of ServerOpts. Calling this function in your code will
// probably look something like this:
//
//     factory := &MyDriverFactory{}
//     server  := server.NewServer(&server.ServerOpts{ Factory: factory })
//
// or:
//
//     factory := &MyDriverFactory{}
//     opts    := &server.ServerOpts{
//       Factory: factory,
//       Port: 2000,
//       Hostname: "127.0.0.1",
//     }
//     server  := server.NewServer(opts)
//
func NewServer(opts *ServerOpts) *Server {
	opts = serverOptsWithDefaults(opts)
	s := new(Server)
	s.ServerOpts = opts
	s.listenTo = buildTcpString(opts.Hostname, opts.Port)
	s.name = opts.Name
	s.driverFactory = opts.Factory
	s.logger = newLogger("")
	return s
}

// NewConn constructs a new object that will handle the FTP protocol over
// an active net.TCPConn. The TCP connection should already be open before
// it is handed to this functions. driver is an instance of FTPDriver that
// will handle all auth and persistence details.
func (server *Server) newConn(tcpConn net.Conn, driver Driver, auth Auth) *Conn {
	c := new(Conn)
	c.namePrefix = "/"
	c.conn = tcpConn
	c.controlReader = bufio.NewReader(tcpConn)
	c.controlWriter = bufio.NewWriter(tcpConn)
	c.driver = driver
	c.auth = auth
	c.server = server
	c.sessionId = newSessionId()
	c.logger = newLogger(c.sessionId)
	return c
}

func simpleTLSConfig(certFile, keyFile string) (*tls.Config, error) {
	config := &tls.Config{}
	if config.NextProtos == nil {
		config.NextProtos = []string{"ftp"}
	}

	var err error
	config.Certificates = make([]tls.Certificate, 1)
	config.Certificates[0], err = tls.LoadX509KeyPair(certFile, keyFile)
	if err != nil {
		return nil, err
	}
	return config, nil
}

// ListenAndServe asks a new Server to begin accepting client connections. It
// accepts no arguments - all configuration is provided via the NewServer
// function.
//
// If the server fails to start for any reason, an error will be returned. Common
// errors are trying to bind to a privileged port or something else is already
// listening on the same port.
//
func (Server *Server) ListenAndServe() error {
	/*laddr, err := net.ResolveTCPAddr("tcp", Server.listenTo)
	if err != nil {
		return err
	}*/

	var listener net.Listener
	var err error
	//fmt.Println("-------", *Server.ServerOpts)
	if Server.ServerOpts.TLS {
		//fmt.Println("use tls")
		config, err := simpleTLSConfig(Server.CertFile, Server.KeyFile)
		if err != nil {
			return err
		}

		listener, err = tls.Listen("tcp", Server.listenTo, config)
	} else {
		listener, err = net.Listen("tcp", Server.listenTo)
	}
	if err != nil {
		return err
	}

	Server.logger.Printf("%s listening on %d", Server.Name, Server.Port)

	Server.listener = listener
	for {
		tcpConn, err := Server.listener.Accept()
		if err != nil {
			Server.logger.Printf("listening error: %v", err)
			break
		}
		driver, err := Server.driverFactory.NewDriver()
		if err != nil {
			Server.logger.Printf("Error creating driver, aborting client connection: %v", err)
			tcpConn.Close()
		} else {
			ftpConn := Server.newConn(tcpConn, driver, Server.Auth)
			go ftpConn.Serve()
		}
	}
	return nil
}

// Gracefully stops a server. Already connected clients will retain their connections
func (Server *Server) Shutdown() error {
	if Server.listener != nil {
		return Server.listener.Close()
	}
	// server wasnt even started
	return nil
}

func buildTcpString(hostname string, port int) (result string) {
	if strings.Contains(hostname, ":") {
		// ipv6
		if port == 0 {
			result = "[" + hostname + "]"
		} else {
			result = "[" + hostname + "]:" + strconv.Itoa(port)
		}
	} else {
		// ipv4
		if port == 0 {
			result = hostname
		} else {
			result = hostname + ":" + strconv.Itoa(port)
		}
	}
	return
}
<|endoftext|>