<commit_before>package pgtype

import (
	"bytes"
	"encoding/binary"
	"errors"
	"fmt"
	"io"
	"strings"
	"unicode"
	"unicode/utf8"

	"github.com/jackc/pgx/pgio"
)

// Hstore represents an hstore column that can be null or have null values
// associated with its keys.
type Hstore struct {
	Map    map[string]Text
	Status Status
}

func (dst *Hstore) Set(src interface{}) error {
	switch value := src.(type) {
	case map[string]string:
		m := make(map[string]Text, len(value))
		for k, v := range value {
			m[k] = Text{String: v, Status: Present}
		}
		*dst = Hstore{Map: m, Status: Present}
	default:
		return fmt.Errorf("cannot convert %v to Tid", src)
	}

	return nil
}

func (dst *Hstore) Get() interface{} {
	switch dst.Status {
	case Present:
		return dst.Map
	case Null:
		return nil
	default:
		return dst.Status
	}
}

func (src *Hstore) AssignTo(dst interface{}) error {
	switch v := dst.(type) {
	case *map[string]string:
		switch src.Status {
		case Present:
			*v = make(map[string]string, len(src.Map))
			for k, val := range src.Map {
				if val.Status != Present {
					return fmt.Errorf("cannot decode %v into %T", src, dst)
				}
				(*v)[k] = val.String
			}
		case Null:
			*v = nil
		default:
			return fmt.Errorf("cannot decode %v into %T", src, dst)
		}
	default:
		return fmt.Errorf("cannot decode %v into %T", src, dst)
	}

	return nil
}

func (dst *Hstore) DecodeText(src []byte) error {
	if src == nil {
		*dst = Hstore{Status: Null}
		return nil
	}

	keys, values, err := parseHstore(string(src))
	if err != nil {
		return err
	}

	m := make(map[string]Text, len(keys))
	for i := range keys {
		m[keys[i]] = values[i]
	}

	*dst = Hstore{Map: m, Status: Present}
	return nil
}

func (dst *Hstore) DecodeBinary(src []byte) error {
	if src == nil {
		*dst = Hstore{Status: Null}
		return nil
	}

	rp := 0

	if len(src[rp:]) < 4 {
		return fmt.Errorf("hstore incomplete %v", src)
	}
	pairCount := int(int32(binary.BigEndian.Uint32(src[rp:])))
	rp += 4

	m := make(map[string]Text, pairCount)

	for i := 0; i < pairCount; i++ {
		if len(src[rp:]) < 4 {
			return fmt.Errorf("hstore incomplete %v", src)
		}
		keyLen := int(int32(binary.BigEndian.Uint32(src[rp:])))
		rp += 4

		if len(src[rp:]) < keyLen {
			return fmt.Errorf("hstore incomplete %v", src)
		}
		key := string(src[rp : rp+keyLen])
		rp += keyLen

		if len(src[rp:]) < 4 {
			return fmt.Errorf("hstore incomplete %v", src)
		}
		valueLen := int(int32(binary.BigEndian.Uint32(src[rp:])))
		rp += 4

		var valueBuf []byte
		if valueLen >= 0 {
			valueBuf = src[rp : rp+valueLen]
		}
		rp += valueLen

		var value Text
		err := value.DecodeBinary(valueBuf)
		if err != nil {
			return err
		}
		m[key] = value
	}

	*dst = Hstore{Map: m, Status: Present}

	return nil
}

func (src Hstore) EncodeText(w io.Writer) (bool, error) {
	switch src.Status {
	case Null:
		return true, nil
	case Undefined:
		return false, errUndefined
	}

	firstPair := true

	for k, v := range src.Map {
		if firstPair {
			firstPair = false
		} else {
			err := pgio.WriteByte(w, ',')
			if err != nil {
				return false, err
			}
		}

		_, err := io.WriteString(w, quoteHstoreElementIfNeeded(k))
		if err != nil {
			return false, err
		}

		_, err = io.WriteString(w, "=>")
		if err != nil {
			return false, err
		}

		elemBuf := &bytes.Buffer{}
		null, err := v.EncodeText(elemBuf)
		if err != nil {
			return false, err
		}

		if null {
			_, err = io.WriteString(w, "NULL")
			if err != nil {
				return false, err
			}
		} else {
			_, err := io.WriteString(w, quoteHstoreElementIfNeeded(elemBuf.String()))
			if err != nil {
				return false, err
			}
		}
	}

	return false, nil
}

func (src Hstore) EncodeBinary(w io.Writer) (bool, error) {
	switch src.Status {
	case Null:
		return true, nil
	case Undefined:
		return false, errUndefined
	}

	_, err := pgio.WriteInt32(w, int32(len(src.Map)))
	if err != nil {
		return false, err
	}

	elemBuf := &bytes.Buffer{}
	for k, v := range src.Map {
		_, err := pgio.WriteInt32(w, int32(len(k)))
		if err != nil {
			return false, err
		}
		_, err = io.WriteString(w, k)
		if err != nil {
			return false, err
		}

		null, err := v.EncodeText(elemBuf)
		if err != nil {
			return false, err
		}
		if null {
			_, err := pgio.WriteInt32(w, -1)
			if err != nil {
				return false, err
			}
		} else {
			_, err := pgio.WriteInt32(w, int32(elemBuf.Len()))
			if err != nil {
				return false, err
			}
			_, err = elemBuf.WriteTo(w)
			if err != nil {
				return false, err
			}
		}
	}

	return false, err
}

