<commit_before>package tlsconfig

import (
	"crypto/tls"
	"encoding/hex"
	"fmt"
	"strings"
)

//********** ProtocolType

type TLSProtocolVersion uint16

func (pv TLSProtocolVersion) String() string {
	switch pv {
	case tls.VersionSSL30:
		return "SSLv3"
	case tls.VersionTLS10:
		return "TLSv1"
	case tls.VersionTLS11:
		return "TLSv1.1"
	case tls.VersionTLS12:
		return "TLSv1.2"
	}
	return "unknown tls protocol"
}

func (pv *TLSProtocolVersion) fromString(str string) (err error) {
	switch strings.ToUpper(str) {
	case "SSLV3":
		*pv = tls.VersionSSL30
	case "TLSV1":
		*pv = tls.VersionTLS10
	case "TLSV1.1":
		*pv = tls.VersionTLS11
	case "TLSV1.2":
		*pv = tls.VersionTLS12
	default:
		return fmt.Errorf("invalid tls protocol version: '" + str + "'")
	}
	return
}

func (pv TLSProtocolVersion) MarshalText() (data []byte, err error) {
	data = []byte(pv.String())
	return
}

func (pv *TLSProtocolVersion) UnmarshalText(data []byte) (err error) {
	return pv.fromString(string(data))
}

//********** CipherSuite

type TLSCipherSuite uint16

func (cs TLSCipherSuite) String() string {
	switch uint16(cs) {
	case tls.TLS_RSA_WITH_RC4_128_SHA:
		return "RSA_WITH_RC4_128_SHA"
	case tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA:
		return "RSA_WITH_3DES_EDE_CBC_SHA"
	case tls.TLS_RSA_WITH_AES_128_CBC_SHA:
		return "RSA_WITH_AES_128_CBC_SHA"
	case tls.TLS_RSA_WITH_AES_256_CBC_SHA:
		return "RSA_WITH_AES_256_CBC_SHA"
	case tls.TLS_RSA_WITH_AES_128_CBC_SHA256:
		return "RSA_WITH_AES_128_CBC_SHA256"
	case tls.TLS_RSA_WITH_AES_128_GCM_SHA256:
		return "RSA_WITH_AES_128_GCM_SHA256"
	case tls.TLS_RSA_WITH_AES_256_GCM_SHA384:
		return "RSA_WITH_AES_256_GCM_SHA384"
	case tls.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA:
		return "ECDHE_ECDSA_WITH_RC4_128_SHA"
	case tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA:
		return "ECDHE_ECDSA_WITH_AES_128_CBC_SHA"
	case tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA:
		return "ECDHE_ECDSA_WITH_AES_256_CBC_SHA"
	case tls.TLS_ECDHE_RSA_WITH_RC4_128_SHA:
		return "ECDHE_RSA_WITH_RC4_128_SHA"
	case tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA:
		return "ECDHE_RSA_WITH_3DES_EDE_CBC_SHA"
	case tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA:
		return "ECDHE_RSA_WITH_AES_128_CBC_SHA"
	case tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA:
		return "ECDHE_RSA_WITH_AES_256_CBC_SHA"
	case tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256:
		return "ECDHE_ECDSA_WITH_AES_128_CBC_SHA256"
	case tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256:
		return "ECDHE_RSA_WITH_AES_128_CBC_SHA256"
	case tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256:
		return "ECDHE_RSA_WITH_AES_128_GCM_SHA256"
	case tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256:
		return "ECDHE_ECDSA_WITH_AES_128_GCM_SHA256"
	case tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384:
		return "ECDHE_RSA_WITH_AES_256_GCM_SHA384"
	case tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384:
		return "ECDHE_ECDSA_WITH_AES_256_GCM_SHA384"
	case tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305:
		return "ECDHE_RSA_WITH_CHACHA20_POLY1305"
	case tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305:
		return "ECDHE_ECDSA_WITH_CHACHA20_POLY1305"
	}
	return "unknown tls cipher suite"
}

func (cs *TLSCipherSuite) fromString(str string) (err error) {
	switch strings.Replace(strings.ToUpper(str), "-", "_", -1) {
	case "RSA_WITH_RC4_128_SHA":
		*cs = TLSCipherSuite(tls.TLS_RSA_WITH_RC4_128_SHA)
	case "RSA_WITH_3DES_EDE_CBC_SHA":
		*cs = TLSCipherSuite(tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA)
	case "RSA_WITH_AES_128_CBC_SHA":
		*cs = TLSCipherSuite(tls.TLS_RSA_WITH_AES_128_CBC_SHA)
	case "RSA_WITH_AES_256_CBC_SHA":
		*cs = TLSCipherSuite(tls.TLS_RSA_WITH_AES_256_CBC_SHA)
	case "RSA_WITH_AES_128_CBC_SHA256":
		*cs = TLSCipherSuite(tls.TLS_RSA_WITH_AES_128_CBC_SHA256)
	case "RSA_WITH_AES_128_GCM_SHA256":
		*cs = TLSCipherSuite(tls.TLS_RSA_WITH_AES_128_GCM_SHA256)
	case "RSA_WITH_AES_256_GCM_SHA384":
		*cs = TLSCipherSuite(tls.TLS_RSA_WITH_AES_256_GCM_SHA384)
	case "ECDHE_ECDSA_WITH_RC4_128_SHA":
		*cs = TLSCipherSuite(tls.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA)
	case "ECDHE_ECDSA_WITH_AES_128_CBC_SHA":
		*cs = TLSCipherSuite(tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA)
	case "ECDHE_ECDSA_WITH_AES_256_CBC_SHA":
		*cs = TLSCipherSuite(tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA)
	case "ECDHE_RSA_WITH_RC4_128_SHA":
		*cs = TLSCipherSuite(tls.TLS_ECDHE_RSA_WITH_RC4_128_SHA)
	case "ECDHE_RSA_WITH_3DES_EDE_CBC_SHA":
		*cs = TLSCipherSuite(tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA)
	case "ECDHE_RSA_WITH_AES_128_CBC_SHA":
		*cs = TLSCipherSuite(tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA)
	case "ECDHE_RSA_WITH_AES_256_CBC_SHA":
		*cs = TLSCipherSuite(tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA)
	case "ECDHE_ECDSA_WITH_AES_128_CBC_SHA256":
		*cs = TLSCipherSuite(tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256)
	case "ECDHE_RSA_WITH_AES_128_CBC_SHA256":
		*cs = TLSCipherSuite(tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256)
	case "ECDHE_RSA_WITH_AES_128_GCM_SHA256":
		*cs = TLSCipherSuite(tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256)
	case "ECDHE_ECDSA_WITH_AES_128_GCM_SHA256":
		*cs = TLSCipherSuite(tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256)
	case "ECDHE_RSA_WITH_AES_256_GCM_SHA384":
		*cs = TLSCipherSuite(tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384)
	case "ECDHE_ECDSA_WITH_AES_256_GCM_SHA384":
		*cs = TLSCipherSuite(tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384)
	case "ECDHE_RSA_WITH_CHACHA20_POLY1305":
		*cs = TLSCipherSuite(tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305)
	case "ECDHE_ECDSA_WITH_CHACHA20_POLY1305":
		*cs = TLSCipherSuite(tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305)
	default:
		return fmt.Errorf("invalid tls cipher suite: '" + str + "'")
	}
	return
}

func (cs TLSCipherSuite) MarshalText() (data []byte, err error) {
	data = []byte(cs.String())
	return
}

func (cs *TLSCipherSuite) UnmarshalText(data []byte) (err error) {
	return cs.fromString(string(data))
}

//********** TLSCurve

type TLSCurve tls.CurveID

func (c TLSCurve) String() string {
	switch tls.CurveID(c) {
	case tls.CurveP256:
		return "secp256r1"
	case tls.CurveP384:
		return "secp384r1"
	case tls.CurveP521:
		return "secp521r1"
	case tls.X25519:
		return "x25519"
	}
	return "unknown tls echd curve"
}

func (c *TLSCurve) fromString(str string) (err error) {
	switch strings.ToLower(str) {
	case "prime256v1":
		fallthrough
	case "secp256r1":
		*c = TLSCurve(tls.CurveP256)
	case "prime384v1":
		fallthrough
	case "secp384r1":
		*c = TLSCurve(tls.CurveP384)
	case "prime521v1":
		fallthrough
	case "secp521r1":
		*c = TLSCurve(tls.CurveP521)
	case "x25519":
		*c = TLSCurve(tls.X25519)
	default:
		return fmt.Errorf("invalid tls ecdh curve: '" + str + "'")
	}
	return
}

func (c TLSCurve) MarshalText() (data []byte, err error) {
	data = []byte(c.String())
	return
}

func (c *TLSCurve) UnmarshalText(data []byte) (err error) {
	return c.fromString(string(data))
}

//********** TLSSessionTicketKey

type TLSSessionTicketKey [32]byte

func (stk TLSSessionTicketKey) String() string {
	return hex.EncodeToString([]byte(stk[:]))
}

func (stk *TLSSessionTicketKey) fromString(str string) error {
	key, err := hex.DecodeString(str)
	if err != nil {
		return fmt.Errorf("invalid tls session ticket key: %v", err)
	}
	if len(key) != len(stk) {
		return fmt.Errorf("invalid tls session ticket key length: got %d bytes, expected %d bytes", len(key), len(stk))
	}
	copy(stk[:], key)
	return nil
}

func (stk TLSSessionTicketKey) MarshalText() (data []byte, err error) {
	data = []byte(stk.String())
	return
}

func (stk *TLSSessionTicketKey) UnmarshalText(data []byte) (err error) {
	return stk.fromString(string(data))
}

//********** TLSConfig

type TLSConfig struct {
	CertFile                 string              `json:"certificate"`
	KeyFile                  string              `json:"certificate-key"`
	MinVersion               TLSProtocolVersion  `json:"min-protocol-version"`
	MaxVersion               TLSProtocolVersion  `json:"max-protocol-version"`
	CipherSuites             []TLSCipherSuite    `json:"ciphers"`
	PreferServerCipherSuites bool                `json:"prefer-server-ciphers"`
	CurvePreferences         []TLSCurve          `json:"ecdh-curves"`
	SessionTickets           bool                `json:"session-tickets"`
	SessionTicketKey         TLSSessionTicketKey `json:"session-ticket-key"`
}

func (t TLSConfig) ToGoTLSConfig() (*tls.Config, error) {
	cert, err := tls.LoadX509KeyPair(t.CertFile, t.KeyFile)
	if err != nil {
		return nil, err
	}

	cfg := &tls.Config{Certificates: []tls.Certificate{cert}}
	cfg.MinVersion = uint16(t.MinVersion)
	cfg.MaxVersion = uint16(t.MaxVersion)
	for _, cs := range t.CipherSuites {
		cfg.CipherSuites = append(cfg.CipherSuites, uint16(cs))
	}
	cfg.PreferServerCipherSuites = t.PreferServerCipherSuites
	for _, cp := range t.CurvePreferences {
		cfg.CurvePreferences = append(cfg.CurvePreferences, tls.CurveID(cp))
	}
	cfg.SessionTicketsDisabled = !t.SessionTickets
	cfg.SessionTicketKey = [32]byte(t.SessionTicketKey)

	return cfg, nil
}
<commit_msg>added more struct tags (yaml and toml)<commit_after>package tlsconfig

import (
	"crypto/tls"
	"encoding/hex"
	"fmt"
	"strings"
)

//********** ProtocolType

type TLSProtocolVersion uint16

func (pv TLSProtocolVersion) String() string {
	switch pv {
	case tls.VersionSSL30:
		return "SSLv3"
	case tls.VersionTLS10:
		return "TLSv1"
	case tls.VersionTLS11:
		return "TLSv1.1"
	case tls.VersionTLS12:
		return "TLSv1.2"
	}
	return "unknown tls protocol"
}

func (pv *TLSProtocolVersion) fromString(str string) (err error) {
	switch strings.ToUpper(str) {
	case "SSLV3":
		*pv = tls.VersionSSL30
	case "TLSV1":
		*pv = tls.VersionTLS10
	case "TLSV1.1":
		*pv = tls.VersionTLS11
	case "TLSV1.2":
		*pv = tls.VersionTLS12
	default:
		return fmt.Errorf("invalid tls protocol version: '" + str + "'")
	}
	return
}

func (pv TLSProtocolVersion) MarshalText() (data []byte, err error) {
	data = []byte(pv.String())
	return
}

func (pv *TLSProtocolVersion) UnmarshalText(data []byte) (err error) {
	return pv.fromString(string(data))
}

//********** CipherSuite

type TLSCipherSuite uint16

func (cs TLSCipherSuite) String() string {
	switch uint16(cs) {
	case tls.TLS_RSA_WITH_RC4_128_SHA:
		return "RSA_WITH_RC4_128_SHA"
	case tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA:
		return "RSA_WITH_3DES_EDE_CBC_SHA"
	case tls.TLS_RSA_WITH_AES_128_CBC_SHA:
		return "RSA_WITH_AES_128_CBC_SHA"
	case tls.TLS_RSA_WITH_AES_256_CBC_SHA:
		return "RSA_WITH_AES_256_CBC_SHA"
	case tls.TLS_RSA_WITH_AES_128_CBC_SHA256:
		return "RSA_WITH_AES_128_CBC_SHA256"
	case tls.TLS_RSA_WITH_AES_128_GCM_SHA256:
		return "RSA_WITH_AES_128_GCM_SHA256"
	case tls.TLS_RSA_WITH_AES_256_GCM_SHA384:
		return "RSA_WITH_AES_256_GCM_SHA384"
	case tls.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA:
		return "ECDHE_ECDSA_WITH_RC4_128_SHA"
	case tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA:
		return "ECDHE_ECDSA_WITH_AES_128_CBC_SHA"
	case tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA:
		return "ECDHE_ECDSA_WITH_AES_256_CBC_SHA"
	case tls.TLS_ECDHE_RSA_WITH_RC4_128_SHA:
		return "ECDHE_RSA_WITH_RC4_128_SHA"
	case tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA:
		return "ECDHE_RSA_WITH_3DES_EDE_CBC_SHA"
	case tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA:
		return "ECDHE_RSA_WITH_AES_128_CBC_SHA"
	case tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA:
		return "ECDHE_RSA_WITH_AES_256_CBC_SHA"
	case tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256:
		return "ECDHE_ECDSA_WITH_AES_128_CBC_SHA256"
	case tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256:
		return "ECDHE_RSA_WITH_AES_128_CBC_SHA256"
	case tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256:
		return "ECDHE_RSA_WITH_AES_128_GCM_SHA256"
	case tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256:
		return "ECDHE_ECDSA_WITH_AES_128_GCM_SHA256"
	case tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384:
		return "ECDHE_RSA_WITH_AES_256_GCM_SHA384"
	case tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384:
		return "ECDHE_ECDSA_WITH_AES_256_GCM_SHA384"
	case tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305:
		return "ECDHE_RSA_WITH_CHACHA20_POLY1305"
	case tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305:
		return "ECDHE_ECDSA_WITH_CHACHA20_POLY1305"
	}
	return "unknown tls cipher suite"
}

