<commit_before>package main

import (
	"fmt"
	"github.com/scode/saltybox/secretcrypt"
	"github.com/scode/saltybox/varmor"
	"golang.org/x/crypto/ssh/terminal"
	"io/ioutil"
	"log"
	"os"
	"path"
)

type passphraseReader interface {
	ReadPassphrase() string
}

type stdinPassphraseReader struct{}

// cachingPassphraseReader will wrap a passphraseReader by adding caching.
//
// This is useful to allow "at most once" semantics when reading the passphrase, while
// still lazily deferring the first invocation.
type cachingPassphraseReader struct {
	Upstream         passphraseReader
	cachedPassphrase string
	cached           bool
}

func (r *stdinPassphraseReader) ReadPassphrase() string {
	fmt.Fprint(os.Stderr, "Passphrase (saltybox): ")
	phrase, err := terminal.ReadPassword(0)
	if err != nil {
		panic(fmt.Sprintf("failure reading passphrase: %s", err))
	}

	return string(phrase)
}

func (r *cachingPassphraseReader) ReadPassphrase() string {
	if !r.cached {
		r.cachedPassphrase = r.Upstream.ReadPassphrase()
		r.cached = true
	}

	return r.cachedPassphrase
}

func passphraseEncrypt(passphrase string, plaintext []byte) (string, error) {
	cipherBytes, err := secretcrypt.Encrypt(passphrase, plaintext)
	if err != nil {
		return "", fmt.Errorf("encryption failed: %s", err)
	}

	varmoredBytes := varmor.Wrap(cipherBytes)

	return string(varmoredBytes), nil
}

func passphraseEncryptFile(inpath string, outpath string, preader passphraseReader) error {
	plaintext, err := ioutil.ReadFile(inpath)
	if err != nil {
		return fmt.Errorf("failed to read from %s: %s", inpath, err)
	}

	passphrase := preader.ReadPassphrase()
	encryptedString, err := passphraseEncrypt(passphrase, plaintext)
	if err != nil {
		return fmt.Errorf("encryption failed: %s", err)
	}

	err = ioutil.WriteFile(outpath, []byte(encryptedString), 0700)
	if err != nil {
		return fmt.Errorf("failed to write to %s: %s", outpath, err)
	}

	return nil
}

func passphraseDecrypt(passphrase string, encryptedString string) ([]byte, error) {
	cipherBytes, err := varmor.Unwrap(encryptedString)
	if err != nil {
		return nil, fmt.Errorf("failed to unarmor: %s", err)
	}

	plaintext, err := secretcrypt.Decrypt(passphrase, cipherBytes)
	if err != nil {
		return nil, fmt.Errorf("failed to decrypt: %s", err)
	}

	return plaintext, nil
}

func passphraseDecryptFile(inpath string, outpath string, preader passphraseReader) error {
	varmoredBytes, err := ioutil.ReadFile(inpath)
	if err != nil {
		return fmt.Errorf("failed to read from %s: %s", inpath, err)
	}

	passphrase := preader.ReadPassphrase()
	plaintext, err := passphraseDecrypt(passphrase, string(varmoredBytes))
	if err != nil {
		return fmt.Errorf("failed to decrypt: %s", err)
	}

	err = ioutil.WriteFile(outpath, plaintext, 0700)
	if err != nil {
		return fmt.Errorf("failed to write to %s: %s", outpath, err)
	}

	return nil
}

func passphraseUpdateFile(plainfile string, cryptfile string, preader passphraseReader) error {
	// Decrypt existing file in order to validate that the provided passphrase is correct,
	// in order to prevent accidental changing of the passphrase (but we discard the plain
	// text).
	varmoredBytes, err := ioutil.ReadFile(cryptfile)
	if err != nil {
		return fmt.Errorf("failed to read from %s: %s", cryptfile, err)
	}

	cachingPreader := cachingPassphraseReader{Upstream: preader}

	passphrase := cachingPreader.ReadPassphrase()
	_, err = passphraseDecrypt(passphrase, string(varmoredBytes))
	if err != nil {
		return fmt.Errorf("failed to decrypt: %s", err)
	}

	// Encrypt contents into the target file using atomic semantics (write to tempfile, fsync()
	// and rename). This guarantees that the resulting file will either be the old file or the new
	// file, but never corrupt (assuming a correctly functioning filesystem I/O stack).
	cryptDir, _ := path.Split(cryptfile)

	tmpfile, err := ioutil.TempFile(cryptDir, "saltybox-update-tmp")
	if err != nil {
		return fmt.Errorf("failed to create tempfile: %s", err)
	}
	defer os.Remove(tmpfile.Name())
	defer tmpfile.Close()

	err = passphraseEncryptFile(plainfile, tmpfile.Name(), &cachingPreader)
	if err != nil {
		return fmt.Errorf("failed to encrypt failed: %s", err)
	}

	// Re-open the file to ensure that we are Sync():ing the correct file. Technically this is not
	// required because passphraseEncryptFile() will cause the target file to be truncated rather than recreated.
	// However, let's defensively avoid relying on that subtle behavior and re-open the file.
	reopenedTmpFile, err := os.Open(tmpfile.Name())
	if err != nil {
		return fmt.Errorf("failed to re-open tempfile after encryption: %s", err)
	}
	defer reopenedTmpFile.Close()

	err = reopenedTmpFile.Sync()
	if err != nil {
		return fmt.Errorf("failed to sync file prior to rename: %s", err)
	}

	err = os.Rename(reopenedTmpFile.Name(), cryptfile)
	if err != nil {
		return fmt.Errorf("failed to rename to target file: %s", err)
	}

	return nil
}

func main() {
	log.SetFlags(0)
	if len(os.Args) < 2 {
		log.Printf("Usage: %s <command> [-help]", os.Args[0])
		log.Print("")
		log.Print("Commands:")
		log.Print("   passphrase-encrypt-file <inpath> <outpath> - encrypt file using passphrase")
		log.Print("   passphrase-decrypt-file <inpath> <outpath> - decrypt file using passphrase")
		log.Print("   passphrase-update-file  <inpath> <cryptpath> - update an encrypted file")
		os.Exit(1)
	}

	if os.Args[1] == "passphrase-encrypt-file" {
		err := passphraseEncryptFile(os.Args[2], os.Args[3], &stdinPassphraseReader{})
		if err != nil {
			log.Fatal(err)
		}
	} else if os.Args[1] == "passphrase-decrypt-file" {
		err := passphraseDecryptFile(os.Args[2], os.Args[3], &stdinPassphraseReader{})
		if err != nil {
			log.Fatal(err)
		}
	} else if os.Args[1] == "passphrase-update-file" {
		err := passphraseUpdateFile(os.Args[2], os.Args[3], &stdinPassphraseReader{})
		if err != nil {
			log.Fatal(err)
		}
	} else {
		log.Fatal("unrecognized command")
	}
}
<commit_msg>Fix error message.<commit_after>package main

import (
	"fmt"
	"github.com/scode/saltybox/secretcrypt"
	"github.com/scode/saltybox/varmor"
	"golang.org/x/crypto/ssh/terminal"
	"io/ioutil"
	"log"
	"os"
	"path"
)

type passphraseReader interface {
	ReadPassphrase() string
}

type stdinPassphraseReader struct{}

// cachingPassphraseReader will wrap a passphraseReader by adding caching.
//
// This is useful to allow "at most once" semantics when reading the passphrase, while
// still lazily deferring the first invocation.
type cachingPassphraseReader struct {
	Upstream         passphraseReader
	cachedPassphrase string
	cached           bool
}

func (r *stdinPassphraseReader) ReadPassphrase() string {
	fmt.Fprint(os.Stderr, "Passphrase (saltybox): ")
	phrase, err := terminal.ReadPassword(0)
	if err != nil {
		panic(fmt.Sprintf("failure reading passphrase: %s", err))
	}

	return string(phrase)
}

func (r *cachingPassphraseReader) ReadPassphrase() string {
	if !r.cached {
		r.cachedPassphrase = r.Upstream.ReadPassphrase()
		r.cached = true
	}

	return r.cachedPassphrase
}

func passphraseEncrypt(passphrase string, plaintext []byte) (string, error) {
	cipherBytes, err := secretcrypt.Encrypt(passphrase, plaintext)
	if err != nil {
		return "", fmt.Errorf("encryption failed: %s", err)
	}

	varmoredBytes := varmor.Wrap(cipherBytes)

	return string(varmoredBytes), nil
}

func passphraseEncryptFile(inpath string, outpath string, preader passphraseReader) error {
	plaintext, err := ioutil.ReadFile(inpath)
	if err != nil {
		return fmt.Errorf("failed to read from %s: %s", inpath, err)
	}

	passphrase := preader.ReadPassphrase()
	encryptedString, err := passphraseEncrypt(passphrase, plaintext)
	if err != nil {
		return fmt.Errorf("encryption failed: %s", err)
	}

	err = ioutil.WriteFile(outpath, []byte(encryptedString), 0700)
	if err != nil {
		return fmt.Errorf("failed to write to %s: %s", outpath, err)
	}

	return nil
}

func passphraseDecrypt(passphrase string, encryptedString string) ([]byte, error) {
	cipherBytes, err := varmor.Unwrap(encryptedString)
	if err != nil {
		return nil, fmt.Errorf("failed to unarmor: %s", err)
	}

	plaintext, err := secretcrypt.Decrypt(passphrase, cipherBytes)
	if err != nil {
		return nil, fmt.Errorf("failed to decrypt: %s", err)
	}

	return plaintext, nil
}

func passphraseDecryptFile(inpath string, outpath string, preader passphraseReader) error {
	varmoredBytes, err := ioutil.ReadFile(inpath)
	if err != nil {
		return fmt.Errorf("failed to read from %s: %s", inpath, err)
	}

	passphrase := preader.ReadPassphrase()
	plaintext, err := passphraseDecrypt(passphrase, string(varmoredBytes))
	if err != nil {
		return fmt.Errorf("failed to decrypt: %s", err)
	}

	err = ioutil.WriteFile(outpath, plaintext, 0700)
	if err != nil {
		return fmt.Errorf("failed to write to %s: %s", outpath, err)
	}

	return nil
}

func passphraseUpdateFile(plainfile string, cryptfile string, preader passphraseReader) error {
	// Decrypt existing file in order to validate that the provided passphrase is correct,
	// in order to prevent accidental changing of the passphrase (but we discard the plain
	// text).
	varmoredBytes, err := ioutil.ReadFile(cryptfile)
	if err != nil {
		return fmt.Errorf("failed to read from %s: %s", cryptfile, err)
	}

	cachingPreader := cachingPassphraseReader{Upstream: preader}

	passphrase := cachingPreader.ReadPassphrase()
	_, err = passphraseDecrypt(passphrase, string(varmoredBytes))
	if err != nil {
		return fmt.Errorf("failed to decrypt: %s", err)
	}

	// Encrypt contents into the target file using atomic semantics (write to tempfile, fsync()
	// and rename). This guarantees that the resulting file will either be the old file or the new
	// file, but never corrupt (assuming a correctly functioning filesystem I/O stack).
	cryptDir, _ := path.Split(cryptfile)

	tmpfile, err := ioutil.TempFile(cryptDir, "saltybox-update-tmp")
	if err != nil {
		return fmt.Errorf("failed to create tempfile: %s", err)
	}
	defer os.Remove(tmpfile.Name())
	defer tmpfile.Close()

	err = passphraseEncryptFile(plainfile, tmpfile.Name(), &cachingPreader)
	if err != nil {
		return fmt.Errorf("failed to encrypt: %s", err)
	}

	// Re-open the file to ensure that we are Sync():ing the correct file. Technically this is not
	// required because passphraseEncryptFile() will cause the target file to be truncated rather than recreated.
	// However, let's defensively avoid relying on that subtle behavior and re-open the file.
	reopenedTmpFile, err := os.Open(tmpfile.Name())
	if err != nil {
		return fmt.Errorf("failed to re-open tempfile after encryption: %s", err)
	}
	defer reopenedTmpFile.Close()

	err = reopenedTmpFile.Sync()
	if err != nil {
		return fmt.Errorf("failed to sync file prior to rename: %s", err)
	}

	err = os.Rename(reopenedTmpFile.Name(), cryptfile)
	if err != nil {
		return fmt.Errorf("failed to rename to target file: %s", err)
	}

	return nil
}

func main() {
	log.SetFlags(0)
	if len(os.Args) < 2 {
		log.Printf("Usage: %s <command> [-help]", os.Args[0])
		log.Print("")
		log.Print("Commands:")
		log.Print("   passphrase-encrypt-file <inpath> <outpath> - encrypt file using passphrase")
		log.Print("   passphrase-decrypt-file <inpath> <outpath> - decrypt file using passphrase")
		log.Print("   passphrase-update-file  <inpath> <cryptpath> - update an encrypted file")
		os.Exit(1)
	}

	if os.Args[1] == "passphrase-encrypt-file" {
		err := passphraseEncryptFile(os.Args[2], os.Args[3], &stdinPassphraseReader{})
		if err != nil {
			log.Fatal(err)
		}
	} else if os.Args[1] == "passphrase-decrypt-file" {
		err := passphraseDecryptFile(os.Args[2], os.Args[3], &stdinPassphraseReader{})
		if err != nil {
			log.Fatal(err)
		}
	} else if os.Args[1] == "passphrase-update-file" {
		err := passphraseUpdateFile(os.Args[2], os.Args[3], &stdinPassphraseReader{})
		if err != nil {
			log.Fatal(err)
		}
	} else {
		log.Fatal("unrecognized command")
	}
}
<|endoftext|>


<commit_before>package lifted

// Quantifier removal

func (d *Domain) ExpandQuants(objs []TypedName) {
	f := newExpandFrame(objs)
	acts := make([]Action, 0, len(d.Actions))
	for i, _ := range d.Actions {
		a := &d.Actions[i]
		a.Precondition = a.Precondition.ExpandQuants(f)
		a.Effect = a.Effect.ExpandQuants(f)
		acts = append(acts, expandParams(f, a, a.Parameters)...)
	}
	d.Actions = acts
}