var quoteHstoreReplacer = strings.NewReplacer(`\`, `\\`, `"`, `\"`)

func quoteHstoreElement(src string) string {
	return `"` + quoteArrayReplacer.Replace(src) + `"`
}

func quoteHstoreElementIfNeeded(src string) string {
	if src == "" || (len(src) == 4 && strings.ToLower(src) == "null") || strings.ContainsAny(src, ` {},"\=>`) {
		return quoteArrayElement(src)
	}
	return src
}

const (
	hsPre = iota
	hsKey
	hsSep
	hsVal
	hsNul
	hsNext
)

type hstoreParser struct {
	str string
	pos int
}

func newHSP(in string) *hstoreParser {
	return &hstoreParser{
		pos: 0,
		str: in,
	}
}

func (p *hstoreParser) Consume() (r rune, end bool) {
	if p.pos >= len(p.str) {
		end = true
		return
	}
	r, w := utf8.DecodeRuneInString(p.str[p.pos:])
	p.pos += w
	return
}

func (p *hstoreParser) Peek() (r rune, end bool) {
	if p.pos >= len(p.str) {
		end = true
		return
	}
	r, _ = utf8.DecodeRuneInString(p.str[p.pos:])
	return
}

// parseHstore parses the string representation of an hstore column (the same
// you would get from an ordinary SELECT) into two slices of keys and values. it
// is used internally in the default parsing of hstores.
func parseHstore(s string) (k []string, v []Text, err error) {
	if s == "" {
		return
	}

	buf := bytes.Buffer{}
	keys := []string{}
	values := []Text{}
	p := newHSP(s)

	r, end := p.Consume()
	state := hsPre

	for !end {
		switch state {
		case hsPre:
			if r == '"' {
				state = hsKey
			} else {
				err = errors.New("String does not begin with \"")
			}
		case hsKey:
			switch r {
			case '"': //End of the key
				if buf.Len() == 0 {
					err = errors.New("Empty Key is invalid")
				} else {
					keys = append(keys, buf.String())
					buf = bytes.Buffer{}
					state = hsSep
				}
			case '\\': //Potential escaped character
				n, end := p.Consume()
				switch {
				case end:
					err = errors.New("Found EOS in key, expecting character or \"")
				case n == '"', n == '\\':
					buf.WriteRune(n)
				default:
					buf.WriteRune(r)
					buf.WriteRune(n)
				}
			default: //Any other character
				buf.WriteRune(r)
			}
		case hsSep:
			if r == '=' {
				r, end = p.Consume()
				switch {
				case end:
					err = errors.New("Found EOS after '=', expecting '>'")
				case r == '>':
					r, end = p.Consume()
					switch {
					case end:
						err = errors.New("Found EOS after '=>', expecting '\"' or 'NULL'")
					case r == '"':
						state = hsVal
					case r == 'N':
						state = hsNul
					default:
						err = fmt.Errorf("Invalid character '%c' after '=>', expecting '\"' or 'NULL'", r)
					}
				default:
					err = fmt.Errorf("Invalid character after '=', expecting '>'")
				}
			} else {
				err = fmt.Errorf("Invalid character '%c' after value, expecting '='", r)
			}
		case hsVal:
			switch r {
			case '"': //End of the value
				values = append(values, Text{String: buf.String(), Status: Present})
				buf = bytes.Buffer{}
				state = hsNext
			case '\\': //Potential escaped character
				n, end := p.Consume()
				switch {
				case end:
					err = errors.New("Found EOS in key, expecting character or \"")
				case n == '"', n == '\\':
					buf.WriteRune(n)
				default:
					buf.WriteRune(r)
					buf.WriteRune(n)
				}
			default: //Any other character
				buf.WriteRune(r)
			}
		case hsNul:
			nulBuf := make([]rune, 3)
			nulBuf[0] = r
			for i := 1; i < 3; i++ {
				r, end = p.Consume()
				if end {
					err = errors.New("Found EOS in NULL value")
					return
				}
				nulBuf[i] = r
			}
			if nulBuf[0] == 'U' && nulBuf[1] == 'L' && nulBuf[2] == 'L' {
				values = append(values, Text{Status: Null})
				state = hsNext
			} else {
				err = fmt.Errorf("Invalid NULL value: 'N%s'", string(nulBuf))
			}
		case hsNext:
			if r == ',' {
				r, end = p.Consume()
				switch {
				case end:
					err = errors.New("Found EOS after ',', expcting space")
				case (unicode.IsSpace(r)):
					r, end = p.Consume()
					state = hsKey
				default:
					err = fmt.Errorf("Invalid character '%c' after ', ', expecting \"", r)
				}
			} else {
				err = fmt.Errorf("Invalid character '%c' after value, expecting ','", r)
			}
		}

		if err != nil {
			return
		}
		r, end = p.Consume()
	}
	if state != hsNext {
		err = errors.New("Improperly formatted hstore")
		return
	}
	k = keys
	v = values
	return
}
<commit_msg>Fix error message for hstore<commit_after>package pgtype

import (
	"bytes"
	"encoding/binary"
	"errors"
	"fmt"
	"io"
	"strings"
	"unicode"
	"unicode/utf8"

	"github.com/jackc/pgx/pgio"
)

// Hstore represents an hstore column that can be null or have null values
// associated with its keys.
type Hstore struct {
	Map    map[string]Text
	Status Status
}

func (dst *Hstore) Set(src interface{}) error {
	switch value := src.(type) {
	case map[string]string:
		m := make(map[string]Text, len(value))
		for k, v := range value {
			m[k] = Text{String: v, Status: Present}
		}
		*dst = Hstore{Map: m, Status: Present}
	default:
		return fmt.Errorf("cannot convert %v to Hstore", src)
	}

	return nil
}

func (dst *Hstore) Get() interface{} {
	switch dst.Status {
	case Present:
		return dst.Map
	case Null:
		return nil
	default:
		return dst.Status
	}
}

func (src *Hstore) AssignTo(dst interface{}) error {
	switch v := dst.(type) {
	case *map[string]string:
		switch src.Status {
		case Present:
			*v = make(map[string]string, len(src.Map))
			for k, val := range src.Map {
				if val.Status != Present {
					return fmt.Errorf("cannot decode %v into %T", src, dst)
				}
				(*v)[k] = val.String
			}
		case Null:
			*v = nil
		default:
			return fmt.Errorf("cannot decode %v into %T", src, dst)
		}
	default:
		return fmt.Errorf("cannot decode %v into %T", src, dst)
	}

	return nil
}

func (dst *Hstore) DecodeText(src []byte) error {
	if src == nil {
		*dst = Hstore{Status: Null}
		return nil
	}

	keys, values, err := parseHstore(string(src))
	if err != nil {
		return err
	}

	m := make(map[string]Text, len(keys))
	for i := range keys {
		m[keys[i]] = values[i]
	}

	*dst = Hstore{Map: m, Status: Present}
	return nil
}

func (dst *Hstore) DecodeBinary(src []byte) error {
	if src == nil {
		*dst = Hstore{Status: Null}
		return nil
	}

	rp := 0

	if len(src[rp:]) < 4 {
		return fmt.Errorf("hstore incomplete %v", src)
	}
	pairCount := int(int32(binary.BigEndian.Uint32(src[rp:])))
	rp += 4

	m := make(map[string]Text, pairCount)

	for i := 0; i < pairCount; i++ {
		if len(src[rp:]) < 4 {
			return fmt.Errorf("hstore incomplete %v", src)
		}
		keyLen := int(int32(binary.BigEndian.Uint32(src[rp:])))
		rp += 4

		if len(src[rp:]) < keyLen {
			return fmt.Errorf("hstore incomplete %v", src)
		}
		key := string(src[rp : rp+keyLen])
		rp += keyLen

		if len(src[rp:]) < 4 {
			return fmt.Errorf("hstore incomplete %v", src)
		}
		valueLen := int(int32(binary.BigEndian.Uint32(src[rp:])))
		rp += 4

		var valueBuf []byte
		if valueLen >= 0 {
			valueBuf = src[rp : rp+valueLen]
		}
		rp += valueLen

		var value Text
		err := value.DecodeBinary(valueBuf)
		if err != nil {
			return err
		}
		m[key] = value
	}

	*dst = Hstore{Map: m, Status: Present}

	return nil
}

func (src Hstore) EncodeText(w io.Writer) (bool, error) {
	switch src.Status {
	case Null:
		return true, nil
	case Undefined:
		return false, errUndefined
	}

	firstPair := true

	for k, v := range src.Map {
		if firstPair {
			firstPair = false
		} else {
			err := pgio.WriteByte(w, ',')
			if err != nil {
				return false, err
			}
		}

		_, err := io.WriteString(w, quoteHstoreElementIfNeeded(k))
		if err != nil {
			return false, err
		}

		_, err = io.WriteString(w, "=>")
		if err != nil {
			return false, err
		}

		elemBuf := &bytes.Buffer{}
		null, err := v.EncodeText(elemBuf)
		if err != nil {
			return false, err
		}

		if null {
			_, err = io.WriteString(w, "NULL")
			if err != nil {
				return false, err
			}
		} else {
			_, err := io.WriteString(w, quoteHstoreElementIfNeeded(elemBuf.String()))
			if err != nil {
				return false, err
			}
		}
	}

	return false, nil
}

func (src Hstore) EncodeBinary(w io.Writer) (bool, error) {
	switch src.Status {
	case Null:
		return true, nil
	case Undefined:
		return false, errUndefined
	}

	_, err := pgio.WriteInt32(w, int32(len(src.Map)))
	if err != nil {
		return false, err
	}

	elemBuf := &bytes.Buffer{}
	for k, v := range src.Map {
		_, err := pgio.WriteInt32(w, int32(len(k)))
		if err != nil {
			return false, err
		}
		_, err = io.WriteString(w, k)
		if err != nil {
			return false, err
		}

		null, err := v.EncodeText(elemBuf)
		if err != nil {
			return false, err
		}
		if null {
			_, err := pgio.WriteInt32(w, -1)
			if err != nil {
				return false, err
			}
		} else {
			_, err := pgio.WriteInt32(w, int32(elemBuf.Len()))
			if err != nil {
				return false, err
			}
			_, err = elemBuf.WriteTo(w)
			if err != nil {
				return false, err
			}
		}
	}

	return false, err
}

var quoteHstoreReplacer = strings.NewReplacer(`\`, `\\`, `"`, `\"`)

func quoteHstoreElement(src string) string {
	return `"` + quoteArrayReplacer.Replace(src) + `"`
}

func quoteHstoreElementIfNeeded(src string) string {
	if src == "" || (len(src) == 4 && strings.ToLower(src) == "null") || strings.ContainsAny(src, ` {},"\=>`) {
		return quoteArrayElement(src)
	}
	return src
}

const (
	hsPre = iota
	hsKey
	hsSep
	hsVal
	hsNul
	hsNext
)

type hstoreParser struct {
	str string
	pos int
}

func newHSP(in string) *hstoreParser {
	return &hstoreParser{
		pos: 0,
		str: in,
	}
}

func (p *hstoreParser) Consume() (r rune, end bool) {
	if p.pos >= len(p.str) {
		end = true
		return
	}
	r, w := utf8.DecodeRuneInString(p.str[p.pos:])
	p.pos += w
	return
}

func (p *hstoreParser) Peek() (r rune, end bool) {
	if p.pos >= len(p.str) {
		end = true
		return
	}
	r, _ = utf8.DecodeRuneInString(p.str[p.pos:])
	return
}

// parseHstore parses the string representation of an hstore column (the same
// you would get from an ordinary SELECT) into two slices of keys and values. it
// is used internally in the default parsing of hstores.
func parseHstore(s string) (k []string, v []Text, err error) {
	if s == "" {
		return
	}

	buf := bytes.Buffer{}
	keys := []string{}
	values := []Text{}
	p := newHSP(s)

	r, end := p.Consume()
	state := hsPre

	for !end {
		switch state {
		case hsPre:
			if r == '"' {
				state = hsKey
			} else {
				err = errors.New("String does not begin with \"")
			}
		case hsKey:
			switch r {
			case '"': //End of the key
				if buf.Len() == 0 {
					err = errors.New("Empty Key is invalid")
				} else {
					keys = append(keys, buf.String())
					buf = bytes.Buffer{}
					state = hsSep
				}
			case '\\': //Potential escaped character
				n, end := p.Consume()
				switch {
				case end:
					err = errors.New("Found EOS in key, expecting character or \"")
				case n == '"', n == '\\':
					buf.WriteRune(n)
				default:
					buf.WriteRune(r)
					buf.WriteRune(n)
				}
			default: //Any other character
				buf.WriteRune(r)
			}
		case hsSep:
			if r == '=' {
				r, end = p.Consume()
				switch {
				case end:
					err = errors.New("Found EOS after '=', expecting '>'")
				case r == '>':
					r, end = p.Consume()
					switch {
					case end:
						err = errors.New("Found EOS after '=>', expecting '\"' or 'NULL'")
					case r == '"':
						state = hsVal
					case r == 'N':
						state = hsNul
					default:
						err = fmt.Errorf("Invalid character '%c' after '=>', expecting '\"' or 'NULL'", r)
					}
				default:
					err = fmt.Errorf("Invalid character after '=', expecting '>'")
				}
			} else {
				err = fmt.Errorf("Invalid character '%c' after value, expecting '='", r)
			}
		case hsVal:
			switch r {
			case '"': //End of the value
				values = append(values, Text{String: buf.String(), Status: Present})
				buf = bytes.Buffer{}
				state = hsNext
			case '\\': //Potential escaped character
				n, end := p.Consume()
				switch {
				case end:
					err = errors.New("Found EOS in key, expecting character or \"")
				case n == '"', n == '\\':
					buf.WriteRune(n)
				default:
					buf.WriteRune(r)
					buf.WriteRune(n)
				}
			default: //Any other character
				buf.WriteRune(r)
			}
		case hsNul:
			nulBuf := make([]rune, 3)
			nulBuf[0] = r
			for i := 1; i < 3; i++ {
				r, end = p.Consume()
				if end {
					err = errors.New("Found EOS in NULL value")
					return
				}
				nulBuf[i] = r
			}
			if nulBuf[0] == 'U' && nulBuf[1] == 'L' && nulBuf[2] == 'L' {
				values = append(values, Text{Status: Null})
				state = hsNext
			} else {
				err = fmt.Errorf("Invalid NULL value: 'N%s'", string(nulBuf))
			}
		case hsNext:
			if r == ',' {
				r, end = p.Consume()
				switch {
				case end:
					err = errors.New("Found EOS after ',', expcting space")
				case (unicode.IsSpace(r)):
					r, end = p.Consume()
					state = hsKey
				default:
					err = fmt.Errorf("Invalid character '%c' after ', ', expecting \"", r)
				}
			} else {
				err = fmt.Errorf("Invalid character '%c' after value, expecting ','", r)
			}
		}

		if err != nil {
			return
		}
		r, end = p.Consume()
	}
	if state != hsNext {
		err = errors.New("Improperly formatted hstore")
		return
	}
	k = keys
	v = values
	return
}
<|endoftext|>


<commit_before>package parsecli

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"os"
	"path/filepath"
	"runtime"
	"strings"
	"testing"

	"github.com/bgentry/heroku-go"
	"github.com/facebookgo/clock"
	"github.com/facebookgo/ensure"
	"github.com/facebookgo/parse"
	"github.com/facebookgo/testname"
)

const (
	Version        = "3.0.1"
	CloudDir       = "cloud"
	HostingDir     = "public"
	DefaultBaseURL = "https://api.parse.com/1/"
)

var UserAgent = fmt.Sprintf("parse-cli-%s-%s", runtime.GOOS, Version)

type Env struct {
	Root            string // project root
	Server          string // parse api server
	Type            int    // project type
	ParserEmail     string // email associated with developer parse account
	ErrorStack      bool
	Out             io.Writer
	Err             io.Writer
	In              io.Reader
	Exit            func(int)
	Clock           clock.Clock
	ParseAPIClient  *ParseAPIClient
	HerokuAPIClient *heroku.Client
}

type Harness struct {
	T      testing.TB
	Out    bytes.Buffer
	Err    bytes.Buffer
	Clock  *clock.Mock
	Env    *Env
	remove []string
}

func (h *Harness) MakeEmptyRoot() {
	var err error
	prefix := fmt.Sprintf("%s-", testname.Get("parse-cli-"))
	h.Env.Root, err = ioutil.TempDir("", prefix)
	ensure.Nil(h.T, err)
	h.remove = append(h.remove, h.Env.Root)
}

func (h *Harness) MakeWithConfig(global string) {
	h.Env.Root = makeDirWithConfig(h.T, global)
}

func (h *Harness) Stop() {
	for _, p := range h.remove {
		os.RemoveAll(p)
	}
}

func NewHarness(t testing.TB) *Harness {
	te := Harness{
		T:     t,
		Clock: clock.NewMock(),
	}
	te.Env = &Env{
		Out:            &te.Out,
		Err:            &te.Err,
		Clock:          te.Clock,
		ParseAPIClient: &ParseAPIClient{APIClient: &parse.Client{}},
	}
	return &te
}

// makes a temp directory with the given global config.
func makeDirWithConfig(t testing.TB, global string) string {
	dir, err := ioutil.TempDir("", testname.Get("parse-cli-"))
	ensure.Nil(t, err)
	ensure.Nil(t, os.Mkdir(filepath.Join(dir, "config"), 0755))
	ensure.Nil(t, ioutil.WriteFile(
		filepath.Join(dir, LegacyConfigFile),
		[]byte(global),
		os.FileMode(0600),
	))
	return dir
}

type TransportFunc func(r *http.Request) (*http.Response, error)

func (t TransportFunc) RoundTrip(r *http.Request) (*http.Response, error) {
	return t(r)
}

func NewTokenHarness(t testing.TB) *Harness {
	h := NewHarness(t)
	ht := TransportFunc(func(r *http.Request) (*http.Response, error) {
		ensure.DeepEqual(t, r.URL.Path, "/1/accountkey")
		ensure.DeepEqual(t, r.Method, "POST")

		key := &struct {
			AccountKey string `json:"accountKey"`
		}{}
		ensure.Nil(t, json.NewDecoder(ioutil.NopCloser(r.Body)).Decode(key))

		if key.AccountKey != "token" {
			return &http.Response{
				StatusCode: http.StatusUnauthorized,
				Body:       ioutil.NopCloser(strings.NewReader(`{"error": "incorrect token"}`)),
			}, nil
		}

		return &http.Response{
			StatusCode: http.StatusOK,
			Body:       ioutil.NopCloser(strings.NewReader(`{"email": "email"}`)),
		}, nil
	})
	h.Env.ParseAPIClient = &ParseAPIClient{APIClient: &parse.Client{Transport: ht}}
	return h
}

func NewAppHarness(t testing.TB) (*Harness, []*App) {
	h := NewHarness(t)

	apps := []*App{
		newTestApp("A"),
		newTestApp("B"),
	}
	res := map[string][]*App{"results": apps}
	ht := TransportFunc(func(r *http.Request) (*http.Response, error) {

		email := r.Header.Get("X-Parse-Email")
		password := r.Header.Get("X-Parse-Password")
		token := r.Header.Get("X-Parse-Account-Key")
		if !((email == "email" && password == "password") || (token == "token")) {
			return &http.Response{
				StatusCode: http.StatusUnauthorized,
				Body:       ioutil.NopCloser(strings.NewReader(`{"error": "incorrect credentials"}`)),
			}, nil
		}

		switch r.URL.Path {
		case "/1/apps":
			if r.Method == "GET" {
				return &http.Response{
					StatusCode: http.StatusOK,
					Body:       ioutil.NopCloser(strings.NewReader(jsonStr(t, &res))),
				}, nil

			}

			if r.Method != "POST" || r.Body == nil {
				return &http.Response{
					StatusCode: http.StatusNotFound,
				}, errors.New("unknown resource")
			}

			var params map[string]string
			if err := json.NewDecoder(r.Body).Decode(&params); err != nil {
				return &http.Response{
					StatusCode: http.StatusInternalServerError,
				}, err
			}
			details, err := json.Marshal(newTestApp(params["appName"]))
			if err != nil {
				return &http.Response{
					StatusCode: http.StatusInternalServerError,
				}, err
			}

			return &http.Response{
				StatusCode: http.StatusOK,
				Body:       ioutil.NopCloser(bytes.NewReader(details)),
			}, nil

		case "/1/apps/an-app":
			ensure.DeepEqual(t, r.Method, "GET")
			return &http.Response{
				StatusCode: http.StatusOK,
				Body:       ioutil.NopCloser(strings.NewReader(jsonStr(t, newTestApp("an-app")))),
			}, nil
		default:
			return &http.Response{
				StatusCode: http.StatusNotFound,
			}, nil
		}
	})

	h.Env.ParseAPIClient = &ParseAPIClient{APIClient: &parse.Client{Transport: ht}}
	return h, apps
}

func newTestApp(suffix string) *App {
	return &App{
		Name:                          suffix,
		DashboardURL:                  fmt.Sprintf("https://api.example.com/dashboard/%s", suffix),
		ApplicationID:                 fmt.Sprintf("applicationID.%s", suffix),
		ClientKey:                     fmt.Sprintf("clientKey.%s", suffix),
		JavaScriptKey:                 fmt.Sprintf("javaScriptKey.%s", suffix),
		WindowsKey:                    fmt.Sprintf("windowsKey.%s", suffix),
		WebhookKey:                    fmt.Sprintf("webhookKey.%s", suffix),
		RestKey:                       fmt.Sprintf("restKey.%s", suffix),
		MasterKey:                     fmt.Sprintf("masterKey.%s", suffix),
		ClientPushEnabled:             false,
		ClientClassCreationEnabled:    true,
		RequireRevocableSessions:      true,
		RevokeSessionOnPasswordChange: true,
	}
}

func jsonStr(t testing.TB, v interface{}) string {
	b, err := json.Marshal(v)
	ensure.Nil(t, err)
	return string(b)
}
<commit_msg>[release] release 3.0.2 version of parse cli<commit_after>package parsecli

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"os"
	"path/filepath"
	"runtime"
	"strings"
	"testing"

	"github.com/bgentry/heroku-go"
	"github.com/facebookgo/clock"
	"github.com/facebookgo/ensure"
	"github.com/facebookgo/parse"
	"github.com/facebookgo/testname"
)

const (
	Version        = "3.0.2"
	CloudDir       = "cloud"
	HostingDir     = "public"
	DefaultBaseURL = "https://api.parse.com/1/"
)

var UserAgent = fmt.Sprintf("parse-cli-%s-%s", runtime.GOOS, Version)

type Env struct {
	Root            string // project root
	Server          string // parse api server
	Type            int    // project type
	ParserEmail     string // email associated with developer parse account
	ErrorStack      bool
	Out             io.Writer
	Err             io.Writer
	In              io.Reader
	Exit            func(int)
	Clock           clock.Clock
	ParseAPIClient  *ParseAPIClient
	HerokuAPIClient *heroku.Client
}