func (cs *TLSCipherSuite) fromString(str string) (err error) {
	switch strings.Replace(strings.ToUpper(str), "-", "_", -1) {
	case "RSA_WITH_RC4_128_SHA":
		*cs = TLSCipherSuite(tls.TLS_RSA_WITH_RC4_128_SHA)
	case "RSA_WITH_3DES_EDE_CBC_SHA":
		*cs = TLSCipherSuite(tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA)
	case "RSA_WITH_AES_128_CBC_SHA":
		*cs = TLSCipherSuite(tls.TLS_RSA_WITH_AES_128_CBC_SHA)
	case "RSA_WITH_AES_256_CBC_SHA":
		*cs = TLSCipherSuite(tls.TLS_RSA_WITH_AES_256_CBC_SHA)
	case "RSA_WITH_AES_128_CBC_SHA256":
		*cs = TLSCipherSuite(tls.TLS_RSA_WITH_AES_128_CBC_SHA256)
	case "RSA_WITH_AES_128_GCM_SHA256":
		*cs = TLSCipherSuite(tls.TLS_RSA_WITH_AES_128_GCM_SHA256)
	case "RSA_WITH_AES_256_GCM_SHA384":
		*cs = TLSCipherSuite(tls.TLS_RSA_WITH_AES_256_GCM_SHA384)
	case "ECDHE_ECDSA_WITH_RC4_128_SHA":
		*cs = TLSCipherSuite(tls.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA)
	case "ECDHE_ECDSA_WITH_AES_128_CBC_SHA":
		*cs = TLSCipherSuite(tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA)
	case "ECDHE_ECDSA_WITH_AES_256_CBC_SHA":
		*cs = TLSCipherSuite(tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA)
	case "ECDHE_RSA_WITH_RC4_128_SHA":
		*cs = TLSCipherSuite(tls.TLS_ECDHE_RSA_WITH_RC4_128_SHA)
	case "ECDHE_RSA_WITH_3DES_EDE_CBC_SHA":
		*cs = TLSCipherSuite(tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA)
	case "ECDHE_RSA_WITH_AES_128_CBC_SHA":
		*cs = TLSCipherSuite(tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA)
	case "ECDHE_RSA_WITH_AES_256_CBC_SHA":
		*cs = TLSCipherSuite(tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA)
	case "ECDHE_ECDSA_WITH_AES_128_CBC_SHA256":
		*cs = TLSCipherSuite(tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256)
	case "ECDHE_RSA_WITH_AES_128_CBC_SHA256":
		*cs = TLSCipherSuite(tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256)
	case "ECDHE_RSA_WITH_AES_128_GCM_SHA256":
		*cs = TLSCipherSuite(tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256)
	case "ECDHE_ECDSA_WITH_AES_128_GCM_SHA256":
		*cs = TLSCipherSuite(tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256)
	case "ECDHE_RSA_WITH_AES_256_GCM_SHA384":
		*cs = TLSCipherSuite(tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384)
	case "ECDHE_ECDSA_WITH_AES_256_GCM_SHA384":
		*cs = TLSCipherSuite(tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384)
	case "ECDHE_RSA_WITH_CHACHA20_POLY1305":
		*cs = TLSCipherSuite(tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305)
	case "ECDHE_ECDSA_WITH_CHACHA20_POLY1305":
		*cs = TLSCipherSuite(tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305)
	default:
		return fmt.Errorf("invalid tls cipher suite: '" + str + "'")
	}
	return
}

func (cs TLSCipherSuite) MarshalText() (data []byte, err error) {
	data = []byte(cs.String())
	return
}

func (cs *TLSCipherSuite) UnmarshalText(data []byte) (err error) {
	return cs.fromString(string(data))
}

//********** TLSCurve

type TLSCurve tls.CurveID

func (c TLSCurve) String() string {
	switch tls.CurveID(c) {
	case tls.CurveP256:
		return "secp256r1"
	case tls.CurveP384:
		return "secp384r1"
	case tls.CurveP521:
		return "secp521r1"
	case tls.X25519:
		return "x25519"
	}
	return "unknown tls echd curve"
}

func (c *TLSCurve) fromString(str string) (err error) {
	switch strings.ToLower(str) {
	case "prime256v1":
		fallthrough
	case "secp256r1":
		*c = TLSCurve(tls.CurveP256)
	case "prime384v1":
		fallthrough
	case "secp384r1":
		*c = TLSCurve(tls.CurveP384)
	case "prime521v1":
		fallthrough
	case "secp521r1":
		*c = TLSCurve(tls.CurveP521)
	case "x25519":
		*c = TLSCurve(tls.X25519)
	default:
		return fmt.Errorf("invalid tls ecdh curve: '" + str + "'")
	}
	return
}

func (c TLSCurve) MarshalText() (data []byte, err error) {
	data = []byte(c.String())
	return
}

func (c *TLSCurve) UnmarshalText(data []byte) (err error) {
	return c.fromString(string(data))
}

//********** TLSSessionTicketKey

type TLSSessionTicketKey [32]byte

func (stk TLSSessionTicketKey) String() string {
	return hex.EncodeToString([]byte(stk[:]))
}

func (stk *TLSSessionTicketKey) fromString(str string) error {
	key, err := hex.DecodeString(str)
	if err != nil {
		return fmt.Errorf("invalid tls session ticket key: %v", err)
	}
	if len(key) != len(stk) {
		return fmt.Errorf("invalid tls session ticket key length: got %d bytes, expected %d bytes", len(key), len(stk))
	}
	copy(stk[:], key)
	return nil
}

func (stk TLSSessionTicketKey) MarshalText() (data []byte, err error) {
	data = []byte(stk.String())
	return
}

func (stk *TLSSessionTicketKey) UnmarshalText(data []byte) (err error) {
	return stk.fromString(string(data))
}

//********** TLSConfig

type TLSConfig struct {
	CertFile                 string              `json:"certificate" yaml:"certificate" toml:"certificate"`
	KeyFile                  string              `json:"certificate-key" yaml:"certificate-key" toml:"certificate-key"`
	MinVersion               TLSProtocolVersion  `json:"min-protocol-version" yaml:"min-protocol-version" toml:"min-protocol-version"`
	MaxVersion               TLSProtocolVersion  `json:"max-protocol-version" yaml:"max-protocol-version" toml:"max-protocol-version"`
	CipherSuites             []TLSCipherSuite    `json:"ciphers" yaml:"ciphers" toml:"ciphers"`
	PreferServerCipherSuites bool                `json:"prefer-server-ciphers" yaml:"prefer-server-ciphers" toml:"prefer-server-ciphers"`
	CurvePreferences         []TLSCurve          `json:"ecdh-curves" yaml:"ecdh-curves" toml:"ecdh-curves"`
	SessionTickets           bool                `json:"session-tickets" yaml:"session-tickets" toml:"session-tickets"`
	SessionTicketKey         TLSSessionTicketKey `json:"session-ticket-key" yaml:"session-ticket-key" toml:"session-ticket-key"`
}

func (t TLSConfig) ToGoTLSConfig() (*tls.Config, error) {
	cert, err := tls.LoadX509KeyPair(t.CertFile, t.KeyFile)
	if err != nil {
		return nil, err
	}

	cfg := &tls.Config{Certificates: []tls.Certificate{cert}}
	cfg.MinVersion = uint16(t.MinVersion)
	cfg.MaxVersion = uint16(t.MaxVersion)
	for _, cs := range t.CipherSuites {
		cfg.CipherSuites = append(cfg.CipherSuites, uint16(cs))
	}
	cfg.PreferServerCipherSuites = t.PreferServerCipherSuites
	for _, cp := range t.CurvePreferences {
		cfg.CurvePreferences = append(cfg.CurvePreferences, tls.CurveID(cp))
	}
	cfg.SessionTicketsDisabled = !t.SessionTickets
	cfg.SessionTicketKey = [32]byte(t.SessionTicketKey)

	return cfg, nil
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"bytes"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
	"regexp"
	"strings"
	"time"

	lz4 "github.com/cloudflare/golz4"
	"github.com/slugalisk/overrustlelogs/common"
)

var commands = map[string]command{
	"compress":   compress,
	"uncompress": uncompress,
	"read":       read,
	"readnicks":  readNicks,
	"nicks":      nicks,
	"migrate":    migrate,
	"namechange": namechange,
	"cleanup":    cleanup,
	"convert":    convertToZSTD,
}

func main() {
	log.SetFlags(log.Lshortfile | log.LstdFlags)
	if len(os.Args) < 2 {
		os.Exit(1)
	}
	if c, ok := commands[os.Args[1]]; ok {
		if err := c(); err != nil {
			fmt.Println(err)
			os.Exit(1)
		}
	} else {
		fmt.Println("invalid command")
		os.Exit(1)
	}
	os.Exit(0)
}

type command func() error

func compress() error {
	if len(os.Args) < 3 {
		return errors.New("not enough args")
	}
	path := os.Args[2]
	_, err := common.CompressFile(path)
	return err
}

func uncompress() error {
	if len(os.Args) < 3 {
		return errors.New("not enough args")
	}
	path := os.Args[2]
	_, err := common.UncompressFile(path)
	return err
}

func nicks() error {
	if len(os.Args) < 3 {
		return errors.New("not enough args")
	}
	path := os.Args[2]
	var data []byte
	data, err := common.ReadCompressedFile(path)
	if os.IsNotExist(err) {
		f, err := os.Open(path)
		if err != nil {
			return err
		}
		data, err = ioutil.ReadAll(f)
		if err != nil {
			return err
		}
	} else if err != nil {
		return err
	}
	r := bufio.NewReaderSize(bytes.NewReader(data), len(data))
	nick := regexp.MustCompile("^\\[[^\\]]+\\]\\s*([a-zA-Z0-9\\_\\-]+):")
	nicks := common.NickList{}
	for {
		line, err := r.ReadSlice('\n')
		if err != nil {
			if err != io.EOF {
				return err
			}
			break
		}
		if ok := nick.Match(line); ok {
			match := nick.FindSubmatch(line)
			nicks.Add(string(match[1]))
		}
	}
	if err := nicks.WriteTo(regexp.MustCompile("\\.txt(\\.gz)?$").ReplaceAllString(path, ".nicks")); err != nil {
		return err
	}
	return nil
}

func read() error {
	if len(os.Args) < 3 {
		return errors.New("not enough args")
	}
	path := os.Args[2]
	if regexp.MustCompile("\\.txt\\.gz$").MatchString(path) {
		buf, err := common.ReadCompressedFile(path)
		if err != nil {
			return err
		}
		os.Stdout.Write(buf)
	} else {
		return errors.New("invalid file")
	}
	return nil
}

func readNicks() error {
	if len(os.Args) < 3 {
		return errors.New("not enough args")
	}
	path := os.Args[2]
	if regexp.MustCompile("\\.nicks\\.gz$").MatchString(path) {
		nicks := common.NickList{}
		if err := common.ReadNickList(nicks, path); err != nil {
			return err
		}
		for nick := range nicks {
			fmt.Println(nick)
		}
	} else {
		return errors.New("invalid file")
	}
	return nil
}

func namechange() error {
	if len(os.Args) < 5 {
		return errors.New("not enough args")
	}
	validNick := regexp.MustCompile("^[a-zA-Z0-9_]+$")
	log := os.Args[2]
	oldName := os.Args[3]
	if !validNick.Match([]byte(oldName)) {
		return errors.New("the old name is not a valid nick")
	}
	newName := os.Args[4]

	replacer := strings.NewReplacer(
		"] "+oldName+":", "] "+newName+":",
		" "+oldName+" ", " "+newName+" ",
		" "+oldName+"\n", " "+newName+"\n",
	)

	log = strings.Replace(log, "txt", "nicks", 1)

	if strings.Contains(log, time.Now().UTC().Format("2006-01-02")) {
		return errors.New("can't modify todays log file")
	}
	fmt.Println(log)

	n := common.NickList{}
	err := common.ReadNickList(n, log)
	if err != nil {
		fmt.Println(err)
		return err
	}

	if _, ok := n[newName]; ok {
		return errors.New("nick already used, choose another one")
	}
	if _, ok := n[oldName]; !ok {
		return errors.New("nick not found")
	}
	n.Remove(oldName)
	n.Add(newName)
	err = n.WriteTo(log[:len(log)-4])
	if err != nil {
		fmt.Println(err)
		return err
	}

	log = strings.Replace(log, "nicks", "txt", 1)

	d, err := common.ReadCompressedFile(log)
	if err != nil {
		fmt.Println(err)
		return err
	}

	newData := []byte(replacer.Replace(string(d)))
	f, err := common.WriteCompressedFile(log, newData)
	if err != nil {
		fmt.Println(err)
		return err
	}
	fmt.Println("replaced nicks in", f.Name())
	f.Close()
	return nil
}