func expandParams(f *expandFrame, a *Action, ps []TypedName) (acts []Action) {
	if len(ps) == 0 {
		act := Action{
			Name:         a.Name,
			Parameters:   make([]TypedName, len(a.Parameters)),
			Precondition: a.Precondition.ExpandQuants(f),
			Effect:       a.Effect.ExpandQuants(f),
		}
		copy(act.Parameters, a.Parameters)
		return []Action{act}
	}

	pnum := len(a.Parameters) - len(ps)
	saved := a.Parameters[pnum]

	objs := objsOfType(f, saved.Type)
	for _, obj := range objs {
		a.Parameters[pnum].Name = obj
		g := f.push(saved.Name, obj)
		acts = append(acts, expandParams(g, a, ps[1:])...)
	}
	a.Parameters[pnum] = saved

	return
}

func (l *Literal) ExpandQuants(f *expandFrame) *Literal {
	res := &Literal{
		Positive:   l.Positive,
		Name:       l.Name,
		Parameters: make([]Term, len(l.Parameters)),
	}
	for i, t := range l.Parameters {
		if t.Kind == TermConstant {
			res.Parameters[i] = t
			continue
		}
		o, ok := f.lookup(t.Name)
		if !ok {
			res.Parameters[i] = t
			continue
		}
		res.Parameters[i] = Term{
			Kind: TermConstant,
			Name: o,
			Loc:  t.Loc,
		}
	}
	return res
}

func (e ExprTrue) ExpandQuants(*expandFrame) Expr {
	return e
}

func (e ExprFalse) ExpandQuants(*expandFrame) Expr {
	return e
}

func (e *ExprAnd) ExpandQuants(f *expandFrame) Expr {
	return ExprConj(e.Left.ExpandQuants(f), e.Right.ExpandQuants(f))
}

func (e *ExprOr) ExpandQuants(f *expandFrame) Expr {
	return ExprDisj(e.Left.ExpandQuants(f), e.Right.ExpandQuants(f))
}

func (e *ExprNot) ExpandQuants(f *expandFrame) Expr {
	return ExprNeg(e.Expr.ExpandQuants(f))
}

func (e *ExprQuant) ExpandQuants(f *expandFrame, base Expr,
comb func(Expr, Expr) Expr) Expr {
	objs := objsOfType(f, e.Variable.Type)
	seq := base
	for _, obj := range objs {
		g := f.push(e.Variable.Name, obj)
		seq = comb(e.Expr.ExpandQuants(g), seq)
	}
	return seq
}

func (e *ExprForall) ExpandQuants(f *expandFrame) Expr {
	return (*ExprQuant)(e).ExpandQuants(f, ExprTrue(1), ExprConj)
}

func (e *ExprExists) ExpandQuants(f *expandFrame) Expr {
	return (*ExprQuant)(e).ExpandQuants(f, ExprFalse(0), ExprDisj)
}

func (e *ExprLiteral) ExpandQuants(f *expandFrame) Expr {
	return (*ExprLiteral)((*Literal)(e).ExpandQuants(f))
}

func (EffectNone) ExpandQuants(*expandFrame) Effect {
	return EffectNone(0)
}

func (e *EffectAnd) ExpandQuants(f *expandFrame) Effect {
	return EffectConj(e.Left.ExpandQuants(f), e.Right.ExpandQuants(f))
}

func (e *EffectForall) ExpandQuants(f *expandFrame) Effect {
	objs := objsOfType(f, e.Variable.Type)
	seq := Effect(EffectNone(0))
	for _, obj := range objs {
		g := f.push(e.Variable.Name, obj)
		seq = EffectConj(e.Effect.ExpandQuants(g), seq)
	}
	return seq
}

func (e *EffectWhen) ExpandQuants(f *expandFrame) Effect {
	return &EffectWhen{
		Condition:   e.Condition.ExpandQuants(f),
		EffectUnary: EffectUnary{e.Effect.ExpandQuants(f)},
	}
}

func (e *EffectLiteral) ExpandQuants(f *expandFrame) Effect {
	return (*EffectLiteral)((*Literal)(e).ExpandQuants(f))
}

func (e *EffectAssign) ExpandQuants(f *expandFrame) Effect {
	return &EffectAssign{
		Op:   e.Op,
		Lval: e.Lval,
		Rval: e.Rval,
	}
}

type expandFrame struct {
	objsByType map[string][]string
	variable   string
	object     string
	up         *expandFrame
}

func newExpandFrame(objs []TypedName) *expandFrame {
	objsByType := make(map[string][]string)

	for _, obj := range objs {
		for _, t := range obj.Type {
			lst, _ := objsByType[t]
			objsByType[t] = append(lst, obj.Name)
		}
	}

	return &expandFrame{objsByType: objsByType}
}

func (f *expandFrame) push(vr string, obj string) *expandFrame {
	return &expandFrame{
		objsByType: f.objsByType,
		variable:   vr,
		object:     obj,
		up:         f,
	}
}

func (f *expandFrame) lookup(vr string) (string, bool) {
	if f == nil {
		return "", false
	}
	if f.variable == vr {
		return f.object, true
	}
	return f.up.lookup(vr)
}

func objsOfType(f *expandFrame, typ []string) (objs []string) {
	seen := make(map[string]bool)
	for _, t := range typ {
		for _, o := range f.objsByType[t] {
			if _, ok := seen[o]; ok {
				continue
			}
			seen[o] = true
			objs = append(objs, o)
		}
	}
	return
}
<commit_msg>Lazy evaluation to avoid expanding extra quantifiers.<commit_after>package lifted

// Quantifier removal

func (d *Domain) ExpandQuants(objs []TypedName) {
	f := newExpandFrame(objs)
	acts := make([]Action, 0, len(d.Actions))
	for i, _ := range d.Actions {
		a := &d.Actions[i]
		a.Precondition = a.Precondition.ExpandQuants(f)
		a.Effect = a.Effect.ExpandQuants(f)
		acts = append(acts, expandParams(f, a, a.Parameters)...)
	}
	d.Actions = acts
}

func expandParams(f *expandFrame, a *Action, ps []TypedName) (acts []Action) {
	if len(ps) == 0 {
		act := Action{
			Name:         a.Name,
			Parameters:   make([]TypedName, len(a.Parameters)),
			Precondition: a.Precondition.ExpandQuants(f),
			Effect:       a.Effect.ExpandQuants(f),
		}
		copy(act.Parameters, a.Parameters)
		return []Action{act}
	}

	pnum := len(a.Parameters) - len(ps)
	saved := a.Parameters[pnum]

	objs := objsOfType(f, saved.Type)
	for _, obj := range objs {
		a.Parameters[pnum].Name = obj
		g := f.push(saved.Name, obj)
		acts = append(acts, expandParams(g, a, ps[1:])...)
	}
	a.Parameters[pnum] = saved

	return
}

func (l *Literal) ExpandQuants(f *expandFrame) *Literal {
	res := &Literal{
		Positive:   l.Positive,
		Name:       l.Name,
		Parameters: make([]Term, len(l.Parameters)),
	}
	for i, t := range l.Parameters {
		if t.Kind == TermConstant {
			res.Parameters[i] = t
			continue
		}
		o, ok := f.lookup(t.Name)
		if !ok {
			res.Parameters[i] = t
			continue
		}
		res.Parameters[i] = Term{
			Kind: TermConstant,
			Name: o,
			Loc:  t.Loc,
		}
	}
	return res
}

func (e ExprTrue) ExpandQuants(*expandFrame) Expr {
	return e
}

func (e ExprFalse) ExpandQuants(*expandFrame) Expr {
	return e
}

func (e *ExprAnd) ExpandQuants(f *expandFrame) Expr {
	l := e.Left.ExpandQuants(f)
	if _, ok := l.(ExprFalse); ok {
		return ExprFalse(0)
	}
	return ExprConj(l, e.Right.ExpandQuants(f))
}

func (e *ExprOr) ExpandQuants(f *expandFrame) Expr {
	l := e.Left.ExpandQuants(f)
	if _, ok := l.(ExprTrue); ok {
		return ExprTrue(1)
	}
	return ExprDisj(l, e.Right.ExpandQuants(f))
}

func (e *ExprNot) ExpandQuants(f *expandFrame) Expr {
	return ExprNeg(e.Expr.ExpandQuants(f))
}

func (e *ExprQuant) ExpandQuants(f *expandFrame, base Expr,
comb func(Expr, Expr) Expr) Expr {
	objs := objsOfType(f, e.Variable.Type)
	seq := base
	for _, obj := range objs {
		g := f.push(e.Variable.Name, obj)
		seq = comb(e.Expr.ExpandQuants(g), seq)
	}
	return seq
}

func (e *ExprForall) ExpandQuants(f *expandFrame) Expr {
	return (*ExprQuant)(e).ExpandQuants(f, ExprTrue(1), ExprConj)
}

func (e *ExprExists) ExpandQuants(f *expandFrame) Expr {
	return (*ExprQuant)(e).ExpandQuants(f, ExprFalse(0), ExprDisj)
}

func (e *ExprLiteral) ExpandQuants(f *expandFrame) Expr {
	return (*ExprLiteral)((*Literal)(e).ExpandQuants(f))
}

func (EffectNone) ExpandQuants(*expandFrame) Effect {
	return EffectNone(0)
}

func (e *EffectAnd) ExpandQuants(f *expandFrame) Effect {
	return EffectConj(e.Left.ExpandQuants(f), e.Right.ExpandQuants(f))
}

func (e *EffectForall) ExpandQuants(f *expandFrame) Effect {
	objs := objsOfType(f, e.Variable.Type)
	seq := Effect(EffectNone(0))
	for _, obj := range objs {
		g := f.push(e.Variable.Name, obj)
		seq = EffectConj(e.Effect.ExpandQuants(g), seq)
	}
	return seq
}

func (e *EffectWhen) ExpandQuants(f *expandFrame) Effect {
	return &EffectWhen{
		Condition:   e.Condition.ExpandQuants(f),
		EffectUnary: EffectUnary{e.Effect.ExpandQuants(f)},
	}
}

func (e *EffectLiteral) ExpandQuants(f *expandFrame) Effect {
	return (*EffectLiteral)((*Literal)(e).ExpandQuants(f))
}

func (e *EffectAssign) ExpandQuants(f *expandFrame) Effect {
	return &EffectAssign{
		Op:   e.Op,
		Lval: e.Lval,
		Rval: e.Rval,
	}
}

type expandFrame struct {
	objsByType map[string][]string
	variable   string
	object     string
	up         *expandFrame
}

func newExpandFrame(objs []TypedName) *expandFrame {
	objsByType := make(map[string][]string)

	for _, obj := range objs {
		for _, t := range obj.Type {
			lst, _ := objsByType[t]
			objsByType[t] = append(lst, obj.Name)
		}
	}

	return &expandFrame{objsByType: objsByType}
}

func (f *expandFrame) push(vr string, obj string) *expandFrame {
	return &expandFrame{
		objsByType: f.objsByType,
		variable:   vr,
		object:     obj,
		up:         f,
	}
}

func (f *expandFrame) lookup(vr string) (string, bool) {
	if f == nil {
		return "", false
	}
	if f.variable == vr {
		return f.object, true
	}
	return f.up.lookup(vr)
}

func objsOfType(f *expandFrame, typ []string) (objs []string) {
	seen := make(map[string]bool)
	for _, t := range typ {
		for _, o := range f.objsByType[t] {
			if _, ok := seen[o]; ok {
				continue
			}
			seen[o] = true
			objs = append(objs, o)
		}
	}
	return
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	_ "os/exec"
	"regexp"
	"strings"
)

type File struct {
	Name string
}

type Folder struct {
	Name    string
	Files   []File
	Folders []Folder
}

func parseFileContents(path string) []string {
	b, err := ioutil.ReadFile(path)
	if err != nil {
		log.Fatal(err)
	}
	return strings.Split(string(b), "\n")
}

func traverseDirectory(rootPtr *Folder, contents []string) {
	for _, element := range contents {
		if strings.TrimSpace(element) == "" {
			continue
		}

		isDirectory, err := regexp.MatchString("/$", element)
		if err != nil {
			log.Fatal(err)
		}

		level := len(regexp.MustCompile("(\\s)(\\s)").FindAllString(element, -1))

		current := rootPtr
		element = strings.TrimSpace(element)

		for i := 0; i < level; i++ {
			current = &current.Folders[len(current.Folders)-1]
		}

		if isDirectory {
			current.Folders = append(current.Folders, Folder{strings.TrimRight(element, "/"), []File{}, []Folder{}})
		} else {
			current.Files = append(current.Files, File{element})
		}
	}
}

func mkdir(rootPtr *Folder) string {
	if len(rootPtr.Folders) == 0 {
		return rootPtr.Name
	}

	mkdirString := fmt.Sprintf("%s/{", rootPtr.Name)

	for _, folder := range rootPtr.Folders {
		mkdirString += fmt.Sprintf("%s,", mkdir(&folder))
	}

	return fmt.Sprintf("%s}", mkdirString)
}

func touch(rootPtr *Folder) string {
	if len(rootPtr.Files) == 0 && len(rootPtr.Folders) == 0 {
		return ""
	}

	touchString := fmt.Sprintf("%s/{", rootPtr.Name)

	for _, file := range rootPtr.Files {
		touchString += fmt.Sprintf("%s,", file.Name)
	}

	for _, folder := range rootPtr.Folders {
		touchString += fmt.Sprintf("%s,", touch(&folder))
	}

	return fmt.Sprintf("%s}", touchString)
}