type Harness struct {
	T      testing.TB
	Out    bytes.Buffer
	Err    bytes.Buffer
	Clock  *clock.Mock
	Env    *Env
	remove []string
}

func (h *Harness) MakeEmptyRoot() {
	var err error
	prefix := fmt.Sprintf("%s-", testname.Get("parse-cli-"))
	h.Env.Root, err = ioutil.TempDir("", prefix)
	ensure.Nil(h.T, err)
	h.remove = append(h.remove, h.Env.Root)
}

func (h *Harness) MakeWithConfig(global string) {
	h.Env.Root = makeDirWithConfig(h.T, global)
}

func (h *Harness) Stop() {
	for _, p := range h.remove {
		os.RemoveAll(p)
	}
}

func NewHarness(t testing.TB) *Harness {
	te := Harness{
		T:     t,
		Clock: clock.NewMock(),
	}
	te.Env = &Env{
		Out:            &te.Out,
		Err:            &te.Err,
		Clock:          te.Clock,
		ParseAPIClient: &ParseAPIClient{APIClient: &parse.Client{}},
	}
	return &te
}

// makes a temp directory with the given global config.
func makeDirWithConfig(t testing.TB, global string) string {
	dir, err := ioutil.TempDir("", testname.Get("parse-cli-"))
	ensure.Nil(t, err)
	ensure.Nil(t, os.Mkdir(filepath.Join(dir, "config"), 0755))
	ensure.Nil(t, ioutil.WriteFile(
		filepath.Join(dir, LegacyConfigFile),
		[]byte(global),
		os.FileMode(0600),
	))
	return dir
}

type TransportFunc func(r *http.Request) (*http.Response, error)

func (t TransportFunc) RoundTrip(r *http.Request) (*http.Response, error) {
	return t(r)
}

func NewTokenHarness(t testing.TB) *Harness {
	h := NewHarness(t)
	ht := TransportFunc(func(r *http.Request) (*http.Response, error) {
		ensure.DeepEqual(t, r.URL.Path, "/1/accountkey")
		ensure.DeepEqual(t, r.Method, "POST")

		key := &struct {
			AccountKey string `json:"accountKey"`
		}{}
		ensure.Nil(t, json.NewDecoder(ioutil.NopCloser(r.Body)).Decode(key))

		if key.AccountKey != "token" {
			return &http.Response{
				StatusCode: http.StatusUnauthorized,
				Body:       ioutil.NopCloser(strings.NewReader(`{"error": "incorrect token"}`)),
			}, nil
		}

		return &http.Response{
			StatusCode: http.StatusOK,
			Body:       ioutil.NopCloser(strings.NewReader(`{"email": "email"}`)),
		}, nil
	})
	h.Env.ParseAPIClient = &ParseAPIClient{APIClient: &parse.Client{Transport: ht}}
	return h
}

func NewAppHarness(t testing.TB) (*Harness, []*App) {
	h := NewHarness(t)

	apps := []*App{
		newTestApp("A"),
		newTestApp("B"),
	}
	res := map[string][]*App{"results": apps}
	ht := TransportFunc(func(r *http.Request) (*http.Response, error) {

		email := r.Header.Get("X-Parse-Email")
		password := r.Header.Get("X-Parse-Password")
		token := r.Header.Get("X-Parse-Account-Key")
		if !((email == "email" && password == "password") || (token == "token")) {
			return &http.Response{
				StatusCode: http.StatusUnauthorized,
				Body:       ioutil.NopCloser(strings.NewReader(`{"error": "incorrect credentials"}`)),
			}, nil
		}

		switch r.URL.Path {
		case "/1/apps":
			if r.Method == "GET" {
				return &http.Response{
					StatusCode: http.StatusOK,
					Body:       ioutil.NopCloser(strings.NewReader(jsonStr(t, &res))),
				}, nil

			}

			if r.Method != "POST" || r.Body == nil {
				return &http.Response{
					StatusCode: http.StatusNotFound,
				}, errors.New("unknown resource")
			}

			var params map[string]string
			if err := json.NewDecoder(r.Body).Decode(&params); err != nil {
				return &http.Response{
					StatusCode: http.StatusInternalServerError,
				}, err
			}
			details, err := json.Marshal(newTestApp(params["appName"]))
			if err != nil {
				return &http.Response{
					StatusCode: http.StatusInternalServerError,
				}, err
			}

			return &http.Response{
				StatusCode: http.StatusOK,
				Body:       ioutil.NopCloser(bytes.NewReader(details)),
			}, nil

		case "/1/apps/an-app":
			ensure.DeepEqual(t, r.Method, "GET")
			return &http.Response{
				StatusCode: http.StatusOK,
				Body:       ioutil.NopCloser(strings.NewReader(jsonStr(t, newTestApp("an-app")))),
			}, nil
		default:
			return &http.Response{
				StatusCode: http.StatusNotFound,
			}, nil
		}
	})

	h.Env.ParseAPIClient = &ParseAPIClient{APIClient: &parse.Client{Transport: ht}}
	return h, apps
}

func newTestApp(suffix string) *App {
	return &App{
		Name:                          suffix,
		DashboardURL:                  fmt.Sprintf("https://api.example.com/dashboard/%s", suffix),
		ApplicationID:                 fmt.Sprintf("applicationID.%s", suffix),
		ClientKey:                     fmt.Sprintf("clientKey.%s", suffix),
		JavaScriptKey:                 fmt.Sprintf("javaScriptKey.%s", suffix),
		WindowsKey:                    fmt.Sprintf("windowsKey.%s", suffix),
		WebhookKey:                    fmt.Sprintf("webhookKey.%s", suffix),
		RestKey:                       fmt.Sprintf("restKey.%s", suffix),
		MasterKey:                     fmt.Sprintf("masterKey.%s", suffix),
		ClientPushEnabled:             false,
		ClientClassCreationEnabled:    true,
		RequireRevocableSessions:      true,
		RevokeSessionOnPasswordChange: true,
	}
}

func jsonStr(t testing.TB, v interface{}) string {
	b, err := json.Marshal(v)
	ensure.Nil(t, err)
	return string(b)
}
<|endoftext|>


<commit_before>package parser

const (
	nginx     = `(?P<remote>[^ ]*) (?P<host>[^ ]*) (?P<user>[^ ]*) \[(?P<time>[^]]*)\] "(?P<method>\S+)(?: +(?P<path>[^" ]*) +\S+)?" (?P<code>\d*) (?P<size>\d*)(?: "(?P<referer>[^"]*)" "(?P<agent>[^"]*)")?.*`
	nginxTime = "02/Jan/2006:15:04:05 -0700"
)

type Parser interface {
	Parse(string) (map[string]interface{}, error)
}

func Get(format, timeFormat, tz string) (p Parser, tp *TimeParser, err error) {
	switch format {
	case "ltsv":
		p = &LTSVParser{}
	case "nginx":
		p, err = NewRegexpParser(nginx)
		timeFormat = nginxTime
	default:
		p, err = NewRegexpParser(format)
	}

	if err == nil && timeFormat != "" {
		tp, err = NewTimeParser(timeFormat, tz)
	}
	return
}
<commit_msg>Add default line parser<commit_after>package parser

const (
	nginx     = `(?P<remote>[^ ]*) (?P<host>[^ ]*) (?P<user>[^ ]*) \[(?P<time>[^]]*)\] "(?P<method>\S+)(?: +(?P<path>[^" ]*) +\S+)?" (?P<code>\d*) (?P<size>\d*)(?: "(?P<referer>[^"]*)" "(?P<agent>[^"]*)")?.*`
	nginxTime = "02/Jan/2006:15:04:05 -0700"
)

type Parser interface {
	Parse(string) (map[string]interface{}, error)
}

type ParserFunc func(string) (map[string]interface{}, error)

func (f ParserFunc) Parse(s string) (map[string]interface{}, error) {
	return f(s)
}

func Get(format, timeFormat, tz string) (p Parser, tp *TimeParser, err error) {
	switch format {
	case "":
		p = ParserFunc(func(s string) (map[string]interface{}, error) {
			return map[string]interface{}{"message": s}, nil
		})
	case "ltsv":
		p = &LTSVParser{}
	case "nginx":
		p, err = NewRegexpParser(nginx)
		timeFormat = nginxTime
	default:
		p, err = NewRegexpParser(format)
	}

	if err == nil && timeFormat != "" {
		tp, err = NewTimeParser(timeFormat, tz)
	}
	return
}
<|endoftext|>


<commit_before>package lidar_test