func cleanup() error {
	now := time.Now()

	logsPath := os.Args[2]

	filepaths, err := filepath.Glob(filepath.Join(logsPath, "/*/*/*"))
	if err != nil {
		log.Printf("error getting filepaths: %v", err)
		return err
	}
	log.Printf("found %d files, starting cleanup...", len(filepaths))

	r := regexp.MustCompile(`\.gz$`)

	for _, fp := range filepaths {
		if r.MatchString(fp) || strings.Contains(fp, now.Format("2006-01-02")) {
			continue
		}
		_, err := common.CompressFile(fp)
		if err != nil {
			log.Panicf("error writing compressed file: %v", err)
		}
		log.Println("compressed", fp)
	}
	return nil
}

func convertToZSTD() error {
	logsPath := os.Args[2]

	filepaths, err := filepath.Glob(filepath.Join(logsPath, "/*/*/*"))
	if err != nil {
		log.Printf("error getting filepaths: %v", err)
		return err
	}
	log.Printf("found %d files, starting cleanup...", len(filepaths))
	// now := time.Now().UTC()
	for _, fp := range filepaths {
		if strings.HasSuffix(fp, ".lz4") { //!strings.Contains(fp, now.Format("2006-01-02")) {
			data, err := UncompressFile(fp)
			if err != nil {
				log.Printf("error reading compressed file: %v", err)
			}
			data.Close()
			fp = fp[:len(fp)-4]
		}
		if strings.HasSuffix(fp, ".txt") || strings.HasSuffix(fp, ".nicks") {
			_, err = common.CompressFile(fp)
			if err != nil {
				log.Println(err)
			}
			// log.Println("compressed", fp)
			continue
		}
		// log.Println("error:", fp)
	}
	return nil
}

// ReadCompressedFile read compressed file
func ReadCompressedFile(path string) ([]byte, error) {
	f, err := os.Open(lz4Path(path))
	if err != nil {
		return nil, err
	}
	defer f.Close()
	c, err := ioutil.ReadAll(f)
	if err != nil {
		return nil, err
	}
	size := uint32(0)
	size |= uint32(c[0]) << 24
	size |= uint32(c[1]) << 16
	size |= uint32(c[2]) << 8
	size |= uint32(c[3])
	data := make([]byte, size)
	if err := lz4.Uncompress(c[4:], data); err != nil {
		return nil, err
	}
	return data, nil
}

// UncompressFile uncompress an existing file
func UncompressFile(path string) (*os.File, error) {
	d, err := ReadCompressedFile(path)
	if err != nil {
		return nil, err
	}
	f, err := os.OpenFile(strings.Replace(path, ".lz4", "", -1), os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644)
	if err != nil {
		return nil, err
	}
	defer f.Close()
	if _, err := f.Write(d); err != nil {
		return nil, err
	}
	if err := os.Remove(lz4Path(path)); err != nil {
		return nil, err
	}
	return f, nil
}

func lz4Path(path string) string {
	if path[len(path)-4:] != ".lz4" {
		path += ".lz4"
	}
	return path
}
<commit_msg>create command for toplist of the month<commit_after>package main

import (
	"bufio"
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
	"regexp"
	"sort"
	"strings"
	"time"

	lz4 "github.com/cloudflare/golz4"
	"github.com/slugalisk/overrustlelogs/common"
)

var commands = map[string]command{
	"compress":      compress,
	"uncompress":    uncompress,
	"read":          read,
	"readnicks":     readNicks,
	"nicks":         nicks,
	"migrate":       migrate,
	"namechange":    namechange,
	"cleanup":       cleanup,
	"convert":       convertToZSTD,
	"createtoplist": createTopList,
}

func main() {
	log.SetFlags(log.Lshortfile | log.LstdFlags)
	if len(os.Args) < 2 {
		os.Exit(1)
	}
	if c, ok := commands[os.Args[1]]; ok {
		if err := c(); err != nil {
			fmt.Println(err)
			os.Exit(1)
		}
	} else {
		fmt.Println("invalid command")
		os.Exit(1)
	}
	os.Exit(0)
}

type command func() error

func compress() error {
	if len(os.Args) < 3 {
		return errors.New("not enough args")
	}
	path := os.Args[2]
	_, err := common.CompressFile(path)
	return err
}

func uncompress() error {
	if len(os.Args) < 3 {
		return errors.New("not enough args")
	}
	path := os.Args[2]
	_, err := common.UncompressFile(path)
	return err
}

func nicks() error {
	if len(os.Args) < 3 {
		return errors.New("not enough args")
	}
	path := os.Args[2]
	var data []byte
	data, err := common.ReadCompressedFile(path)
	if os.IsNotExist(err) {
		f, err := os.Open(path)
		if err != nil {
			return err
		}
		data, err = ioutil.ReadAll(f)
		if err != nil {
			return err
		}
	} else if err != nil {
		return err
	}
	r := bufio.NewReaderSize(bytes.NewReader(data), len(data))
	nick := regexp.MustCompile("^\\[[^\\]]+\\]\\s*([a-zA-Z0-9\\_\\-]+):")
	nicks := common.NickList{}
	for {
		line, err := r.ReadSlice('\n')
		if err != nil {
			if err != io.EOF {
				return err
			}
			break
		}
		if ok := nick.Match(line); ok {
			match := nick.FindSubmatch(line)
			nicks.Add(string(match[1]))
		}
	}
	return nicks.WriteTo(regexp.MustCompile("\\.txt(\\.gz)?$").ReplaceAllString(path, ".nicks"))
}

func read() error {
	if len(os.Args) < 3 {
		return errors.New("not enough args")
	}
	path := os.Args[2]
	if regexp.MustCompile("\\.txt\\.gz$").MatchString(path) {
		buf, err := common.ReadCompressedFile(path)
		if err != nil {
			return err
		}
		os.Stdout.Write(buf)
	} else {
		return errors.New("invalid file")
	}
	return nil
}

func readNicks() error {
	if len(os.Args) < 3 {
		return errors.New("not enough args")
	}
	path := os.Args[2]
	if regexp.MustCompile("\\.nicks\\.gz$").MatchString(path) {
		nicks := common.NickList{}
		if err := common.ReadNickList(nicks, path); err != nil {
			return err
		}
		for nick := range nicks {
			fmt.Println(nick)
		}
	} else {
		return errors.New("invalid file")
	}
	return nil
}

func namechange() error {
	if len(os.Args) < 5 {
		return errors.New("not enough args")
	}
	validNick := regexp.MustCompile("^[a-zA-Z0-9_]+$")
	log := os.Args[2]
	oldName := os.Args[3]
	if !validNick.Match([]byte(oldName)) {
		return errors.New("the old name is not a valid nick")
	}
	newName := os.Args[4]

	replacer := strings.NewReplacer(
		"] "+oldName+":", "] "+newName+":",
		" "+oldName+" ", " "+newName+" ",
		" "+oldName+"\n", " "+newName+"\n",
	)

	log = strings.Replace(log, "txt", "nicks", 1)

	if strings.Contains(log, time.Now().UTC().Format("2006-01-02")) {
		return errors.New("can't modify todays log file")
	}
	fmt.Println(log)

	n := common.NickList{}
	err := common.ReadNickList(n, log)
	if err != nil {
		fmt.Println(err)
		return err
	}

	if _, ok := n[newName]; ok {
		return errors.New("nick already used, choose another one")
	}
	if _, ok := n[oldName]; !ok {
		return errors.New("nick not found")
	}
	n.Remove(oldName)
	n.Add(newName)
	err = n.WriteTo(log[:len(log)-4])
	if err != nil {
		fmt.Println(err)
		return err
	}

	log = strings.Replace(log, "nicks", "txt", 1)

	d, err := common.ReadCompressedFile(log)
	if err != nil {
		fmt.Println(err)
		return err
	}

	newData := []byte(replacer.Replace(string(d)))
	f, err := common.WriteCompressedFile(log, newData)
	if err != nil {
		fmt.Println(err)
		return err
	}
	fmt.Println("replaced nicks in", f.Name())
	f.Close()
	return nil
}

func cleanup() error {
	now := time.Now()

	logsPath := os.Args[2]

	filepaths, err := filepath.Glob(filepath.Join(logsPath, "/*/*/*"))
	if err != nil {
		log.Printf("error getting filepaths: %v", err)
		return err
	}
	log.Printf("found %d files, starting cleanup...", len(filepaths))

	r := regexp.MustCompile(`\.gz$`)

	for _, fp := range filepaths {
		if r.MatchString(fp) || strings.Contains(fp, now.Format("2006-01-02")) {
			continue
		}
		_, err := common.CompressFile(fp)
		if err != nil {
			log.Panicf("error writing compressed file: %v", err)
		}
		log.Println("compressed", fp)
	}
	return nil
}

func convertToZSTD() error {
	logsPath := os.Args[2]

	filepaths, err := filepath.Glob(filepath.Join(logsPath, "/*/*/*"))
	if err != nil {
		log.Printf("error getting filepaths: %v", err)
		return err
	}
	log.Printf("found %d files, starting cleanup...", len(filepaths))
	// now := time.Now().UTC()
	for _, fp := range filepaths {
		if strings.HasSuffix(fp, ".lz4") { //!strings.Contains(fp, now.Format("2006-01-02")) {
			data, err := UncompressFile(fp)
			if err != nil {
				log.Printf("error reading compressed file: %v", err)
			}
			data.Close()
			fp = fp[:len(fp)-4]
		}
		if strings.HasSuffix(fp, ".txt") || strings.HasSuffix(fp, ".nicks") {
			_, err = common.CompressFile(fp)
			if err != nil {
				log.Println(err)
			}
			// log.Println("compressed", fp)
			continue
		}
		// log.Println("error:", fp)
	}
	return nil
}

// ReadCompressedFile read compressed file
func ReadCompressedFile(path string) ([]byte, error) {
	f, err := os.Open(lz4Path(path))
	if err != nil {
		return nil, err
	}
	defer f.Close()
	c, err := ioutil.ReadAll(f)
	if err != nil {
		return nil, err
	}
	size := uint32(0)
	size |= uint32(c[0]) << 24
	size |= uint32(c[1]) << 16
	size |= uint32(c[2]) << 8
	size |= uint32(c[3])
	data := make([]byte, size)
	if err := lz4.Uncompress(c[4:], data); err != nil {
		return nil, err
	}
	return data, nil
}

// UncompressFile uncompress an existing file
func UncompressFile(path string) (*os.File, error) {
	d, err := ReadCompressedFile(path)
	if err != nil {
		return nil, err
	}
	f, err := os.OpenFile(strings.Replace(path, ".lz4", "", -1), os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644)
	if err != nil {
		return nil, err
	}
	defer f.Close()
	if _, err := f.Write(d); err != nil {
		return nil, err
	}
	if err := os.Remove(lz4Path(path)); err != nil {
		return nil, err
	}
	return f, nil
}

func lz4Path(path string) string {
	if path[len(path)-4:] != ".lz4" {
		path += ".lz4"
	}
	return path
}

// ./tool createToplist /path/to/logs/ "September *"
func createTopList() error {

	month := os.Args[3]
	logsPath := os.Args[2]

	filepaths, err := filepath.Glob(filepath.Join(logsPath, "/*", month))
	if err != nil {
		log.Printf("error getting filepaths: %v", err)
		return err
	}

	for _, mpath := range filepaths {
		fmt.Printf("creating toplist for %s\n", mpath)
		toplist := make(map[string]*user)

		files, err := ioutil.ReadDir(mpath)
		if err != nil {
			fmt.Printf("error reading folder: %s with: %v", mpath, err)
			continue
		}

		for _, file := range files {
			if !strings.HasSuffix(file.Name(), ".txt.gz") {
				continue
			}
			b, err := common.ReadCompressedFile(filepath.Join(mpath, file.Name()))
			if err != nil {
				fmt.Printf("error: %v reading file %s", err, file.Name())
				continue
			}

			buf := bytes.NewBuffer(b)
			scanner := bufio.NewScanner(buf)

			for scanner.Scan() {
				line := scanner.Bytes()
				// some 2016 files are borked
				if !strings.HasPrefix(scanner.Text(), "[") {
					continue
				}

				endofdate := len("[2017-08-27 01:57:59 UTC] ")
				endofnick := bytes.Index(line[endofdate:], []byte(":"))

				nick := line[endofdate : endofnick+endofdate]

				if _, ok := toplist[string(nick)]; !ok {
					toplist[string(nick)] = &user{
						Lines:    1,
						Bytes:    len(line[endofnick+endofdate:]),
						Username: string(nick),
					}
					continue
				}

				toplist[string(nick)].Lines++
				toplist[string(nick)].Bytes += len(line[endofnick+endofdate:])
			}

			if err := scanner.Err(); err != nil {
				fmt.Fprintln(os.Stderr, "reading standard input:", err)
			}
		}

		users := []*user{}
		for _, u := range toplist {
			users = append(users, u)
		}
		sort.Sort(ByLines(users))

		j, err := json.MarshalIndent(users, "", "\t")
		if err != nil {
			fmt.Printf("error marshaling: %v", err)
			continue
		}

		err = ioutil.WriteFile(filepath.Join(mpath, "toplist.json"), j, 0644)
		if err != nil {
			fmt.Printf("error writing toplist file: %v", err)
		}
	}

	return nil
}

type user struct {
	Username string `json:"username,omitempty"`
	Lines    int    `json:"lines,omitempty"`
	Bytes    int    `json:"bytes,omitempty"`
}

type ByLines []*user

func (a ByLines) Len() int           { return len(a) }
func (a ByLines) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
func (a ByLines) Less(i, j int) bool { return a[i].Lines > a[j].Lines }
<|endoftext|>


<commit_before>// Copyright 2013 Google LLC. All rights reserved.
//
// 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 imageproxy

import (
	"fmt"
	"net/http"
	"net/url"
	"regexp"
	"sort"
	"strconv"
	"strings"
)

const (
	optFit             = "fit"
	optFlipVertical    = "fv"
	optFlipHorizontal  = "fh"
	optFormatJPEG      = "jpeg"
	optFormatPNG       = "png"
	optFormatTIFF      = "tiff"
	optRotatePrefix    = "r"
	optQualityPrefix   = "q"
	optSignaturePrefix = "s"
	optSizeDelimiter   = "x"
	optScaleUp         = "scaleUp"
	optCropX           = "cx"
	optCropY           = "cy"
	optCropWidth       = "cw"
	optCropHeight      = "ch"
	optSmartCrop       = "sc"
)