func main() {
	configPtr := flag.String("config", "", "Config file")
	directoryPtr := flag.String("directory", ".", "Root directory")
	flag.Parse()

	if *configPtr == "" {
		log.Fatal("Missing --config argument")
	}

	if _, err := os.Stat(*configPtr); os.IsNotExist(err) {
		log.Fatal("Config file doesn't exist")
	}

	contents := parseFileContents(*configPtr)

	// Trim trailing `/` on directory name, this is added when forming the mkdir command later
	directory := strings.TrimRight(*directoryPtr, "/")
	rootPtr := &Folder{directory, []File{}, []Folder{}}

	traverseDirectory(rootPtr, contents)

	mkdirCmd := mkdir(rootPtr)
	touchCmd := touch(rootPtr)

	fmt.Printf("mkdir -p %s; touch %s\n", mkdirCmd, touchCmd)

	// err := exec.Command("mkdir", "-p", mkdirCmd, "; ", "touch", touchCmd).Run()
	// if err != nil {
	// 	log.Fatal(err)
	// }
}
<commit_msg>Refactor path formation function (#1)<commit_after>package main

import (
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"os/user"
	"regexp"
	"strings"
)

type File struct {
	Name string
}

type Folder struct {
	Name    string
	Files   []File
	Folders []Folder
}

type Path struct {
	FilePath    string
	IsDirectory bool
}

func parseFileContents(path string) []string {
	b, err := ioutil.ReadFile(path)
	if err != nil {
		log.Fatal(err)
	}
	return strings.Split(string(b), "\n")
}

func traverseDirectory(rootPtr *Folder, contents []string) {
	for _, element := range contents {
		if strings.TrimSpace(element) == "" {
			continue
		}

		isDirectory, err := regexp.MatchString("/$", element)
		if err != nil {
			log.Fatal(err)
		}

		level := len(regexp.MustCompile("(\\s)(\\s)").FindAllString(element, -1))

		current := rootPtr
		element = strings.TrimSpace(element)

		for i := 0; i < level; i++ {
			current = &current.Folders[len(current.Folders)-1]
		}

		if isDirectory {
			current.Folders = append(current.Folders, Folder{strings.TrimRight(element, "/"), []File{}, []Folder{}})
		} else {
			current.Files = append(current.Files, File{element})
		}
	}
}

func recursivelyFormPathList(dirPtr *Folder, parent string) []Path {
	if parent != "" {
		parent = fmt.Sprintf("%s/%s", parent, dirPtr.Name)
	} else {
		parent = dirPtr.Name
	}

	parentPath := Path{parent, true}

	paths := []Path{parentPath}

	for _, file := range dirPtr.Files {
		paths = append(paths, Path{
			fmt.Sprintf("%s/%s", parent, file.Name),
			false,
		})
	}

	for _, folder := range dirPtr.Folders {
		paths = append(paths, recursivelyFormPathList(&folder, parent)...)
	}

	return paths
}

func main() {
	templatePtr := flag.String("template", "", "Template file (directory structure)")
	directoryPtr := flag.String("directory", ".", "Root directory")
	flag.Parse()

	if *templatePtr == "" {
		log.Fatal("Missing -template argument")
	}

	if _, err := os.Stat(*templatePtr); os.IsNotExist(err) {
		log.Fatal("Template file doesn't exist")
	}

	usr, _ := user.Current()
	contents := parseFileContents(*templatePtr)
	directory := *directoryPtr

	fmt.Println(usr.HomeDir)

	// Trim trailing `/` on directory name, this is added when forming the mkdir command later
	directory = strings.TrimRight(directory, "/")
	// Replace ~/ with absolute path to home directory
	directory = strings.Replace(directory, "~/", fmt.Sprintf("%s/", usr.HomeDir), 1)

	rootPtr := &Folder{directory, []File{}, []Folder{}}

	traverseDirectory(rootPtr, contents)

	paths := recursivelyFormPathList(rootPtr, "")

	for _, path := range paths {
		func(path Path) {
			fmt.Printf("Creating %s...", path.FilePath)

			if path.IsDirectory {
				err := os.MkdirAll(path.FilePath, os.ModePerm)
				if err != nil {
					log.Fatal(err)
				}
			} else {
				_, err := os.Create(path.FilePath)
				if err != nil {
					log.Fatal(err)
				}
			}
			fmt.Println("done.")
		}(path)
	}
}
<|endoftext|>


<commit_before>package onetime

import (
    "crypto/hmac"
    "crypto/sha1"
    "encoding/binary"
    "math"
)

type OneTimePassword struct {
    Digit int
}

func (otp *OneTimePassword) HOTP(secret []byte, count uint64) uint {
    hs := hmacSha1(secret, count)
    return otp.truncate(hs)
}

func hmacSha1(secret []byte, count uint64) []byte {
    mac := hmac.New(sha1.New, secret)
    binary.Write(mac, binary.BigEndian, count)
    return mac.Sum(nil)
}

func (otp *OneTimePassword) truncate(hs []byte) uint {
    sbits := dt(hs)
    snum := uint(sbits[3]) | uint(sbits[2])<<8
    snum |= uint(sbits[1])<<16 | uint(sbits[0])<<24
    return snum % uint(math.Pow(10, float64(otp.Digit)))
}

func dt(hs []byte) []byte {
    offset := int(hs[19] & 0xf)
    p := hs[offset : offset+4]
    p[0] &= 0x7f
    return p
}
<commit_msg>digit length validation<commit_after>package onetime

import (
    "crypto/hmac"
    "crypto/sha1"
    "encoding/binary"
    "errors"
    "math"
)

type OneTimePassword struct {
    Digit int
}

func (otp *OneTimePassword) HOTP(secret []byte, count uint64) uint {
    hs := hmacSha1(secret, count)
    return otp.truncate(hs)
}

func hmacSha1(secret []byte, count uint64) []byte {
    mac := hmac.New(sha1.New, secret)
    binary.Write(mac, binary.BigEndian, count)
    return mac.Sum(nil)
}

func (otp *OneTimePassword) truncate(hs []byte) uint {
    sbits := dt(hs)
    snum := uint(sbits[3]) | uint(sbits[2])<<8
    snum |= uint(sbits[1])<<16 | uint(sbits[0])<<24
    return snum % uint(math.Pow(10, float64(otp.Digit)))
}

func New(digit int) (otp OneTimePassword, err error) {
    if digit < 6 {
        err = errors.New("A minimum of 6 digits is required for a valid HTOP code.")
        return
    } else if digit > 9 {
        err = errors.New("An HTOP code cannot be longer than 9 digits.")
        return
    }
    otp = OneTimePassword{digit}
    return
}

func dt(hs []byte) []byte {
    offset := int(hs[19] & 0xf)
    p := hs[offset : offset+4]
    p[0] &= 0x7f
    return p
}
<|endoftext|>


<commit_before>package monitor

import (
	"bufio"
	"fmt"
	"io"
	_ "io/ioutil"
	"log"
	"os"
	"os/exec"
	_ "regexp"
)

/* The Monitor interface defines a series of methods which will be defined on
 * monitor structs. The Start method takes a channel to send messages over,
 * back to the configurator. The Stop method kills the process which is
 * performing the actual monitoring.
 */
type Monitor interface {
	Start(messages chan<- []byte, dockerComposeName string)
	Stop()
}

/* Store the MonitorName, which is the name of the program to execute, the
 * DockerDirs which are the directories to monitor which our Docker containers
 * of interest live in, and a pointer to the exec.Cmd struct which describes
 * the running command.
 */
type FSMonitor struct {
	MonitorName   string
	DockerDirs    []string
	fsWatcherProc *exec.Cmd
}

type NetMonitor struct {
	MonitorName  string
	ContainerIds []string
}

// Memoize this. It's kind of expensive to get.
var dockerContainerIds = []string{}

func getDockerContainerIds(dockerComposeName string) []string {
	if len(dockerContainerIds) != 0 {
		return dockerContainerIds
	}
	dockerComposeCommand := exec.Command(dockerComposeName, "ps", "-q")
	outpipe, err := dockerComposeCommand.StdoutPipe()
	if err != nil {
		panic(err)
	}

	dockerComposeCommand.Start()
	defer dockerComposeCommand.Wait()

	ids := []string{}
	stdoutReader := bufio.NewReader(outpipe)
	for {
		line, _, err := stdoutReader.ReadLine()
		if err == io.EOF {
			break
		} else if err != nil {
			panic(err)
		}
		if string(line) != "" {
			ids = append(ids, string(line))
		}
	}
	dockerContainerIds = ids
	return ids
}

func (n NetMonitor) Start(messages chan<- []byte, dockerComposeName string) {
	ids := getDockerContainerIds(dockerComposeName)
	arguments := []string{"inspect", "-f", "'{{ .State.Pid }}'"}
	arguments = append(arguments, ids...)
	dockerComposeCommand := exec.Command("docker", arguments...)
	outpipe, err := dockerComposeCommand.StdoutPipe()
	if err != nil {
		panic(err)
	}

	dockerComposeCommand.Start()
	defer dockerComposeCommand.Wait()

	procIds := []string{}
	stdoutReader := bufio.NewReader(outpipe)
	for {
		line, _, err := stdoutReader.ReadLine()
		if err == io.EOF {
			break
		} else if err != nil {
			panic(err)
		}
		if string(line) != "" {
			procIds = append(procIds, string(line))
			err = setSymlink(string(line), "/var/run/netns/")
			if err != nil {
				panic(err)
			}
			go startIPProcess(messages, string(line), "tcpdump", "")
		}
	}
}

func startIPProcess(messages chan<- []byte, procId string, watcherName string,
	watcherArgs ...string) {
	arguments := []string{"netns", "exec", procId, watcherName}
	arguments = append(arguments, watcherArgs...)
	netWatcherCommand := exec.Command("ip", arguments...)
	outpipe, err := netWatcherCommand.StdoutPipe()
	if err != nil {
		panic(err)
	}

	netWatcherCommand.Start()
	defer netWatcherCommand.Wait()

	stdoutReader := bufio.NewReader(outpipe)
	for {
		line, _, err := stdoutReader.ReadLine()
		if err == io.EOF {
			break
		} else if err != nil {
			panic(err)
		}
		messages <- line
	}
}

func setSymlink(procId string, destination string) error {
	err := os.Symlink("/proc/"+procId+"/ns/net", "/var/run/netns/"+procId)
	return err
}

/* This function is going to need some comments describing why we chose this
 * approach, because it will be hairy.
 * FIXME: Break out functionality into multiple functions so we can test this
 * easily!
 */
func (m FSMonitor) getDockerFSDirectory(dockerComposeName string) []string {
	ids := getDockerContainerIds(dockerComposeName)
	/*
		dockerInfoCommand := exec.Command("docker", "info")
		infoOutPipe, err := dockerInfoCommand.StdoutPipe()

		if err != nil {
			panic(err)
		}

		dockerInfoCommand.Start()
		defer dockerInfoCommand.Wait()

		infoBuf, err := ioutil.ReadAll(infoOutPipe)

		if err != nil {
			panic(err)
		}

		info := string(infoBuf)

		re := regexp.MustCompile("Docker Root Dir: (.*)")

		submatch := re.FindStringSubmatch(info)

		if len(submatch) < 2 {
			fmt.Println(submatch, info)
			panic("Couldn't find the docker root directory")
		}
		dockerRootPath := submatch[1]
	*/
	dockerRootPath := "/var/lib/docker/aufs"

	for i := 0; i < len(ids); i++ {
		ids[i] = dockerRootPath + "/mnt/" + ids[i]
	}
	return ids
}

/* Start the process running on the honeypot host to monitor the Docker
 * container. The Docker container's filesysem is mounted on the host. Find
 * the location of this filesysem with the getDockerFSDirectory function and
 * store it in the struct. Then create and start the process and forward
 * the output of the process on to the messages channel.
 */
func (m FSMonitor) Start(messages chan<- []byte, dockerComposeName string) {
	m.DockerDirs = m.getDockerFSDirectory(dockerComposeName)
	m.fsWatcherProc = exec.Command(m.MonitorName, m.DockerDirs...)
	fmt.Println(m.MonitorName, m.DockerDirs)
	defer m.fsWatcherProc.Wait()

	outpipe, err := m.fsWatcherProc.StdoutPipe()
	if err != nil {
		log.Println("Could not open the ", m.MonitorName, "stdout pipe")
		panic(err)
	}
	stderr, err := m.fsWatcherProc.StderrPipe()
	if err != nil {
		log.Println("Could not open the ", m.MonitorName, "stderr pipe")
		panic(err)
	}
	go io.Copy(os.Stderr, stderr)

	m.fsWatcherProc.Start()

	stdoutReader := bufio.NewReader(outpipe)
	for {
		line, _, err := stdoutReader.ReadLine()
		if err != nil {
			panic(err)
		}
		sb := line
		messages <- sb

	}
}

/* Stop the filesystem monitor. Kill the process monitoring the Docker
 * container's filesysem.
 */
func (m FSMonitor) Stop() {
	err := m.fsWatcherProc.Process.Kill()
	if err != nil {
		log.Fatal("Could not kill ", m.MonitorName, err)
	}
}
<commit_msg>Add support for long lines from the bufio reader<commit_after>package monitor

import (
	"bufio"
	"fmt"
	"io"
	_ "io/ioutil"
	"log"
	"os"
	"os/exec"
	_ "regexp"
)

/* The Monitor interface defines a series of methods which will be defined on
 * monitor structs. The Start method takes a channel to send messages over,
 * back to the configurator. The Stop method kills the process which is
 * performing the actual monitoring.
 */
type Monitor interface {
	Start(messages chan<- []byte, dockerComposeName string)
	Stop()
}

/* Store the MonitorName, which is the name of the program to execute, the
 * DockerDirs which are the directories to monitor which our Docker containers
 * of interest live in, and a pointer to the exec.Cmd struct which describes
 * the running command.
 */
type FSMonitor struct {
	MonitorName   string
	DockerDirs    []string
	fsWatcherProc *exec.Cmd
}

type NetMonitor struct {
	MonitorName  string
	ContainerIds []string
}

// Memoize this. It's kind of expensive to get.
var dockerContainerIds = []string{}

func getDockerContainerIds(dockerComposeName string) []string {
	if len(dockerContainerIds) != 0 {
		return dockerContainerIds
	}
	dockerComposeCommand := exec.Command(dockerComposeName, "ps", "-q")
	outpipe, err := dockerComposeCommand.StdoutPipe()
	if err != nil {
		panic(err)
	}

	dockerComposeCommand.Start()
	defer dockerComposeCommand.Wait()

	ids := []string{}
	stdoutReader := bufio.NewReader(outpipe)

	for {
		fetch := true
		line := []byte{}
		for fetch {
			partial_line, f, err := stdoutReader.ReadLine()
			fetch = f
			line = append(line, partial_line...)
			if err == io.EOF {
				break
			} else if err != nil {
				panic(err)
			}
		}
		if string(line) != "" {
			ids = append(ids, string(line))
		}
	}
	dockerContainerIds = ids
	return ids
}

func (n NetMonitor) Start(messages chan<- []byte, dockerComposeName string) {
	ids := getDockerContainerIds(dockerComposeName)
	arguments := []string{"inspect", "-f", "'{{ .State.Pid }}'"}
	arguments = append(arguments, ids...)
	dockerComposeCommand := exec.Command("docker", arguments...)
	outpipe, err := dockerComposeCommand.StdoutPipe()
	if err != nil {
		panic(err)
	}

	dockerComposeCommand.Start()
	defer dockerComposeCommand.Wait()

	procIds := []string{}
	stdoutReader := bufio.NewReader(outpipe)
	for {
		fetch := true
		line := []byte{}
		for fetch {
			partial_line, f, err := stdoutReader.ReadLine()
			fetch = f
			line = append(line, partial_line...)
			if err == io.EOF {
				break
			} else if err != nil {
				panic(err)
			}
		}
		if string(line) != "" {
			procIds = append(procIds, string(line))
			err = setSymlink(string(line), "/var/run/netns/")
			if err != nil {
				panic(err)
			}
			go startIPProcess(messages, string(line), "tcpdump", "")
		}
	}
}

func startIPProcess(messages chan<- []byte, procId string, watcherName string,
	watcherArgs ...string) {
	arguments := []string{"netns", "exec", procId, watcherName}
	arguments = append(arguments, watcherArgs...)
	netWatcherCommand := exec.Command("ip", arguments...)
	outpipe, err := netWatcherCommand.StdoutPipe()
	if err != nil {
		panic(err)
	}

	netWatcherCommand.Start()
	defer netWatcherCommand.Wait()

	stdoutReader := bufio.NewReader(outpipe)
	for {
		fetch := true
		line := []byte{}
		for fetch {
			partial_line, f, err := stdoutReader.ReadLine()
			fetch = f
			line = append(line, partial_line...)
			if err == io.EOF {
				break
			} else if err != nil {
				panic(err)
			}
		}
		messages <- line
	}
}

func setSymlink(procId string, destination string) error {
	err := os.Symlink("/proc/"+procId+"/ns/net", "/var/run/netns/"+procId)
	return err
}

/* This function is going to need some comments describing why we chose this
 * approach, because it will be hairy.
 * FIXME: Break out functionality into multiple functions so we can test this
 * easily!
 */
func (m FSMonitor) getDockerFSDirectory(dockerComposeName string) []string {
	ids := getDockerContainerIds(dockerComposeName)
	/*
		dockerInfoCommand := exec.Command("docker", "info")
		infoOutPipe, err := dockerInfoCommand.StdoutPipe()

		if err != nil {
			panic(err)
		}

		dockerInfoCommand.Start()
		defer dockerInfoCommand.Wait()

		infoBuf, err := ioutil.ReadAll(infoOutPipe)

		if err != nil {
			panic(err)
		}

		info := string(infoBuf)

		re := regexp.MustCompile("Docker Root Dir: (.*)")

		submatch := re.FindStringSubmatch(info)

		if len(submatch) < 2 {
			fmt.Println(submatch, info)
			panic("Couldn't find the docker root directory")
		}
		dockerRootPath := submatch[1]
	*/
	dockerRootPath := "/var/lib/docker/aufs"

	for i := 0; i < len(ids); i++ {
		ids[i] = dockerRootPath + "/mnt/" + ids[i]
	}
	return ids
}

/* Start the process running on the honeypot host to monitor the Docker
 * container. The Docker container's filesysem is mounted on the host. Find
 * the location of this filesysem with the getDockerFSDirectory function and
 * store it in the struct. Then create and start the process and forward
 * the output of the process on to the messages channel.
 */
func (m FSMonitor) Start(messages chan<- []byte, dockerComposeName string) {
	m.DockerDirs = m.getDockerFSDirectory(dockerComposeName)
	m.fsWatcherProc = exec.Command(m.MonitorName, m.DockerDirs...)
	fmt.Println(m.MonitorName, m.DockerDirs)
	defer m.fsWatcherProc.Wait()

	outpipe, err := m.fsWatcherProc.StdoutPipe()
	if err != nil {
		log.Println("Could not open the ", m.MonitorName, "stdout pipe")
		panic(err)
	}
	stderr, err := m.fsWatcherProc.StderrPipe()
	if err != nil {
		log.Println("Could not open the ", m.MonitorName, "stderr pipe")
		panic(err)
	}
	go io.Copy(os.Stderr, stderr)

	m.fsWatcherProc.Start()

	stdoutReader := bufio.NewReader(outpipe)

	for {
		fetch := true
		line := []byte{}
		for fetch {
			partial_line, f, err := stdoutReader.ReadLine()
			fetch = f
			line = append(line, partial_line...)
			if err != nil {
				panic(err)
			}
		}
		messages <- line

	}
}

/* Stop the filesystem monitor. Kill the process monitoring the Docker
 * container's filesysem.
 */
func (m FSMonitor) Stop() {
	err := m.fsWatcherProc.Process.Kill()
	if err != nil {
		log.Fatal("Could not kill ", m.MonitorName, err)
	}
}
<|endoftext|>


<commit_before>package bimg

/*
#cgo pkg-config: vips
#include "vips/vips.h"
*/
import "C"

const (
	// Quality defines the default JPEG quality to be used.
	Quality = 75
	// MaxSize defines the maximum pixels width or height supported.
	MaxSize = 16383
)

// Gravity represents the image gravity value.
type Gravity int

const (
	// GravityCentre represents the centre value used for image gravity orientation.
	GravityCentre Gravity = iota
	// GravityNorth represents the north value used for image gravity orientation.
	GravityNorth
	// GravityEast represents the east value used for image gravity orientation.
	GravityEast
	// GravitySouth represents the south value used for image gravity orientation.
	GravitySouth
	// GravityWest represents the west value used for image gravity orientation.
	GravityWest
	// GravitySmart enables libvips Smart Crop algorithm for image gravity orientation.
	GravitySmart
)

// Interpolator represents the image interpolation value.
type Interpolator int

const (
	// Bicubic interpolation value.
	Bicubic Interpolator = iota
	// Bilinear interpolation value.
	Bilinear
	// Nohalo interpolation value.
	Nohalo
	// Nearest neighbour interpolation value.
	Nearest
)

var interpolations = map[Interpolator]string{
	Bicubic:  "bicubic",
	Bilinear: "bilinear",
	Nohalo:   "nohalo",
	Nearest:  "nearest",
}

func (i Interpolator) String() string {
	return interpolations[i]
}

// Angle represents the image rotation angle value.
type Angle int

const (
	// D0 represents the rotation angle 0 degrees.
	D0 Angle = 0
	// D45 represents the rotation angle 90 degrees.
	D45 Angle = 45
	// D90 represents the rotation angle 90 degrees.
	D90 Angle = 90
	// D135 represents the rotation angle 90 degrees.
	D135 Angle = 135
	// D180 represents the rotation angle 180 degrees.
	D180 Angle = 180
	// D235 represents the rotation angle 235 degrees.
	D235 Angle = 235
	// D270 represents the rotation angle 270 degrees.
	D270 Angle = 270
	// D315 represents the rotation angle 180 degrees.
	D315 Angle = 315
)

// Direction represents the image direction value.
type Direction int

const (
	// Horizontal represents the orizontal image direction value.
	Horizontal Direction = C.VIPS_DIRECTION_HORIZONTAL
	// Vertical represents the vertical image direction value.
	Vertical Direction = C.VIPS_DIRECTION_VERTICAL
)

// Interpretation represents the image interpretation type.
// See: https://libvips.github.io/libvips/API/current/VipsImage.html#VipsInterpretation
type Interpretation int

const (
	// InterpretationError points to the libvips interpretation error type.
	InterpretationError Interpretation = C.VIPS_INTERPRETATION_ERROR
	// InterpretationMultiband points to its libvips interpretation equivalent type.
	InterpretationMultiband Interpretation = C.VIPS_INTERPRETATION_MULTIBAND
	// InterpretationBW points to its libvips interpretation equivalent type.
	InterpretationBW Interpretation = C.VIPS_INTERPRETATION_B_W
	// InterpretationCMYK points to its libvips interpretation equivalent type.
	InterpretationCMYK Interpretation = C.VIPS_INTERPRETATION_CMYK
	// InterpretationRGB points to its libvips interpretation equivalent type.
	InterpretationRGB Interpretation = C.VIPS_INTERPRETATION_RGB
	// InterpretationSRGB points to its libvips interpretation equivalent type.
	InterpretationSRGB Interpretation = C.VIPS_INTERPRETATION_sRGB
	// InterpretationRGB16 points to its libvips interpretation equivalent type.
	InterpretationRGB16 Interpretation = C.VIPS_INTERPRETATION_RGB16
	// InterpretationGREY16 points to its libvips interpretation equivalent type.
	InterpretationGREY16 Interpretation = C.VIPS_INTERPRETATION_GREY16
	// InterpretationScRGB points to its libvips interpretation equivalent type.
	InterpretationScRGB Interpretation = C.VIPS_INTERPRETATION_scRGB
	// InterpretationLAB points to its libvips interpretation equivalent type.
	InterpretationLAB Interpretation = C.VIPS_INTERPRETATION_LAB
	// InterpretationXYZ points to its libvips interpretation equivalent type.
	InterpretationXYZ Interpretation = C.VIPS_INTERPRETATION_XYZ
)

// Extend represents the image extend mode, used when the edges
// of an image are extended, you can specify how you want the extension done.
// See: https://libvips.github.io/libvips/API/current/libvips-conversion.html#VIPS-EXTEND-BACKGROUND:CAPS
type Extend int

const (
	// ExtendBlack extend with black (all 0) pixels mode.
	ExtendBlack Extend = C.VIPS_EXTEND_BLACK
	// ExtendCopy copy the image edges.
	ExtendCopy Extend = C.VIPS_EXTEND_COPY
	// ExtendRepeat repeat the whole image.
	ExtendRepeat Extend = C.VIPS_EXTEND_REPEAT
	// ExtendMirror mirror the whole image.
	ExtendMirror Extend = C.VIPS_EXTEND_MIRROR
	// ExtendWhite extend with white (all bits set) pixels.
	ExtendWhite Extend = C.VIPS_EXTEND_WHITE
	// ExtendBackground with colour from the background property.
	ExtendBackground Extend = C.VIPS_EXTEND_BACKGROUND
	// ExtendLast extend with last pixel.
	ExtendLast Extend = C.VIPS_EXTEND_LAST
)

// WatermarkFont defines the default watermark font to be used.
var WatermarkFont = "sans 10"

// Color represents a traditional RGB color scheme.
type Color struct {
	R, G, B uint8
}

// ColorBlack is a shortcut to black RGB color representation.
var ColorBlack = Color{0, 0, 0}

// Watermark represents the text-based watermark supported options.
type Watermark struct {
	Width       int
	DPI         int
	Margin      int
	Opacity     float32
	NoReplicate bool
	Text        string
	Font        string
	Background  Color
}

// WatermarkImage represents the image-based watermark supported options.
type WatermarkImage struct {
	Left    int
	Top     int
	Buf     []byte
	Opacity float32
}

// GaussianBlur represents the gaussian image transformation values.
type GaussianBlur struct {
	Sigma   float64
	MinAmpl float64
}

// Sharpen represents the image sharp transformation options.
type Sharpen struct {
	Radius int
	X1     float64
	Y2     float64
	Y3     float64
	M1     float64
	M2     float64
}

// Options represents the supported image transformation options.
type Options struct {
	Height         int
	Width          int
	AreaHeight     int
	AreaWidth      int
	Top            int
	Left           int
	Quality        int
	Compression    int
	Zoom           int
	Crop           bool
	SmartCrop      bool // Deprecated, use: bimg.Options.Gravity = bimg.GravitySmart
	Enlarge        bool
	Embed          bool
	Flip           bool
	Flop           bool
	Force          bool
	NoAutoRotate   bool
	NoProfile      bool
	Interlace      bool
	StripMetadata  bool
	Trim           bool
	Lossless       bool
	Extend         Extend
	Rotate         Angle
	Background     Color
	Gravity        Gravity
	Watermark      Watermark
	WatermarkImage WatermarkImage
	Type           ImageType
	Interpolator   Interpolator
	Interpretation Interpretation
	GaussianBlur   GaussianBlur
	Sharpen        Sharpen
	Threshold      float64
	Gamma          float64
	OutputICC      string
	InputICC       string
	Palette        bool
	// Speed defines the AVIF encoders CPU effort. Valid values are 0-8.
	Speed          int

	// private fields
	autoRotateOnly bool
}
<commit_msg>fix typo in comments<commit_after>package bimg

/*
#cgo pkg-config: vips
#include "vips/vips.h"
*/
import "C"

const (
	// Quality defines the default JPEG quality to be used.
	Quality = 75
	// MaxSize defines the maximum pixels width or height supported.
	MaxSize = 16383
)

// Gravity represents the image gravity value.
type Gravity int

const (
	// GravityCentre represents the centre value used for image gravity orientation.
	GravityCentre Gravity = iota
	// GravityNorth represents the north value used for image gravity orientation.
	GravityNorth
	// GravityEast represents the east value used for image gravity orientation.
	GravityEast
	// GravitySouth represents the south value used for image gravity orientation.
	GravitySouth
	// GravityWest represents the west value used for image gravity orientation.
	GravityWest
	// GravitySmart enables libvips Smart Crop algorithm for image gravity orientation.
	GravitySmart
)

// Interpolator represents the image interpolation value.
type Interpolator int

const (
	// Bicubic interpolation value.
	Bicubic Interpolator = iota
	// Bilinear interpolation value.
	Bilinear
	// Nohalo interpolation value.
	Nohalo
	// Nearest neighbour interpolation value.
	Nearest
)

var interpolations = map[Interpolator]string{
	Bicubic:  "bicubic",
	Bilinear: "bilinear",
	Nohalo:   "nohalo",
	Nearest:  "nearest",
}

func (i Interpolator) String() string {
	return interpolations[i]
}

// Angle represents the image rotation angle value.
type Angle int

const (
	// D0 represents the rotation angle 0 degrees.
	D0 Angle = 0
	// D45 represents the rotation angle 45 degrees.
	D45 Angle = 45
	// D90 represents the rotation angle 90 degrees.
	D90 Angle = 90
	// D135 represents the rotation angle 135 degrees.
	D135 Angle = 135
	// D180 represents the rotation angle 180 degrees.
	D180 Angle = 180
	// D235 represents the rotation angle 235 degrees.
	D235 Angle = 235
	// D270 represents the rotation angle 270 degrees.
	D270 Angle = 270
	// D315 represents the rotation angle 315 degrees.
	D315 Angle = 315
)

// Direction represents the image direction value.
type Direction int

const (
	// Horizontal represents the orizontal image direction value.
	Horizontal Direction = C.VIPS_DIRECTION_HORIZONTAL
	// Vertical represents the vertical image direction value.
	Vertical Direction = C.VIPS_DIRECTION_VERTICAL
)

// Interpretation represents the image interpretation type.
// See: https://libvips.github.io/libvips/API/current/VipsImage.html#VipsInterpretation
type Interpretation int

const (
	// InterpretationError points to the libvips interpretation error type.
	InterpretationError Interpretation = C.VIPS_INTERPRETATION_ERROR
	// InterpretationMultiband points to its libvips interpretation equivalent type.
	InterpretationMultiband Interpretation = C.VIPS_INTERPRETATION_MULTIBAND
	// InterpretationBW points to its libvips interpretation equivalent type.
	InterpretationBW Interpretation = C.VIPS_INTERPRETATION_B_W
	// InterpretationCMYK points to its libvips interpretation equivalent type.
	InterpretationCMYK Interpretation = C.VIPS_INTERPRETATION_CMYK
	// InterpretationRGB points to its libvips interpretation equivalent type.
	InterpretationRGB Interpretation = C.VIPS_INTERPRETATION_RGB
	// InterpretationSRGB points to its libvips interpretation equivalent type.
	InterpretationSRGB Interpretation = C.VIPS_INTERPRETATION_sRGB
	// InterpretationRGB16 points to its libvips interpretation equivalent type.
	InterpretationRGB16 Interpretation = C.VIPS_INTERPRETATION_RGB16
	// InterpretationGREY16 points to its libvips interpretation equivalent type.
	InterpretationGREY16 Interpretation = C.VIPS_INTERPRETATION_GREY16
	// InterpretationScRGB points to its libvips interpretation equivalent type.
	InterpretationScRGB Interpretation = C.VIPS_INTERPRETATION_scRGB
	// InterpretationLAB points to its libvips interpretation equivalent type.
	InterpretationLAB Interpretation = C.VIPS_INTERPRETATION_LAB
	// InterpretationXYZ points to its libvips interpretation equivalent type.
	InterpretationXYZ Interpretation = C.VIPS_INTERPRETATION_XYZ
)

// Extend represents the image extend mode, used when the edges
// of an image are extended, you can specify how you want the extension done.
// See: https://libvips.github.io/libvips/API/current/libvips-conversion.html#VIPS-EXTEND-BACKGROUND:CAPS
type Extend int

const (
	// ExtendBlack extend with black (all 0) pixels mode.
	ExtendBlack Extend = C.VIPS_EXTEND_BLACK
	// ExtendCopy copy the image edges.
	ExtendCopy Extend = C.VIPS_EXTEND_COPY
	// ExtendRepeat repeat the whole image.
	ExtendRepeat Extend = C.VIPS_EXTEND_REPEAT
	// ExtendMirror mirror the whole image.
	ExtendMirror Extend = C.VIPS_EXTEND_MIRROR
	// ExtendWhite extend with white (all bits set) pixels.
	ExtendWhite Extend = C.VIPS_EXTEND_WHITE
	// ExtendBackground with colour from the background property.
	ExtendBackground Extend = C.VIPS_EXTEND_BACKGROUND
	// ExtendLast extend with last pixel.
	ExtendLast Extend = C.VIPS_EXTEND_LAST
)

// WatermarkFont defines the default watermark font to be used.
var WatermarkFont = "sans 10"

// Color represents a traditional RGB color scheme.
type Color struct {
	R, G, B uint8
}

// ColorBlack is a shortcut to black RGB color representation.
var ColorBlack = Color{0, 0, 0}

// Watermark represents the text-based watermark supported options.
type Watermark struct {
	Width       int
	DPI         int
	Margin      int
	Opacity     float32
	NoReplicate bool
	Text        string
	Font        string
	Background  Color
}

// WatermarkImage represents the image-based watermark supported options.
type WatermarkImage struct {
	Left    int
	Top     int
	Buf     []byte
	Opacity float32
}

// GaussianBlur represents the gaussian image transformation values.
type GaussianBlur struct {
	Sigma   float64
	MinAmpl float64
}

// Sharpen represents the image sharp transformation options.
type Sharpen struct {
	Radius int
	X1     float64
	Y2     float64
	Y3     float64
	M1     float64
	M2     float64
}

// Options represents the supported image transformation options.
type Options struct {
	Height         int
	Width          int
	AreaHeight     int
	AreaWidth      int
	Top            int
	Left           int
	Quality        int
	Compression    int
	Zoom           int
	Crop           bool
	SmartCrop      bool // Deprecated, use: bimg.Options.Gravity = bimg.GravitySmart
	Enlarge        bool
	Embed          bool
	Flip           bool
	Flop           bool
	Force          bool
	NoAutoRotate   bool
	NoProfile      bool
	Interlace      bool
	StripMetadata  bool
	Trim           bool
	Lossless       bool
	Extend         Extend
	Rotate         Angle
	Background     Color
	Gravity        Gravity
	Watermark      Watermark
	WatermarkImage WatermarkImage
	Type           ImageType
	Interpolator   Interpolator
	Interpretation Interpretation
	GaussianBlur   GaussianBlur
	Sharpen        Sharpen
	Threshold      float64
	Gamma          float64
	OutputICC      string
	InputICC       string
	Palette        bool
	// Speed defines the AVIF encoders CPU effort. Valid values are 0-8.
	Speed int

	// private fields
	autoRotateOnly bool
}
<|endoftext|>


<commit_before>package main

import (
	"code.google.com/p/gcfg"
	"github.com/Hranoprovod/api-client"
	"github.com/Hranoprovod/parser"
	"github.com/Hranoprovod/processor"
	"github.com/Hranoprovod/reporter"
	"github.com/codegangsta/cli"
	"os"
	"os/user"
	"time"
)

const (
	optionsFileName = "/.hranoprovod/config"
)

// Options contains the options structure
type Options struct {
	Global struct {
		DbFileName  string
		LogFileName string
		DateFormat  string
	}
	Resolver struct {
		ResolverMaxDepth int
	}
	Parser    parser.Options
	Processor processor.Options
	Reporter  reporter.Options
	API       client.Options
}

// NewOptions returns new options structure.
func NewOptions() *Options {
	o := &Options{}
	o.Reporter = *reporter.NewDefaultOptions()
	o.Reporter.Color = true
	o.Processor = *processor.NewDefaultOptions()
	o.Parser = *parser.NewDefaultOptions()
	o.API = *client.NewDefaultOptions()
	return o
}

// Load loads the settigns from config file/command line params/defauls from given context.
func (o *Options) Load(c *cli.Context) *Options {
	fileName := c.GlobalString("config")
	// First try to load the o file
	if exists(fileName) {
		if err := gcfg.ReadFileInto(o, fileName); err != nil {
			// o file is not valid
			panic(err)
		}
	}
	o.populateGlobals(c)
	o.populateLocals(c)
	return o
}

// GetDefaultFileName returns the default filename for the config file
func GetDefaultFileName() string {
	usr, err := user.Current()
	if err != nil {
		return ""
	}
	return usr.HomeDir + optionsFileName
}

func exists(name string) bool {
	_, err := os.Stat(name)
	return !os.IsNotExist(err)
}

func (o *Options) populateGlobals(c *cli.Context) {
	if c.GlobalIsSet("database") || o.Global.DbFileName == "" {
		o.Global.DbFileName = c.GlobalString("database")
	}

	if c.GlobalIsSet("logfile") || o.Global.LogFileName == "" {
		o.Global.LogFileName = c.GlobalString("logfile")
	}

	if c.GlobalIsSet("date-format") || o.Global.DateFormat == "" {
		o.Global.DateFormat = c.GlobalString("date-format")
	}
}

func (o *Options) populateLocals(c *cli.Context) {
	o.populateResolver(c)
	o.populateProcessor(c)
	o.populateReporter(c)
}

func (o *Options) populateResolver(c *cli.Context) {
	if c.IsSet("maxdepth") || o.Resolver.ResolverMaxDepth == 0 {
		o.Resolver.ResolverMaxDepth = c.Int("maxdepth")
	}
}

func (o *Options) populateProcessor(c *cli.Context) {
	var err error

	if c.IsSet("beginning") {
		o.Processor.BeginningTime, err = time.Parse(o.Global.DateFormat, c.String("beginning"))
		if err != nil {
			panic(err)
		}
		o.Processor.HasBeginning = true
	}

	if c.IsSet("end") {
		o.Processor.EndTime, err = time.Parse(o.Global.DateFormat, c.String("end"))
		if err != nil {
			panic(err)
		}
		o.Processor.HasEnd = true
	}

	o.Processor.Unresolved = c.Bool("unresolved")
	o.Processor.SingleFood = c.String("single-food")
	o.Processor.SingleElement = c.String("single-element")
}

func (o *Options) populateReporter(c *cli.Context) {
	if c.IsSet("csv") {
		o.Reporter.CSV = true
	}
	if c.IsSet("no-color") {
		o.Reporter.Color = false
	}

	if c.IsSet("no-totals") {
		o.Processor.Totals = false
	}
}
<commit_msg>Fixed key name<commit_after>package main

import (
	"code.google.com/p/gcfg"
	"github.com/Hranoprovod/api-client"
	"github.com/Hranoprovod/parser"
	"github.com/Hranoprovod/processor"
	"github.com/Hranoprovod/reporter"
	"github.com/codegangsta/cli"
	"os"
	"os/user"
	"time"
)

const (
	optionsFileName = "/.hranoprovod/config"
)

// Options contains the options structure
type Options struct {
	Global struct {
		DbFileName  string
		LogFileName string
		DateFormat  string
	}
	Resolver struct {
		ResolverMaxDepth int
	}
	Parser    parser.Options
	Processor processor.Options
	Reporter  reporter.Options
	API       client.Options
}

// NewOptions returns new options structure.
func NewOptions() *Options {
	o := &Options{}
	o.Reporter = *reporter.NewDefaultOptions()
	o.Reporter.Color = true
	o.Processor = *processor.NewDefaultOptions()
	o.Parser = *parser.NewDefaultOptions()
	o.API = *client.NewDefaultOptions()
	return o
}

// Load loads the settigns from config file/command line params/defauls from given context.
func (o *Options) Load(c *cli.Context) *Options {
	fileName := c.GlobalString("config")
	// First try to load the o file
	if exists(fileName) {
		if err := gcfg.ReadFileInto(o, fileName); err != nil {
			// o file is not valid
			panic(err)
		}
	}
	o.populateGlobals(c)
	o.populateLocals(c)
	return o
}

// GetDefaultFileName returns the default filename for the config file
func GetDefaultFileName() string {
	usr, err := user.Current()
	if err != nil {
		return ""
	}
	return usr.HomeDir + optionsFileName
}

func exists(name string) bool {
	_, err := os.Stat(name)
	return !os.IsNotExist(err)
}

func (o *Options) populateGlobals(c *cli.Context) {
	if c.GlobalIsSet("database") || o.Global.DbFileName == "" {
		o.Global.DbFileName = c.GlobalString("database")
	}

	if c.GlobalIsSet("logfile") || o.Global.LogFileName == "" {
		o.Global.LogFileName = c.GlobalString("logfile")
	}

	if c.GlobalIsSet("date-format") || o.Global.DateFormat == "" {
		o.Global.DateFormat = c.GlobalString("date-format")
	}
}

func (o *Options) populateLocals(c *cli.Context) {
	o.populateResolver(c)
	o.populateProcessor(c)
	o.populateReporter(c)
}

func (o *Options) populateResolver(c *cli.Context) {
	if c.IsSet("maxdepth") || o.Resolver.ResolverMaxDepth == 0 {
		o.Resolver.ResolverMaxDepth = c.Int("maxdepth")
	}
}

func (o *Options) populateProcessor(c *cli.Context) {
	var err error

	if c.IsSet("begin") {
		o.Processor.BeginningTime, err = time.Parse(o.Global.DateFormat, c.String("begin"))
		if err != nil {
			panic(err)
		}
		o.Processor.HasBeginning = true
	}

	if c.IsSet("end") {
		o.Processor.EndTime, err = time.Parse(o.Global.DateFormat, c.String("end"))
		if err != nil {
			panic(err)
		}
		o.Processor.HasEnd = true
	}

	o.Processor.Unresolved = c.Bool("unresolved")
	o.Processor.SingleFood = c.String("single-food")
	o.Processor.SingleElement = c.String("single-element")
}

func (o *Options) populateReporter(c *cli.Context) {
	if c.IsSet("csv") {
		o.Reporter.CSV = true
	}
	if c.IsSet("no-color") {
		o.Reporter.Color = false
	}

	if c.IsSet("no-totals") {
		o.Processor.Totals = false
	}
}
<|endoftext|>


<commit_before>// Copyright 2016 Google Inc. 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 types

import (
	"encoding/json"
	"reflect"
	"testing"
)

func TestLegacyStatusUnmarshal(t *testing.T) {
	tests := []struct {
		s string
		v []string
	}{
		{`"[]"`, []string{}},
		{`"['one']"`, []string{"one"}},
		{`"['one', u'two']"`, []string{"one", "two"}},
		{`["one", "two"]`, []string{"one", "two"}},
	}
	for i, test := range tests {
		var v LegacyStatus
		if err := json.Unmarshal([]byte(test.s), &v); err != nil {
			t.Errorf("%d: json.Unmarshal(%s): %v", i, test.s, err)
			continue
		}
		if !reflect.DeepEqual(v, LegacyStatus(test.v)) {
			t.Errorf("%d: v = %v; want %v", i, v, test.v)
		}
	}
}

func TestLegacyStatusMarshal(t *testing.T) {
	tests := []struct {
		s string
		v []string
	}{
		{"[]", nil},
		{"[]", []string{}},
		{`["one"]`, []string{"one"}},
	}
	for i, test := range tests {
		s := LegacyStatus(test.v)
		b, err := json.Marshal(&s)
		if err != nil {
			t.Errorf("%d: json.Marshal(%s): %v", i, test.v, err)
			continue
		}
		if string(b) != test.s {
			t.Errorf("%d: b = %s; want %s", i, b, test.s)
		}
	}
}
<commit_msg>Add tests for LegacyStatus' String().<commit_after>// Copyright 2016 Google Inc. 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 types

import (
	"encoding/json"
	"reflect"
	"testing"
)

func TestLegacyStatusUnmarshal(t *testing.T) {
	tests := []struct {
		s string
		v []string
	}{
		{`"[]"`, []string{}},
		{`"['one']"`, []string{"one"}},
		{`"['one', u'two']"`, []string{"one", "two"}},
		{`["one", "two"]`, []string{"one", "two"}},
	}
	for i, test := range tests {
		var v LegacyStatus
		if err := json.Unmarshal([]byte(test.s), &v); err != nil {
			t.Errorf("%d: json.Unmarshal(%s): %v", i, test.s, err)
			continue
		}
		if !reflect.DeepEqual(v, LegacyStatus(test.v)) {
			t.Errorf("%d: v = %v; want %v", i, v, test.v)
		}
	}
}

func TestLegacyStatusMarshal(t *testing.T) {
	tests := []struct {
		s string
		v []string
	}{
		{"[]", nil},
		{"[]", []string{}},
		{`["one"]`, []string{"one"}},
	}
	for i, test := range tests {
		s := LegacyStatus(test.v)
		b, err := json.Marshal(&s)
		if err != nil {
			t.Errorf("%d: json.Marshal(%s): %v", i, test.v, err)
			continue
		}
		if string(b) != test.s {
			t.Errorf("%d: b = %s; want %s", i, b, test.s)
		}
	}
}

func TestString(t *testing.T) {
	tests := []struct {
		name     string
		inStatus LegacyStatus
		out      string
	}{
		{
			name: "Empty",
		},
		{
			name:     "One",
			inStatus: LegacyStatus{"foo"},
			out:      "[foo]",
		},
		{
			name:     "Multiple",
			inStatus: LegacyStatus{"foo", "bar", "baz"},
			out:      "[foo,bar,baz]",
		},
		{
			name:     "Duplicates",
			inStatus: LegacyStatus{"foo", "bar", "baz", "foo"},
			out:      "[foo,bar,baz,foo]",
		},
	}
	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			out := tc.inStatus.String()
			if out != tc.out {
				t.Errorf("LegacyStatus.String() = %q, want %q", out, tc.out)
				return
			}
		})
	}
}
<|endoftext|>