import (
	"context"
	"errors"
	"time"

	"code.cloudfoundry.org/lager/lagertest"
	"github.com/concourse/concourse/atc"
	"github.com/concourse/concourse/atc/creds/credsfakes"
	"github.com/concourse/concourse/atc/db"
	"github.com/concourse/concourse/atc/db/dbfakes"
	"github.com/concourse/concourse/atc/db/lock/lockfakes"
	"github.com/concourse/concourse/atc/lidar"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

type Scanner interface {
	Run(ctx context.Context) error
}

var _ = Describe("Scanner", func() {
	var (
		err error

		fakeCheckFactory *dbfakes.FakeCheckFactory
		fakeSecrets      *credsfakes.FakeSecrets

		logger  *lagertest.TestLogger
		scanner Scanner
	)

	BeforeEach(func() {
		fakeCheckFactory = new(dbfakes.FakeCheckFactory)
		fakeSecrets = new(credsfakes.FakeSecrets)

		logger = lagertest.NewTestLogger("test")
		scanner = lidar.NewScanner(
			logger,
			fakeCheckFactory,
			fakeSecrets,
			time.Minute*1,
			time.Minute*1,
			time.Minute*10,
		)
	})

	JustBeforeEach(func() {
		err = scanner.Run(context.TODO())
	})

	Describe("Run", func() {
		var fakeLock *lockfakes.FakeLock

		BeforeEach(func() {
			fakeLock = new(lockfakes.FakeLock)
			fakeCheckFactory.AcquireScanningLockReturns(fakeLock, true, nil)
		})

		Context("when fetching resources fails", func() {
			BeforeEach(func() {
				fakeCheckFactory.ResourcesReturns(nil, errors.New("nope"))
			})

			It("errors", func() {
				Expect(err).To(HaveOccurred())
			})
		})

		Context("when fetching resources succeeds", func() {
			var fakeResource *dbfakes.FakeResource

			BeforeEach(func() {
				fakeResource = new(dbfakes.FakeResource)
				fakeResource.NameReturns("some-name")
				fakeResource.TagsReturns([]string{"tag-a", "tag-b"})
				fakeResource.SourceReturns(atc.Source{"some": "source"})

				fakeCheckFactory.ResourcesReturns([]db.Resource{fakeResource}, nil)
			})

			Context("when fetching resource types fails", func() {
				BeforeEach(func() {
					fakeCheckFactory.ResourceTypesReturns(nil, errors.New("nope"))
				})

				It("errors", func() {
					Expect(err).To(HaveOccurred())
				})
			})

			Context("when fetching resources types succeeds", func() {
				var fakeResourceType *dbfakes.FakeResourceType

				BeforeEach(func() {
					fakeResourceType = new(dbfakes.FakeResourceType)
					fakeResourceType.NameReturns("some-type")
					fakeResourceType.TypeReturns("some-base-type")
					fakeResourceType.TagsReturns([]string{"some-tag"})
					fakeResourceType.SourceReturns(atc.Source{"some": "type-source"})

					fakeCheckFactory.ResourceTypesReturns([]db.ResourceType{fakeResourceType}, nil)
				})

				Context("when the resource parent type is a base type", func() {
					BeforeEach(func() {
						fakeResource.TypeReturns("some-type")
					})

					Context("when the check interval is parseable", func() {
						BeforeEach(func() {
							fakeResource.CheckEveryReturns("10s")
						})

						Context("when the last check end time is within our interval", func() {
							BeforeEach(func() {
								fakeResource.LastCheckEndTimeReturns(time.Now())
							})

							It("does not check", func() {
								Expect(fakeCheckFactory.CreateCheckCallCount()).To(Equal(0))
							})

							It("clears the check error", func() {
								Expect(fakeResource.SetCheckSetupErrorCallCount()).To(Equal(1))
								Expect(fakeResource.SetCheckSetupErrorArgsForCall(0)).To(BeNil())
							})
						})

						Context("when the last check end time is past our interval", func() {
							BeforeEach(func() {
								fakeResource.LastCheckEndTimeReturns(time.Now().Add(-time.Hour))
							})

							It("creates a check", func() {
								Expect(fakeCheckFactory.TryCreateCheckCallCount()).To(Equal(2))
							})

							It("clears the check error", func() {
								Expect(fakeResource.SetCheckSetupErrorCallCount()).To(Equal(1))
								Expect(fakeResource.SetCheckSetupErrorArgsForCall(0)).To(BeNil())
							})

							It("sends a notification for the checker to run", func() {
								Expect(fakeCheckFactory.NotifyCheckerCallCount()).To(Equal(1))
							})

							Context("when try creating a check panic", func() {
								BeforeEach(func() {
									fakeCheckFactory.TryCreateCheckStub = func(context.Context, db.Checkable, db.ResourceTypes, atc.Version, bool) (db.Check, bool, error) {
										panic("something went wrong")
									}
								})

								It("recover from the panic", func() {
									Expect(err).ToNot(HaveOccurred())
									Eventually(fakeResource.SetCheckSetupErrorCallCount).Should(Equal(1))
									Eventually(fakeResource.SetCheckSetupErrorArgsForCall(0).Error).Should(ContainSubstring("something went wrong"))
								})
							})
						})

						Context("when the checkable has a pinned version", func() {
							BeforeEach(func() {
								fakeResource.CurrentPinnedVersionReturns(atc.Version{"some": "version"})
							})

							It("creates a check", func() {
								Expect(fakeCheckFactory.TryCreateCheckCallCount()).To(Equal(2))
								_, _, _, fromVersion, _ := fakeCheckFactory.TryCreateCheckArgsForCall(1)
								Expect(fromVersion).To(Equal(atc.Version{"some": "version"}))
							})

							It("clears the check error", func() {
								Expect(fakeResource.SetCheckSetupErrorCallCount()).To(Equal(1))
								Expect(fakeResource.SetCheckSetupErrorArgsForCall(0)).To(BeNil())
							})

							It("sends a notification for the checker to run", func() {
								Expect(fakeCheckFactory.NotifyCheckerCallCount()).To(Equal(1))
							})
						})

						Context("when the checkable does not have a pinned version", func() {
							BeforeEach(func() {
								fakeResource.CurrentPinnedVersionReturns(nil)
							})

							It("creates a check", func() {
								Expect(fakeCheckFactory.TryCreateCheckCallCount()).To(Equal(2))
								_, _, _, fromVersion, _ := fakeCheckFactory.TryCreateCheckArgsForCall(0)
								Expect(fromVersion).To(BeNil())
							})

							It("clears the check error", func() {
								Expect(fakeResource.SetCheckSetupErrorCallCount()).To(Equal(1))
								Expect(fakeResource.SetCheckSetupErrorArgsForCall(0)).To(BeNil())
							})

							It("sends a notification for the checker to run", func() {
								Expect(fakeCheckFactory.NotifyCheckerCallCount()).To(Equal(1))
							})
						})
					})
				})

				Context("when the resource has a parent type", func() {
					BeforeEach(func() {
						fakeResource.TypeReturns("custom-type")
						fakeResource.PipelineIDReturns(1)
						fakeResourceType.NameReturns("custom-type")
						fakeResourceType.PipelineIDReturns(1)
					})

					Context("when it fails to create a check for parent resource", func() {
						BeforeEach(func() {
							fakeResourceType.CheckEveryReturns("not-a-duration")
						})

						It("sets the check error", func() {
							Expect(fakeResourceType.SetCheckSetupErrorCallCount()).To(Equal(1))
							Expect(fakeResource.SetCheckSetupErrorCallCount()).To(Equal(1))
							err := fakeResource.SetCheckSetupErrorArgsForCall(0)
							Expect(err.Error()).To(ContainSubstring("parent type 'custom-type' error:"))
						})

						It("does not create a check", func() {
							Expect(fakeCheckFactory.TryCreateCheckCallCount()).To(Equal(0))
						})
					})

					Context("when the parent type requires a check", func() {
						BeforeEach(func() {
							fakeResourceType.LastCheckEndTimeReturns(time.Now().Add(-time.Hour))
							fakeResource.LastCheckEndTimeReturns(time.Now().Add(-time.Hour))
						})

						Context("when the parent type has a version", func() {
							BeforeEach(func() {
								fakeResourceType.VersionReturns(atc.Version{"some": "version"})
							})

							It("creates a check for both the parent and the resource", func() {
								Expect(fakeCheckFactory.TryCreateCheckCallCount()).To(Equal(2))

								_, checkable, _, _, manuallyTriggered := fakeCheckFactory.TryCreateCheckArgsForCall(0)
								Expect(checkable).To(Equal(fakeResourceType))
								Expect(manuallyTriggered).To(BeFalse())

								_, checkable, _, _, manuallyTriggered = fakeCheckFactory.TryCreateCheckArgsForCall(1)
								Expect(checkable).To(Equal(fakeResource))
								Expect(manuallyTriggered).To(BeFalse())
							})

							It("sends a notification for the checker to run", func() {
								Expect(fakeCheckFactory.NotifyCheckerCallCount()).To(Equal(1))
							})
						})
					})
				})
			})
		})

		Context("when there are multiple resources that use the same resource type", func() {
			var fakeResource1, fakeResource2 *dbfakes.FakeResource
			var fakeResourceType *dbfakes.FakeResourceType

			BeforeEach(func() {
				fakeResource1 = new(dbfakes.FakeResource)
				fakeResource1.NameReturns("some-name")
				fakeResource1.SourceReturns(atc.Source{"some": "source"})
				fakeResource1.TypeReturns("custom-type")
				fakeResource1.PipelineIDReturns(1)
				fakeResource1.LastCheckEndTimeReturns(time.Now().Add(-time.Hour))

				fakeResource2 = new(dbfakes.FakeResource)
				fakeResource2.NameReturns("some-name")
				fakeResource2.SourceReturns(atc.Source{"some": "source"})
				fakeResource2.TypeReturns("custom-type")
				fakeResource2.PipelineIDReturns(1)
				fakeResource2.LastCheckEndTimeReturns(time.Now().Add(-time.Hour))

				fakeCheckFactory.ResourcesReturns([]db.Resource{fakeResource1, fakeResource2}, nil)

				fakeResourceType = new(dbfakes.FakeResourceType)
				fakeResourceType.NameReturns("custom-type")
				fakeResourceType.PipelineIDReturns(1)
				fakeResourceType.TypeReturns("some-base-type")
				fakeResourceType.SourceReturns(atc.Source{"some": "type-source"})
				fakeResourceType.LastCheckEndTimeReturns(time.Now().Add(-time.Hour))

				fakeCheckFactory.ResourceTypesReturns([]db.ResourceType{fakeResourceType}, nil)
			})

			It("only tries to create a check for the resource type once", func() {
				Expect(fakeCheckFactory.TryCreateCheckCallCount()).To(Equal(3))

				var checked []string
				_, checkable, _, _, _ := fakeCheckFactory.TryCreateCheckArgsForCall(0)
				checked = append(checked, checkable.Name())

				_, checkable, _, _, _ = fakeCheckFactory.TryCreateCheckArgsForCall(1)
				checked = append(checked, checkable.Name())

				_, checkable, _, _, _ = fakeCheckFactory.TryCreateCheckArgsForCall(2)
				checked = append(checked, checkable.Name())

				Expect(checked).To(ConsistOf([]string{fakeResourceType.Name(), fakeResource1.Name(), fakeResource2.Name()}))
			})
		})

		Context("Default with webhook check interval", func() {
			var fakeResource *dbfakes.FakeResource
			BeforeEach(func() {
				fakeResource = new(dbfakes.FakeResource)
				fakeResource.NameReturns("some-name")
				fakeResource.TagsReturns([]string{"tag-a", "tag-b"})
				fakeResource.SourceReturns(atc.Source{"some": "source"})
				fakeResource.TypeReturns("base-type")
				fakeResource.CheckEveryReturns("")
				fakeCheckFactory.ResourcesReturns([]db.Resource{fakeResource}, nil)

			})

			Context("resource has webhook", func() {
				BeforeEach(func() {
					fakeResource.HasWebhookReturns(true)
				})

				Context("last check is 9 minutes ago", func() {
					BeforeEach(func() {
						fakeResource.LastCheckEndTimeReturns(time.Now().Add(-time.Minute * 9))
					})

					It("does not create a check", func() {
						Expect(fakeCheckFactory.TryCreateCheckCallCount()).To(Equal(0))
					})
				})

				Context("last check is 11 minutes ago", func() {
					BeforeEach(func() {
						fakeResource.LastCheckEndTimeReturns(time.Now().Add(-time.Minute * 11))
					})

					It("does not create a check", func() {
						Expect(fakeCheckFactory.TryCreateCheckCallCount()).To(Equal(1))
					})
				})
			})
		})
	})
})
<commit_msg>fix tests<commit_after>package lidar_test

import (
	"context"
	"errors"
	"time"

	"code.cloudfoundry.org/lager/lagertest"
	"github.com/concourse/concourse/atc"
	"github.com/concourse/concourse/atc/creds/credsfakes"
	"github.com/concourse/concourse/atc/db"
	"github.com/concourse/concourse/atc/db/dbfakes"
	"github.com/concourse/concourse/atc/db/lock/lockfakes"
	"github.com/concourse/concourse/atc/lidar"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

type Scanner interface {
	Run(ctx context.Context) error
}

var _ = Describe("Scanner", func() {
	var (
		err error

		fakeCheckFactory *dbfakes.FakeCheckFactory
		fakeSecrets      *credsfakes.FakeSecrets

		logger  *lagertest.TestLogger
		scanner Scanner
	)

	BeforeEach(func() {
		fakeCheckFactory = new(dbfakes.FakeCheckFactory)
		fakeSecrets = new(credsfakes.FakeSecrets)

		logger = lagertest.NewTestLogger("test")
		scanner = lidar.NewScanner(
			logger,
			fakeCheckFactory,
			fakeSecrets,
			time.Minute*1,
			time.Minute*1,
			time.Minute*10,
		)
	})

	JustBeforeEach(func() {
		err = scanner.Run(context.TODO())
	})

	Describe("Run", func() {
		var fakeLock *lockfakes.FakeLock

		BeforeEach(func() {
			fakeLock = new(lockfakes.FakeLock)
			fakeCheckFactory.AcquireScanningLockReturns(fakeLock, true, nil)
		})

		Context("when fetching resources fails", func() {
			BeforeEach(func() {
				fakeCheckFactory.ResourcesReturns(nil, errors.New("nope"))
			})

			It("errors", func() {
				Expect(err).To(HaveOccurred())
			})
		})

		Context("when fetching resources succeeds", func() {
			var fakeResource *dbfakes.FakeResource

			BeforeEach(func() {
				fakeResource = new(dbfakes.FakeResource)
				fakeResource.NameReturns("some-name")
				fakeResource.TagsReturns([]string{"tag-a", "tag-b"})
				fakeResource.SourceReturns(atc.Source{"some": "source"})

				fakeCheckFactory.ResourcesReturns([]db.Resource{fakeResource}, nil)
			})

			Context("when fetching resource types fails", func() {
				BeforeEach(func() {
					fakeCheckFactory.ResourceTypesReturns(nil, errors.New("nope"))
				})

				It("errors", func() {
					Expect(err).To(HaveOccurred())
				})
			})

			Context("when fetching resources types succeeds", func() {
				var fakeResourceType *dbfakes.FakeResourceType

				BeforeEach(func() {
					fakeResourceType = new(dbfakes.FakeResourceType)
					fakeResourceType.NameReturns("some-type")
					fakeResourceType.TypeReturns("some-base-type")
					fakeResourceType.TagsReturns([]string{"some-tag"})
					fakeResourceType.SourceReturns(atc.Source{"some": "type-source"})

					fakeCheckFactory.ResourceTypesReturns([]db.ResourceType{fakeResourceType}, nil)
				})

				Context("when the resource parent type is a base type", func() {
					BeforeEach(func() {
						fakeCheckFactory.ResourceTypesReturns([]db.ResourceType{}, nil)
						fakeResource.TypeReturns("some-type")
					})

					Context("when the check interval is parseable", func() {
						BeforeEach(func() {
							fakeResource.CheckEveryReturns("10s")
						})

						Context("when the last check end time is within our interval", func() {
							BeforeEach(func() {
								fakeResource.LastCheckEndTimeReturns(time.Now())
							})

							It("does not check", func() {
								Expect(fakeCheckFactory.CreateCheckCallCount()).To(Equal(0))
							})

							It("clears the check error", func() {
								Expect(fakeResource.SetCheckSetupErrorCallCount()).To(Equal(1))
								Expect(fakeResource.SetCheckSetupErrorArgsForCall(0)).To(BeNil())
							})
						})

						Context("when the last check end time is past our interval", func() {
							BeforeEach(func() {
								fakeResource.LastCheckEndTimeReturns(time.Now().Add(-time.Hour))
							})

							It("creates a check", func() {
								Expect(fakeCheckFactory.TryCreateCheckCallCount()).To(Equal(1))
							})

							It("clears the check error", func() {
								Expect(fakeResource.SetCheckSetupErrorCallCount()).To(Equal(1))
								Expect(fakeResource.SetCheckSetupErrorArgsForCall(0)).To(BeNil())
							})

							It("sends a notification for the checker to run", func() {
								Expect(fakeCheckFactory.NotifyCheckerCallCount()).To(Equal(1))
							})

							Context("when try creating a check panic", func() {
								BeforeEach(func() {
									fakeCheckFactory.TryCreateCheckStub = func(context.Context, db.Checkable, db.ResourceTypes, atc.Version, bool) (db.Check, bool, error) {
										panic("something went wrong")
									}
								})

								It("recover from the panic", func() {
									Expect(err).ToNot(HaveOccurred())
									Eventually(fakeResource.SetCheckSetupErrorCallCount).Should(Equal(1))
									Eventually(fakeResource.SetCheckSetupErrorArgsForCall(0).Error).Should(ContainSubstring("something went wrong"))
								})
							})
						})

						Context("when the checkable has a pinned version", func() {
							BeforeEach(func() {
								fakeResource.CurrentPinnedVersionReturns(atc.Version{"some": "version"})
							})

							It("creates a check", func() {
								Expect(fakeCheckFactory.TryCreateCheckCallCount()).To(Equal(1))
								_, _, _, fromVersion, _ := fakeCheckFactory.TryCreateCheckArgsForCall(0)
								Expect(fromVersion).To(Equal(atc.Version{"some": "version"}))
							})

							It("clears the check error", func() {
								Expect(fakeResource.SetCheckSetupErrorCallCount()).To(Equal(1))
								Expect(fakeResource.SetCheckSetupErrorArgsForCall(0)).To(BeNil())
							})

							It("sends a notification for the checker to run", func() {
								Expect(fakeCheckFactory.NotifyCheckerCallCount()).To(Equal(1))
							})
						})

						Context("when the checkable does not have a pinned version", func() {
							BeforeEach(func() {
								fakeResource.CurrentPinnedVersionReturns(nil)
							})

							It("creates a check", func() {
								Expect(fakeCheckFactory.TryCreateCheckCallCount()).To(Equal(1))
								_, _, _, fromVersion, _ := fakeCheckFactory.TryCreateCheckArgsForCall(0)
								Expect(fromVersion).To(BeNil())
							})

							It("clears the check error", func() {
								Expect(fakeResource.SetCheckSetupErrorCallCount()).To(Equal(1))
								Expect(fakeResource.SetCheckSetupErrorArgsForCall(0)).To(BeNil())
							})

							It("sends a notification for the checker to run", func() {
								Expect(fakeCheckFactory.NotifyCheckerCallCount()).To(Equal(1))
							})
						})
					})
				})

				Context("when the resource has a parent type", func() {
					BeforeEach(func() {
						fakeResource.TypeReturns("custom-type")
						fakeResource.PipelineIDReturns(1)
						fakeResourceType.NameReturns("custom-type")
						fakeResourceType.PipelineIDReturns(1)
					})

					Context("when it fails to create a check for parent resource", func() {
						BeforeEach(func() {
							fakeResourceType.CheckEveryReturns("not-a-duration")
						})

						It("sets the check error", func() {
							Expect(fakeResourceType.SetCheckSetupErrorCallCount()).To(Equal(1))
							Expect(fakeResource.SetCheckSetupErrorCallCount()).To(Equal(1))
							err := fakeResource.SetCheckSetupErrorArgsForCall(0)
							Expect(err.Error()).To(ContainSubstring("parent type 'custom-type' error:"))
						})

						It("does not create a check", func() {
							Expect(fakeCheckFactory.TryCreateCheckCallCount()).To(Equal(0))
						})
					})

					Context("when the parent type requires a check", func() {
						BeforeEach(func() {
							fakeResourceType.LastCheckEndTimeReturns(time.Now().Add(-time.Hour))
							fakeResource.LastCheckEndTimeReturns(time.Now().Add(-time.Hour))
						})

						Context("when the parent type has a version", func() {
							BeforeEach(func() {
								fakeResourceType.VersionReturns(atc.Version{"some": "version"})
							})

							It("creates a check for both the parent and the resource", func() {
								Expect(fakeCheckFactory.TryCreateCheckCallCount()).To(Equal(2))

								_, checkable, _, _, manuallyTriggered := fakeCheckFactory.TryCreateCheckArgsForCall(0)
								Expect(checkable).To(Equal(fakeResourceType))
								Expect(manuallyTriggered).To(BeFalse())

								_, checkable, _, _, manuallyTriggered = fakeCheckFactory.TryCreateCheckArgsForCall(1)
								Expect(checkable).To(Equal(fakeResource))
								Expect(manuallyTriggered).To(BeFalse())
							})

							It("sends a notification for the checker to run", func() {
								Expect(fakeCheckFactory.NotifyCheckerCallCount()).To(Equal(1))
							})
						})
					})
				})
			})
		})

		Context("when there are multiple resources that use the same resource type", func() {
			var fakeResource1, fakeResource2 *dbfakes.FakeResource
			var fakeResourceType *dbfakes.FakeResourceType

			BeforeEach(func() {
				fakeResource1 = new(dbfakes.FakeResource)
				fakeResource1.NameReturns("some-name")
				fakeResource1.SourceReturns(atc.Source{"some": "source"})
				fakeResource1.TypeReturns("custom-type")
				fakeResource1.PipelineIDReturns(1)
				fakeResource1.LastCheckEndTimeReturns(time.Now().Add(-time.Hour))

				fakeResource2 = new(dbfakes.FakeResource)
				fakeResource2.NameReturns("some-name")
				fakeResource2.SourceReturns(atc.Source{"some": "source"})
				fakeResource2.TypeReturns("custom-type")
				fakeResource2.PipelineIDReturns(1)
				fakeResource2.LastCheckEndTimeReturns(time.Now().Add(-time.Hour))

				fakeCheckFactory.ResourcesReturns([]db.Resource{fakeResource1, fakeResource2}, nil)

				fakeResourceType = new(dbfakes.FakeResourceType)
				fakeResourceType.NameReturns("custom-type")
				fakeResourceType.PipelineIDReturns(1)
				fakeResourceType.TypeReturns("some-base-type")
				fakeResourceType.SourceReturns(atc.Source{"some": "type-source"})
				fakeResourceType.LastCheckEndTimeReturns(time.Now().Add(-time.Hour))

				fakeCheckFactory.ResourceTypesReturns([]db.ResourceType{fakeResourceType}, nil)
			})

			It("only tries to create a check for the resource type once", func() {
				Expect(fakeCheckFactory.TryCreateCheckCallCount()).To(Equal(3))

				var checked []string
				_, checkable, _, _, _ := fakeCheckFactory.TryCreateCheckArgsForCall(0)
				checked = append(checked, checkable.Name())

				_, checkable, _, _, _ = fakeCheckFactory.TryCreateCheckArgsForCall(1)
				checked = append(checked, checkable.Name())

				_, checkable, _, _, _ = fakeCheckFactory.TryCreateCheckArgsForCall(2)
				checked = append(checked, checkable.Name())

				Expect(checked).To(ConsistOf([]string{fakeResourceType.Name(), fakeResource1.Name(), fakeResource2.Name()}))
			})
		})

		Context("Default with webhook check interval", func() {
			var fakeResource *dbfakes.FakeResource
			BeforeEach(func() {
				fakeResource = new(dbfakes.FakeResource)
				fakeResource.NameReturns("some-name")
				fakeResource.TagsReturns([]string{"tag-a", "tag-b"})
				fakeResource.SourceReturns(atc.Source{"some": "source"})
				fakeResource.TypeReturns("base-type")
				fakeResource.CheckEveryReturns("")
				fakeCheckFactory.ResourcesReturns([]db.Resource{fakeResource}, nil)

			})

			Context("resource has webhook", func() {
				BeforeEach(func() {
					fakeResource.HasWebhookReturns(true)
				})

				Context("last check is 9 minutes ago", func() {
					BeforeEach(func() {
						fakeResource.LastCheckEndTimeReturns(time.Now().Add(-time.Minute * 9))
					})

					It("does not create a check", func() {
						Expect(fakeCheckFactory.TryCreateCheckCallCount()).To(Equal(0))
					})
				})

				Context("last check is 11 minutes ago", func() {
					BeforeEach(func() {
						fakeResource.LastCheckEndTimeReturns(time.Now().Add(-time.Minute * 11))
					})

					It("does not create a check", func() {
						Expect(fakeCheckFactory.TryCreateCheckCallCount()).To(Equal(1))
					})
				})
			})
		})
	})
})
<|endoftext|>


<commit_before>package core

import (
	"fmt"
	"strconv"

	"github.com/af83/edwig/audit"
	"github.com/af83/edwig/model"
	"github.com/af83/edwig/siri"
)

type ServiceRequestBroadcaster interface {
	HandleRequests(request *siri.XMLSiriServiceRequest) *siri.SIRIServiceResponse
}

type SIRIServiceRequestBroadcaster struct {
	model.ClockConsumer

	siriConnector
}

type SIRIServiceRequestBroadcasterFactory struct{}

func NewSIRIServiceRequestBroadcaster(partner *Partner) *SIRIServiceRequestBroadcaster {
	siriServiceRequestBroadcaster := &SIRIServiceRequestBroadcaster{}
	siriServiceRequestBroadcaster.partner = partner
	return siriServiceRequestBroadcaster
}

func (connector *SIRIServiceRequestBroadcaster) HandleRequests(request *siri.XMLSiriServiceRequest) *siri.SIRIServiceResponse {
	tx := connector.Partner().Referential().NewTransaction()
	defer tx.Close()

	logStashEvent := make(audit.LogStashEvent)
	defer audit.CurrentLogStash().WriteEvent(logStashEvent)

	logXMLSiriServiceRequest(logStashEvent, request)

	response := new(siri.SIRIServiceResponse)
	response.ProducerRef = connector.Partner().Setting("remote_credential")
	if response.ProducerRef == "" {
		response.ProducerRef = "Edwig"
	}
	response.ResponseMessageIdentifier = connector.SIRIPartner().NewMessageIdentifier()
	response.Status = true
	response.RequestMessageRef = request.MessageIdentifier()
	response.ResponseTimestamp = connector.Clock().Now()

	stopMonitoringConnector := NewSIRIStopMonitoringRequestBroadcaster(connector.partner)

	for _, stopMonitoringRequest := range request.StopMonitoringRequests() {
		SMLogStashEvent := make(audit.LogStashEvent)

		logXMLSiriServiceStopMonitoringRequest(logStashEvent, stopMonitoringRequest)

		objectidKind := connector.Partner().Setting("remote_objectid_kind")
		objectid := model.NewObjectID(objectidKind, stopMonitoringRequest.MonitoringRef())
		stopArea, ok := tx.Model().StopAreas().FindByObjectId(objectid)

		var delivery siri.SIRIStopMonitoringDelivery
		if !ok {
			delivery = siri.SIRIStopMonitoringDelivery{
				RequestMessageRef: stopMonitoringRequest.MessageIdentifier(),
				Status:            false,
				ResponseTimestamp: connector.Clock().Now(),
				ErrorType:         "InvalidDataReferencesError",
				ErrorText:         "StopArea not found",
				Description:       "Error [MonitoringRef] : StopArea not found",
			}
		} else {
			delivery = stopMonitoringConnector.getStopMonitoringDelivery(tx, SMLogStashEvent, stopArea, stopMonitoringRequest.MessageIdentifier())
		}

		logSIRIStopMonitoringDelivery(SMLogStashEvent, delivery)
		audit.CurrentLogStash().WriteEvent(SMLogStashEvent)

		response.Deliveries = append(response.Deliveries, delivery)
	}

	logSIRIServiceResponse(logStashEvent, response)

	return response
}

func (factory *SIRIServiceRequestBroadcasterFactory) Validate(apiPartner *APIPartner) bool {
	ok := apiPartner.ValidatePresenceOfSetting("remote_objectid_kind")
	ok = ok && apiPartner.ValidatePresenceOfSetting("local_credential")
	return ok
}

func (factory *SIRIServiceRequestBroadcasterFactory) CreateConnector(partner *Partner) Connector {
	return NewSIRIServiceRequestBroadcaster(partner)
}

func logXMLSiriServiceRequest(logStashEvent audit.LogStashEvent, request *siri.XMLSiriServiceRequest) {
	logStashEvent["Connector"] = "SIRIServiceRequestBroadcaster"
	logStashEvent["messageIdentifier"] = request.MessageIdentifier()
	logStashEvent["requestorRef"] = request.RequestorRef()
	logStashEvent["requestTimestamp"] = request.RequestTimestamp().String()
	logStashEvent["requestXML"] = request.RawXML()
}

func logSIRIServiceResponse(logStashEvent audit.LogStashEvent, response *siri.SIRIServiceResponse) {
	logStashEvent["producerRef"] = response.ProducerRef
	logStashEvent["requestMessageRef"] = response.RequestMessageRef
	logStashEvent["responseMessageIdentifier"] = response.ResponseMessageIdentifier
	logStashEvent["responseTimestamp"] = response.ResponseTimestamp.String()
	logStashEvent["status"] = strconv.FormatBool(response.Status)
	xml, err := response.BuildXML()
	if err != nil {
		logStashEvent["responseXML"] = fmt.Sprintf("%v", err)
		return
	}
	logStashEvent["responseXML"] = xml
}

func logXMLSiriServiceStopMonitoringRequest(logStashEvent audit.LogStashEvent, request *siri.XMLSiriServiceStopMonitoringRequest) {
	logStashEvent["Connector"] = "StopMonitoringRequestBroadcaster for SIRIServiceRequestBroadcaster"
	logStashEvent["messageIdentifier"] = request.MessageIdentifier()
	logStashEvent["monitoringRef"] = request.MonitoringRef()
	logStashEvent["stopVisitTypes"] = request.StopVisitTypes()
	logStashEvent["requestTimestamp"] = request.RequestTimestamp().String()
	logStashEvent["requestXML"] = request.RawXML()
}

func logSIRIStopMonitoringDelivery(logStashEvent audit.LogStashEvent, delivery siri.SIRIStopMonitoringDelivery) {
	logStashEvent["requestMessageRef"] = delivery.RequestMessageRef
	logStashEvent["responseTimestamp"] = delivery.ResponseTimestamp.String()
	logStashEvent["status"] = strconv.FormatBool(delivery.Status)
	if !delivery.Status {
		logStashEvent["errorType"] = delivery.ErrorType
		logStashEvent["errorNumber"] = strconv.Itoa(delivery.ErrorNumber)
		logStashEvent["errorText"] = delivery.ErrorText
		logStashEvent["errorDescription"] = delivery.Description
	}
}
<commit_msg>Add ObjectID value in ErrorText. Change SIRIServiceResponse status when a StopArea isn't found. Refs #2961<commit_after>package core

import (
	"fmt"
	"strconv"

	"github.com/af83/edwig/audit"
	"github.com/af83/edwig/model"
	"github.com/af83/edwig/siri"
)

type ServiceRequestBroadcaster interface {
	HandleRequests(request *siri.XMLSiriServiceRequest) *siri.SIRIServiceResponse
}

type SIRIServiceRequestBroadcaster struct {
	model.ClockConsumer

	siriConnector
}

type SIRIServiceRequestBroadcasterFactory struct{}

func NewSIRIServiceRequestBroadcaster(partner *Partner) *SIRIServiceRequestBroadcaster {
	siriServiceRequestBroadcaster := &SIRIServiceRequestBroadcaster{}
	siriServiceRequestBroadcaster.partner = partner
	return siriServiceRequestBroadcaster
}

func (connector *SIRIServiceRequestBroadcaster) HandleRequests(request *siri.XMLSiriServiceRequest) *siri.SIRIServiceResponse {
	tx := connector.Partner().Referential().NewTransaction()
	defer tx.Close()

	logStashEvent := make(audit.LogStashEvent)
	defer audit.CurrentLogStash().WriteEvent(logStashEvent)

	logXMLSiriServiceRequest(logStashEvent, request)

	response := new(siri.SIRIServiceResponse)
	response.ProducerRef = connector.Partner().Setting("remote_credential")
	if response.ProducerRef == "" {
		response.ProducerRef = "Edwig"
	}
	response.ResponseMessageIdentifier = connector.SIRIPartner().NewMessageIdentifier()
	response.Status = true
	response.RequestMessageRef = request.MessageIdentifier()
	response.ResponseTimestamp = connector.Clock().Now()

	stopMonitoringConnector := NewSIRIStopMonitoringRequestBroadcaster(connector.partner)

	for _, stopMonitoringRequest := range request.StopMonitoringRequests() {
		SMLogStashEvent := make(audit.LogStashEvent)

		logXMLSiriServiceStopMonitoringRequest(logStashEvent, stopMonitoringRequest)

		objectidKind := connector.Partner().Setting("remote_objectid_kind")
		objectid := model.NewObjectID(objectidKind, stopMonitoringRequest.MonitoringRef())
		stopArea, ok := tx.Model().StopAreas().FindByObjectId(objectid)

		var delivery siri.SIRIStopMonitoringDelivery
		if !ok {
			delivery = siri.SIRIStopMonitoringDelivery{
				RequestMessageRef: stopMonitoringRequest.MessageIdentifier(),
				Status:            false,
				ResponseTimestamp: connector.Clock().Now(),
				ErrorType:         "InvalidDataReferencesError",
				ErrorText:         fmt.Sprintf("StopArea not found: '%s'", objectid.Value()),
			}
			response.Status = false
		} else {
			delivery = stopMonitoringConnector.getStopMonitoringDelivery(tx, SMLogStashEvent, stopArea, stopMonitoringRequest.MessageIdentifier())
		}

		logSIRIStopMonitoringDelivery(SMLogStashEvent, delivery)
		audit.CurrentLogStash().WriteEvent(SMLogStashEvent)

		response.Deliveries = append(response.Deliveries, delivery)
	}

	logSIRIServiceResponse(logStashEvent, response)

	return response
}

func (factory *SIRIServiceRequestBroadcasterFactory) Validate(apiPartner *APIPartner) bool {
	ok := apiPartner.ValidatePresenceOfSetting("remote_objectid_kind")
	ok = ok && apiPartner.ValidatePresenceOfSetting("local_credential")
	return ok
}

func (factory *SIRIServiceRequestBroadcasterFactory) CreateConnector(partner *Partner) Connector {
	return NewSIRIServiceRequestBroadcaster(partner)
}

func logXMLSiriServiceRequest(logStashEvent audit.LogStashEvent, request *siri.XMLSiriServiceRequest) {
	logStashEvent["Connector"] = "SIRIServiceRequestBroadcaster"
	logStashEvent["messageIdentifier"] = request.MessageIdentifier()
	logStashEvent["requestorRef"] = request.RequestorRef()
	logStashEvent["requestTimestamp"] = request.RequestTimestamp().String()
	logStashEvent["requestXML"] = request.RawXML()
}

func logSIRIServiceResponse(logStashEvent audit.LogStashEvent, response *siri.SIRIServiceResponse) {
	logStashEvent["producerRef"] = response.ProducerRef
	logStashEvent["requestMessageRef"] = response.RequestMessageRef
	logStashEvent["responseMessageIdentifier"] = response.ResponseMessageIdentifier
	logStashEvent["responseTimestamp"] = response.ResponseTimestamp.String()
	logStashEvent["status"] = strconv.FormatBool(response.Status)
	xml, err := response.BuildXML()
	if err != nil {
		logStashEvent["responseXML"] = fmt.Sprintf("%v", err)
		return
	}
	logStashEvent["responseXML"] = xml
}

func logXMLSiriServiceStopMonitoringRequest(logStashEvent audit.LogStashEvent, request *siri.XMLSiriServiceStopMonitoringRequest) {
	logStashEvent["Connector"] = "StopMonitoringRequestBroadcaster for SIRIServiceRequestBroadcaster"
	logStashEvent["messageIdentifier"] = request.MessageIdentifier()
	logStashEvent["monitoringRef"] = request.MonitoringRef()
	logStashEvent["stopVisitTypes"] = request.StopVisitTypes()
	logStashEvent["requestTimestamp"] = request.RequestTimestamp().String()
	logStashEvent["requestXML"] = request.RawXML()
}

func logSIRIStopMonitoringDelivery(logStashEvent audit.LogStashEvent, delivery siri.SIRIStopMonitoringDelivery) {
	logStashEvent["requestMessageRef"] = delivery.RequestMessageRef
	logStashEvent["responseTimestamp"] = delivery.ResponseTimestamp.String()
	logStashEvent["status"] = strconv.FormatBool(delivery.Status)
	if !delivery.Status {
		logStashEvent["errorType"] = delivery.ErrorType
		logStashEvent["errorNumber"] = strconv.Itoa(delivery.ErrorNumber)
		logStashEvent["errorText"] = delivery.ErrorText
	}
}
<|endoftext|>


<commit_before>package aws

import (
	"fmt"
	"log"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/service/ec2"
	"github.com/hashicorp/terraform/helper/resource"
	"github.com/hashicorp/terraform/helper/schema"
)

func resourceAwsFlowLog() *schema.Resource {
	return &schema.Resource{
		Create: resourceAwsLogFlowCreate,
		Read:   resourceAwsLogFlowRead,
		Delete: resourceAwsLogFlowDelete,

		Schema: map[string]*schema.Schema{
			"iam_role_arn": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},

			"log_group_name": &schema.Schema{
				Type:     schema.TypeString,
                                Required: true,
                                ForceNew: true,
			},

			"vpc_id": &schema.Schema{
				Type:          schema.TypeString,
				Optional:      true,
				ForceNew:      true,
				ConflictsWith: []string{"subnet_id", "eni_id"},
			},

			"subnet_id": &schema.Schema{
				Type:          schema.TypeString,
				Optional:      true,
				ForceNew:      true,
				ConflictsWith: []string{"eni_id", "vpc_id"},
			},

			"eni_id": &schema.Schema{
				Type:          schema.TypeString,
				Optional:      true,
				ForceNew:      true,
				ConflictsWith: []string{"subnet_id", "vpc_id"},
			},

			"traffic_type": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},

			"flow_log_status": &schema.Schema{
				Type:     schema.TypeString,
				Computed: true,
			},

			"deliver_log_status": &schema.Schema{
				Type:     schema.TypeString,
				Computed: true,
			},
		},
	}
}