// URLError reports a malformed URL error.
type URLError struct {
	Message string
	URL     *url.URL
}

func (e URLError) Error() string {
	return fmt.Sprintf("malformed URL %q: %s", e.URL, e.Message)
}

// Options specifies transformations to be performed on the requested image.
type Options struct {
	// See ParseOptions for interpretation of Width and Height values
	Width  float64
	Height float64

	// If true, resize the image to fit in the specified dimensions.  Image
	// will not be cropped, and aspect ratio will be maintained.
	Fit bool

	// Rotate image the specified degrees counter-clockwise.  Valid values
	// are 90, 180, 270.
	Rotate int

	FlipVertical   bool
	FlipHorizontal bool

	// Quality of output image
	Quality int

	// HMAC Signature for signed requests.
	Signature string

	// Allow image to scale beyond its original dimensions.  This value
	// will always be overwritten by the value of Proxy.ScaleUp.
	ScaleUp bool

	// Desired image format. Valid values are "jpeg", "png", "tiff".
	Format string

	// Crop rectangle params
	CropX      float64
	CropY      float64
	CropWidth  float64
	CropHeight float64

	// Automatically find good crop points based on image content.
	SmartCrop bool
}

func (o Options) String() string {
	opts := []string{fmt.Sprintf("%v%s%v", o.Width, optSizeDelimiter, o.Height)}
	if o.Fit {
		opts = append(opts, optFit)
	}
	if o.Rotate != 0 {
		opts = append(opts, fmt.Sprintf("%s%d", optRotatePrefix, o.Rotate))
	}
	if o.FlipVertical {
		opts = append(opts, optFlipVertical)
	}
	if o.FlipHorizontal {
		opts = append(opts, optFlipHorizontal)
	}
	if o.Quality != 0 {
		opts = append(opts, fmt.Sprintf("%s%d", optQualityPrefix, o.Quality))
	}
	if o.Signature != "" {
		opts = append(opts, fmt.Sprintf("%s%s", optSignaturePrefix, o.Signature))
	}
	if o.ScaleUp {
		opts = append(opts, optScaleUp)
	}
	if o.Format != "" {
		opts = append(opts, o.Format)
	}
	if o.CropX != 0 {
		opts = append(opts, fmt.Sprintf("%s%v", optCropX, o.CropX))
	}
	if o.CropY != 0 {
		opts = append(opts, fmt.Sprintf("%s%v", optCropY, o.CropY))
	}
	if o.CropWidth != 0 {
		opts = append(opts, fmt.Sprintf("%s%v", optCropWidth, o.CropWidth))
	}
	if o.CropHeight != 0 {
		opts = append(opts, fmt.Sprintf("%s%v", optCropHeight, o.CropHeight))
	}
	if o.SmartCrop {
		opts = append(opts, optSmartCrop)
	}
	sort.Strings(opts)
	return strings.Join(opts, ",")
}

// transform returns whether o includes transformation options.  Some fields
// are not transform related at all (like Signature), and others only apply in
// the presence of other fields (like Fit).  A non-empty Format value is
// assumed to involve a transformation.
func (o Options) transform() bool {
	return o.Width != 0 || o.Height != 0 || o.Rotate != 0 || o.FlipHorizontal || o.FlipVertical || o.Quality != 0 || o.Format != "" || o.CropX != 0 || o.CropY != 0 || o.CropWidth != 0 || o.CropHeight != 0
}

// ParseOptions parses str as a list of comma separated transformation options.
// The options can be specified in in order, with duplicate options overwriting
// previous values.
//
// Rectangle Crop
//
// There are four options controlling rectangle crop:
//
// 	cx{x}      - X coordinate of top left rectangle corner (default: 0)
// 	cy{y}      - Y coordinate of top left rectangle corner (default: 0)
// 	cw{width}  - rectangle width (default: image width)
// 	ch{height} - rectangle height (default: image height)
//
// For all options, integer values are interpreted as exact pixel values and
// floats between 0 and 1 are interpreted as percentages of the original image
// size. Negative values for cx and cy are measured from the right and bottom
// edges of the image, respectively.
//
// If the crop width or height exceed the width or height of the image, the
// crop width or height will be adjusted, preserving the specified cx and cy
// values.  Rectangular crop is applied before any other transformations.
//
// Smart Crop
//
// The "sc" option will perform a content-aware smart crop to fit the
// requested image width and height dimensions (see Size and Cropping below).
// The smart crop option will override any requested rectangular crop.
//
// Size and Cropping
//
// The size option takes the general form "{width}x{height}", where width and
// height are numbers. Integer values greater than 1 are interpreted as exact
// pixel values. Floats between 0 and 1 are interpreted as percentages of the
// original image size. If either value is omitted or set to 0, it will be
// automatically set to preserve the aspect ratio based on the other dimension.
// If a single number is provided (with no "x" separator), it will be used for
// both height and width.
//
// Depending on the size options specified, an image may be cropped to fit the
// requested size. In all cases, the original aspect ratio of the image will be
// preserved; imageproxy will never stretch the original image.
//
// When no explicit crop mode is specified, the following rules are followed:
//
// - If both width and height values are specified, the image will be scaled to
// fill the space, cropping if necessary to fit the exact dimension.
//
// - If only one of the width or height values is specified, the image will be
// resized to fit the specified dimension, scaling the other dimension as
// needed to maintain the aspect ratio.
//
// If the "fit" option is specified together with a width and height value, the
// image will be resized to fit within a containing box of the specified size.
// As always, the original aspect ratio will be preserved. Specifying the "fit"
// option with only one of either width or height does the same thing as if
// "fit" had not been specified.
//
// Rotation and Flips
//
// The "r{degrees}" option will rotate the image the specified number of
// degrees, counter-clockwise. Valid degrees values are 90, 180, and 270.
//
// The "fv" option will flip the image vertically. The "fh" option will flip
// the image horizontally. Images are flipped after being rotated.
//
// Quality
//
// The "q{qualityPercentage}" option can be used to specify the quality of the
// output file (JPEG only). If not specified, the default value of "95" is used.
//
// Format
//
// The "jpeg", "png", and "tiff"  options can be used to specify the desired
// image format of the proxied image.
//
// Signature
//
// The "s{signature}" option specifies an optional base64 encoded HMAC used to
// sign the remote URL in the request.  The HMAC key used to verify signatures is
// provided to the imageproxy server on startup.
//
// See https://github.com/willnorris/imageproxy/blob/master/docs/url-signing.md
// for examples of generating signatures.
//
// Examples
//
// 	0x0         - no resizing
// 	200x        - 200 pixels wide, proportional height
// 	x0.15       - 15% original height, proportional width
// 	100x150     - 100 by 150 pixels, cropping as needed
// 	100         - 100 pixels square, cropping as needed
// 	150,fit     - scale to fit 150 pixels square, no cropping
// 	100,r90     - 100 pixels square, rotated 90 degrees
// 	100,fv,fh   - 100 pixels square, flipped horizontal and vertical
// 	200x,q60    - 200 pixels wide, proportional height, 60% quality
// 	200x,png    - 200 pixels wide, converted to PNG format
// 	cw100,ch100 - crop image to 100px square, starting at (0,0)
// 	cx10,cy20,cw100,ch200 - crop image starting at (10,20) is 100px wide and 200px tall
func ParseOptions(str string) Options {
	var options Options

	for _, opt := range strings.Split(str, ",") {
		switch {
		case len(opt) == 0:
			break
		case opt == optFit:
			options.Fit = true
		case opt == optFlipVertical:
			options.FlipVertical = true
		case opt == optFlipHorizontal:
			options.FlipHorizontal = true
		case opt == optScaleUp: // this option is intentionally not documented above
			options.ScaleUp = true
		case opt == optFormatJPEG, opt == optFormatPNG, opt == optFormatTIFF:
			options.Format = opt
		case opt == optSmartCrop:
			options.SmartCrop = true
		case strings.HasPrefix(opt, optRotatePrefix):
			value := strings.TrimPrefix(opt, optRotatePrefix)
			options.Rotate, _ = strconv.Atoi(value)
		case strings.HasPrefix(opt, optQualityPrefix):
			value := strings.TrimPrefix(opt, optQualityPrefix)
			options.Quality, _ = strconv.Atoi(value)
		case strings.HasPrefix(opt, optSignaturePrefix):
			options.Signature = strings.TrimPrefix(opt, optSignaturePrefix)
		case strings.HasPrefix(opt, optCropX):
			value := strings.TrimPrefix(opt, optCropX)
			options.CropX, _ = strconv.ParseFloat(value, 64)
		case strings.HasPrefix(opt, optCropY):
			value := strings.TrimPrefix(opt, optCropY)
			options.CropY, _ = strconv.ParseFloat(value, 64)
		case strings.HasPrefix(opt, optCropWidth):
			value := strings.TrimPrefix(opt, optCropWidth)
			options.CropWidth, _ = strconv.ParseFloat(value, 64)
		case strings.HasPrefix(opt, optCropHeight):
			value := strings.TrimPrefix(opt, optCropHeight)
			options.CropHeight, _ = strconv.ParseFloat(value, 64)
		case strings.Contains(opt, optSizeDelimiter):
			size := strings.SplitN(opt, optSizeDelimiter, 2)
			if w := size[0]; w != "" {
				options.Width, _ = strconv.ParseFloat(w, 64)
			}
			if h := size[1]; h != "" {
				options.Height, _ = strconv.ParseFloat(h, 64)
			}
		default:
			if size, err := strconv.ParseFloat(opt, 64); err == nil {
				options.Width = size
				options.Height = size
			}
		}
	}

	return options
}

// Request is an imageproxy request which includes a remote URL of an image to
// proxy, and an optional set of transformations to perform.
type Request struct {
	URL      *url.URL      // URL of the image to proxy
	Options  Options       // Image transformation to perform
	Original *http.Request // The original HTTP request
}

// String returns the request URL as a string, with r.Options encoded in the
// URL fragment.
func (r Request) String() string {
	u := *r.URL
	u.Fragment = r.Options.String()
	return u.String()
}

// NewRequest parses an http.Request into an imageproxy Request.  Options and
// the remote image URL are specified in the request path, formatted as:
// /{options}/{remote_url}.  Options may be omitted, so a request path may
// simply contain /{remote_url}.  The remote URL must be an absolute "http" or
// "https" URL, should not be URL encoded, and may contain a query string.
//
// Assuming an imageproxy server running on localhost, the following are all
// valid imageproxy requests:
//
// 	http://localhost/100x200/http://example.com/image.jpg
// 	http://localhost/100x200,r90/http://example.com/image.jpg?foo=bar
// 	http://localhost//http://example.com/image.jpg
// 	http://localhost/http://example.com/image.jpg
func NewRequest(r *http.Request, baseURL *url.URL) (*Request, error) {
	var err error
	req := &Request{Original: r}

	path := r.URL.EscapedPath()[1:] // strip leading slash
	req.URL, err = parseURL(path)
	if err != nil || !req.URL.IsAbs() {
		// first segment should be options
		parts := strings.SplitN(path, "/", 2)
		if len(parts) != 2 {
			return nil, URLError{"too few path segments", r.URL}
		}

		var err error
		req.URL, err = parseURL(parts[1])
		if err != nil {
			return nil, URLError{fmt.Sprintf("unable to parse remote URL: %v", err), r.URL}
		}

		req.Options = ParseOptions(parts[0])
	}

	if baseURL != nil {
		req.URL = baseURL.ResolveReference(req.URL)
	}

	if !req.URL.IsAbs() {
		return nil, URLError{"must provide absolute remote URL", r.URL}
	}

	if req.URL.Scheme != "http" && req.URL.Scheme != "https" {
		return nil, URLError{"remote URL must have http or https scheme", r.URL}
	}

	// query string is always part of the remote URL
	req.URL.RawQuery = r.URL.RawQuery
	return req, nil
}

var reCleanedURL = regexp.MustCompile(`^(https?):/+([^/])`)

// parseURL parses s as a URL, handling URLs that have been munged by
// path.Clean or a webserver that collapses multiple slashes.
func parseURL(s string) (*url.URL, error) {
	s = reCleanedURL.ReplaceAllString(s, "$1://$2")
	return url.Parse(s)
}
<commit_msg>remove ineffective break statement<commit_after>// Copyright 2013 Google LLC. All rights reserved.
//
// 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 imageproxy

import (
	"fmt"
	"net/http"
	"net/url"
	"regexp"
	"sort"
	"strconv"
	"strings"
)

const (
	optFit             = "fit"
	optFlipVertical    = "fv"
	optFlipHorizontal  = "fh"
	optFormatJPEG      = "jpeg"
	optFormatPNG       = "png"
	optFormatTIFF      = "tiff"
	optRotatePrefix    = "r"
	optQualityPrefix   = "q"
	optSignaturePrefix = "s"
	optSizeDelimiter   = "x"
	optScaleUp         = "scaleUp"
	optCropX           = "cx"
	optCropY           = "cy"
	optCropWidth       = "cw"
	optCropHeight      = "ch"
	optSmartCrop       = "sc"
)

// URLError reports a malformed URL error.
type URLError struct {
	Message string
	URL     *url.URL
}

func (e URLError) Error() string {
	return fmt.Sprintf("malformed URL %q: %s", e.URL, e.Message)
}

// Options specifies transformations to be performed on the requested image.
type Options struct {
	// See ParseOptions for interpretation of Width and Height values
	Width  float64
	Height float64

	// If true, resize the image to fit in the specified dimensions.  Image
	// will not be cropped, and aspect ratio will be maintained.
	Fit bool

	// Rotate image the specified degrees counter-clockwise.  Valid values
	// are 90, 180, 270.
	Rotate int

	FlipVertical   bool
	FlipHorizontal bool

	// Quality of output image
	Quality int

	// HMAC Signature for signed requests.
	Signature string

	// Allow image to scale beyond its original dimensions.  This value
	// will always be overwritten by the value of Proxy.ScaleUp.
	ScaleUp bool

	// Desired image format. Valid values are "jpeg", "png", "tiff".
	Format string

	// Crop rectangle params
	CropX      float64
	CropY      float64
	CropWidth  float64
	CropHeight float64

	// Automatically find good crop points based on image content.
	SmartCrop bool
}