<commit_before>package loadbalancer

import (
	"fmt"
	"io/ioutil"
	"net/http"
	"time"

	"crypto/tls"
	"crypto/x509"
	log "github.com/Sirupsen/logrus"
	"github.com/xtracdev/xavi/config"
)

//DefaultHealthCheckInterval is the time between health checks if no value is specified by the configuration
const DefaultHealthCheckInterval = 30 * 1000 //30 seconds

//DefaultHealthCheckTimeout is the timeout for the health check if no value is specified by the configuration
const DefaultHealthCheckTimeout = 10 * 1000 //10 seconds

//IsKnownHealthCheck returns true for the health check types supported by the toolkit
func IsKnownHealthCheck(healthcheck string) bool {
	switch healthcheck {
	case "none":
		return true
	case "http-get":
		return true
	case "https-get":
		return true
	case "custom-http":
		return true
	case "custom-https":
		return true
	default:
		return false
	}
}

//KnownHealthChecks returns the names of the health checks supported bt the toolkit
func KnownHealthChecks() string {
	return "none, http-get, https-get, custom-http, custom-https"
}

func createHealthCheckFnWithTimeout(healthCheckTimeout time.Duration) config.HealthCheckFn {
	return func(endpoint string, transport *http.Transport) <-chan bool {
		statusChannel := make(chan bool)

		client := &http.Client{
			Transport: transport,
			Timeout:   healthCheckTimeout,
		}

		go func() {

			resp, err := client.Get(endpoint)
			if err != nil {
				log.Warn("Error doing get on healthcheck endpoint ", endpoint, " : ", err.Error())

				//Check to see if there's a non-nil response: drain it if present
				if resp != nil {
					log.Info("clean up non-nil response delivered with health check client error")
					defer resp.Body.Close()
					b, err := ioutil.ReadAll(resp.Body)
					if err != nil {
						log.Infof("Error reading resp while cleaning up after error: %v\n", err)
					} else {
						log.Infof("Discarded response body after handling error on healtcheck get: %s\n", b)
					}
				}

				statusChannel <- false
				return
			}

			//Read the entire response and close the body to ensure proper connection hygiene. On the mac you
			//can use something like lsof | grep xavi|wc -l  (and check/timeout values
			//of 5000/2000 ms respectively) to see file handles in use - without the close and read the
			//connections in grow without being released.
			defer resp.Body.Close()
			_, err = ioutil.ReadAll(resp.Body)
			if err != nil {
				log.Warnf("Error reading health check response: %v", err)
				statusChannel <- false
				return
			}

			statusChannel <- resp.StatusCode == 200
		}()

		return statusChannel
	}
}