func resourceAwsLogFlowCreate(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).ec2conn

	types := []struct {
		ID   string
		Type string
	}{
		{ID: d.Get("vpc_id").(string), Type: "VPC"},
		{ID: d.Get("subnet_id").(string), Type: "Subnet"},
		{ID: d.Get("eni_id").(string), Type: "NetworkInterface"},
	}

	var resourceId string
	var resourceType string
	for _, t := range types {
		if t.ID != "" {
			resourceId = t.ID
			resourceType = t.Type
			break
		}
	}

	if resourceId == "" || resourceType == "" {
		return fmt.Errorf("Error: Flow Logs require either a VPC, Subnet, or ENI ID")
	}

	opts := &ec2.CreateFlowLogsInput{
		DeliverLogsPermissionARN: aws.String(d.Get("iam_role_arn").(string)),
		LogGroupName:             aws.String(d.Get("log_group_name").(string)),
		ResourceIDs:              []*string{aws.String(resourceId)},
		ResourceType:             aws.String(resourceType),
		TrafficType:              aws.String(d.Get("traffic_type").(string)),
	}
	resp, err := conn.CreateFlowLogs(opts)
	if err != nil {
		return fmt.Errorf("Error creating Flow Log for (%s), error: %s", resourceId, err)
	}

	if len(resp.FlowLogIDs) > 1 {
		return fmt.Errorf("Error: multiple Flow Logs created for (%s), error: %s", resourceId)
	}

	d.SetId(*resp.FlowLogIDs[0])

	return resourceAwsLogFlowRead(d, meta)
}