func (o Options) String() string {
	opts := []string{fmt.Sprintf("%v%s%v", o.Width, optSizeDelimiter, o.Height)}
	if o.Fit {
		opts = append(opts, optFit)
	}
	if o.Rotate != 0 {
		opts = append(opts, fmt.Sprintf("%s%d", optRotatePrefix, o.Rotate))
	}
	if o.FlipVertical {
		opts = append(opts, optFlipVertical)
	}
	if o.FlipHorizontal {
		opts = append(opts, optFlipHorizontal)
	}
	if o.Quality != 0 {
		opts = append(opts, fmt.Sprintf("%s%d", optQualityPrefix, o.Quality))
	}
	if o.Signature != "" {
		opts = append(opts, fmt.Sprintf("%s%s", optSignaturePrefix, o.Signature))
	}
	if o.ScaleUp {
		opts = append(opts, optScaleUp)
	}
	if o.Format != "" {
		opts = append(opts, o.Format)
	}
	if o.CropX != 0 {
		opts = append(opts, fmt.Sprintf("%s%v", optCropX, o.CropX))
	}
	if o.CropY != 0 {
		opts = append(opts, fmt.Sprintf("%s%v", optCropY, o.CropY))
	}
	if o.CropWidth != 0 {
		opts = append(opts, fmt.Sprintf("%s%v", optCropWidth, o.CropWidth))
	}
	if o.CropHeight != 0 {
		opts = append(opts, fmt.Sprintf("%s%v", optCropHeight, o.CropHeight))
	}
	if o.SmartCrop {
		opts = append(opts, optSmartCrop)
	}
	sort.Strings(opts)
	return strings.Join(opts, ",")
}

// transform returns whether o includes transformation options.  Some fields
// are not transform related at all (like Signature), and others only apply in
// the presence of other fields (like Fit).  A non-empty Format value is
// assumed to involve a transformation.
func (o Options) transform() bool {
	return o.Width != 0 || o.Height != 0 || o.Rotate != 0 || o.FlipHorizontal || o.FlipVertical || o.Quality != 0 || o.Format != "" || o.CropX != 0 || o.CropY != 0 || o.CropWidth != 0 || o.CropHeight != 0
}

// ParseOptions parses str as a list of comma separated transformation options.
// The options can be specified in in order, with duplicate options overwriting
// previous values.
//
// Rectangle Crop
//
// There are four options controlling rectangle crop:
//
// 	cx{x}      - X coordinate of top left rectangle corner (default: 0)
// 	cy{y}      - Y coordinate of top left rectangle corner (default: 0)
// 	cw{width}  - rectangle width (default: image width)
// 	ch{height} - rectangle height (default: image height)
//
// For all options, integer values are interpreted as exact pixel values and
// floats between 0 and 1 are interpreted as percentages of the original image
// size. Negative values for cx and cy are measured from the right and bottom
// edges of the image, respectively.
//
// If the crop width or height exceed the width or height of the image, the
// crop width or height will be adjusted, preserving the specified cx and cy
// values.  Rectangular crop is applied before any other transformations.
//
// Smart Crop
//
// The "sc" option will perform a content-aware smart crop to fit the
// requested image width and height dimensions (see Size and Cropping below).
// The smart crop option will override any requested rectangular crop.
//
// Size and Cropping
//
// The size option takes the general form "{width}x{height}", where width and
// height are numbers. Integer values greater than 1 are interpreted as exact
// pixel values. Floats between 0 and 1 are interpreted as percentages of the
// original image size. If either value is omitted or set to 0, it will be
// automatically set to preserve the aspect ratio based on the other dimension.
// If a single number is provided (with no "x" separator), it will be used for
// both height and width.
//
// Depending on the size options specified, an image may be cropped to fit the
// requested size. In all cases, the original aspect ratio of the image will be
// preserved; imageproxy will never stretch the original image.
//
// When no explicit crop mode is specified, the following rules are followed:
//
// - If both width and height values are specified, the image will be scaled to
// fill the space, cropping if necessary to fit the exact dimension.
//
// - If only one of the width or height values is specified, the image will be
// resized to fit the specified dimension, scaling the other dimension as
// needed to maintain the aspect ratio.
//
// If the "fit" option is specified together with a width and height value, the
// image will be resized to fit within a containing box of the specified size.
// As always, the original aspect ratio will be preserved. Specifying the "fit"
// option with only one of either width or height does the same thing as if
// "fit" had not been specified.
//
// Rotation and Flips
//
// The "r{degrees}" option will rotate the image the specified number of
// degrees, counter-clockwise. Valid degrees values are 90, 180, and 270.
//
// The "fv" option will flip the image vertically. The "fh" option will flip
// the image horizontally. Images are flipped after being rotated.
//
// Quality
//
// The "q{qualityPercentage}" option can be used to specify the quality of the
// output file (JPEG only). If not specified, the default value of "95" is used.
//
// Format
//
// The "jpeg", "png", and "tiff"  options can be used to specify the desired
// image format of the proxied image.
//
// Signature
//
// The "s{signature}" option specifies an optional base64 encoded HMAC used to
// sign the remote URL in the request.  The HMAC key used to verify signatures is
// provided to the imageproxy server on startup.
//
// See https://github.com/willnorris/imageproxy/blob/master/docs/url-signing.md
// for examples of generating signatures.
//
// Examples
//
// 	0x0         - no resizing
// 	200x        - 200 pixels wide, proportional height
// 	x0.15       - 15% original height, proportional width
// 	100x150     - 100 by 150 pixels, cropping as needed
// 	100         - 100 pixels square, cropping as needed
// 	150,fit     - scale to fit 150 pixels square, no cropping
// 	100,r90     - 100 pixels square, rotated 90 degrees
// 	100,fv,fh   - 100 pixels square, flipped horizontal and vertical
// 	200x,q60    - 200 pixels wide, proportional height, 60% quality
// 	200x,png    - 200 pixels wide, converted to PNG format
// 	cw100,ch100 - crop image to 100px square, starting at (0,0)
// 	cx10,cy20,cw100,ch200 - crop image starting at (10,20) is 100px wide and 200px tall
func ParseOptions(str string) Options {
	var options Options

	for _, opt := range strings.Split(str, ",") {
		switch {
		case len(opt) == 0: // do nothing
		case opt == optFit:
			options.Fit = true
		case opt == optFlipVertical:
			options.FlipVertical = true
		case opt == optFlipHorizontal:
			options.FlipHorizontal = true
		case opt == optScaleUp: // this option is intentionally not documented above
			options.ScaleUp = true
		case opt == optFormatJPEG, opt == optFormatPNG, opt == optFormatTIFF:
			options.Format = opt
		case opt == optSmartCrop:
			options.SmartCrop = true
		case strings.HasPrefix(opt, optRotatePrefix):
			value := strings.TrimPrefix(opt, optRotatePrefix)
			options.Rotate, _ = strconv.Atoi(value)
		case strings.HasPrefix(opt, optQualityPrefix):
			value := strings.TrimPrefix(opt, optQualityPrefix)
			options.Quality, _ = strconv.Atoi(value)
		case strings.HasPrefix(opt, optSignaturePrefix):
			options.Signature = strings.TrimPrefix(opt, optSignaturePrefix)
		case strings.HasPrefix(opt, optCropX):
			value := strings.TrimPrefix(opt, optCropX)
			options.CropX, _ = strconv.ParseFloat(value, 64)
		case strings.HasPrefix(opt, optCropY):
			value := strings.TrimPrefix(opt, optCropY)
			options.CropY, _ = strconv.ParseFloat(value, 64)
		case strings.HasPrefix(opt, optCropWidth):
			value := strings.TrimPrefix(opt, optCropWidth)
			options.CropWidth, _ = strconv.ParseFloat(value, 64)
		case strings.HasPrefix(opt, optCropHeight):
			value := strings.TrimPrefix(opt, optCropHeight)
			options.CropHeight, _ = strconv.ParseFloat(value, 64)
		case strings.Contains(opt, optSizeDelimiter):
			size := strings.SplitN(opt, optSizeDelimiter, 2)
			if w := size[0]; w != "" {
				options.Width, _ = strconv.ParseFloat(w, 64)
			}
			if h := size[1]; h != "" {
				options.Height, _ = strconv.ParseFloat(h, 64)
			}
		default:
			if size, err := strconv.ParseFloat(opt, 64); err == nil {
				options.Width = size
				options.Height = size
			}
		}
	}

	return options
}

// Request is an imageproxy request which includes a remote URL of an image to
// proxy, and an optional set of transformations to perform.
type Request struct {
	URL      *url.URL      // URL of the image to proxy
	Options  Options       // Image transformation to perform
	Original *http.Request // The original HTTP request
}

// String returns the request URL as a string, with r.Options encoded in the
// URL fragment.
func (r Request) String() string {
	u := *r.URL
	u.Fragment = r.Options.String()
	return u.String()
}

// NewRequest parses an http.Request into an imageproxy Request.  Options and
// the remote image URL are specified in the request path, formatted as:
// /{options}/{remote_url}.  Options may be omitted, so a request path may
// simply contain /{remote_url}.  The remote URL must be an absolute "http" or
// "https" URL, should not be URL encoded, and may contain a query string.
//
// Assuming an imageproxy server running on localhost, the following are all
// valid imageproxy requests:
//
// 	http://localhost/100x200/http://example.com/image.jpg
// 	http://localhost/100x200,r90/http://example.com/image.jpg?foo=bar
// 	http://localhost//http://example.com/image.jpg
// 	http://localhost/http://example.com/image.jpg
func NewRequest(r *http.Request, baseURL *url.URL) (*Request, error) {
	var err error
	req := &Request{Original: r}

	path := r.URL.EscapedPath()[1:] // strip leading slash
	req.URL, err = parseURL(path)
	if err != nil || !req.URL.IsAbs() {
		// first segment should be options
		parts := strings.SplitN(path, "/", 2)
		if len(parts) != 2 {
			return nil, URLError{"too few path segments", r.URL}
		}

		var err error
		req.URL, err = parseURL(parts[1])
		if err != nil {
			return nil, URLError{fmt.Sprintf("unable to parse remote URL: %v", err), r.URL}
		}

		req.Options = ParseOptions(parts[0])
	}

	if baseURL != nil {
		req.URL = baseURL.ResolveReference(req.URL)
	}

	if !req.URL.IsAbs() {
		return nil, URLError{"must provide absolute remote URL", r.URL}
	}

	if req.URL.Scheme != "http" && req.URL.Scheme != "https" {
		return nil, URLError{"remote URL must have http or https scheme", r.URL}
	}

	// query string is always part of the remote URL
	req.URL.RawQuery = r.URL.RawQuery
	return req, nil
}

var reCleanedURL = regexp.MustCompile(`^(https?):/+([^/])`)

// parseURL parses s as a URL, handling URLs that have been munged by
// path.Clean or a webserver that collapses multiple slashes.
func parseURL(s string) (*url.URL, error) {
	s = reCleanedURL.ReplaceAllString(s, "$1://$2")
	return url.Parse(s)
}
<|endoftext|>


<commit_before>package traffic

import (
	"net/http"
	"path/filepath"
)

type StaticMiddleware struct {
	publicPath string
}

func (middleware *StaticMiddleware) ServeHTTP(w ResponseWriter, r *Request, next NextMiddlewareFunc) {
	callNext := func() {
		if nextMiddleware := next(); nextMiddleware != nil {
			nextMiddleware.ServeHTTP(w, r, next)
		}
	}

	dir := http.Dir(middleware.publicPath)
	path := r.URL.Path

	file, err := dir.Open(path)
	if err != nil {
		callNext()
		return
	}
	defer file.Close()

	if info, err := file.Stat(); err == nil && !info.IsDir() {
		w.Header().Del("Content-Type")
		fullPath := filepath.Join(middleware.publicPath, path)
		http.ServeFile(w, r.Request, fullPath)
		return
	}

	callNext()
}

func NewStaticMiddleware(publicPath string) *StaticMiddleware {
	middleware := &StaticMiddleware{
		publicPath: publicPath,
	}

	return middleware
}
<commit_msg>Replace ServeFile with ServeContent to use previously opened file<commit_after>package traffic

import (
	"net/http"
)

type StaticMiddleware struct {
	publicPath string
}

func (middleware *StaticMiddleware) ServeHTTP(w ResponseWriter, r *Request, next NextMiddlewareFunc) {
	callNext := func() {
		if nextMiddleware := next(); nextMiddleware != nil {
			nextMiddleware.ServeHTTP(w, r, next)
		}
	}

	dir := http.Dir(middleware.publicPath)
	path := r.URL.Path

	file, err := dir.Open(path)
	if err != nil {
		callNext()
		return
	}
	defer file.Close()

	if info, err := file.Stat(); err == nil && !info.IsDir() {
		w.Header().Del("Content-Type")
		http.ServeContent(w, r.Request, path, info.ModTime(), file)
		return
	}

	callNext()
}

func NewStaticMiddleware(publicPath string) *StaticMiddleware {
	middleware := &StaticMiddleware{
		publicPath: publicPath,
	}

	return middleware
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"io"
	"log"
	"net"
	"os"
	"strconv"
	"syscall"
	"time"
)

/* No way to teach net or syscall about vsock sockaddr, so go right to C */

/*
#include <sys/socket.h>
#include "include/uapi/linux/vm_sockets.h"
int bind_sockaddr_vm(int fd, const struct sockaddr_vm *sa_vm) {
    return bind(fd, (const struct sockaddr*)sa_vm, sizeof(*sa_vm));
}
int connect_sockaddr_vm(int fd, const struct sockaddr_vm *sa_vm) {
    return connect(fd, (const struct sockaddr*)sa_vm, sizeof(*sa_vm));
}
int accept_vm(int fd, struct sockaddr_vm *sa_vm, socklen_t *sa_vm_len) {
    return accept4(fd, (struct sockaddr *)sa_vm, sa_vm_len, 0);
}
*/
import "C"