func makeCertPool(caCertPath string) *x509.CertPool {
	pool := x509.NewCertPool()

	pemData, err := ioutil.ReadFile(caCertPath)
	if err != nil {
		log.Warn("Error creating CA Cert Poll for health check: ", err.Error())
		return nil
	}

	ok := pool.AppendCertsFromPEM(pemData)
	if !ok {
		log.Warn("Error append pem data to cert pool for health check: ", err.Error())
		return nil
	}

	return pool
}

func makeTransportForHealthCheck(https bool, caCertPath string) *http.Transport {
	defaultTransport := &http.Transport{DisableKeepAlives: false, DisableCompression: false, Proxy: http.ProxyFromEnvironment}
	//Non-https case
	if https == false {
		return defaultTransport
	}

	if caCertPath == "" {
		log.Info("Using default transport for https health check - will work only for known CAs")
		log.Info("For self signed certs specify -cacert-path in your backend configuration.")
		return defaultTransport
	}

	pool := makeCertPool(caCertPath)
	if pool == nil {
		log.Warn("Unable to create cert pool based on configuration - using default transport")
		return defaultTransport
	}

	log.Info("using custom transport for health check")
	tlsConfig := &tls.Config{RootCAs: pool}
	return &http.Transport{DisableKeepAlives: false, DisableCompression: false, Proxy: http.ProxyFromEnvironment, TLSClientConfig: tlsConfig}

}