func resourceAwsLogFlowRead(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).ec2conn

	opts := &ec2.DescribeFlowLogsInput{
		FlowLogIDs: []*string{aws.String(d.Id())},
	}

	resp, err := conn.DescribeFlowLogs(opts)
	if err != nil {
		log.Printf("[WARN] Error describing Flow Logs for id (%s)", d.Id())
		d.SetId("")
		return nil
	}

	if len(resp.FlowLogs) == 0 {
		log.Printf("[WARN] No Flow Logs found for id (%s)", d.Id())
		d.SetId("")
		return nil
	}

	fl := resp.FlowLogs[0]

	d.Set("traffic_type", fl.TrafficType)
	d.Set("log_group_name", fl.LogGroupName)
	d.Set("iam_role_arn", fl.DeliverLogsPermissionARN)
	d.Set("flow_log_status", fl.FlowLogStatus)
	d.Set("deliver_log_status", fl.DeliverLogsStatus)

	return nil
}

func resourceAwsLogFlowDelete(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).ec2conn
	_, err := conn.DeleteFlowLogs(&ec2.DeleteFlowLogsInput{
		FlowLogIDs: []*string{aws.String(d.Id())},
	})

	if err != nil {
		return fmt.Errorf("[WARN] Error deleting Flow Log with ID (%s), error: %s", d.Id(), err)
	}

	return nil
}

func flowLogStateRefreshFunc(conn *ec2.EC2, sn string) resource.StateRefreshFunc {
	return func() (interface{}, string, error) {
		return nil, "ok", nil
	}
}
<commit_msg>fix go vet error<commit_after>package aws

import (
	"fmt"
	"log"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/service/ec2"
	"github.com/hashicorp/terraform/helper/resource"
	"github.com/hashicorp/terraform/helper/schema"
)

func resourceAwsFlowLog() *schema.Resource {
	return &schema.Resource{
		Create: resourceAwsLogFlowCreate,
		Read:   resourceAwsLogFlowRead,
		Delete: resourceAwsLogFlowDelete,

		Schema: map[string]*schema.Schema{
			"iam_role_arn": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},

			"log_group_name": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},

			"vpc_id": &schema.Schema{
				Type:          schema.TypeString,
				Optional:      true,
				ForceNew:      true,
				ConflictsWith: []string{"subnet_id", "eni_id"},
			},

			"subnet_id": &schema.Schema{
				Type:          schema.TypeString,
				Optional:      true,
				ForceNew:      true,
				ConflictsWith: []string{"eni_id", "vpc_id"},
			},

			"eni_id": &schema.Schema{
				Type:          schema.TypeString,
				Optional:      true,
				ForceNew:      true,
				ConflictsWith: []string{"subnet_id", "vpc_id"},
			},

			"traffic_type": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},

			"flow_log_status": &schema.Schema{
				Type:     schema.TypeString,
				Computed: true,
			},

			"deliver_log_status": &schema.Schema{
				Type:     schema.TypeString,
				Computed: true,
			},
		},
	}
}

func resourceAwsLogFlowCreate(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).ec2conn

	types := []struct {
		ID   string
		Type string
	}{
		{ID: d.Get("vpc_id").(string), Type: "VPC"},
		{ID: d.Get("subnet_id").(string), Type: "Subnet"},
		{ID: d.Get("eni_id").(string), Type: "NetworkInterface"},
	}

	var resourceId string
	var resourceType string
	for _, t := range types {
		if t.ID != "" {
			resourceId = t.ID
			resourceType = t.Type
			break
		}
	}

	if resourceId == "" || resourceType == "" {
		return fmt.Errorf("Error: Flow Logs require either a VPC, Subnet, or ENI ID")
	}

	opts := &ec2.CreateFlowLogsInput{
		DeliverLogsPermissionARN: aws.String(d.Get("iam_role_arn").(string)),
		LogGroupName:             aws.String(d.Get("log_group_name").(string)),
		ResourceIDs:              []*string{aws.String(resourceId)},
		ResourceType:             aws.String(resourceType),
		TrafficType:              aws.String(d.Get("traffic_type").(string)),
	}
	resp, err := conn.CreateFlowLogs(opts)
	if err != nil {
		return fmt.Errorf("Error creating Flow Log for (%s), error: %s", resourceId, err)
	}

	if len(resp.FlowLogIDs) > 1 {
		return fmt.Errorf("Error: multiple Flow Logs created for (%s)", resourceId)
	}

	d.SetId(*resp.FlowLogIDs[0])

	return resourceAwsLogFlowRead(d, meta)
}

func resourceAwsLogFlowRead(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).ec2conn

	opts := &ec2.DescribeFlowLogsInput{
		FlowLogIDs: []*string{aws.String(d.Id())},
	}

	resp, err := conn.DescribeFlowLogs(opts)
	if err != nil {
		log.Printf("[WARN] Error describing Flow Logs for id (%s)", d.Id())
		d.SetId("")
		return nil
	}

	if len(resp.FlowLogs) == 0 {
		log.Printf("[WARN] No Flow Logs found for id (%s)", d.Id())
		d.SetId("")
		return nil
	}

	fl := resp.FlowLogs[0]

	d.Set("traffic_type", fl.TrafficType)
	d.Set("log_group_name", fl.LogGroupName)
	d.Set("iam_role_arn", fl.DeliverLogsPermissionARN)
	d.Set("flow_log_status", fl.FlowLogStatus)
	d.Set("deliver_log_status", fl.DeliverLogsStatus)

	return nil
}

func resourceAwsLogFlowDelete(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).ec2conn
	_, err := conn.DeleteFlowLogs(&ec2.DeleteFlowLogsInput{
		FlowLogIDs: []*string{aws.String(d.Id())},
	})

	if err != nil {
		return fmt.Errorf("[WARN] Error deleting Flow Log with ID (%s), error: %s", d.Id(), err)
	}

	return nil
}

func flowLogStateRefreshFunc(conn *ec2.EC2, sn string) resource.StateRefreshFunc {
	return func() (interface{}, string, error) {
		return nil, "ok", nil
	}
}
<|endoftext|>


<commit_before>package netaddr_test

import (
	"sort"
	"testing"

	"github.com/hashicorp/go-netaddr"
)

func makeTestNetAddrs(t *testing.T) []*netaddr.NetAddr {
	nets := []string{
		"10.0.0.0/8",
		"172.16.1.3/12",
		"192.168.0.0/16",
		"192.168.1.10/24",
		"240.0.0.1/4",
	}
	nas := make([]*netaddr.NetAddr, 0, len(nets))
	for _, n := range nets {
		na, err := netaddr.New(n)
		if err != nil {
			t.Fatalf("Expected valid network")
		}
		nas = append(nas, na)
	}

	return nas
}

func TestNetAddr_Netaddrs_BySpecificMaskLen(t *testing.T) {
	nets := makeTestNetAddrs(t)

	sort.Sort(netaddr.BySpecificMaskLen{nets})

	var lastLen int = 32
	for _, net := range nets {
		maskLen := net.Maskbits()
		if lastLen < maskLen {
			t.Fatalf("Sort by specific mask length failed")
		}
		lastLen = maskLen
	}
}

func TestNetAddr_Netaddrs_ByBroadMaskLen(t *testing.T) {
	nets := makeTestNetAddrs(t)

	sort.Sort(netaddr.ByBroadMaskLen{nets})

	var lastLen int
	for _, net := range nets {
		maskLen := net.Maskbits()
		if lastLen > maskLen {
			t.Fatalf("Sort by specific mask length failed")
		}
		lastLen = maskLen
	}
}