const (
	AF_VSOCK      = 40
	VSOCK_CID_ANY = 4294967295 /* 2^32-1 */
)

var (
	portstr string
	sock    string
	detach  bool
)

func init() {
	flag.StringVar(&portstr, "port", "2376", "vsock port to forward")
	flag.StringVar(&sock, "sock", "/var/run/docker.sock", "path of the local Unix domain socket to forward to")
	flag.BoolVar(&detach, "detach", false, "detach from terminal")
}

func main() {
	log.SetFlags(log.LstdFlags)
	flag.Parse()

	if detach {
		logFile, err := os.Create("/var/log/vsudd.log")
		if err != nil {
			log.Fatalln("Failed to open log file", err)
		}
		log.SetOutput(logFile)
		null, err := os.OpenFile("/dev/null", os.O_RDWR, 0)
		if err != nil {
			log.Fatalln("Failed to open /dev/null", err)
		}
		fd := null.Fd()
		syscall.Dup2(int(fd), int(os.Stdin.Fd()))
		syscall.Dup2(int(fd), int(os.Stdout.Fd()))
		syscall.Dup2(int(fd), int(os.Stderr.Fd()))
	}

	port, err := strconv.ParseUint(portstr, 10, 32)
	if err != nil {
		log.Fatalln("Can't convert %s to a uint.", portstr, err)
	}
	vsockListen(uint(port))
}

func vsockListen(port uint) {
	accept_fd, err := syscall.Socket(AF_VSOCK, syscall.SOCK_STREAM, 0)
	if err != nil {
		log.Fatal(err)
	}

	sa := C.struct_sockaddr_vm{}
	sa.svm_family = AF_VSOCK
	sa.svm_port = C.uint(port)
	sa.svm_cid = VSOCK_CID_ANY

	if ret := C.bind_sockaddr_vm(C.int(accept_fd), &sa); ret != 0 {
		log.Fatal(fmt.Sprintf("failed bind vsock connection to %08x.%08x, returned %d",
			sa.svm_cid, sa.svm_port, ret))
	}

	err = syscall.Listen(accept_fd, syscall.SOMAXCONN)
	if err != nil {
		log.Fatalln("Failed to listen to VSOCK", err)
	}

	log.Printf("Listening on fd %d", accept_fd)

	connid := 0

	for {
		var accept_sa C.struct_sockaddr_vm
		var accept_sa_len C.socklen_t

		connid++
		accept_sa_len = C.sizeof_struct_sockaddr_vm
		fd, err := C.accept_vm(C.int(accept_fd), &accept_sa, &accept_sa_len)
		if err != nil {
			log.Fatalln("Error accepting connection", err)
		}
		log.Printf("%d Accepted connection on fd %d from %08x.%08x",
			connid, fd, uint(accept_sa.svm_cid), uint(accept_sa.svm_port))
		go handleOne(connid, int(fd))
	}
}

func handleOne(connid int, fd int) {
	vsock := os.NewFile(uintptr(fd), fmt.Sprintf("vsock:%d", fd))

	defer func() {
		log.Println(connid, "Closing vsock", vsock)
		if err := vsock.Close(); err != nil {
			log.Println(connid, "Error closing", vsock, ":", err)
		}
	}()

	var docker *net.UnixConn
	var err error

	// Cope with the server socket appearing up to 10s later
	for i := 0; i < 200; i++ {
		docker, err = net.DialUnix("unix", nil, &net.UnixAddr{sock, "unix"})
		if err == nil {
			break
		}
		time.Sleep(50 * time.Millisecond)
	}
	if err != nil {
		// If the forwarding program has broken then close and continue
		log.Println(connid, "Failed to connect to Unix domain socket after 10s", sock, err)
		return
	}
	defer func() {
		log.Println(connid, "Closing docker", docker)
		if err := docker.Close(); err != nil {
			log.Println(connid, "Error closing", docker, ":", err)
		}
	}()
	log.Println(connid, "Connected to docker", docker)

	w := make(chan int64)
	go func() {
		n, err := io.Copy(vsock, docker)
		if err != nil {
			log.Println(connid, "error copying from docker to vsock:", err)
		}
		log.Println(connid, "copying from docker to vsock: ", n, "bytes done")

		err = docker.CloseRead()
		if err != nil {
			log.Println(connid, "error CloseRead on docker socket:", err)
		}
		err = syscall.Shutdown(fd, syscall.SHUT_WR)
		if err != nil {
			log.Println(connid, "error SHUT_WR on vsock:", err)
		}
		w <- n
	}()

	n, err := io.Copy(docker, vsock)
	if err != nil {
		log.Println(connid, "error copying from vsock to docker:", err)
	}
	log.Println(connid, "copying from vsock to docker: ", n, "bytes done")
	totalRead := n

	err = docker.CloseWrite()
	if err != nil {
		log.Println(connid, "error CloseWrite on docker socket:", err)
	}
	err = syscall.Shutdown(fd, syscall.SHUT_RD)
	if err != nil {
		log.Println(connid, "error SHUT_RD on vsock:", err)
	}

	totalWritten := <-w
	log.Println(connid, "Done. read:", totalRead, "written:", totalWritten)
}
<commit_msg>vsudd: Add support for Hyper-V sockets<commit_after>package main

import (
	"flag"
	"fmt"
	"io"
	"log"
	"net"
	"os"
	"strconv"
	"strings"
	"syscall"
	"time"
)

/* No way to teach net or syscall about vsock sockaddr, so go right to C */

/*
#include <stdio.h>
#include <sys/socket.h>
#include "include/uapi/linux/vm_sockets.h"
int bind_sockaddr_vm(int fd, const struct sockaddr_vm *sa_vm) {
    return bind(fd, (const struct sockaddr*)sa_vm, sizeof(*sa_vm));
}
int connect_sockaddr_vm(int fd, const struct sockaddr_vm *sa_vm) {
    return connect(fd, (const struct sockaddr*)sa_vm, sizeof(*sa_vm));
}
int accept_vm(int fd, struct sockaddr_vm *sa_vm, socklen_t *sa_vm_len) {
    return accept4(fd, (struct sockaddr *)sa_vm, sa_vm_len, 0);
}

struct sockaddr_hv {
	unsigned short shv_family;
	unsigned short reserved;
	unsigned char  shv_vm_id[16];
	unsigned char  shv_service_id[16];
};
int bind_sockaddr_hv(int fd, const struct sockaddr_hv *sa_hv) {
    return bind(fd, (const struct sockaddr*)sa_hv, sizeof(*sa_hv));
}
int connect_sockaddr_hv(int fd, const struct sockaddr_hv *sa_hv) {
    return connect(fd, (const struct sockaddr*)sa_hv, sizeof(*sa_hv));
}
int accept_hv(int fd, struct sockaddr_hv *sa_hv, socklen_t *sa_hv_len) {
    return accept4(fd, (struct sockaddr *)sa_hv, sa_hv_len, 0);
}
*/
import "C"

type GUID [16]byte

const (
	AF_VSOCK      = 40
	VSOCK_CID_ANY = 4294967295 /* 2^32-1 */

	AF_HYPERV     = 42
	SHV_PROTO_RAW = 1
)