func httpGet(lbEndpoint *LoadBalancerEndpoint, serverConfig config.ServerConfig, loop bool, https bool, hcfn config.HealthCheckFn) func() {

	var url string
	transport := makeTransportForHealthCheck(https, lbEndpoint.CACertPath)
	if https {
		url = fmt.Sprintf("https://%s:%d%s", serverConfig.Address, serverConfig.Port, serverConfig.PingURI)
	} else {
		url = fmt.Sprintf("http://%s:%d%s", serverConfig.Address, serverConfig.Port, serverConfig.PingURI)
	}

	log.Info("Setting healthcheck url to ", url)
	healthCheckInterval := time.Duration(serverConfig.HealthCheckInterval) * time.Millisecond

	return func() {
		for {
			time.Sleep(healthCheckInterval)
			log.Debug("checking health")
			select {
			case healthStatus := <-hcfn(url, transport):
				if !healthStatus {
					log.Warn("Endpoint ", serverConfig.Address, ":", serverConfig.Port, " is not healthy")
					lbEndpoint.MarkLoadBalancerEndpointUp(false)
				} else {
					log.Debug("Endpoint is up: ", serverConfig.Address, ":", serverConfig.Port)
					lbEndpoint.MarkLoadBalancerEndpointUp(true)
				}

			case <-time.After(time.Duration(serverConfig.HealthCheckTimeout) * time.Millisecond):
				log.Warn("Health check timed out - marking endpoint ", serverConfig.Address, ":", serverConfig.Port, " as not healthy")
				lbEndpoint.MarkLoadBalancerEndpointUp(false)
			}

			if loop == false {
				return
			}
		}
	}
}

func noop() {}

//MakeHealthCheck returns a health check function based on the server configuration and load balancer endpoint. The
//loop arguement is meant to enable testability - normal health check functions run until the listener is shutdown,
//unit test health checks run once typically.
func MakeHealthCheck(lbEndpoint *LoadBalancerEndpoint, serverConfig config.ServerConfig, loop bool) func() {
	log.Debugf("Making health check for %s", serverConfig.Name)
	switch serverConfig.HealthCheck {
	default:
		log.Debug("returning no-op health check")
		return noop
	case "http-get":
		log.Debug("returning http-get health check")
		healthCheckTimeout := time.Duration(DefaultHealthCheckTimeout)
		if serverConfig.HealthCheckTimeout > 0 {
			healthCheckTimeout = time.Duration(serverConfig.HealthCheckTimeout) * time.Millisecond
		}
		return httpGet(lbEndpoint, serverConfig, loop, false,
			createHealthCheckFnWithTimeout(healthCheckTimeout))
	case "https-get":
		log.Debug("returning https-get health check")
		healthCheckTimeout := time.Duration(DefaultHealthCheckTimeout)
		if serverConfig.HealthCheckTimeout > 0 {
			healthCheckTimeout = time.Duration(serverConfig.HealthCheckTimeout) * time.Millisecond
		}
		return httpGet(lbEndpoint, serverConfig, loop, true,
			createHealthCheckFnWithTimeout(healthCheckTimeout))
	case "custom-http":
		log.Debug("returning custom http-get health check")
		hcfn := config.HealthCheckForServer(serverConfig.Name)
		if hcfn == nil {
			log.Fatalf("No custom health check registered for %s - add code to register healthcheck or change config",
				serverConfig.Name)
		}
		return httpGet(lbEndpoint, serverConfig, loop, false, hcfn)
	case "custom-https":
		log.Debug("returning custom https-get health check")
		hcfn := config.HealthCheckForServer(serverConfig.Name)
		if hcfn == nil {
			log.Fatalf("No custom health check registered for %s - add code to register healthcheck or change config",
				serverConfig.Name)
		}
		return httpGet(lbEndpoint, serverConfig, loop, true, hcfn)
	}
}
<commit_msg>reduce health check logs<commit_after>package loadbalancer

import (
	"fmt"
	"io/ioutil"
	"net/http"
	"time"

	"crypto/tls"
	"crypto/x509"
	log "github.com/Sirupsen/logrus"
	"github.com/xtracdev/xavi/config"
)

//DefaultHealthCheckInterval is the time between health checks if no value is specified by the configuration
const DefaultHealthCheckInterval = 30 * 1000 //30 seconds

//DefaultHealthCheckTimeout is the timeout for the health check if no value is specified by the configuration
const DefaultHealthCheckTimeout = 10 * 1000 //10 seconds

//IsKnownHealthCheck returns true for the health check types supported by the toolkit
func IsKnownHealthCheck(healthcheck string) bool {
	switch healthcheck {
	case "none":
		return true
	case "http-get":
		return true
	case "https-get":
		return true
	case "custom-http":
		return true
	case "custom-https":
		return true
	default:
		return false
	}
}

//KnownHealthChecks returns the names of the health checks supported bt the toolkit
func KnownHealthChecks() string {
	return "none, http-get, https-get, custom-http, custom-https"
}

func createHealthCheckFnWithTimeout(healthCheckTimeout time.Duration) config.HealthCheckFn {
	return func(endpoint string, transport *http.Transport) <-chan bool {
		statusChannel := make(chan bool)

		client := &http.Client{
			Transport: transport,
			Timeout:   healthCheckTimeout,
		}

		go func() {

			resp, err := client.Get(endpoint)
			if err != nil {
				log.Warn("Error doing get on healthcheck endpoint ", endpoint, " : ", err.Error())

				//Check to see if there's a non-nil response: drain it if present
				if resp != nil {
					log.Info("clean up non-nil response delivered with health check client error")
					defer resp.Body.Close()
					b, err := ioutil.ReadAll(resp.Body)
					if err != nil {
						log.Infof("Error reading resp while cleaning up after error: %v\n", err)
					} else {
						log.Infof("Discarded response body after handling error on healtcheck get: %s\n", b)
					}
				}

				statusChannel <- false
				return
			}

			//Read the entire response and close the body to ensure proper connection hygiene. On the mac you
			//can use something like lsof | grep xavi|wc -l  (and check/timeout values
			//of 5000/2000 ms respectively) to see file handles in use - without the close and read the
			//connections in grow without being released.
			defer resp.Body.Close()
			_, err = ioutil.ReadAll(resp.Body)
			if err != nil {
				log.Warnf("Error reading health check response: %v", err)
				statusChannel <- false
				return
			}

			statusChannel <- resp.StatusCode == 200
		}()

		return statusChannel
	}
}

func makeCertPool(caCertPath string) *x509.CertPool {
	pool := x509.NewCertPool()

	pemData, err := ioutil.ReadFile(caCertPath)
	if err != nil {
		log.Warn("Error creating CA Cert Poll for health check: ", err.Error())
		return nil
	}

	ok := pool.AppendCertsFromPEM(pemData)
	if !ok {
		log.Warn("Error append pem data to cert pool for health check: ", err.Error())
		return nil
	}

	return pool
}