func TestNetAddr_Netaddrs_ByNetwork(t *testing.T) {
	nets := makeTestNetAddrs(t)

	sort.Sort(netaddr.ByNetwork{nets})

	var lastIpUint uint32
	for _, net := range nets {
		ipuint, ok := net.ToUint32()
		if !ok {
			t.Errorf("Unable to create a Uin32 from a network")
		}
		if lastIpUint > ipuint {
			t.Fatalf("Sort by network failed")
		}
		lastIpUint = ipuint
	}
}
<commit_msg>Convert NetAddrs testing to be table driven<commit_after>package netaddr_test

import (
	"sort"
	"testing"

	"github.com/hashicorp/go-netaddr"
)

type GoodFixtureInput struct {
	inputNets               []string
	sortedBySpecificMasklen []string
	sortedByBroadMasklen    []string
	sortedByNetwork         []string
}
type GoodFixture struct {
	inputNets               netaddr.NetAddrs
	sortedBySpecificMasklen netaddr.NetAddrs
	sortedByBroadMasklen    netaddr.NetAddrs
	sortedByNetwork         netaddr.NetAddrs
}
type GoodFixtures []*GoodFixture

func makeTestNetAddrs(t *testing.T) GoodFixtures {
	goodFixtureInputs := []GoodFixtureInput{
		{
			inputNets: []string{
				"10.0.0.0/8",
				"172.16.1.3/12",
				"192.168.0.0/16",
				"128.95.120.1/32",
				"192.168.1.10/24",
				"240.0.0.1/4",
			},
			sortedBySpecificMasklen: []string{
				"128.95.120.1/32",
				"192.168.1.10/24",
				"192.168.0.0/16",
				"172.16.1.3/12",
				"10.0.0.0/8",
				"240.0.0.1/4",
			},
			sortedByBroadMasklen: []string{
				"240.0.0.1/4",
				"10.0.0.0/8",
				"172.16.1.3/12",
				"192.168.0.0/16",
				"192.168.1.10/24",
				"128.95.120.1/32",
			},
			sortedByNetwork: []string{
				"10.0.0.0/8",
				"128.95.120.1/32",
				"172.16.1.3/12",
				"192.168.0.0/16",
				"192.168.1.10/24",
				"240.0.0.1/4",
			},
		},
	}
	gfs := make(GoodFixtures, 0, len(goodFixtureInputs))
	for _, gfi := range goodFixtureInputs {
		gf := new(GoodFixture)
		gf.inputNets = make(netaddr.NetAddrs, 0, len(gfi.inputNets))
		for _, n := range gfi.inputNets {
			na, err := netaddr.New(n)
			if err != nil {
				t.Fatalf("Expected valid network")
			}
			gf.inputNets = append(gf.inputNets, na)
		}

		gf.sortedBySpecificMasklen = make(netaddr.NetAddrs, 0, len(gfi.sortedBySpecificMasklen))
		for _, n := range gfi.sortedBySpecificMasklen {
			na, err := netaddr.New(n)
			if err != nil {
				t.Fatalf("Expected valid network")
			}
			gf.sortedBySpecificMasklen = append(gf.sortedBySpecificMasklen, na)
		}

		if len(gf.inputNets) != len(gf.sortedBySpecificMasklen) {
			t.Fatalf("Expected same number of sortedBySpecificMasklen networks")
		}

		gf.sortedByBroadMasklen = make(netaddr.NetAddrs, 0, len(gfi.sortedByBroadMasklen))
		for _, n := range gfi.sortedByBroadMasklen {
			na, err := netaddr.New(n)
			if err != nil {
				t.Fatalf("Expected valid network")
			}
			gf.sortedByBroadMasklen = append(gf.sortedByBroadMasklen, na)
		}

		if len(gf.inputNets) != len(gf.sortedByBroadMasklen) {
			t.Fatalf("Expected same number of sortedByBroadMasklen networks")
		}

		gf.sortedByNetwork = make(netaddr.NetAddrs, 0, len(gfi.sortedByNetwork))
		for _, n := range gfi.sortedByNetwork {
			na, err := netaddr.New(n)
			if err != nil {
				t.Fatalf("Expected valid network")
			}
			gf.sortedByNetwork = append(gf.sortedByNetwork, na)
		}

		if len(gf.inputNets) != len(gf.sortedByNetwork) {
			t.Fatalf("Expected same number of sortedByNetwork networks")
		}
	}

	return gfs
}

func TestNetAddr_Netaddrs_BySpecificMaskLen(t *testing.T) {
	goodFixtures := makeTestNetAddrs(t)

	for _, goodFixture := range goodFixtures {
		inputNets := append(netaddr.NetAddrs(nil), goodFixture.inputNets...)
		sort.Sort(netaddr.BySpecificMaskLen{inputNets})

		var lastLen int = 32
		for i, net := range inputNets {
			maskLen := net.Maskbits()
			if lastLen < maskLen {
				t.Fatalf("Sort by specific mask length failed")
			}
			lastLen = maskLen

			if goodFixture.sortedBySpecificMasklen[i] != net {
				t.Errorf("Expected %s, received %s in iteration %d", goodFixture.sortedBySpecificMasklen[i], net, i)
			}
		}
	}
}

func TestNetAddr_Netaddrs_ByBroadMaskLen(t *testing.T) {
	goodFixtures := makeTestNetAddrs(t)

	for _, goodFixture := range goodFixtures {
		inputNets := append(netaddr.NetAddrs(nil), goodFixture.inputNets...)
		sort.Sort(netaddr.ByBroadMaskLen{inputNets})

		var lastLen int
		for i, net := range inputNets {
			maskLen := net.Maskbits()
			if lastLen > maskLen {
				t.Fatalf("Sort by specific mask length failed")
			}
			lastLen = maskLen

			if goodFixture.sortedByBroadMasklen[i] != net {
				t.Errorf("Expected %s, received %s in iteration %d", goodFixture.sortedByBroadMasklen[i], net, i)
			}
		}
	}
}

func TestNetAddr_Netaddrs_ByNetwork(t *testing.T) {
	goodFixtures := makeTestNetAddrs(t)

	for _, goodFixture := range goodFixtures {
		inputNets := append(netaddr.NetAddrs(nil), goodFixture.inputNets...)
		sort.Sort(netaddr.ByNetwork{inputNets})

		var lastIpUint uint32
		for i, net := range inputNets {
			ipuint, ok := net.ToUint32()
			if !ok {
				t.Errorf("Unable to create a Uin32 from a network")
			}
			if lastIpUint > ipuint {
				t.Fatalf("Sort by network failed")
			}
			lastIpUint = ipuint

			if goodFixture.sortedByNetwork[i] != net {
				t.Errorf("Expected %s, received %s in iteration %d", goodFixture.sortedByNetwork[i], net, i)
			}
		}
	}
}
<|endoftext|>


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

package tls

import (
	"crypto"
	"crypto/rsa"
	"crypto/subtle"
	"crypto/x509"
	"errors"
	"io"
)

func (c *Conn) serverHandshake() error {
	config := c.config
	msg, err := c.readHandshake()
	if err != nil {
		return err
	}
	clientHello, ok := msg.(*clientHelloMsg)
	if !ok {
		return c.sendAlert(alertUnexpectedMessage)
	}
	vers, ok := mutualVersion(clientHello.vers)
	if !ok {
		return c.sendAlert(alertProtocolVersion)
	}
	c.vers = vers
	c.haveVers = true

	finishedHash := newFinishedHash(vers)
	finishedHash.Write(clientHello.marshal())

	hello := new(serverHelloMsg)

	supportedCurve := false
Curves:
	for _, curve := range clientHello.supportedCurves {
		switch curve {
		case curveP256, curveP384, curveP521:
			supportedCurve = true
			break Curves
		}
	}

	supportedPointFormat := false
	for _, pointFormat := range clientHello.supportedPoints {
		if pointFormat == pointFormatUncompressed {
			supportedPointFormat = true
			break
		}
	}

	ellipticOk := supportedCurve && supportedPointFormat

	var suite *cipherSuite
FindCipherSuite:
	for _, id := range clientHello.cipherSuites {
		for _, supported := range config.cipherSuites() {
			if id == supported {
				var candidate *cipherSuite

				for _, s := range cipherSuites {
					if s.id == id {
						candidate = s
						break
					}
				}
				if candidate == nil {
					continue
				}
				// Don't select a ciphersuite which we can't
				// support for this client.
				if candidate.elliptic && !ellipticOk {
					continue
				}
				suite = candidate
				break FindCipherSuite
			}
		}
	}

	foundCompression := false
	// We only support null compression, so check that the client offered it.
	for _, compression := range clientHello.compressionMethods {
		if compression == compressionNone {
			foundCompression = true
			break
		}
	}

	if suite == nil || !foundCompression {
		return c.sendAlert(alertHandshakeFailure)
	}

	hello.vers = vers
	hello.cipherSuite = suite.id
	t := uint32(config.time().Unix())
	hello.random = make([]byte, 32)
	hello.random[0] = byte(t >> 24)
	hello.random[1] = byte(t >> 16)
	hello.random[2] = byte(t >> 8)
	hello.random[3] = byte(t)
	_, err = io.ReadFull(config.rand(), hello.random[4:])
	if err != nil {
		return c.sendAlert(alertInternalError)
	}
	hello.compressionMethod = compressionNone
	if clientHello.nextProtoNeg {
		hello.nextProtoNeg = true
		hello.nextProtos = config.NextProtos
	}

	if len(config.Certificates) == 0 {
		return c.sendAlert(alertInternalError)
	}
	cert := &config.Certificates[0]
	if len(clientHello.serverName) > 0 {
		c.serverName = clientHello.serverName
		cert = config.getCertificateForName(clientHello.serverName)
	}

	if clientHello.ocspStapling && len(cert.OCSPStaple) > 0 {
		hello.ocspStapling = true
	}

	finishedHash.Write(hello.marshal())
	c.writeRecord(recordTypeHandshake, hello.marshal())

	certMsg := new(certificateMsg)
	certMsg.certificates = cert.Certificate
	finishedHash.Write(certMsg.marshal())
	c.writeRecord(recordTypeHandshake, certMsg.marshal())

	if hello.ocspStapling {
		certStatus := new(certificateStatusMsg)
		certStatus.statusType = statusTypeOCSP
		certStatus.response = cert.OCSPStaple
		finishedHash.Write(certStatus.marshal())
		c.writeRecord(recordTypeHandshake, certStatus.marshal())
	}

	keyAgreement := suite.ka()
	skx, err := keyAgreement.generateServerKeyExchange(config, cert, clientHello, hello)
	if err != nil {
		c.sendAlert(alertHandshakeFailure)
		return err
	}
	if skx != nil {
		finishedHash.Write(skx.marshal())
		c.writeRecord(recordTypeHandshake, skx.marshal())
	}

	if config.ClientAuth >= RequestClientCert {
		// Request a client certificate
		certReq := new(certificateRequestMsg)
		certReq.certificateTypes = []byte{certTypeRSASign}

		// An empty list of certificateAuthorities signals to
		// the client that it may send any certificate in response
		// to our request. When we know the CAs we trust, then
		// we can send them down, so that the client can choose
		// an appropriate certificate to give to us.
		if config.ClientCAs != nil {
			certReq.certificateAuthorities = config.ClientCAs.Subjects()
		}
		finishedHash.Write(certReq.marshal())
		c.writeRecord(recordTypeHandshake, certReq.marshal())
	}

	helloDone := new(serverHelloDoneMsg)
	finishedHash.Write(helloDone.marshal())
	c.writeRecord(recordTypeHandshake, helloDone.marshal())

	var pub *rsa.PublicKey // public key for client auth, if any

	msg, err = c.readHandshake()
	if err != nil {
		return err
	}

	// If we requested a client certificate, then the client must send a
	// certificate message, even if it's empty.
	if config.ClientAuth >= RequestClientCert {
		if certMsg, ok = msg.(*certificateMsg); !ok {
			return c.sendAlert(alertHandshakeFailure)
		}
		finishedHash.Write(certMsg.marshal())

		if len(certMsg.certificates) == 0 {
			// The client didn't actually send a certificate
			switch config.ClientAuth {
			case RequireAnyClientCert, RequireAndVerifyClientCert:
				c.sendAlert(alertBadCertificate)
				return errors.New("tls: client didn't provide a certificate")
			}
		}

		certs := make([]*x509.Certificate, len(certMsg.certificates))
		for i, asn1Data := range certMsg.certificates {
			if certs[i], err = x509.ParseCertificate(asn1Data); err != nil {
				c.sendAlert(alertBadCertificate)
				return errors.New("tls: failed to parse client certificate: " + err.Error())
			}
		}

		if c.config.ClientAuth >= VerifyClientCertIfGiven && len(certs) > 0 {
			opts := x509.VerifyOptions{
				Roots:         c.config.ClientCAs,
				CurrentTime:   c.config.time(),
				Intermediates: x509.NewCertPool(),
			}

			for i, cert := range certs {
				if i == 0 {
					continue
				}
				opts.Intermediates.AddCert(cert)
			}

			chains, err := certs[0].Verify(opts)
			if err != nil {
				c.sendAlert(alertBadCertificate)
				return errors.New("tls: failed to verify client's certificate: " + err.Error())
			}

			ok := false
			for _, ku := range certs[0].ExtKeyUsage {
				if ku == x509.ExtKeyUsageClientAuth {
					ok = true
					break
				}
			}
			if !ok {
				c.sendAlert(alertHandshakeFailure)
				return errors.New("tls: client's certificate's extended key usage doesn't permit it to be used for client authentication")
			}

			c.verifiedChains = chains
		}

		if len(certs) > 0 {
			if pub, ok = certs[0].PublicKey.(*rsa.PublicKey); !ok {
				return c.sendAlert(alertUnsupportedCertificate)
			}
			c.peerCertificates = certs
		}

		msg, err = c.readHandshake()
		if err != nil {
			return err
		}
	}

	// Get client key exchange
	ckx, ok := msg.(*clientKeyExchangeMsg)
	if !ok {
		return c.sendAlert(alertUnexpectedMessage)
	}
	finishedHash.Write(ckx.marshal())

	// If we received a client cert in response to our certificate request message,
	// the client will send us a certificateVerifyMsg immediately after the
	// clientKeyExchangeMsg.  This message is a MD5SHA1 digest of all preceding
	// handshake-layer messages that is signed using the private key corresponding
	// to the client's certificate. This allows us to verify that the client is in
	// possession of the private key of the certificate.
	if len(c.peerCertificates) > 0 {
		msg, err = c.readHandshake()
		if err != nil {
			return err
		}
		certVerify, ok := msg.(*certificateVerifyMsg)
		if !ok {
			return c.sendAlert(alertUnexpectedMessage)
		}

		digest := make([]byte, 0, 36)
		digest = finishedHash.serverMD5.Sum(digest)
		digest = finishedHash.serverSHA1.Sum(digest)
		err = rsa.VerifyPKCS1v15(pub, crypto.MD5SHA1, digest, certVerify.signature)
		if err != nil {
			c.sendAlert(alertBadCertificate)
			return errors.New("could not validate signature of connection nonces: " + err.Error())
		}

		finishedHash.Write(certVerify.marshal())
	}

	preMasterSecret, err := keyAgreement.processClientKeyExchange(config, cert, ckx, c.vers)
	if err != nil {
		c.sendAlert(alertHandshakeFailure)
		return err
	}

	masterSecret, clientMAC, serverMAC, clientKey, serverKey, clientIV, serverIV :=
		keysFromPreMasterSecret(c.vers, preMasterSecret, clientHello.random, hello.random, suite.macLen, suite.keyLen, suite.ivLen)

	clientCipher := suite.cipher(clientKey, clientIV, true /* for reading */)
	clientHash := suite.mac(c.vers, clientMAC)
	c.in.prepareCipherSpec(c.vers, clientCipher, clientHash)
	c.readRecord(recordTypeChangeCipherSpec)
	if err := c.error(); err != nil {
		return err
	}

	if hello.nextProtoNeg {
		msg, err = c.readHandshake()
		if err != nil {
			return err
		}
		nextProto, ok := msg.(*nextProtoMsg)
		if !ok {
			return c.sendAlert(alertUnexpectedMessage)
		}
		finishedHash.Write(nextProto.marshal())
		c.clientProtocol = nextProto.proto
	}

	msg, err = c.readHandshake()
	if err != nil {
		return err
	}
	clientFinished, ok := msg.(*finishedMsg)
	if !ok {
		return c.sendAlert(alertUnexpectedMessage)
	}

	verify := finishedHash.clientSum(masterSecret)
	if len(verify) != len(clientFinished.verifyData) ||
		subtle.ConstantTimeCompare(verify, clientFinished.verifyData) != 1 {
		return c.sendAlert(alertHandshakeFailure)
	}

	finishedHash.Write(clientFinished.marshal())

	serverCipher := suite.cipher(serverKey, serverIV, false /* not for reading */)
	serverHash := suite.mac(c.vers, serverMAC)
	c.out.prepareCipherSpec(c.vers, serverCipher, serverHash)
	c.writeRecord(recordTypeChangeCipherSpec, []byte{1})

	finished := new(finishedMsg)
	finished.verifyData = finishedHash.serverSum(masterSecret)
	c.writeRecord(recordTypeHandshake, finished.marshal())

	c.handshakeComplete = true
	c.cipherSuite = suite.id

	return nil
}
<commit_msg>crypto/tls: explicitly require ExtKeyUsageClientAuth for client certs<commit_after>// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package tls