var (
	portstr string
	sock    string
	detach  bool
	SHV_VMID_GUEST = GUID{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
)

func init() {
	flag.StringVar(&portstr, "port", "2376", "vsock port to forward")
	flag.StringVar(&sock, "sock", "/var/run/docker.sock", "path of the local Unix domain socket to forward to")
	flag.BoolVar(&detach, "detach", false, "detach from terminal")
}

func main() {
	log.SetFlags(log.LstdFlags)
	flag.Parse()

	if detach {
		logFile, err := os.Create("/var/log/vsudd.log")
		if err != nil {
			log.Fatalln("Failed to open log file", err)
		}
		log.SetOutput(logFile)
		null, err := os.OpenFile("/dev/null", os.O_RDWR, 0)
		if err != nil {
			log.Fatalln("Failed to open /dev/null", err)
		}
		fd := null.Fd()
		syscall.Dup2(int(fd), int(os.Stdin.Fd()))
		syscall.Dup2(int(fd), int(os.Stdout.Fd()))
		syscall.Dup2(int(fd), int(os.Stderr.Fd()))
	}

	if strings.Contains(portstr, "-") {
		guid, err := guidFromString(portstr)
		if err != nil {
			log.Fatalln("Failed to parse GUID", portstr, err)
		}
		hvsockListen(guid)
	} else {
		port, err := strconv.ParseUint(portstr, 10, 32)
		if err != nil {
			log.Fatalln("Can't convert %s to a uint.", portstr, err)
		}
		vsockListen(uint(port))
	}
}

func hvsockListen(port GUID) {
	accept_fd, err := syscall.Socket(AF_HYPERV, syscall.SOCK_STREAM, SHV_PROTO_RAW)
	if err != nil {
		log.Fatal(err)
	}

	sa := C.struct_sockaddr_hv{}
	sa.shv_family = AF_HYPERV
	sa.reserved = 0
	/* TODO: Turn this into a function */
	for i := 0; i < 16; i++ {
		sa.shv_vm_id[i] = C.uchar(SHV_VMID_GUEST[i])
	}
	for i := 0; i < 16; i++ {
		sa.shv_service_id[i] = C.uchar(port[i])
	}

	if ret := C.bind_sockaddr_hv(C.int(accept_fd), &sa); ret != 0 {
		log.Fatal(fmt.Sprintf("failed bind hvsock connection to %s.%s, returned %d",
			SHV_VMID_GUEST.toString(), port.toString(), ret))
	}

	err = syscall.Listen(accept_fd, syscall.SOMAXCONN)
	if err != nil {
		log.Fatalln("Failed to listen to VSOCK", err)
	}

	log.Printf("Listening on fd %d", accept_fd)

	connid := 0

	for {
		var accept_sa C.struct_sockaddr_hv
		var accept_sa_len C.socklen_t

		connid++
		accept_sa_len = C.sizeof_struct_sockaddr_hv
		fd, err := C.accept_hv(C.int(accept_fd), &accept_sa, &accept_sa_len)
		if err != nil {
			log.Fatalln("Error accepting connection", err)
		}

		accept_vm_id := guidFromC(accept_sa.shv_vm_id)
		accept_svc_id := guidFromC(accept_sa.shv_service_id)
		log.Printf("%d Accepted connection on fd %d from %s.%s",
			connid, fd, accept_vm_id.toString(), accept_svc_id.toString())
		go handleOne(connid, int(fd))
	}
}

func vsockListen(port uint) {
	accept_fd, err := syscall.Socket(AF_VSOCK, syscall.SOCK_STREAM, 0)
	if err != nil {
		log.Fatal(err)
	}

	sa := C.struct_sockaddr_vm{}
	sa.svm_family = AF_VSOCK
	sa.svm_port = C.uint(port)
	sa.svm_cid = VSOCK_CID_ANY

	if ret := C.bind_sockaddr_vm(C.int(accept_fd), &sa); ret != 0 {
		log.Fatal(fmt.Sprintf("failed bind vsock connection to %08x.%08x, returned %d",
			sa.svm_cid, sa.svm_port, ret))
	}

	err = syscall.Listen(accept_fd, syscall.SOMAXCONN)
	if err != nil {
		log.Fatalln("Failed to listen to VSOCK", err)
	}

	log.Printf("Listening on fd %d", accept_fd)

	connid := 0

	for {
		var accept_sa C.struct_sockaddr_vm
		var accept_sa_len C.socklen_t

		connid++
		accept_sa_len = C.sizeof_struct_sockaddr_vm
		fd, err := C.accept_vm(C.int(accept_fd), &accept_sa, &accept_sa_len)
		if err != nil {
			log.Fatalln("Error accepting connection", err)
		}
		log.Printf("%d Accepted connection on fd %d from %08x.%08x",
			connid, fd, uint(accept_sa.svm_cid), uint(accept_sa.svm_port))
		go handleOne(connid, int(fd))
	}
}

func handleOne(connid int, fd int) {
	vsock := os.NewFile(uintptr(fd), fmt.Sprintf("vsock:%d", fd))

	defer func() {
		log.Println(connid, "Closing vsock", vsock)
		if err := vsock.Close(); err != nil {
			log.Println(connid, "Error closing", vsock, ":", err)
		}
	}()

	var docker *net.UnixConn
	var err error

	// Cope with the server socket appearing up to 10s later
	for i := 0; i < 200; i++ {
		docker, err = net.DialUnix("unix", nil, &net.UnixAddr{sock, "unix"})
		if err == nil {
			break
		}
		time.Sleep(50 * time.Millisecond)
	}
	if err != nil {
		// If the forwarding program has broken then close and continue
		log.Println(connid, "Failed to connect to Unix domain socket after 10s", sock, err)
		return
	}
	defer func() {
		log.Println(connid, "Closing docker", docker)
		if err := docker.Close(); err != nil {
			log.Println(connid, "Error closing", docker, ":", err)
		}
	}()
	log.Println(connid, "Connected to docker", docker)

	w := make(chan int64)
	go func() {
		n, err := io.Copy(vsock, docker)
		if err != nil {
			log.Println(connid, "error copying from docker to vsock:", err)
		}
		log.Println(connid, "copying from docker to vsock: ", n, "bytes done")

		err = docker.CloseRead()
		if err != nil {
			log.Println(connid, "error CloseRead on docker socket:", err)
		}
		err = syscall.Shutdown(fd, syscall.SHUT_WR)
		if err != nil {
			log.Println(connid, "error SHUT_WR on vsock:", err)
		}
		w <- n
	}()

	n, err := io.Copy(docker, vsock)
	if err != nil {
		log.Println(connid, "error copying from vsock to docker:", err)
	}
	log.Println(connid, "copying from vsock to docker: ", n, "bytes done")
	totalRead := n

	err = docker.CloseWrite()
	if err != nil {
		log.Println(connid, "error CloseWrite on docker socket:", err)
	}
	err = syscall.Shutdown(fd, syscall.SHUT_RD)
	if err != nil {
		log.Println(connid, "error SHUT_RD on vsock:", err)
	}

	totalWritten := <-w
	log.Println(connid, "Done. read:", totalRead, "written:", totalWritten)
}

func (g *GUID) toString() string {
	/* XXX This assume little endian */
	return fmt.Sprintf("%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x",
		g[3], g[2], g[1], g[0],
		g[5], g[4],
		g[7], g[6],
		g[8], g[9],
		g[10], g[11], g[12], g[13], g[14], g[15])
}

func guidFromString(s string) (GUID, error) {
	var g GUID
	var err error
	_, err = fmt.Sscanf(s, "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x",
		&g[3], &g[2], &g[1], &g[0],
		&g[5], &g[4],
		&g[7], &g[6],
		&g[8], &g[9],
		&g[10], &g[11], &g[12], &g[13], &g[14], &g[15])
	return g, err
}

func guidFromC(cg [16]C.uchar) GUID {
       var g GUID
       for i := 0; i < 16; i++ {
               g[i] = byte(cg[i])
       }
       return g
}
<|endoftext|>


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

// Package dataflow provides data flow analyses that can be performed on a
// previously constructed control flow graph, including a reaching definitions
// analysis and a live variables analysis for local variables.
package dataflow

// This file contains functions common to all data flow analyses, as well as
// one exported function.

import (
	"go/ast"
	"go/token"

	"code.google.com/p/go.tools/go/loader"
	"code.google.com/p/go.tools/go/types"
)

// ReferencedVars returns the sets of local variables that are defined or used
// within the given list of statements (based on syntax).
func ReferencedVars(stmts []ast.Stmt, info *loader.PackageInfo) (def, use map[*types.Var]struct{}) {
	def = make(map[*types.Var]struct{})
	use = make(map[*types.Var]struct{})

	for _, stmt := range stmts {
		for _, d := range defs(stmt, info) {
			def[d] = struct{}{}
		}
		for _, u := range uses(stmt, info) {
			use[u] = struct{}{}
		}
	}
	return def, use
}

// defs extracts any local variables whose values are assigned in the given statement.
func defs(stmt ast.Stmt, info *loader.PackageInfo) []*types.Var {
	idnts := make(map[*ast.Ident]struct{})

	switch stmt := stmt.(type) {
	case *ast.DeclStmt: // vars (1+) in decl; zero values
		ast.Inspect(stmt, func(n ast.Node) bool {
			if v, ok := n.(*ast.ValueSpec); ok {
				idnts = union(idnts, idents(v))
			}
			return true
		})
	case *ast.IncDecStmt: // i++, i--
		idnts = idents(stmt.X)
	case *ast.AssignStmt: // :=, =, &=, etc. except x[i] (IndexExpr)
		for _, x := range stmt.Lhs {
			indExp := false
			ast.Inspect(x, func(n ast.Node) bool {
				if _, ok := n.(*ast.IndexExpr); ok {
					indExp = true
					return false
				}
				return true
			})
			if !indExp {
				idnts = union(idnts, idents(x))
			}
		}
	case *ast.RangeStmt: // only [ x, y ] on Lhs
		idnts = union(idents(stmt.Key), idents(stmt.Value))
	case *ast.TypeSwitchStmt:
		// The assigned variable does not have a types.Var
		// associated in this stmt; rather, the uses of that
		// variable in the case clauses have several different
		// types.Vars associated with them, according to type
		var vars []*types.Var
		ast.Inspect(stmt.Body, func(n ast.Node) bool {
			switch cc := n.(type) {
			case *ast.CaseClause:
				v := info.TypeCaseVar(cc)
				if v != nil {
					vars = append(vars, v)
				}
				return false
			default:
				return true
			}
		})
		return vars
	}

	var vars []*types.Var
	// should all map to types.Var's, if not we don't want anyway
	for i, _ := range idnts {
		if v, ok := info.ObjectOf(i).(*types.Var); ok {
			vars = append(vars, v)
		}
	}
	return vars
}

// uses extracts local variables whose values are used in the given statement.
func uses(stmt ast.Stmt, info *loader.PackageInfo) []*types.Var {
	idnts := make(map[*ast.Ident]struct{})

	ast.Inspect(stmt, func(n ast.Node) bool {
		switch stmt := stmt.(type) {
		case *ast.AssignStmt: // mostly rhs of =, :=, &=, etc.
			// some LHS are uses, e.g. x[i]
			for _, x := range stmt.Lhs {
				indExp := false
				ast.Inspect(stmt, func(n ast.Node) bool {
					if _, ok := n.(*ast.IndexExpr); ok {
						indExp = true
						return false
					}
					return true
				})
				if indExp || // x[i] is a uses of x and i
					(stmt.Tok != token.ASSIGN &&
						stmt.Tok != token.DEFINE) { // e.g. +=, ^=, etc.
					idnts = union(idnts, idents(x))
				}
			}
			// all RHS are uses
			for _, s := range stmt.Rhs {
				idnts = union(idnts, idents(s))
			}
		case *ast.BlockStmt: // no uses, skip
		case *ast.BranchStmt: // no uses, skip
		case *ast.CaseClause: // no uses, skip
		case *ast.DeclStmt: // no uses, skip
		case *ast.DeferStmt:
			idnts = idents(stmt.Call)
		case *ast.ForStmt:
			idnts = idents(stmt.Cond)
		case *ast.IfStmt:
			idnts = idents(stmt.Cond)
		case *ast.LabeledStmt: // no uses, skip
		case *ast.RangeStmt: // list in _, _ = range [ list ]
			idnts = idents(stmt.X)
		case *ast.SelectStmt: // no uses, skip
		case *ast.SwitchStmt:
			idnts = idents(stmt.Tag)
		case *ast.TypeSwitchStmt: // no uses, skip
		case ast.Stmt: // everything else is all uses
			idnts = idents(stmt)
		}
		return true
	})

	var vars []*types.Var

	// should all map to types.Var's, if not we don't want anyway
	for i, _ := range idnts {
		if v, ok := info.ObjectOf(i).(*types.Var); ok {
			vars = append(vars, v)
		}
	}

	return vars
}

// idents returns the set of all identifiers in given node.
func idents(node ast.Node) map[*ast.Ident]struct{} {
	idents := make(map[*ast.Ident]struct{})
	if node == nil {
		return idents
	}
	ast.Inspect(node, func(n ast.Node) bool {
		switch n := n.(type) {
		case *ast.Ident:
			idents[n] = struct{}{}
		}
		return true
	})
	return idents
}

func union(one, two map[*ast.Ident]struct{}) map[*ast.Ident]struct{} {
	for o, _ := range one {
		two[o] = struct{}{}
	}
	return two
}
<commit_msg>Fixed uses for CommClause<commit_after>// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Package dataflow provides data flow analyses that can be performed on a
// previously constructed control flow graph, including a reaching definitions
// analysis and a live variables analysis for local variables.
package dataflow

// This file contains functions common to all data flow analyses, as well as
// one exported function.

import (
	"go/ast"
	"go/token"

	"code.google.com/p/go.tools/go/loader"
	"code.google.com/p/go.tools/go/types"
)

// ReferencedVars returns the sets of local variables that are defined or used
// within the given list of statements (based on syntax).
func ReferencedVars(stmts []ast.Stmt, info *loader.PackageInfo) (def, use map[*types.Var]struct{}) {
	def = make(map[*types.Var]struct{})
	use = make(map[*types.Var]struct{})

	for _, stmt := range stmts {
		for _, d := range defs(stmt, info) {
			def[d] = struct{}{}
		}
		for _, u := range uses(stmt, info) {
			use[u] = struct{}{}
		}
	}
	return def, use
}

// defs extracts any local variables whose values are assigned in the given statement.
func defs(stmt ast.Stmt, info *loader.PackageInfo) []*types.Var {
	idnts := make(map[*ast.Ident]struct{})

	switch stmt := stmt.(type) {
	case *ast.DeclStmt: // vars (1+) in decl; zero values
		ast.Inspect(stmt, func(n ast.Node) bool {
			if v, ok := n.(*ast.ValueSpec); ok {
				idnts = union(idnts, idents(v))
			}
			return true
		})
	case *ast.IncDecStmt: // i++, i--
		idnts = idents(stmt.X)
	case *ast.AssignStmt: // :=, =, &=, etc. except x[i] (IndexExpr)
		for _, x := range stmt.Lhs {
			indExp := false
			ast.Inspect(x, func(n ast.Node) bool {
				if _, ok := n.(*ast.IndexExpr); ok {
					indExp = true
					return false
				}
				return true
			})
			if !indExp {
				idnts = union(idnts, idents(x))
			}
		}
	case *ast.RangeStmt: // only [ x, y ] on Lhs
		idnts = union(idents(stmt.Key), idents(stmt.Value))
	case *ast.TypeSwitchStmt:
		// The assigned variable does not have a types.Var
		// associated in this stmt; rather, the uses of that
		// variable in the case clauses have several different
		// types.Vars associated with them, according to type
		var vars []*types.Var
		ast.Inspect(stmt.Body, func(n ast.Node) bool {
			switch cc := n.(type) {
			case *ast.CaseClause:
				v := info.TypeCaseVar(cc)
				if v != nil {
					vars = append(vars, v)
				}
				return false
			default:
				return true
			}
		})
		return vars
	}

	var vars []*types.Var
	// should all map to types.Var's, if not we don't want anyway
	for i, _ := range idnts {
		if v, ok := info.ObjectOf(i).(*types.Var); ok {
			vars = append(vars, v)
		}
	}
	return vars
}

// uses extracts local variables whose values are used in the given statement.
func uses(stmt ast.Stmt, info *loader.PackageInfo) []*types.Var {
	idnts := make(map[*ast.Ident]struct{})

	ast.Inspect(stmt, func(n ast.Node) bool {
		switch stmt := stmt.(type) {
		case *ast.AssignStmt: // mostly rhs of =, :=, &=, etc.
			// some LHS are uses, e.g. x[i]
			for _, x := range stmt.Lhs {
				indExp := false
				ast.Inspect(stmt, func(n ast.Node) bool {
					if _, ok := n.(*ast.IndexExpr); ok {
						indExp = true
						return false
					}
					return true
				})
				if indExp || // x[i] is a uses of x and i
					(stmt.Tok != token.ASSIGN &&
						stmt.Tok != token.DEFINE) { // e.g. +=, ^=, etc.
					idnts = union(idnts, idents(x))
				}
			}
			// all RHS are uses
			for _, s := range stmt.Rhs {
				idnts = union(idnts, idents(s))
			}
		case *ast.BlockStmt: // no uses, skip
		case *ast.BranchStmt: // no uses, skip
		case *ast.CaseClause: // no uses, skip
		case *ast.CommClause: // no uses, skip
		case *ast.DeclStmt: // no uses, skip
		case *ast.DeferStmt:
			idnts = idents(stmt.Call)
		case *ast.ForStmt:
			idnts = idents(stmt.Cond)
		case *ast.IfStmt:
			idnts = idents(stmt.Cond)
		case *ast.LabeledStmt: // no uses, skip
		case *ast.RangeStmt: // list in _, _ = range [ list ]
			idnts = idents(stmt.X)
		case *ast.SelectStmt: // no uses, skip
		case *ast.SwitchStmt:
			idnts = idents(stmt.Tag)
		case *ast.TypeSwitchStmt: // no uses, skip
		case ast.Stmt: // everything else is all uses
			idnts = idents(stmt)
		}
		return true
	})

	var vars []*types.Var

	// should all map to types.Var's, if not we don't want anyway
	for i, _ := range idnts {
		if v, ok := info.ObjectOf(i).(*types.Var); ok {
			vars = append(vars, v)
		}
	}

	return vars
}

// idents returns the set of all identifiers in given node.
func idents(node ast.Node) map[*ast.Ident]struct{} {
	idents := make(map[*ast.Ident]struct{})
	if node == nil {
		return idents
	}
	ast.Inspect(node, func(n ast.Node) bool {
		switch n := n.(type) {
		case *ast.Ident:
			idents[n] = struct{}{}
		}
		return true
	})
	return idents
}

func union(one, two map[*ast.Ident]struct{}) map[*ast.Ident]struct{} {
	for o, _ := range one {
		two[o] = struct{}{}
	}
	return two
}
<|endoftext|>


<commit_before>/*
Package hsts implements a RoundTripper that supports HTTP Strict Transport Security.

It comes preloaded with sites from Chromium (https://www.chromium.org/hsts),
updated with go generate.
*/
//go:generate go run generate/preload.go -p hsts -v preload -o preload.go
//go:generate gofmt -w preload.go
package hsts

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

// Transport implements a RoundTripper adding HSTS to an existing RoundTripper.
type Transport struct {
	wrap  http.RoundTripper
	m     sync.Mutex            // protects state
	state map[string]*directive // key is host (RFC section 8.3)
}

// New wraps around a RoundTripper transport to add HTTP Strict Transport Security (HSTS).
// It starts preloaded with Chromium's list (https://www.chromium.org/hsts).
// Just like an http.Client if transport is nil, http.DefaultTransport is used.
func New(transport http.RoundTripper) *Transport {
	if transport == nil {
		transport = http.DefaultTransport
	}

	// Make a copy or we would change the default for future callers.
	state := make(map[string]*directive)
	for host, directive := range preload {
		state[host] = directive
	}

	return &Transport{
		wrap:  transport,
		state: state,
	}
}

// RoundTrip executes a single HTTP transaction and adds support for HSTS.
// It is safe for concurrent use by multiple goroutines.
func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) {
	resp, err := t.wrap.RoundTrip(t.request(req))
	if err != nil {
		return resp, err
	}
	t.response(resp)
	return resp, nil
}