func makeTransportForHealthCheck(https bool, caCertPath string) *http.Transport {
	defaultTransport := &http.Transport{DisableKeepAlives: false, DisableCompression: false, Proxy: http.ProxyFromEnvironment}
	//Non-https case
	if https == false {
		return defaultTransport
	}

	if caCertPath == "" {
		log.Info("Using default transport for https health check - will work only for known CAs")
		log.Info("For self signed certs specify -cacert-path in your backend configuration.")
		return defaultTransport
	}

	pool := makeCertPool(caCertPath)
	if pool == nil {
		log.Warn("Unable to create cert pool based on configuration - using default transport")
		return defaultTransport
	}

	log.Info("using custom transport for health check")
	tlsConfig := &tls.Config{RootCAs: pool}
	return &http.Transport{DisableKeepAlives: false, DisableCompression: false, Proxy: http.ProxyFromEnvironment, TLSClientConfig: tlsConfig}

}

func httpGet(lbEndpoint *LoadBalancerEndpoint, serverConfig config.ServerConfig, loop bool, https bool, hcfn config.HealthCheckFn) func() {

	var url string
	transport := makeTransportForHealthCheck(https, lbEndpoint.CACertPath)
	if https {
		url = fmt.Sprintf("https://%s:%d%s", serverConfig.Address, serverConfig.Port, serverConfig.PingURI)
	} else {
		url = fmt.Sprintf("http://%s:%d%s", serverConfig.Address, serverConfig.Port, serverConfig.PingURI)
	}

	log.Info("Setting healthcheck url to ", url)
	healthCheckInterval := time.Duration(serverConfig.HealthCheckInterval) * time.Millisecond

	return func() {
		for {
			time.Sleep(healthCheckInterval)
			log.Debug("checking health")
			select {
			case healthStatus := <-hcfn(url, transport):
				if !healthStatus {
					if lbEndpoint.IsUp() {
						log.Warn("Endpoint ", serverConfig.Address, ":", serverConfig.Port, " is not healthy")
						lbEndpoint.MarkLoadBalancerEndpointUp(false)
					}
				} else {
					if !lbEndpoint.IsUp() {
						log.Debug("Endpoint is up: ", serverConfig.Address, ":", serverConfig.Port)
						lbEndpoint.MarkLoadBalancerEndpointUp(true)
					}
				}

			case <-time.After(time.Duration(serverConfig.HealthCheckTimeout) * time.Millisecond):
				log.Warn("Health check timed out - marking endpoint ", serverConfig.Address, ":", serverConfig.Port, " as not healthy")
				lbEndpoint.MarkLoadBalancerEndpointUp(false)
			}

			if loop == false {
				return
			}
		}
	}
}

func noop() {}

//MakeHealthCheck returns a health check function based on the server configuration and load balancer endpoint. The
//loop arguement is meant to enable testability - normal health check functions run until the listener is shutdown,
//unit test health checks run once typically.
func MakeHealthCheck(lbEndpoint *LoadBalancerEndpoint, serverConfig config.ServerConfig, loop bool) func() {
	log.Debugf("Making health check for %s", serverConfig.Name)
	switch serverConfig.HealthCheck {
	default:
		log.Debug("returning no-op health check")
		return noop
	case "http-get":
		log.Debug("returning http-get health check")
		healthCheckTimeout := time.Duration(DefaultHealthCheckTimeout)
		if serverConfig.HealthCheckTimeout > 0 {
			healthCheckTimeout = time.Duration(serverConfig.HealthCheckTimeout) * time.Millisecond
		}
		return httpGet(lbEndpoint, serverConfig, loop, false,
			createHealthCheckFnWithTimeout(healthCheckTimeout))
	case "https-get":
		log.Debug("returning https-get health check")
		healthCheckTimeout := time.Duration(DefaultHealthCheckTimeout)
		if serverConfig.HealthCheckTimeout > 0 {
			healthCheckTimeout = time.Duration(serverConfig.HealthCheckTimeout) * time.Millisecond
		}
		return httpGet(lbEndpoint, serverConfig, loop, true,
			createHealthCheckFnWithTimeout(healthCheckTimeout))
	case "custom-http":
		log.Debug("returning custom http-get health check")
		hcfn := config.HealthCheckForServer(serverConfig.Name)
		if hcfn == nil {
			log.Fatalf("No custom health check registered for %s - add code to register healthcheck or change config",
				serverConfig.Name)
		}
		return httpGet(lbEndpoint, serverConfig, loop, false, hcfn)
	case "custom-https":
		log.Debug("returning custom https-get health check")
		hcfn := config.HealthCheckForServer(serverConfig.Name)
		if hcfn == nil {
			log.Fatalf("No custom health check registered for %s - add code to register healthcheck or change config",
				serverConfig.Name)
		}
		return httpGet(lbEndpoint, serverConfig, loop, true, hcfn)
	}
}
<|endoftext|>


<commit_before>package proxy

import (
	"bufio"
	"bytes"
	"crypto/tls"
	"fmt"
	"io"
	"log"
	"net"
	"net/http"
	"strings"
	"sync/atomic"
	"time"

	"github.com/yinqiwen/gsnova/common/event"
	"github.com/yinqiwen/gsnova/common/fakecert"
	"github.com/yinqiwen/gsnova/common/helper"
	"github.com/yinqiwen/gsnova/local/socks"
)

var sidSeed uint32 = 0
var proxyServerRunning = true

func getSessionId() uint32 {
	return atomic.AddUint32(&sidSeed, 1)
}

func serveProxyConn(conn net.Conn, proxy ProxyConfig) {
	var p Proxy
	protocol := "tcp"
	sid := getSessionId()
	queue := event.NewEventQueue()
	connClosed := false
	session := newProxySession(sid, queue)
	defer closeProxySession(sid)

	socksTargetHost := ""
	socksTargetPort := ""
	tryRemoteResolve := false
	socksConn, bufconn, err := socks.NewSocksConn(conn)

	socksInitProxy := func(addr string) {
		if socksTargetHost == "127.0.0.1" {
			proxyName := "Direct"
			p = getProxyByName(proxyName)
		} else {
			creq, _ := http.NewRequest("Connect", "https://"+addr, nil)
			p = proxy.findProxyByRequest(protocol, socksTargetHost, creq)
		}
		if nil == p {
			conn.Close()
			return
		}
		log.Printf("Session:%d select channel:%s", sid, p.Config().Name)
		if p.Config().IsDirect() && net.ParseIP(socksTargetHost) != nil {
			addr = net.JoinHostPort(socksTargetHost, socksTargetPort)
		}
		tcpOpen := &event.TCPOpenEvent{}
		tcpOpen.SetId(sid)
		tcpOpen.Addr = addr
		p.Serve(session, tcpOpen)
	}

	if nil == err {
		log.Printf("Local proxy recv %s proxy conn to %s", socksConn.Version(), socksConn.Req.Target)
		socksConn.Grant(&net.TCPAddr{
			IP: net.ParseIP("0.0.0.0"), Port: 0})

		if socksConn.Req.Target == GConf.UDPGWAddr {
			log.Printf("Handle udpgw conn for %v", socksConn.Req.Target)
			handleUDPGatewayConn(conn, proxy)
			return
		}
		conn = socksConn
		session.Hijacked = true

		socksTargetHost, socksTargetPort, err = net.SplitHostPort(socksConn.Req.Target)
		if nil != err {
			log.Printf("Invalid socks target addresss:%s with reason %v", socksConn.Req.Target, err)
			return
		}
		if socksTargetHost != "127.0.0.1" && net.ParseIP(socksTargetHost) != nil && proxy.SNISniff {
			//this is a ip from local dns query
			tryRemoteResolve = true
			if socksTargetPort == "80" {
				//we can parse http request directly
				session.Hijacked = false
			}
		} else {
			socksInitProxy(net.JoinHostPort(socksTargetHost, socksTargetPort))
		}

	} else {
		if nil == bufconn {
			conn.Close()
			return
		}
	}
	if nil == bufconn {
		bufconn = bufio.NewReader(conn)
	}
	defer conn.Close()

	testConn := func() {
		if nil != p {
			var testConnEv event.ConnTestEvent
			testConnEv.SetId(sid)
			p.Serve(session, &testConnEv)
		}
	}

	go func() {
		for !connClosed {
			ev, err := queue.Read(1 * time.Second)
			if err != nil {
				if err != io.EOF {
					continue
				}
				return
			}
			//log.Printf("Session:%d recv event:%T", sid, ev)
			switch ev.(type) {
			case *event.NotifyEvent:
				//donothing now
			case *event.ConnCloseEvent:
				connClosed = true
				conn.Close()
				return
			case *event.TCPChunkEvent:
				conn.Write(ev.(*event.TCPChunkEvent).Content)
			case *event.HTTPResponseEvent:
				ev.(*event.HTTPResponseEvent).Write(conn)
				code := ev.(*event.HTTPResponseEvent).StatusCode
				log.Printf("Session:%d response:%d %v", ev.GetId(), code, http.StatusText(int(code)))
			default:
				log.Printf("Invalid event type:%T to process", ev)
			}
		}
	}()

	sniSniffed := true

	if tryRemoteResolve && session.Hijacked {
		sniSniffed = false
	}
	sniChunk := make([]byte, 0)
	for !connClosed {
		conn.SetReadDeadline(time.Now().Add(60 * time.Second))
		if session.Hijacked {
			buffer := make([]byte, 8192)
			n, err := bufconn.Read(buffer)
			if nil != err {
				if helper.IsTimeoutError(err) {
					testConn()
					continue
				}
				if err != io.EOF && !connClosed {
					log.Printf("Session:%d read chunk failed from proxy connection:%v", sid, err)
				}
				break
			}
			chunkContent := buffer[0:n]
			if !sniSniffed {
				sniChunk = append(sniChunk, chunkContent...)
				sni, err := helper.TLSParseSNI(sniChunk)
				if err != nil {
					if err != helper.ErrTLSIncomplete {
						sniSniffed = true
						chunkContent = sniChunk
						//downgrade to use old address
						tryRemoteResolve = false
						socksInitProxy(net.JoinHostPort(socksTargetHost, socksTargetPort))
					} else {
						continue
					}
				} else {
					sniSniffed = true
					chunkContent = sniChunk
					log.Printf("Sniffed SNI:%s:%s for IP:%s:%s", sni, socksTargetPort, socksTargetHost, socksTargetPort)
					socksInitProxy(net.JoinHostPort(sni, socksTargetPort))
				}
			}
			if nil == p {
				return
			}
			var chunk event.TCPChunkEvent
			chunk.SetId(sid)
			chunk.Content = chunkContent
			p.Serve(session, &chunk)
			continue
		}
		req, err := http.ReadRequest(bufconn)
		if nil != err {
			if helper.IsTimeoutError(err) {
				testConn()
				continue
			}
			if err != io.EOF && !connClosed {
				if len(socksTargetHost) > 0 {
					log.Printf("Session:%d read request failed from proxy connection to %s:%s for reason:%v", sid, socksTargetHost, socksTargetPort, err)
				} else {
					log.Printf("Session:%d read request failed from proxy connection for reason:%v", sid, err)
				}
			}
			break
		}

		if nil == p {
			p = proxy.findProxyByRequest("http", socksTargetHost, req)
			if nil == p {
				connClosed = true
				conn.Close()
				return
			}
			//log.Printf("Session:%d select handler:%s", sid, p.Name())
		}
		reqUrl := req.URL.String()
		if strings.EqualFold(req.Method, "Connect") {
			reqUrl = req.URL.Host
		} else {
			if !strings.HasPrefix(reqUrl, "http://") && !strings.HasPrefix(reqUrl, "https://") {
				if session.SSLHijacked {
					reqUrl = "https://" + req.Host + reqUrl
				} else {
					reqUrl = "http://" + req.Host + reqUrl
				}
			}
		}
		//log.Printf("[%s]Session:%d request:%s %v", p.Name(), sid, req.Method, reqUrl)

		req.Header.Del("Proxy-Connection")
		ev := event.NewHTTPRequestEvent(req)
		ev.SetId(sid)
		maxBody := p.Features().MaxRequestBody
		if maxBody > 0 && req.ContentLength > 0 {
			if int64(maxBody) < req.ContentLength {
				log.Printf("[ERROR]Too large request:%d for limit:%d", req.ContentLength, maxBody)
				return
			}
			ev.Headers.Del("Transfer-Encoding")
			for int64(len(ev.Content)) < req.ContentLength {
				buffer := make([]byte, 8192)
				n, err := req.Body.Read(buffer)
				if nil != err {
					break
				}
				ev.Content = append(ev.Content, buffer[0:n]...)
			}
		}

		if tryRemoteResolve && p.Config().IsDirect() && net.ParseIP(socksTargetHost) != nil && session.Remote == nil {
			tcpOpen := &event.TCPOpenEvent{}
			tcpOpen.SetId(sid)
			tcpOpen.Addr = net.JoinHostPort(socksTargetHost, socksTargetPort)
			p.Serve(session, tcpOpen)
		}

		p.Serve(session, ev)
		if maxBody < 0 && req.ContentLength != 0 {
			for nil != req.Body {
				buffer := make([]byte, 8192)
				n, err := req.Body.Read(buffer)
				if nil != err {
					//HTTP chunked body EOF
					if err == io.EOF && req.ContentLength < 0 {
						var eofChunk event.TCPChunkEvent
						eofChunk.SetId(sid)
						eofChunk.Content = []byte("0\r\n")
						p.Serve(session, &eofChunk)
					}
					break
				}
				buffer = buffer[0:n]
				var chunk event.TCPChunkEvent
				chunk.SetId(sid)
				if req.ContentLength > 0 {
					chunk.Content = buffer
				} else {
					//HTTP chunked body
					var chunkBuffer bytes.Buffer
					fmt.Fprintf(&chunkBuffer, "%x\r\n", len(buffer))
					chunkBuffer.Write(buffer)
					chunkBuffer.WriteString("\r\n")
					chunk.Content = chunkBuffer.Bytes()
				}
				p.Serve(session, &chunk)
			}
		}
		if strings.EqualFold(req.Method, "Connect") && (session.SSLHijacked || session.Hijacked) {
			conn.Write([]byte("HTTP/1.1 200 Connection established\r\n\r\n"))
		}

		//do not parse http rquest next process,since it would upgrade to websocket/spdy/http2
		if len(req.Header.Get("Upgrade")) > 0 {
			log.Printf("Session:%d upgrade protocol to %s", sid, req.Header.Get("Upgrade"))
			session.Hijacked = true
		}
		if session.SSLHijacked {
			if tlsconn, ok := conn.(*tls.Conn); !ok {
				tlscfg, err := fakecert.TLSConfig(req.Host)
				if nil != err {
					log.Printf("[ERROR]Failed to generate fake cert for %s:%v", req.Host, err)
					return
				}
				tlsconn = tls.Server(conn, tlscfg)
				conn = tlsconn
				bufconn = bufio.NewReader(conn)
			}
		}
	}
	if nil != p {
		tcpclose := &event.ConnCloseEvent{}
		tcpclose.SetId(sid)
		p.Serve(session, tcpclose)
	}
	connClosed = true
	conn.Close()
}