import (
	"crypto"
	"crypto/rsa"
	"crypto/subtle"
	"crypto/x509"
	"errors"
	"io"
)

func (c *Conn) serverHandshake() error {
	config := c.config
	msg, err := c.readHandshake()
	if err != nil {
		return err
	}
	clientHello, ok := msg.(*clientHelloMsg)
	if !ok {
		return c.sendAlert(alertUnexpectedMessage)
	}
	vers, ok := mutualVersion(clientHello.vers)
	if !ok {
		return c.sendAlert(alertProtocolVersion)
	}
	c.vers = vers
	c.haveVers = true

	finishedHash := newFinishedHash(vers)
	finishedHash.Write(clientHello.marshal())

	hello := new(serverHelloMsg)

	supportedCurve := false
Curves:
	for _, curve := range clientHello.supportedCurves {
		switch curve {
		case curveP256, curveP384, curveP521:
			supportedCurve = true
			break Curves
		}
	}

	supportedPointFormat := false
	for _, pointFormat := range clientHello.supportedPoints {
		if pointFormat == pointFormatUncompressed {
			supportedPointFormat = true
			break
		}
	}

	ellipticOk := supportedCurve && supportedPointFormat

	var suite *cipherSuite
FindCipherSuite:
	for _, id := range clientHello.cipherSuites {
		for _, supported := range config.cipherSuites() {
			if id == supported {
				var candidate *cipherSuite

				for _, s := range cipherSuites {
					if s.id == id {
						candidate = s
						break
					}
				}
				if candidate == nil {
					continue
				}
				// Don't select a ciphersuite which we can't
				// support for this client.
				if candidate.elliptic && !ellipticOk {
					continue
				}
				suite = candidate
				break FindCipherSuite
			}
		}
	}

	foundCompression := false
	// We only support null compression, so check that the client offered it.
	for _, compression := range clientHello.compressionMethods {
		if compression == compressionNone {
			foundCompression = true
			break
		}
	}

	if suite == nil || !foundCompression {
		return c.sendAlert(alertHandshakeFailure)
	}

	hello.vers = vers
	hello.cipherSuite = suite.id
	t := uint32(config.time().Unix())
	hello.random = make([]byte, 32)
	hello.random[0] = byte(t >> 24)
	hello.random[1] = byte(t >> 16)
	hello.random[2] = byte(t >> 8)
	hello.random[3] = byte(t)
	_, err = io.ReadFull(config.rand(), hello.random[4:])
	if err != nil {
		return c.sendAlert(alertInternalError)
	}
	hello.compressionMethod = compressionNone
	if clientHello.nextProtoNeg {
		hello.nextProtoNeg = true
		hello.nextProtos = config.NextProtos
	}

	if len(config.Certificates) == 0 {
		return c.sendAlert(alertInternalError)
	}
	cert := &config.Certificates[0]
	if len(clientHello.serverName) > 0 {
		c.serverName = clientHello.serverName
		cert = config.getCertificateForName(clientHello.serverName)
	}

	if clientHello.ocspStapling && len(cert.OCSPStaple) > 0 {
		hello.ocspStapling = true
	}

	finishedHash.Write(hello.marshal())
	c.writeRecord(recordTypeHandshake, hello.marshal())

	certMsg := new(certificateMsg)
	certMsg.certificates = cert.Certificate
	finishedHash.Write(certMsg.marshal())
	c.writeRecord(recordTypeHandshake, certMsg.marshal())

	if hello.ocspStapling {
		certStatus := new(certificateStatusMsg)
		certStatus.statusType = statusTypeOCSP
		certStatus.response = cert.OCSPStaple
		finishedHash.Write(certStatus.marshal())
		c.writeRecord(recordTypeHandshake, certStatus.marshal())
	}

	keyAgreement := suite.ka()
	skx, err := keyAgreement.generateServerKeyExchange(config, cert, clientHello, hello)
	if err != nil {
		c.sendAlert(alertHandshakeFailure)
		return err
	}
	if skx != nil {
		finishedHash.Write(skx.marshal())
		c.writeRecord(recordTypeHandshake, skx.marshal())
	}

	if config.ClientAuth >= RequestClientCert {
		// Request a client certificate
		certReq := new(certificateRequestMsg)
		certReq.certificateTypes = []byte{certTypeRSASign}

		// An empty list of certificateAuthorities signals to
		// the client that it may send any certificate in response
		// to our request. When we know the CAs we trust, then
		// we can send them down, so that the client can choose
		// an appropriate certificate to give to us.
		if config.ClientCAs != nil {
			certReq.certificateAuthorities = config.ClientCAs.Subjects()
		}
		finishedHash.Write(certReq.marshal())
		c.writeRecord(recordTypeHandshake, certReq.marshal())
	}

	helloDone := new(serverHelloDoneMsg)
	finishedHash.Write(helloDone.marshal())
	c.writeRecord(recordTypeHandshake, helloDone.marshal())

	var pub *rsa.PublicKey // public key for client auth, if any

	msg, err = c.readHandshake()
	if err != nil {
		return err
	}

	// If we requested a client certificate, then the client must send a
	// certificate message, even if it's empty.
	if config.ClientAuth >= RequestClientCert {
		if certMsg, ok = msg.(*certificateMsg); !ok {
			return c.sendAlert(alertHandshakeFailure)
		}
		finishedHash.Write(certMsg.marshal())

		if len(certMsg.certificates) == 0 {
			// The client didn't actually send a certificate
			switch config.ClientAuth {
			case RequireAnyClientCert, RequireAndVerifyClientCert:
				c.sendAlert(alertBadCertificate)
				return errors.New("tls: client didn't provide a certificate")
			}
		}

		certs := make([]*x509.Certificate, len(certMsg.certificates))
		for i, asn1Data := range certMsg.certificates {
			if certs[i], err = x509.ParseCertificate(asn1Data); err != nil {
				c.sendAlert(alertBadCertificate)
				return errors.New("tls: failed to parse client certificate: " + err.Error())
			}
		}

		if c.config.ClientAuth >= VerifyClientCertIfGiven && len(certs) > 0 {
			opts := x509.VerifyOptions{
				Roots:         c.config.ClientCAs,
				CurrentTime:   c.config.time(),
				Intermediates: x509.NewCertPool(),
				KeyUsages:     []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
			}

			for i, cert := range certs {
				if i == 0 {
					continue
				}
				opts.Intermediates.AddCert(cert)
			}

			chains, err := certs[0].Verify(opts)
			if err != nil {
				c.sendAlert(alertBadCertificate)
				return errors.New("tls: failed to verify client's certificate: " + err.Error())
			}

			ok := false
			for _, ku := range certs[0].ExtKeyUsage {
				if ku == x509.ExtKeyUsageClientAuth {
					ok = true
					break
				}
			}
			if !ok {
				c.sendAlert(alertHandshakeFailure)
				return errors.New("tls: client's certificate's extended key usage doesn't permit it to be used for client authentication")
			}

			c.verifiedChains = chains
		}

		if len(certs) > 0 {
			if pub, ok = certs[0].PublicKey.(*rsa.PublicKey); !ok {
				return c.sendAlert(alertUnsupportedCertificate)
			}
			c.peerCertificates = certs
		}

		msg, err = c.readHandshake()
		if err != nil {
			return err
		}
	}

	// Get client key exchange
	ckx, ok := msg.(*clientKeyExchangeMsg)
	if !ok {
		return c.sendAlert(alertUnexpectedMessage)
	}
	finishedHash.Write(ckx.marshal())

	// If we received a client cert in response to our certificate request message,
	// the client will send us a certificateVerifyMsg immediately after the
	// clientKeyExchangeMsg.  This message is a MD5SHA1 digest of all preceding
	// handshake-layer messages that is signed using the private key corresponding
	// to the client's certificate. This allows us to verify that the client is in
	// possession of the private key of the certificate.
	if len(c.peerCertificates) > 0 {
		msg, err = c.readHandshake()
		if err != nil {
			return err
		}
		certVerify, ok := msg.(*certificateVerifyMsg)
		if !ok {
			return c.sendAlert(alertUnexpectedMessage)
		}

		digest := make([]byte, 0, 36)
		digest = finishedHash.serverMD5.Sum(digest)
		digest = finishedHash.serverSHA1.Sum(digest)
		err = rsa.VerifyPKCS1v15(pub, crypto.MD5SHA1, digest, certVerify.signature)
		if err != nil {
			c.sendAlert(alertBadCertificate)
			return errors.New("could not validate signature of connection nonces: " + err.Error())
		}

		finishedHash.Write(certVerify.marshal())
	}

	preMasterSecret, err := keyAgreement.processClientKeyExchange(config, cert, ckx, c.vers)
	if err != nil {
		c.sendAlert(alertHandshakeFailure)
		return err
	}

	masterSecret, clientMAC, serverMAC, clientKey, serverKey, clientIV, serverIV :=
		keysFromPreMasterSecret(c.vers, preMasterSecret, clientHello.random, hello.random, suite.macLen, suite.keyLen, suite.ivLen)

	clientCipher := suite.cipher(clientKey, clientIV, true /* for reading */)
	clientHash := suite.mac(c.vers, clientMAC)
	c.in.prepareCipherSpec(c.vers, clientCipher, clientHash)
	c.readRecord(recordTypeChangeCipherSpec)
	if err := c.error(); err != nil {
		return err
	}

	if hello.nextProtoNeg {
		msg, err = c.readHandshake()
		if err != nil {
			return err
		}
		nextProto, ok := msg.(*nextProtoMsg)
		if !ok {
			return c.sendAlert(alertUnexpectedMessage)
		}
		finishedHash.Write(nextProto.marshal())
		c.clientProtocol = nextProto.proto
	}

	msg, err = c.readHandshake()
	if err != nil {
		return err
	}
	clientFinished, ok := msg.(*finishedMsg)
	if !ok {
		return c.sendAlert(alertUnexpectedMessage)
	}

	verify := finishedHash.clientSum(masterSecret)
	if len(verify) != len(clientFinished.verifyData) ||
		subtle.ConstantTimeCompare(verify, clientFinished.verifyData) != 1 {
		return c.sendAlert(alertHandshakeFailure)
	}

	finishedHash.Write(clientFinished.marshal())

	serverCipher := suite.cipher(serverKey, serverIV, false /* not for reading */)
	serverHash := suite.mac(c.vers, serverMAC)
	c.out.prepareCipherSpec(c.vers, serverCipher, serverHash)
	c.writeRecord(recordTypeChangeCipherSpec, []byte{1})

	finished := new(finishedMsg)
	finished.verifyData = finishedHash.serverSum(masterSecret)
	c.writeRecord(recordTypeHandshake, finished.marshal())

	c.handshakeComplete = true
	c.cipherSuite = suite.id

	return nil
}
<|endoftext|>