// request modifies an HTTP request for HSTS if needed.
func (t *Transport) request(req *http.Request) *http.Request {
	t.m.Lock()
	defer t.m.Unlock()

	// TODO(StalkR): check host isn't an IP-literal or IPv4 (section 8.3.3).

	host := req.URL.Host
	d := t.find(host, true)
	if d == nil { // not found
		return req
	}

	// Preloaded sites do not expire; dynamic entries do.
	preloaded := d.received.IsZero()
	if !preloaded && time.Now().After(d.received.Add(d.maxAge)) {
		delete(t.state, host)
		return req
	}

	// Section 8.3 step 5a says to replace the http scheme with https.
	if req.URL.Scheme == "http" {
		req.URL.Scheme = "https"
	}
	// Section 8.3 step 5b says to replace explicit 80 with 443.
	if strings.Contains(req.URL.Host, ":") {
		hp := strings.SplitN(req.URL.Host, ":", 2)
		if port, err := strconv.Atoi(hp[1]); err == nil {
			if port == 80 {
				port = 443
			}
			req.URL.Host = fmt.Sprintf("%s:%d", hp[0], port)
		}
	}
	// Section 8.3 step 5c and 5d says to preserve otherwise.

	return req
}

// find finds a host including subdomains. Lock must be taken already.
func (t *Transport) find(host string, exact bool) *directive {
	d, ok := t.state[host]
	if ok && (exact || d.includeSubDomains) {
		return d
	}
	i := strings.Index(host, ".")
	if i == -1 {
		return nil
	}
	return t.find(host[i+1:], false)
}

// response looks into an HTTP response to see if HSTS state needs to be updated.
func (t *Transport) response(resp *http.Response) {
	header := resp.Header.Get("Strict-Transport-Security")
	if header == "" {
		return // missing
	}
	d := parse(header)
	if d == nil {
		return // invalid
	}
	host := resp.Request.URL.Host
	t.m.Lock()
	defer t.m.Unlock()
	if d.maxAge == 0 { // Section 6.1.1 says 0 signals the UA to forget about it.
		delete(t.state, host)
		return
	}
	t.state[host] = d
}
<commit_msg>transport: put go generate outside of doc<commit_after>/*
Package hsts implements a RoundTripper that supports HTTP Strict Transport Security.

It comes preloaded with sites from Chromium (https://www.chromium.org/hsts),
updated with go generate.
*/
package hsts

//go:generate go run generate/preload.go -p hsts -v preload -o preload.go
//go:generate gofmt -w preload.go

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

// Transport implements a RoundTripper adding HSTS to an existing RoundTripper.
type Transport struct {
	wrap  http.RoundTripper
	m     sync.Mutex            // protects state
	state map[string]*directive // key is host (RFC section 8.3)
}

// New wraps around a RoundTripper transport to add HTTP Strict Transport Security (HSTS).
// It starts preloaded with Chromium's list (https://www.chromium.org/hsts).
// Just like an http.Client if transport is nil, http.DefaultTransport is used.
func New(transport http.RoundTripper) *Transport {
	if transport == nil {
		transport = http.DefaultTransport
	}

	// Make a copy or we would change the default for future callers.
	state := make(map[string]*directive)
	for host, directive := range preload {
		state[host] = directive
	}

	return &Transport{
		wrap:  transport,
		state: state,
	}
}

// RoundTrip executes a single HTTP transaction and adds support for HSTS.
// It is safe for concurrent use by multiple goroutines.
func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) {
	resp, err := t.wrap.RoundTrip(t.request(req))
	if err != nil {
		return resp, err
	}
	t.response(resp)
	return resp, nil
}

// request modifies an HTTP request for HSTS if needed.
func (t *Transport) request(req *http.Request) *http.Request {
	t.m.Lock()
	defer t.m.Unlock()

	// TODO(StalkR): check host isn't an IP-literal or IPv4 (section 8.3.3).

	host := req.URL.Host
	d := t.find(host, true)
	if d == nil { // not found
		return req
	}

	// Preloaded sites do not expire; dynamic entries do.
	preloaded := d.received.IsZero()
	if !preloaded && time.Now().After(d.received.Add(d.maxAge)) {
		delete(t.state, host)
		return req
	}

	// Section 8.3 step 5a says to replace the http scheme with https.
	if req.URL.Scheme == "http" {
		req.URL.Scheme = "https"
	}
	// Section 8.3 step 5b says to replace explicit 80 with 443.
	if strings.Contains(req.URL.Host, ":") {
		hp := strings.SplitN(req.URL.Host, ":", 2)
		if port, err := strconv.Atoi(hp[1]); err == nil {
			if port == 80 {
				port = 443
			}
			req.URL.Host = fmt.Sprintf("%s:%d", hp[0], port)
		}
	}
	// Section 8.3 step 5c and 5d says to preserve otherwise.

	return req
}

// find finds a host including subdomains. Lock must be taken already.
func (t *Transport) find(host string, exact bool) *directive {
	d, ok := t.state[host]
	if ok && (exact || d.includeSubDomains) {
		return d
	}
	i := strings.Index(host, ".")
	if i == -1 {
		return nil
	}
	return t.find(host[i+1:], false)
}

// response looks into an HTTP response to see if HSTS state needs to be updated.
func (t *Transport) response(resp *http.Response) {
	header := resp.Header.Get("Strict-Transport-Security")
	if header == "" {
		return // missing
	}
	d := parse(header)
	if d == nil {
		return // invalid
	}
	host := resp.Request.URL.Host
	t.m.Lock()
	defer t.m.Unlock()
	if d.maxAge == 0 { // Section 6.1.1 says 0 signals the UA to forget about it.
		delete(t.state, host)
		return
	}
	t.state[host] = d
}
<|endoftext|>


<commit_before>package flash

import (
	"net/http"
	"testing"
)

// import "testing"

// func TestTreeSimple(t *testing.T) {
// 	r := NewRouter()
// 	r.tree.assign("GET", "/index/:id/:name", HTTPHandler)

// 	m := r.tree.match("GET", "/index/1/act")
// 	assertEqual(t, "/index/:id/:name", m.route.prefix)
// 	assertEqual(t, "1", m.params["id"])
// 	assertEqual(t, "act", m.params["name"])

// 	m = r.tree.match("GET", "/index/1")
// 	assertEqual(t, true, m.route == nil)
// }

func testH(p map[string]string) http.Handler {
	return http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("test")) }))
}

func TestTreeAssign(t *testing.T) {
	r := NewRouter()
	r.tree.assign("GET", "/api/pages", testH)
	l := r.tree["GET"]
	assertNil(t, l.f)
	l = l.routes["api"]
	assertNil(t, l.f)
	l = l.routes["pages"]
	assertNotNil(t, l.f)
}

func TestTreeMatch(t *testing.T) {
	r := NewRouter()
	r.tree.assign("GET", "/api/pages", testH)
	r.tree.assign("GET", "/api/pages/:id", testH)
	r.tree.assign("GET", "/api/pages/:id/hello", testH)
	assertNotNil(t, r.tree.match("GET", "/api/pages"))
	assertNotNil(t, r.tree.match("GET", "/api/pages/1"))
	assertNotNil(t, r.tree.match("GET", "/api/pages/1/hello"))
	assertNil(t, r.tree.match("GET", "/api/page"))
	assertNil(t, r.tree.match("GET", "/api/pages/1/wrongAction"))
}

// func TestTreeAssign(t *testing.T) {
// 	r := NewRouter()
// 	r.tree.assign("GET", "/pagesindex/:id/:name")
// 	l := r.tree.routes["GET"]
// 	assertEqual(t, 0, len(l.params))
// 	l = l.routes["index"]
// 	assertEqual(t, 0, len(l.params))
// 	l = l.routes["*"]
// 	assertEqual(t, 0, len(l.params))
// 	l = l.routes["*"]
// 	assertEqual(t, 2, len(l.params))
// 	assertEqual(t, "id", l.params[0])
// 	assertEqual(t, "name", l.params[1])
// }

// func TestTreeAssignOptional(t *testing.T) {
// 	r := NewRouter()
// 	r.tree.assign("GET", "/index"}, "id", "name)
// 	l := r.tree.routes["GET"]
// 	l = l.routes["index"]
// 	assertEqual(t, []string{}, l.params)
// 	l = l.routes["*"]
// 	assertEqual(t, []string{"id"}, l.params)
// 	l = l.routes["*"]
// 	assertEqual(t, []string{"id", "name"}, l.params)

// 	r = NewRouter()
// 	r.tree.assign("GET", "/index/&id/&name")
// 	l = r.tree.routes["GET"]
// 	l = l.routes["index"]
// 	assertEqual(t, []string{}, l.params)
// 	l = l.routes["*"]
// 	assertEqual(t, []string{"id"}, l.params)
// 	l = l.routes["*"]
// 	assertEqual(t, []string{"id", "name"}, l.params)
// }

// func TestTreeAssignExtended(t *testing.T) {
// 	r := NewRouter()
// 	r.tree.assign("GET", "/index/:id/@path")
// 	l := r.tree.routes["GET"]
// 	l = l.routes["index"]
// 	assertEqual(t, []string(nil), l.params)
// 	l = l.routes["*"]
// 	assertEqual(t, []string{"id"}, l.params)
// 	l = l.routes["**"]
// 	assertEqual(t, []string{"id", "path"}, l.params)
// }

// func TestTreeAssignNested(t *testing.T) {
// 	r := NewRouter()
// 	r.tree.assign("GET", "/index"}, "id", "action)
// 	r.tree.assign("GET", "/index/:sid/a"}, "id", "action)

// 	l := r.tree.routes["GET"]
// 	l = l.routes["index"]
// 	assertEqual(t, []string{}, l.params)
// 	l = l.routes["*"]
// 	assertEqual(t, []string{"id"}, l.params)

// 	l1 := l.routes["a"]
// 	assertEqual(t, []string{"sid"}, l1.params)
// 	l1 = l1.routes["*"]
// 	assertEqual(t, []string{"sid", "id"}, l1.params)
// 	l1 = l1.routes["*"]
// 	assertEqual(t, []string{"sid", "id", "action"}, l1.params)

// 	l = l.routes["*"]
// 	assertEqual(t, []string{"id", "action"}, l.params)

// }

// func TestTreeOptional(t *testing.T) {
// 	r := NewRouter()
// 	r.tree.assign("GET", "/index"}, "id", "action)

// 	m := r.tree.match("GET", "/index/1/act")
// 	assertEqual(t, map[string]string{"id": "1", "action": "act"}, m.params)

// 	m = r.tree.match("GET", "/index/1")
// 	assertEqual(t, map[string]string{"id": "1"}, m.params)

// 	m = r.tree.match("GET", "/index")
// 	assertEqual(t, map[string]string{}, m.params)
// 	assertEqual(t, true, m.found)

// 	r = NewRouter()
// 	r.tree.assign("GET", "/index/&id/&action")

// 	m = r.tree.match("GET", "/index/1/act")
// 	assertEqual(t, map[string]string{"id": "1", "action": "act"}, m.params)

// 	m = r.tree.match("GET", "/index/1")
// 	assertEqual(t, map[string]string{"id": "1"}, m.params)

// 	m = r.tree.match("GET", "/index")
// 	assertEqual(t, "/index/&id/&action", m.route.prefix)
// 	assertEqual(t, true, m.found)
// }

// func TestTreeExtra(t *testing.T) {
// 	r := NewRouter()
// 	r.tree.assign("GET", "/index/:id/@path")

// 	m := r.tree.match("GET", "/index/1/a/b/c")
// 	assertEqual(t, map[string]string{"id": "1", "path": "a/b/c"}, m.params)

// 	m = r.tree.match("GET", "/index/1")
// 	assertEqual(t, map[string]string{"id": "1"}, m.params)

// 	m = r.tree.match("GET", "/index")
// 	assertEqual(t, map[string]string{}, m.params)
// 	assertEqual(t, false, m.found)
// }

// func TestTreeSubdir(t *testing.T) {
// 	r := NewRouter()
// 	r.tree.assign("GET", "/images"}, "**)

// 	m := r.tree.match("GET", "/images/image.gif")
// 	assertEqual(t, true, m.found)

// 	m = r.tree.match("GET", "/images/sub/image.gif")
// 	assertEqual(t, true, m.found)

// 	m = r.tree.match("GET", "/images1/image.gif")
// 	assertEqual(t, false, m.found)
// }
<commit_msg>update<commit_after>package flash

import (
	"net/http"
	"testing"
)

func testH(p map[string]string) http.Handler {
	return http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("test")) }))
}

func TestTreeAssign(t *testing.T) {
	r := NewRouter()
	r.tree.assign("GET", "/api/pages", testH)
	l := r.tree["GET"]
	assertNil(t, l.f)
	l = l.routes["api"]
	assertNil(t, l.f)
	l = l.routes["pages"]
	assertNotNil(t, l.f)
}

func TestTreeMatch(t *testing.T) {
	r := NewRouter()
	r.tree.assign("GET", "/api/pages", testH)
	r.tree.assign("GET", "/api/pages/:id", testH)
	r.tree.assign("GET", "/api/pages/:id/hello", testH)
	assertNotNil(t, r.tree.match("GET", "/api/pages"))
	assertNil(t, r.tree.match("POST", "/api/pages"))
	assertNotNil(t, r.tree.match("GET", "/api/pages/1"))
	assertNotNil(t, r.tree.match("GET", "/api/pages/1/hello"))
	assertNil(t, r.tree.match("GET", "/api/page"))
	assertNil(t, r.tree.match("GET", "/api/pages/1/wrongAction"))
}
<|endoftext|>


<commit_before>// Package server encapsulates operations on the http server
package server

import (
)

type FloorplanetsServer struct {
	httpServer *HttpServer
}

func NewFloorplanetsServer(address string) (*FloorplanetsServer, error) {

	httpServer, err := NewHttpServer(address)

	if err != nil {
		return nil, err
	}

	s := FloorplanetsServer{httpServer: httpServer}

	return &s, nil
}

func (s *FloorplanetsServer) Close() error {
	return s.httpServer.Close()
}
<commit_msg>floorplanets_server: remove empty import<commit_after>// Package server encapsulates operations on the http server
package server

type FloorplanetsServer struct {
	httpServer *HttpServer
}

func NewFloorplanetsServer(address string) (*FloorplanetsServer, error) {

	httpServer, err := NewHttpServer(address)

	if err != nil {
		return nil, err
	}

	s := FloorplanetsServer{httpServer: httpServer}

	return &s, nil
}

func (s *FloorplanetsServer) Close() error {
	return s.httpServer.Close()
}
<|endoftext|>