func startLocalProxyServer(proxy ProxyConfig) (*net.TCPListener, error) {
	tcpaddr, err := net.ResolveTCPAddr("tcp", proxy.Local)
	if nil != err {
		log.Fatalf("[ERROR]Local server address:%s error:%v", proxy.Local, err)
		return nil, err
	}
	var lp *net.TCPListener
	lp, err = net.ListenTCP("tcp", tcpaddr)
	if nil != err {
		log.Fatalf("Can NOT listen on address:%s", proxy.Local)
		return nil, err
	}
	log.Printf("Listen on address %s", proxy.Local)
	go func() {
		for proxyServerRunning {
			conn, err := lp.AcceptTCP()
			if nil != err {
				continue
			}
			go serveProxyConn(conn, proxy)
		}
		lp.Close()
	}()
	return lp, nil
}

var runningServers []*net.TCPListener

func startLocalServers() error {
	proxyServerRunning = true
	runningServers = make([]*net.TCPListener, 0)
	for _, proxy := range GConf.Proxy {
		l, _ := startLocalProxyServer(proxy)
		if nil != l {
			runningServers = append(runningServers, l)
		}
	}
	return nil
}

func stopLocalServers() {
	proxyServerRunning = false
	for _, l := range runningServers {
		l.Close()
	}
	closeAllProxySession()
	closeAllUDPSession()
}
<commit_msg>continue connect remote if snisniff read timeout<commit_after>package proxy

import (
	"bufio"
	"bytes"
	"crypto/tls"
	"fmt"
	"io"
	"log"
	"net"
	"net/http"
	"strings"
	"sync/atomic"
	"time"

	"github.com/yinqiwen/gsnova/common/event"
	"github.com/yinqiwen/gsnova/common/fakecert"
	"github.com/yinqiwen/gsnova/common/helper"
	"github.com/yinqiwen/gsnova/local/socks"
)

var sidSeed uint32 = 0
var proxyServerRunning = true

func getSessionId() uint32 {
	return atomic.AddUint32(&sidSeed, 1)
}

func serveProxyConn(conn net.Conn, proxy ProxyConfig) {
	var p Proxy
	protocol := "tcp"
	sid := getSessionId()
	queue := event.NewEventQueue()
	connClosed := false
	session := newProxySession(sid, queue)
	defer closeProxySession(sid)

	socksTargetHost := ""
	socksTargetPort := ""
	tryRemoteResolve := false
	socksConn, bufconn, err := socks.NewSocksConn(conn)

	//indicate that if remote opened by event
	remoteOpened := false

	socksInitProxy := func(addr string) {
		if socksTargetHost == "127.0.0.1" {
			proxyName := "Direct"
			p = getProxyByName(proxyName)
		} else {
			creq, _ := http.NewRequest("Connect", "https://"+addr, nil)
			p = proxy.findProxyByRequest(protocol, socksTargetHost, creq)
		}
		if nil == p {
			conn.Close()
			return
		}
		log.Printf("Session:%d select channel:%s", sid, p.Config().Name)
		if p.Config().IsDirect() && net.ParseIP(socksTargetHost) != nil {
			addr = net.JoinHostPort(socksTargetHost, socksTargetPort)
		}
		tcpOpen := &event.TCPOpenEvent{}
		tcpOpen.SetId(sid)
		tcpOpen.Addr = addr
		remoteOpened = true
		p.Serve(session, tcpOpen)
	}

	if nil == err {
		log.Printf("Local proxy recv %s proxy conn to %s", socksConn.Version(), socksConn.Req.Target)
		socksConn.Grant(&net.TCPAddr{
			IP: net.ParseIP("0.0.0.0"), Port: 0})

		if socksConn.Req.Target == GConf.UDPGWAddr {
			log.Printf("Handle udpgw conn for %v", socksConn.Req.Target)
			handleUDPGatewayConn(conn, proxy)
			return
		}
		conn = socksConn
		session.Hijacked = true

		socksTargetHost, socksTargetPort, err = net.SplitHostPort(socksConn.Req.Target)
		if nil != err {
			log.Printf("Invalid socks target addresss:%s with reason %v", socksConn.Req.Target, err)
			return
		}
		if socksTargetHost != "127.0.0.1" && net.ParseIP(socksTargetHost) != nil && proxy.SNISniff {
			//this is a ip from local dns query
			tryRemoteResolve = true
			if socksTargetPort == "80" {
				//we can parse http request directly
				session.Hijacked = false
			}
		} else {
			socksInitProxy(net.JoinHostPort(socksTargetHost, socksTargetPort))
		}

	} else {
		if nil == bufconn {
			conn.Close()
			return
		}
	}
	if nil == bufconn {
		bufconn = bufio.NewReader(conn)
	}
	defer conn.Close()

	testConn := func() {
		if nil != p {
			var testConnEv event.ConnTestEvent
			testConnEv.SetId(sid)
			p.Serve(session, &testConnEv)
		}
	}

	go func() {
		for !connClosed {
			ev, err := queue.Read(1 * time.Second)
			if err != nil {
				if err != io.EOF {
					continue
				}
				return
			}
			//log.Printf("Session:%d recv event:%T", sid, ev)
			switch ev.(type) {
			case *event.NotifyEvent:
				//donothing now
			case *event.ConnCloseEvent:
				connClosed = true
				conn.Close()
				return
			case *event.TCPChunkEvent:
				conn.Write(ev.(*event.TCPChunkEvent).Content)
			case *event.HTTPResponseEvent:
				ev.(*event.HTTPResponseEvent).Write(conn)
				code := ev.(*event.HTTPResponseEvent).StatusCode
				log.Printf("Session:%d response:%d %v", ev.GetId(), code, http.StatusText(int(code)))
			default:
				log.Printf("Invalid event type:%T to process", ev)
			}
		}
	}()

	sniSniffed := true

	if tryRemoteResolve && session.Hijacked {
		sniSniffed = false
	}
	sniChunk := make([]byte, 0)
	for !connClosed {
		if remoteOpened {
			conn.SetReadDeadline(time.Now().Add(60 * time.Second))
		} else {
			conn.SetReadDeadline(time.Now().Add(500 * time.Millisecond))
		}
		if session.Hijacked {
			buffer := make([]byte, 8192)
			n, err := bufconn.Read(buffer)
			if nil != err {
				if helper.IsTimeoutError(err) {
					if remoteOpened {
						testConn()
					} else {
						socksInitProxy(net.JoinHostPort(socksTargetHost, socksTargetPort))
					}
					continue
				}
				if err != io.EOF && !connClosed {
					log.Printf("Session:%d read chunk failed from proxy connection:%v", sid, err)
				}
				break
			}
			chunkContent := buffer[0:n]
			if !sniSniffed {
				sniChunk = append(sniChunk, chunkContent...)
				sni, err := helper.TLSParseSNI(sniChunk)
				if err != nil {
					if err != helper.ErrTLSIncomplete {
						sniSniffed = true
						chunkContent = sniChunk
						//downgrade to use old address
						tryRemoteResolve = false
						socksInitProxy(net.JoinHostPort(socksTargetHost, socksTargetPort))
					} else {
						continue
					}
				} else {
					sniSniffed = true
					chunkContent = sniChunk
					log.Printf("Sniffed SNI:%s:%s for IP:%s:%s", sni, socksTargetPort, socksTargetHost, socksTargetPort)
					socksInitProxy(net.JoinHostPort(sni, socksTargetPort))
				}
			}
			if nil == p {
				return
			}
			var chunk event.TCPChunkEvent
			chunk.SetId(sid)
			chunk.Content = chunkContent
			p.Serve(session, &chunk)
			continue
		}
		req, err := http.ReadRequest(bufconn)
		if nil != err {
			if helper.IsTimeoutError(err) {
				testConn()
				continue
			}
			if err != io.EOF && !connClosed {
				if len(socksTargetHost) > 0 {
					log.Printf("Session:%d read request failed from proxy connection to %s:%s for reason:%v", sid, socksTargetHost, socksTargetPort, err)
				} else {
					log.Printf("Session:%d read request failed from proxy connection for reason:%v", sid, err)
				}
			}
			break
		}

		if nil == p {
			p = proxy.findProxyByRequest("http", socksTargetHost, req)
			if nil == p {
				connClosed = true
				conn.Close()
				return
			}
			//log.Printf("Session:%d select handler:%s", sid, p.Name())
		}
		reqUrl := req.URL.String()
		if strings.EqualFold(req.Method, "Connect") {
			reqUrl = req.URL.Host
		} else {
			if !strings.HasPrefix(reqUrl, "http://") && !strings.HasPrefix(reqUrl, "https://") {
				if session.SSLHijacked {
					reqUrl = "https://" + req.Host + reqUrl
				} else {
					reqUrl = "http://" + req.Host + reqUrl
				}
			}
		}
		log.Printf("Session:%d request:%s %v", sid, req.Method, reqUrl)

		req.Header.Del("Proxy-Connection")
		ev := event.NewHTTPRequestEvent(req)
		ev.SetId(sid)
		maxBody := p.Features().MaxRequestBody
		if maxBody > 0 && req.ContentLength > 0 {
			if int64(maxBody) < req.ContentLength {
				log.Printf("[ERROR]Too large request:%d for limit:%d", req.ContentLength, maxBody)
				return
			}
			ev.Headers.Del("Transfer-Encoding")
			for int64(len(ev.Content)) < req.ContentLength {
				buffer := make([]byte, 8192)
				n, err := req.Body.Read(buffer)
				if nil != err {
					break
				}
				ev.Content = append(ev.Content, buffer[0:n]...)
			}
		}

		if tryRemoteResolve && p.Config().IsDirect() && net.ParseIP(socksTargetHost) != nil && session.Remote == nil {
			tcpOpen := &event.TCPOpenEvent{}
			tcpOpen.SetId(sid)
			tcpOpen.Addr = net.JoinHostPort(socksTargetHost, socksTargetPort)
			p.Serve(session, tcpOpen)
		}

		p.Serve(session, ev)
		if maxBody < 0 && req.ContentLength != 0 {
			for nil != req.Body {
				buffer := make([]byte, 8192)
				n, err := req.Body.Read(buffer)
				if nil != err {
					//HTTP chunked body EOF
					if err == io.EOF && req.ContentLength < 0 {
						var eofChunk event.TCPChunkEvent
						eofChunk.SetId(sid)
						eofChunk.Content = []byte("0\r\n")
						p.Serve(session, &eofChunk)
					}
					break
				}
				buffer = buffer[0:n]
				var chunk event.TCPChunkEvent
				chunk.SetId(sid)
				if req.ContentLength > 0 {
					chunk.Content = buffer
				} else {
					//HTTP chunked body
					var chunkBuffer bytes.Buffer
					fmt.Fprintf(&chunkBuffer, "%x\r\n", len(buffer))
					chunkBuffer.Write(buffer)
					chunkBuffer.WriteString("\r\n")
					chunk.Content = chunkBuffer.Bytes()
				}
				p.Serve(session, &chunk)
			}
		}
		if strings.EqualFold(req.Method, "Connect") && (session.SSLHijacked || session.Hijacked) {
			conn.Write([]byte("HTTP/1.1 200 Connection established\r\n\r\n"))
		}

		//do not parse http rquest next process,since it would upgrade to websocket/spdy/http2
		if len(req.Header.Get("Upgrade")) > 0 {
			log.Printf("Session:%d upgrade protocol to %s", sid, req.Header.Get("Upgrade"))
			session.Hijacked = true
		}
		if session.SSLHijacked {
			if tlsconn, ok := conn.(*tls.Conn); !ok {
				tlscfg, err := fakecert.TLSConfig(req.Host)
				if nil != err {
					log.Printf("[ERROR]Failed to generate fake cert for %s:%v", req.Host, err)
					return
				}
				tlsconn = tls.Server(conn, tlscfg)
				conn = tlsconn
				bufconn = bufio.NewReader(conn)
			}
		}
	}
	if nil != p {
		tcpclose := &event.ConnCloseEvent{}
		tcpclose.SetId(sid)
		p.Serve(session, tcpclose)
	}
	connClosed = true
	conn.Close()
}

func startLocalProxyServer(proxy ProxyConfig) (*net.TCPListener, error) {
	tcpaddr, err := net.ResolveTCPAddr("tcp", proxy.Local)
	if nil != err {
		log.Fatalf("[ERROR]Local server address:%s error:%v", proxy.Local, err)
		return nil, err
	}
	var lp *net.TCPListener
	lp, err = net.ListenTCP("tcp", tcpaddr)
	if nil != err {
		log.Fatalf("Can NOT listen on address:%s", proxy.Local)
		return nil, err
	}
	log.Printf("Listen on address %s", proxy.Local)
	go func() {
		for proxyServerRunning {
			conn, err := lp.AcceptTCP()
			if nil != err {
				continue
			}
			go serveProxyConn(conn, proxy)
		}
		lp.Close()
	}()
	return lp, nil
}

var runningServers []*net.TCPListener

func startLocalServers() error {
	proxyServerRunning = true
	runningServers = make([]*net.TCPListener, 0)
	for _, proxy := range GConf.Proxy {
		l, _ := startLocalProxyServer(proxy)
		if nil != l {
			runningServers = append(runningServers, l)
		}
	}
	return nil
}

func stopLocalServers() {
	proxyServerRunning = false
	for _, l := range runningServers {
		l.Close()
	}
	closeAllProxySession()
	closeAllUDPSession()
}
<|endoftext|>