<commit_before>package fields

import (
	"database/sql"
	"database/sql/driver"
	"encoding"
	"encoding/json"
	"fmt"
	"reflect"
	"time"

	"github.com/go-sql-driver/mysql"
)

// Valuer fulfills the sql/driver.Valuer interface which deserializes our
// struct field value into a valid SQL value.
type Valuer struct {
	*Descriptor
	value reflect.Value
}

// Value satisfies the sql/driver.Valuer interface.
// The value should be one of the following:
//    int64
//    float64
//    bool
//    []byte
//    string
//    time.Time
//    nil - for NULL values
func (f Valuer) Value() (driver.Value, error) {
	// Return early if the value is nil. Ideally we would do a `i == nil` comparison here, but
	// unfortunately for us, `nil` is typed and that would always return false. This has to be
	// before `.Interface()` as that method panics otherwise.
	switch f.value.Kind() {
	// IsNil panics if the value isn't one of these kinds.
	case reflect.Chan, reflect.Map, reflect.Func,
		reflect.Ptr, reflect.Interface, reflect.Slice:
		if f.value.IsNil() {
			return nil, nil
		}
	case reflect.Invalid:
		return nil, nil
	}

	i := f.value.Interface()

	// If our interface supports driver.Valuer we can immediately short-circuit as this is what the
	// MySQL driver would do.
	if valuer, ok := i.(driver.Valuer); ok {
		return valuer.Value()
	}

	// Override serialization behavior with tags (these take precedence over how a type would
	// usually be serialized).
	// Example:
	// struct {
	//   Blob proto.Blob `sql:",binary"`       // ensures that Marshal or MarshalBinary is used.
	//   IP IP `sql:",string"`                 // ensures that its MarshalText method
	//	                                       // is used for serialization.
	//   JSON map[string]string `sql:",json"`  // ensures that json.Marshal is used on the value.
	// }
	switch {
	case f.Tags.Contains("binary"):
		if iface, ok := i.(marshaler); ok {
			return iface.Marshal()
		}
		if iface, ok := i.(encoding.BinaryMarshaler); ok {
			return iface.MarshalBinary()
		}
	case f.Tags.Contains("string"):
		if iface, ok := i.(encoding.TextMarshaler); ok {
			return iface.MarshalText()
		}
	case f.Tags.Contains("json"):
		if iface, ok := i.(json.Marshaler); ok {
			return iface.MarshalJSON()
		}
		return json.Marshal(i)
	}

	// At this point we have already handled `nil` above, so we can assume that all
	// other values can be coerced into dereferenced types of bool/int/float/string.
	if f.value.Kind() == reflect.Ptr {
		f.value = f.value.Elem()
	}

	// Coerce our value into a valid sql/driver.Value (see sql/driver.IsValue).
	// This not only converts base types into their sql counterparts (like int32 -> int64) but also
	// handles custom types (like `type customString string` -> string).
	switch f.Kind {
	case reflect.Bool:
		return f.value.Bool(), nil
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		return f.value.Int(), nil
	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
		return int64(f.value.Uint()), nil
	case reflect.Float32, reflect.Float64:
		return f.value.Float(), nil
	case reflect.String:
		return f.value.String(), nil
	}

	// If we can't figure out what the type is supposed to be, we pass it straight through to SQL,
	// which will return an error if it can't handle it.
	// This means we don't have to handle []byte or time.Time specially, since they'll just pass on
	// through.
	return f.value.Interface(), nil
}

var _ driver.Valuer = Valuer{}
var _ driver.Valuer = &Valuer{}

// Scanner fulfills the sql.Scanner interface which deserializes SQL values
// into the type dictated by our descriptor.
type Scanner struct {
	*Descriptor
	value   reflect.Value
	isValid bool
}

func (s *Scanner) Target(value reflect.Value) {
	s.value = value
}

// Scan satisfies the sql.Scanner interface.
// The src value will be one of the following:
//    int64
//    float64
//    bool
//    []byte
//    string
//    time.Time
//    nil - for NULL values
func (s *Scanner) Scan(src interface{}) error {
	// Clear out the value after a scan so we aren't holding onto references.
	defer func() { s.value = reflect.Value{} }()

	// Keep track of whether our value was empty.
	s.isValid = src != nil

	if s.isValid && s.Ptr {
		s.value.Set(reflect.New(s.Type))
	}

	// Get a value of the pointer of our type. The Scanner and Unmarshalers should
	// only be implemented as dereference methods, since they would do nothing otherwise. Therefore
	// we can safely assume that we should check for these interfaces on the pointer value.
	i := s.value.Interface()
	// Our value however should continue referencing a non-pointer for easier assignment.
	s.value = s.value.Elem()

	// If our interface supports sql.Scanner we can immediately short-circuit as this is what the
	// MySQL driver would do.
	if scanner, ok := i.(sql.Scanner); ok {
		// If the scanner base type is nullable (pointer or one of the below), make it nil,
		// otherwise allow it to scan and handle nil.
		if s.Ptr && src == nil {
			return nil
		}
		switch s.Kind {
		case reflect.Chan, reflect.Map, reflect.Func, reflect.Interface, reflect.Slice:
			if src == nil {
				return nil
			}
		}

		// If we have a scanner it will handle its own validity.
		s.isValid = true
		return scanner.Scan(src)
	}

	// Null values are simply set to zero. Because we're not holding on to pointers, we need to
	// represent this as a boolean. This comes _after_ the scanner step, just in case the scanner
	// handles nil differently.
	if !s.isValid {
		return nil
	}

	// Handle coercion into native types []byte and time.Time (this method will return an error if
	// we don't handle them). These are pointers here because we want to pass around a pointer
	// for interfaces.
	switch i.(type) {
	case *[]byte:
		if str, ok := src.(string); ok {
			s.value.Set(reflect.ValueOf([]byte(str)))
			return nil
		}
		if b, ok := src.([]byte); ok {
			bCopy := make([]byte, len(b), len(b))
			copy(bCopy, b)
			s.value.Set(reflect.ValueOf(bCopy))
			return nil
		}
	case *time.Time:
		t := mysql.NullTime{}
		if err := t.Scan(src); err != nil {
			return err
		}
		s.value.Set(reflect.ValueOf(t.Time))
		return nil
	}

	// Override deserialization behavior with tags (these take precedence over how a type would
	// usually be deserialized).
	// Example:
	// struct {
	//   Blob proto.Blob `sql:",binary"`       // ensures that Unmarshal or UnmarshalBinary is used.
	//   IP IP `sql:",string"`                 // ensures that its UnmarshalText method
	//	                                       // is used for deserialization.
	//   JSON map[string]string `sql:",json"`  // ensures that json.Unmarshal is used on the value.
	// }
	switch {
	case s.Tags.Contains("binary"):
		b, ok := src.([]byte)
		if !ok {
			return fmt.Errorf("binary column must be of type []byte, got %T", src)
		}
		if iface, ok := i.(unmarshaler); ok {
			return iface.Unmarshal(b)
		}
		if iface, ok := i.(encoding.BinaryUnmarshaler); ok {
			return iface.UnmarshalBinary(b)
		}
	case s.Tags.Contains("string"):
		if str, ok := src.(string); ok {
			src = []byte(str)
		}
		b, isByte := src.([]byte)
		if !isByte {
			return fmt.Errorf("string/text column must be of type []byte or string, got %T", src)
		}
		if iface, ok := i.(encoding.TextUnmarshaler); isByte && ok {
			return iface.UnmarshalText(b)
		}
	case s.Tags.Contains("json"):
		if str, ok := src.(string); ok {
			src = []byte(str)
		}
		b, isByte := src.([]byte)
		if !isByte {
			return fmt.Errorf("json column must be of type string or []byte, got %T", src)
		}
		// Implicitly will check for json.Unmarshaler.
		return json.Unmarshal(b, i)
	}

	// If a MySQL value can be coerced into our type, we do so here.
	// This not only converts base types into their sql counterparts (like int64 -> int32) but also
	// handles custom types (like string -> `type customString string`).
	switch s.Kind {
	case reflect.Bool:
		b := sql.NullBool{}
		if err := b.Scan(src); err != nil {
			return err
		}
		s.value.Set(reflect.ValueOf(b.Bool).Convert(s.Type))
		return nil
	case
		reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
		reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
		i := sql.NullInt64{}
		if err := i.Scan(src); err != nil {
			return err
		}
		s.value.Set(reflect.ValueOf(i.Int64).Convert(s.Type))
		return nil
	case reflect.Float32, reflect.Float64:
		float := sql.NullFloat64{}
		if err := float.Scan(src); err != nil {
			return err
		}
		s.value.Set(reflect.ValueOf(float.Float64).Convert(s.Type))
		return nil
	case reflect.String:
		str := sql.NullString{}
		if err := str.Scan(src); err != nil {
			return err
		}
		s.value.Set(reflect.ValueOf(str.String).Convert(s.Type))
		return nil
	}

	return fmt.Errorf("couldn't coerce type %T into %T", src, i)
}

var _ sql.Scanner = &Scanner{}
<commit_msg>go internal/fields: support values not strictly matching descriptor kind<commit_after>package fields

import (
	"database/sql"
	"database/sql/driver"
	"encoding"
	"encoding/json"
	"fmt"
	"reflect"
	"time"

	"github.com/go-sql-driver/mysql"
)

// Valuer fulfills the sql/driver.Valuer interface which deserializes our
// struct field value into a valid SQL value.
type Valuer struct {
	*Descriptor
	value reflect.Value
}

// Value satisfies the sql/driver.Valuer interface.
// The value should be one of the following:
//    int64
//    float64
//    bool
//    []byte
//    string
//    time.Time
//    nil - for NULL values
func (f Valuer) Value() (driver.Value, error) {
	// Return early if the value is nil. Ideally we would do a `i == nil` comparison here, but
	// unfortunately for us, `nil` is typed and that would always return false. This has to be
	// before `.Interface()` as that method panics otherwise.
	switch f.value.Kind() {
	// IsNil panics if the value isn't one of these kinds.
	case reflect.Chan, reflect.Map, reflect.Func,
		reflect.Ptr, reflect.Interface, reflect.Slice:
		if f.value.IsNil() {
			return nil, nil
		}
	case reflect.Invalid:
		return nil, nil
	}

	i := f.value.Interface()

	// If our interface supports driver.Valuer we can immediately short-circuit as this is what the
	// MySQL driver would do.
	if valuer, ok := i.(driver.Valuer); ok {
		return valuer.Value()
	}

	// Override serialization behavior with tags (these take precedence over how a type would
	// usually be serialized).
	// Example:
	// struct {
	//   Blob proto.Blob `sql:",binary"`       // ensures that Marshal or MarshalBinary is used.
	//   IP IP `sql:",string"`                 // ensures that its MarshalText method
	//	                                       // is used for serialization.
	//   JSON map[string]string `sql:",json"`  // ensures that json.Marshal is used on the value.
	// }
	switch {
	case f.Tags.Contains("binary"):
		if iface, ok := i.(marshaler); ok {
			return iface.Marshal()
		}
		if iface, ok := i.(encoding.BinaryMarshaler); ok {
			return iface.MarshalBinary()
		}
	case f.Tags.Contains("string"):
		if iface, ok := i.(encoding.TextMarshaler); ok {
			return iface.MarshalText()
		}
	case f.Tags.Contains("json"):
		if iface, ok := i.(json.Marshaler); ok {
			return iface.MarshalJSON()
		}
		return json.Marshal(i)
	}

	// At this point we have already handled `nil` above, so we can assume that all
	// other values can be coerced into dereferenced types of bool/int/float/string.
	if f.value.Kind() == reflect.Ptr {
		f.value = f.value.Elem()
	}

	// Coerce our value into a valid sql/driver.Value (see sql/driver.IsValue).
	// This not only converts base types into their sql counterparts (like int32 -> int64) but also
	// handles custom types (like `type customString string` -> string).
	switch f.value.Kind() {
	case reflect.Bool:
		return f.value.Bool(), nil
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		return f.value.Int(), nil
	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
		return int64(f.value.Uint()), nil
	case reflect.Float32, reflect.Float64:
		return f.value.Float(), nil
	case reflect.String:
		return f.value.String(), nil
	}

	// If we can't figure out what the type is supposed to be, we pass it straight through to SQL,
	// which will return an error if it can't handle it.
	// This means we don't have to handle []byte or time.Time specially, since they'll just pass on
	// through.
	return f.value.Interface(), nil
}

var _ driver.Valuer = Valuer{}
var _ driver.Valuer = &Valuer{}

// Scanner fulfills the sql.Scanner interface which deserializes SQL values
// into the type dictated by our descriptor.
type Scanner struct {
	*Descriptor
	value   reflect.Value
	isValid bool
}

func (s *Scanner) Target(value reflect.Value) {
	s.value = value
}

// Scan satisfies the sql.Scanner interface.
// The src value will be one of the following:
//    int64
//    float64
//    bool
//    []byte
//    string
//    time.Time
//    nil - for NULL values
func (s *Scanner) Scan(src interface{}) error {
	// Clear out the value after a scan so we aren't holding onto references.
	defer func() { s.value = reflect.Value{} }()

	// Keep track of whether our value was empty.
	s.isValid = src != nil

	if s.isValid && s.Ptr {
		s.value.Set(reflect.New(s.Type))
	}

	// Get a value of the pointer of our type. The Scanner and Unmarshalers should
	// only be implemented as dereference methods, since they would do nothing otherwise. Therefore
	// we can safely assume that we should check for these interfaces on the pointer value.
	i := s.value.Interface()
	// Our value however should continue referencing a non-pointer for easier assignment.
	s.value = s.value.Elem()

	// If our interface supports sql.Scanner we can immediately short-circuit as this is what the
	// MySQL driver would do.
	if scanner, ok := i.(sql.Scanner); ok {
		// If the scanner base type is nullable (pointer or one of the below), make it nil,
		// otherwise allow it to scan and handle nil.
		if s.Ptr && src == nil {
			return nil
		}
		switch s.Kind {
		case reflect.Chan, reflect.Map, reflect.Func, reflect.Interface, reflect.Slice:
			if src == nil {
				return nil
			}
		}

		// If we have a scanner it will handle its own validity.
		s.isValid = true
		return scanner.Scan(src)
	}

	// Null values are simply set to zero. Because we're not holding on to pointers, we need to
	// represent this as a boolean. This comes _after_ the scanner step, just in case the scanner
	// handles nil differently.
	if !s.isValid {
		return nil
	}

	// Handle coercion into native types []byte and time.Time (this method will return an error if
	// we don't handle them). These are pointers here because we want to pass around a pointer
	// for interfaces.
	switch i.(type) {
	case *[]byte:
		if str, ok := src.(string); ok {
			s.value.Set(reflect.ValueOf([]byte(str)))
			return nil
		}
		if b, ok := src.([]byte); ok {
			bCopy := make([]byte, len(b), len(b))
			copy(bCopy, b)
			s.value.Set(reflect.ValueOf(bCopy))
			return nil
		}
	case *time.Time:
		t := mysql.NullTime{}
		if err := t.Scan(src); err != nil {
			return err
		}
		s.value.Set(reflect.ValueOf(t.Time))
		return nil
	}

	// Override deserialization behavior with tags (these take precedence over how a type would
	// usually be deserialized).
	// Example:
	// struct {
	//   Blob proto.Blob `sql:",binary"`       // ensures that Unmarshal or UnmarshalBinary is used.
	//   IP IP `sql:",string"`                 // ensures that its UnmarshalText method
	//	                                       // is used for deserialization.
	//   JSON map[string]string `sql:",json"`  // ensures that json.Unmarshal is used on the value.
	// }
	switch {
	case s.Tags.Contains("binary"):
		b, ok := src.([]byte)
		if !ok {
			return fmt.Errorf("binary column must be of type []byte, got %T", src)
		}
		if iface, ok := i.(unmarshaler); ok {
			return iface.Unmarshal(b)
		}
		if iface, ok := i.(encoding.BinaryUnmarshaler); ok {
			return iface.UnmarshalBinary(b)
		}
	case s.Tags.Contains("string"):
		if str, ok := src.(string); ok {
			src = []byte(str)
		}
		b, isByte := src.([]byte)
		if !isByte {
			return fmt.Errorf("string/text column must be of type []byte or string, got %T", src)
		}
		if iface, ok := i.(encoding.TextUnmarshaler); isByte && ok {
			return iface.UnmarshalText(b)
		}
	case s.Tags.Contains("json"):
		if str, ok := src.(string); ok {
			src = []byte(str)
		}
		b, isByte := src.([]byte)
		if !isByte {
			return fmt.Errorf("json column must be of type string or []byte, got %T", src)
		}
		// Implicitly will check for json.Unmarshaler.
		return json.Unmarshal(b, i)
	}

	// If a MySQL value can be coerced into our type, we do so here.
	// This not only converts base types into their sql counterparts (like int64 -> int32) but also
	// handles custom types (like string -> `type customString string`).
	switch s.Kind {
	case reflect.Bool:
		b := sql.NullBool{}
		if err := b.Scan(src); err != nil {
			return err
		}
		s.value.Set(reflect.ValueOf(b.Bool).Convert(s.Type))
		return nil
	case
		reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
		reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
		i := sql.NullInt64{}
		if err := i.Scan(src); err != nil {
			return err
		}
		s.value.Set(reflect.ValueOf(i.Int64).Convert(s.Type))
		return nil
	case reflect.Float32, reflect.Float64:
		float := sql.NullFloat64{}
		if err := float.Scan(src); err != nil {
			return err
		}
		s.value.Set(reflect.ValueOf(float.Float64).Convert(s.Type))
		return nil
	case reflect.String:
		str := sql.NullString{}
		if err := str.Scan(src); err != nil {
			return err
		}
		s.value.Set(reflect.ValueOf(str.String).Convert(s.Type))
		return nil
	}

	return fmt.Errorf("couldn't coerce type %T into %T", src, i)
}

var _ sql.Scanner = &Scanner{}
<|endoftext|>


<commit_before>package mpd

import (
	"context"
	"errors"
	"strconv"
	"strings"
	"time"
)

var (
	// ErrClosed is returned when connection is closed by client.
	ErrClosed = errors.New("mpd: connection closed")
)

// Song represents song tag.
type Song map[string][]string

// Dialer contains options for connecting to mpd
type Dialer struct {
	// Timeout is the maximum amount of time a dial will wait for a connect to complete.
	Timeout              time.Duration
	HealthCheckInterval  time.Duration
	ReconnectionInterval time.Duration
}

// Dial connects to mpd server.
func (d Dialer) Dial(proto, addr, password string) (*Client, error) {
	pool, err := newPool(proto, addr, password, d.Timeout, d.ReconnectionInterval)
	if err != nil {
		return nil, err
	}
	hCtx, hStop := context.WithCancel(context.Background())
	c := &Client{
		pool:            pool,
		dialer:          &d,
		stopHealthCheck: hStop,
	}
	go c.healthCheck(hCtx)
	return c, nil
}

// Client is a mpd client.
type Client struct {
	proto           string
	addr            string
	password        string
	pool            *pool
	stopHealthCheck func()
	dialer          *Dialer
}

// Close closes mpd connection.
func (c *Client) Close(ctx context.Context) error {
	c.stopHealthCheck()
	return c.pool.Close(ctx)
}

// Version returns mpd server version.
func (c *Client) Version() string {
	return c.pool.Version()
}

// Querying MPD’s status

// CurrentSong displays the song info of the current song
func (c *Client) CurrentSong(ctx context.Context) (song map[string][]string, err error) {
	err = c.pool.Exec(ctx, func(conn *conn) error {
		if _, err := conn.Writeln("currentsong"); err != nil {
			return err
		}
		song = map[string][]string{}
		for {
			line, err := conn.Readln()
			if err != nil {
				return err
			}
			if line == "OK" {
				return nil
			}
			i := strings.Index(line, ": ")
			if i < 0 {
				return newCommandError(line)
			}
			key := line[0:i]
			if _, found := song[key]; !found {
				song[key] = []string{line[i+2:]}
			} else {
				song[key] = append(song[key], line[i+2:])
			}
		}
	})
	return
}

// Status reports the current status of the player and the volume level.
func (c *Client) Status(ctx context.Context) (map[string]string, error) {
	return c.mapStr(ctx, "status")
}

// Stats displays statistics.
func (c *Client) Stats(ctx context.Context) (map[string]string, error) {
	return c.mapStr(ctx, "stats")
}

// Music Database Commands

// Playback options

// Consume sets consume state.
func (c *Client) Consume(ctx context.Context, state bool) error {
	return c.ok(ctx, "consume", btoa(state, "1", "0"))
}

// Crossfade sets crossfading between song
func (c *Client) Crossfade(ctx context.Context, t time.Duration) error {
	return c.ok(ctx, "crossfade", int(t/time.Second))
}

func btoa(s bool, t string, f string) string {
	if s {
		return t
	}
	return f
}

// Random sets random state.
func (c *Client) Random(ctx context.Context, state bool) error {
	return c.ok(ctx, "random", btoa(state, "1", "0"))
}

// Repeat sets repeat state.
func (c *Client) Repeat(ctx context.Context, state bool) error {
	return c.ok(ctx, "repeat", btoa(state, "1", "0"))
}

// Single sets single state.
func (c *Client) Single(ctx context.Context, state bool) error {
	return c.ok(ctx, "single", btoa(state, "1", "0"))
}

// OneShot sets single state to oneshot.
func (c *Client) OneShot(ctx context.Context) error {
	return c.ok(ctx, "single", "oneshot")
}

// SetVol sets the volume to vol.
func (c *Client) SetVol(ctx context.Context, vol int) error {
	return c.ok(ctx, "setvol", vol)
}

// Controlling playback

// Next plays next song in the playlist.
func (c *Client) Next(ctx context.Context) error {
	return c.ok(ctx, "next")
}

// Pause toggles pause/resumes playing
func (c *Client) Pause(ctx context.Context, state bool) error {
	return c.ok(ctx, "pause", btoa(state, "1", "0"))
}

// Play Begins playing the playlist at song number pos
func (c *Client) Play(ctx context.Context, pos int) error {
	return c.ok(ctx, "play", pos)
}

// Previous plays next song in the playlist.
func (c *Client) Previous(ctx context.Context) error {
	return c.ok(ctx, "previous")
}

// SeekCur seeks to the position t within the current song
func (c *Client) SeekCur(ctx context.Context, t float64) error {
	return c.ok(ctx, "seekcur", t)
}

// The Queue

// PlaylistInfo displays a list of all songs in the playlist.
func (c *Client) PlaylistInfo(ctx context.Context) (songs []map[string][]string, err error) {
	err = c.pool.Exec(ctx, func(conn *conn) error {
		if _, err := conn.Writeln("playlistinfo"); err != nil {
			return err
		}
		var song map[string][]string
		for {
			line, err := conn.Readln()
			if err != nil {
				return err
			}
			if line == "OK" {
				return nil
			}
			if strings.HasPrefix(line, "file: ") {
				song = map[string][]string{}
				songs = append(songs, song)
			}
			if len(songs) == 0 {
				return newCommandError(line)
			}
			i := strings.Index(line, ": ")
			if i < 0 {
				return newCommandError(line)
			}
			key := line[0:i]
			if _, found := song[key]; !found {
				song[key] = []string{line[i+2:]}
			} else {
				song[key] = append(song[key], line[i+2:])
			}
		}
	})
	return
}

// The music database

// Albumart locates album art for the given song and return a chunk of an album art image file at offset.
func (c *Client) AlbumArt(ctx context.Context, uri string) ([]byte, error) {
	return c.readBinary(ctx, "albumart", quote(uri))
}

// ListAllInfo lists all songs and directories in uri.
func (c *Client) ListAllInfo(ctx context.Context, uri string) (songs []map[string][]string, err error) {
	err = c.pool.Exec(ctx, func(conn *conn) error {
		if _, err := conn.Writeln("listallinfo", uri); err != nil {
			return err
		}
		var song map[string][]string
		var inEntry bool
		for {
			line, err := conn.Readln()
			if err != nil {
				return err
			}
			if line == "OK" {
				return nil
			}
			if strings.HasPrefix(line, "file: ") {
				song = map[string][]string{}
				songs = append(songs, song)
				inEntry = true
			} else if strings.HasPrefix(line, "directory: ") {
				inEntry = false

			}
			if inEntry {
				if len(songs) == 0 {
					return newCommandError(line)
				}
				i := strings.Index(line, ": ")
				if i < 0 {
					return newCommandError(line)
				}
				key := line[0:i]
				if _, found := song[key]; !found {
					song[key] = []string{line[i+2:]}
				} else {
					song[key] = append(song[key], line[i+2:])
				}
			} else if strings.HasPrefix(line, "ACK [") {
				return newCommandError(line)
			}
		}
	})
	return
}

// Update updates the music database.
func (c *Client) Update(ctx context.Context, uri string) (map[string]string, error) {
	return c.mapStr(ctx, "update", uri)
}

// Audio output devices

// DisableOutput turns an output off.
func (c *Client) DisableOutput(ctx context.Context, id string) error {
	return c.ok(ctx, "disableoutput", id)
}

// EnableOutput turns an output on.
func (c *Client) EnableOutput(ctx context.Context, id string) error {
	return c.ok(ctx, "enableoutput", id)
}

// Outputs shows information about all outputs.
func (c *Client) Outputs(ctx context.Context) ([]map[string]string, error) {
	return c.listMap(ctx, "outputid: ", "outputs")
}

func (c *Client) healthCheck(ctx context.Context) {
	if c.dialer.HealthCheckInterval == 0 {
		return
	}
	ticker := time.NewTicker(c.dialer.HealthCheckInterval)
	go func() {
		defer ticker.Stop()
		for {
			select {
			case <-ctx.Done():
				return
			case <-ticker.C:
				ctx, cancel := context.WithTimeout(ctx, c.dialer.HealthCheckInterval)
				c.Ping(ctx)
				cancel()
			}
		}
	}()
}

// Ping tests connection.
func (c *Client) Ping(ctx context.Context) error {
	return c.ok(ctx, "ping")
}

// Reflection

// Config dumps configuration values that may be interesting for the client.
// This command is only permitted to “local” clients (connected via local socket).
func (c *Client) Config(ctx context.Context) (map[string]string, error) {
	return c.mapStr(ctx, "config")
}

func (c *Client) ok(ctx context.Context, cmd ...interface{}) error {
	return c.pool.Exec(ctx, func(conn *conn) error {
		return conn.OK(cmd...)
	})
}

func (c *Client) readBinary(ctx context.Context, cmd, uri string) (b []byte, err error) {
	err = c.pool.Exec(ctx, func(conn *conn) error {
		var m map[string]string
		m, b, err = conn.ReadBinary(cmd, uri, 0)
		size, err := strconv.Atoi(m["size"])
		if err != nil {
			return err
		}
		for {
			if size == len(b) {
				return nil
			}
			if size < len(b) {
				return errors.New("oversize")
			}
			_, nb, err := conn.ReadBinary(cmd, uri, len(b))
			if err != nil {
				return err
			}
			b = append(b, nb...)
		}
	})
	return
}

func (c *Client) mapStr(ctx context.Context, cmd ...interface{}) (m map[string]string, err error) {
	err = c.pool.Exec(ctx, func(conn *conn) error {
		if _, err := conn.Writeln(cmd...); err != nil {
			return err
		}
		m = map[string]string{}
		for {
			line, err := conn.Readln()
			if err != nil {
				return err
			}
			if line == "OK" {
				return nil
			}
			i := strings.Index(line, ": ")
			if i < 0 {
				return newCommandError(line)
			}
			m[line[0:i]] = line[i+2:]
		}
	})
	return
}

func (c *Client) listMap(ctx context.Context, newKey string, cmd ...interface{}) (l []map[string]string, err error) {
	err = c.pool.Exec(ctx, func(conn *conn) error {
		if _, err := conn.Writeln(cmd...); err != nil {
			return err
		}
		var m map[string]string
		for {
			line, err := conn.Readln()
			if err != nil {
				return err
			}
			if line == "OK" {
				return nil
			}
			if strings.HasPrefix(line, newKey) {
				m = map[string]string{}
				l = append(l, m)
			}
			if m == nil {
				return newCommandError(line)
			}
			i := strings.Index(line, ": ")
			if i < 0 {
				return newCommandError(line)
			}
			m[line[0:i]] = line[i+2:]
		}
	})
	return
}

// quote escaping strings values for mpd.
func quote(s string) string {
	return `"` + strings.Replace(
		strings.Replace(s, "\\", "\\\\", -1),
		`"`, `\"`, -1) + `"`
}
<commit_msg>fix doc<commit_after>package mpd

import (
	"context"
	"errors"
	"strconv"
	"strings"
	"time"
)

var (
	// ErrClosed is returned when connection is closed by client.
	ErrClosed = errors.New("mpd: connection closed")
)

// Song represents song tag.
type Song map[string][]string

// Dialer contains options for connecting to mpd
type Dialer struct {
	// Timeout is the maximum amount of time a dial will wait for a connect to complete.
	Timeout              time.Duration
	HealthCheckInterval  time.Duration
	ReconnectionInterval time.Duration
}

// Dial connects to mpd server.
func (d Dialer) Dial(proto, addr, password string) (*Client, error) {
	pool, err := newPool(proto, addr, password, d.Timeout, d.ReconnectionInterval)
	if err != nil {
		return nil, err
	}
	hCtx, hStop := context.WithCancel(context.Background())
	c := &Client{
		pool:            pool,
		dialer:          &d,
		stopHealthCheck: hStop,
	}
	go c.healthCheck(hCtx)
	return c, nil
}

// Client is a mpd client.
type Client struct {
	proto           string
	addr            string
	password        string
	pool            *pool
	stopHealthCheck func()
	dialer          *Dialer
}

// Close closes mpd connection.
func (c *Client) Close(ctx context.Context) error {
	c.stopHealthCheck()
	return c.pool.Close(ctx)
}

// Version returns mpd server version.
func (c *Client) Version() string {
	return c.pool.Version()
}

// Querying MPD’s status

// CurrentSong displays the song info of the current song
func (c *Client) CurrentSong(ctx context.Context) (song map[string][]string, err error) {
	err = c.pool.Exec(ctx, func(conn *conn) error {
		if _, err := conn.Writeln("currentsong"); err != nil {
			return err
		}
		song = map[string][]string{}
		for {
			line, err := conn.Readln()
			if err != nil {
				return err
			}
			if line == "OK" {
				return nil
			}
			i := strings.Index(line, ": ")
			if i < 0 {
				return newCommandError(line)
			}
			key := line[0:i]
			if _, found := song[key]; !found {
				song[key] = []string{line[i+2:]}
			} else {
				song[key] = append(song[key], line[i+2:])
			}
		}
	})
	return
}

// Status reports the current status of the player and the volume level.
func (c *Client) Status(ctx context.Context) (map[string]string, error) {
	return c.mapStr(ctx, "status")
}

// Stats displays statistics.
func (c *Client) Stats(ctx context.Context) (map[string]string, error) {
	return c.mapStr(ctx, "stats")
}

// Music Database Commands

// Playback options

// Consume sets consume state.
func (c *Client) Consume(ctx context.Context, state bool) error {
	return c.ok(ctx, "consume", btoa(state, "1", "0"))
}

// Crossfade sets crossfading between song
func (c *Client) Crossfade(ctx context.Context, t time.Duration) error {
	return c.ok(ctx, "crossfade", int(t/time.Second))
}

func btoa(s bool, t string, f string) string {
	if s {
		return t
	}
	return f
}

// Random sets random state.
func (c *Client) Random(ctx context.Context, state bool) error {
	return c.ok(ctx, "random", btoa(state, "1", "0"))
}

// Repeat sets repeat state.
func (c *Client) Repeat(ctx context.Context, state bool) error {
	return c.ok(ctx, "repeat", btoa(state, "1", "0"))
}

// Single sets single state.
func (c *Client) Single(ctx context.Context, state bool) error {
	return c.ok(ctx, "single", btoa(state, "1", "0"))
}

// OneShot sets single state to oneshot.
func (c *Client) OneShot(ctx context.Context) error {
	return c.ok(ctx, "single", "oneshot")
}

// SetVol sets the volume to vol.
func (c *Client) SetVol(ctx context.Context, vol int) error {
	return c.ok(ctx, "setvol", vol)
}

// Controlling playback

// Next plays next song in the playlist.
func (c *Client) Next(ctx context.Context) error {
	return c.ok(ctx, "next")
}

// Pause toggles pause/resumes playing
func (c *Client) Pause(ctx context.Context, state bool) error {
	return c.ok(ctx, "pause", btoa(state, "1", "0"))
}

// Play Begins playing the playlist at song number pos
func (c *Client) Play(ctx context.Context, pos int) error {
	return c.ok(ctx, "play", pos)
}

// Previous plays next song in the playlist.
func (c *Client) Previous(ctx context.Context) error {
	return c.ok(ctx, "previous")
}

// SeekCur seeks to the position t within the current song
func (c *Client) SeekCur(ctx context.Context, t float64) error {
	return c.ok(ctx, "seekcur", t)
}

// The Queue

// PlaylistInfo displays a list of all songs in the playlist.
func (c *Client) PlaylistInfo(ctx context.Context) (songs []map[string][]string, err error) {
	err = c.pool.Exec(ctx, func(conn *conn) error {
		if _, err := conn.Writeln("playlistinfo"); err != nil {
			return err
		}
		var song map[string][]string
		for {
			line, err := conn.Readln()
			if err != nil {
				return err
			}
			if line == "OK" {
				return nil
			}
			if strings.HasPrefix(line, "file: ") {
				song = map[string][]string{}
				songs = append(songs, song)
			}
			if len(songs) == 0 {
				return newCommandError(line)
			}
			i := strings.Index(line, ": ")
			if i < 0 {
				return newCommandError(line)
			}
			key := line[0:i]
			if _, found := song[key]; !found {
				song[key] = []string{line[i+2:]}
			} else {
				song[key] = append(song[key], line[i+2:])
			}
		}
	})
	return
}

// The music database

// AlbumArt locates album art for the given song and return a chunk of an album art image file at offset.
func (c *Client) AlbumArt(ctx context.Context, uri string) ([]byte, error) {
	return c.readBinary(ctx, "albumart", quote(uri))
}

// ListAllInfo lists all songs and directories in uri.
func (c *Client) ListAllInfo(ctx context.Context, uri string) (songs []map[string][]string, err error) {
	err = c.pool.Exec(ctx, func(conn *conn) error {
		if _, err := conn.Writeln("listallinfo", uri); err != nil {
			return err
		}
		var song map[string][]string
		var inEntry bool
		for {
			line, err := conn.Readln()
			if err != nil {
				return err
			}
			if line == "OK" {
				return nil
			}
			if strings.HasPrefix(line, "file: ") {
				song = map[string][]string{}
				songs = append(songs, song)
				inEntry = true
			} else if strings.HasPrefix(line, "directory: ") {
				inEntry = false

			}
			if inEntry {
				if len(songs) == 0 {
					return newCommandError(line)
				}
				i := strings.Index(line, ": ")
				if i < 0 {
					return newCommandError(line)
				}
				key := line[0:i]
				if _, found := song[key]; !found {
					song[key] = []string{line[i+2:]}
				} else {
					song[key] = append(song[key], line[i+2:])
				}
			} else if strings.HasPrefix(line, "ACK [") {
				return newCommandError(line)
			}
		}
	})
	return
}

// Update updates the music database.
func (c *Client) Update(ctx context.Context, uri string) (map[string]string, error) {
	return c.mapStr(ctx, "update", uri)
}

// Audio output devices

// DisableOutput turns an output off.
func (c *Client) DisableOutput(ctx context.Context, id string) error {
	return c.ok(ctx, "disableoutput", id)
}

// EnableOutput turns an output on.
func (c *Client) EnableOutput(ctx context.Context, id string) error {
	return c.ok(ctx, "enableoutput", id)
}

// Outputs shows information about all outputs.
func (c *Client) Outputs(ctx context.Context) ([]map[string]string, error) {
	return c.listMap(ctx, "outputid: ", "outputs")
}

func (c *Client) healthCheck(ctx context.Context) {
	if c.dialer.HealthCheckInterval == 0 {
		return
	}
	ticker := time.NewTicker(c.dialer.HealthCheckInterval)
	go func() {
		defer ticker.Stop()
		for {
			select {
			case <-ctx.Done():
				return
			case <-ticker.C:
				ctx, cancel := context.WithTimeout(ctx, c.dialer.HealthCheckInterval)
				c.Ping(ctx)
				cancel()
			}
		}
	}()
}

// Ping tests connection.
func (c *Client) Ping(ctx context.Context) error {
	return c.ok(ctx, "ping")
}

// Reflection

// Config dumps configuration values that may be interesting for the client.
// This command is only permitted to “local” clients (connected via local socket).
func (c *Client) Config(ctx context.Context) (map[string]string, error) {
	return c.mapStr(ctx, "config")
}

func (c *Client) ok(ctx context.Context, cmd ...interface{}) error {
	return c.pool.Exec(ctx, func(conn *conn) error {
		return conn.OK(cmd...)
	})
}

func (c *Client) readBinary(ctx context.Context, cmd, uri string) (b []byte, err error) {
	err = c.pool.Exec(ctx, func(conn *conn) error {
		var m map[string]string
		m, b, err = conn.ReadBinary(cmd, uri, 0)
		size, err := strconv.Atoi(m["size"])
		if err != nil {
			return err
		}
		for {
			if size == len(b) {
				return nil
			}
			if size < len(b) {
				return errors.New("oversize")
			}
			_, nb, err := conn.ReadBinary(cmd, uri, len(b))
			if err != nil {
				return err
			}
			b = append(b, nb...)
		}
	})
	return
}

func (c *Client) mapStr(ctx context.Context, cmd ...interface{}) (m map[string]string, err error) {
	err = c.pool.Exec(ctx, func(conn *conn) error {
		if _, err := conn.Writeln(cmd...); err != nil {
			return err
		}
		m = map[string]string{}
		for {
			line, err := conn.Readln()
			if err != nil {
				return err
			}
			if line == "OK" {
				return nil
			}
			i := strings.Index(line, ": ")
			if i < 0 {
				return newCommandError(line)
			}
			m[line[0:i]] = line[i+2:]
		}
	})
	return
}

func (c *Client) listMap(ctx context.Context, newKey string, cmd ...interface{}) (l []map[string]string, err error) {
	err = c.pool.Exec(ctx, func(conn *conn) error {
		if _, err := conn.Writeln(cmd...); err != nil {
			return err
		}
		var m map[string]string
		for {
			line, err := conn.Readln()
			if err != nil {
				return err
			}
			if line == "OK" {
				return nil
			}
			if strings.HasPrefix(line, newKey) {
				m = map[string]string{}
				l = append(l, m)
			}
			if m == nil {
				return newCommandError(line)
			}
			i := strings.Index(line, ": ")
			if i < 0 {
				return newCommandError(line)
			}
			m[line[0:i]] = line[i+2:]
		}
	})
	return
}

// quote escaping strings values for mpd.
func quote(s string) string {
	return `"` + strings.Replace(
		strings.Replace(s, "\\", "\\\\", -1),
		`"`, `\"`, -1) + `"`
}
<|endoftext|>


<commit_before>package types

import "github.com/go-pg/pg/internal/parser"

func AppendField(b []byte, field string, quote int) []byte {
	return appendField(b, parser.NewString(field), quote)
}

func AppendFieldBytes(b []byte, field []byte, quote int) []byte {
	return appendField(b, parser.New(field), quote)
}

func appendField(b []byte, p *parser.Parser, quote int) []byte {
	var quoted bool
	for p.Valid() {
		c := p.Read()
		switch c {
		case '*':
			if !quoted {
				b = append(b, '*')
				continue
			}
		case '.':
			if quoted && quote == 1 {
				b = append(b, '"')
				quoted = false
			}
			b = append(b, '.')
			if p.Skip('*') {
				b = append(b, '*')
			} else if quote == 1 {
				b = append(b, '"')
				quoted = true
			}
			continue
		}

		if !quoted && quote == 1 {
			b = append(b, '"')
			quoted = true
		}
		if c == '"' {
			b = append(b, '"', '"')
		} else {
			b = append(b, c)
		}
	}
	if quoted && quote == 1 {
		b = append(b, '"')
	}
	return b
}
<commit_msg>types: simplify AppendField<commit_after>package types

import "github.com/go-pg/pg/internal"

func AppendField(b []byte, field string, quote int) []byte {
	return appendField(b, internal.StringToBytes(field), quote)
}

func AppendFieldBytes(b []byte, field []byte, quote int) []byte {
	return appendField(b, field, quote)
}

func appendField(b, src []byte, quote int) []byte {
	var quoted bool
loop:
	for _, c := range src {
		switch c {
		case '*':
			if !quoted {
				b = append(b, '*')
				continue loop
			}
		case '.':
			if quoted && quote == 1 {
				b = append(b, '"')
				quoted = false
			}
			b = append(b, '.')
			continue loop
		}

		if !quoted && quote == 1 {
			b = append(b, '"')
			quoted = true
		}
		if c == '"' {
			b = append(b, '"', '"')
		} else {
			b = append(b, c)
		}
	}
	if quoted && quote == 1 {
		b = append(b, '"')
	}
	return b
}
<|endoftext|>


<commit_before>package cloudpelican

// @author Robin Verlangen
// @todo Support bulk index requests
// Tool for logging data to CloudPelican directly from Go

// Imports
import (
    "net"
    "net/http"
    "net/url"
    "log"
    "sync"
    "time"
    "strconv"
    "os"
)

// Settings
var ENDPOINT string = "https://app.cloudpelican.com/api"
var TOKEN string = ""
var backendTimeout = time.Duration(5 * time.Second)
var debugMode bool = false
var maxBulkSize uint64 = uint64(100)

// Monitor drain status
var startCounter uint64 = uint64(0)
var startCounterMux sync.Mutex
var doneCounter uint64 = uint64(0)
var doneCounterMux sync.Mutex
var isDraining bool = false
var drained = make(chan bool);

// Log queue
var writeAheadBufferSize int = 1000
var writeAhead chan map[string]string = make(chan map[string]string, writeAheadBufferSize)
var writeAheadInit bool
var dropOnFullWriteAheadBuffer bool = true

// Set token
func SetToken(t string) {
    // Validate before setting
    validateToken(t)

    // Store
    TOKEN = t
}

// Set endpoint
func SetEndpoint(e string) {
    // Store
    ENDPOINT = e
}

// Set timeout
func SetBackendTimeout(to time.Duration) {
    backendTimeout = to
}

// Debug
func SetDebugMode(b bool) {
    debugMode = b
}

// Drain
func Drain() {
    isDraining = true
    if startCounter > doneCounter {
        // Wait for signal
        <- drained
    }
}

// Write a message
func LogMessage(msg string) bool {
    // Create fields map
    params := make(map[string]string)
    params["__token__"] = TOKEN
    params["msg"] = msg

    // Push to channel
    return requestAsync(params)
}

// Request async
func requestAsync(params map[string]string) bool {
    // Check amount of open items in the channel, if the channel is full, return false and drop this message
    if dropOnFullWriteAheadBuffer {
        var lwa int = len(writeAhead)
        if lwa == writeAheadBufferSize {
            log.Printf("Write ahead buffer is full and contains %d items. Dropping current log message", lwa)
        }
    }

    // Add counter
    startCounterMux.Lock()
    startCounter++
    startCounterMux.Unlock()

    // Do we have to start a writer?
    if writeAheadInit == false {
        writeAheadInit = true
        backendWriter()
    }

    // Insert into channel
    writeAhead <- params

    // OK
    return true
}

// Get hostname of this system
func getHostname() string {
    // Hostname
    name, err := os.Hostname()
    if err != nil {
        return ""
    }
    return name
}

// Backend writer
func backendWriter() {
    hostname = getHostname()
    go func() {
        // Client
        transport := &http.Transport{
            Dial: func(netw, addr string) (net.Conn, error) {
                    deadline := time.Now().Add(backendTimeout)
                    c, err := net.DialTimeout(netw, addr, time.Second)
                    if err != nil {
                            return nil, err
                    }
                    c.SetDeadline(deadline)
                    return c, nil
            }}
        httpclient := &http.Client{Transport: transport}

        // Wait for messages
        var urlParams url.Values
        var currentEventCount uint64 = uint64(0)
        for {
            // Read from channel
            var fields map[string]string
            fields = <- writeAhead

            // Populate url params
            if currentEventCount == 0 {
                urlParams = url.Values{}
            }
            for k, _ := range fields {
                if k == "__token__" {
                    // Token
                    urlParams.Add("t", fields[k]);
                } else {
                    // Field
                    urlParams.Add("f[" + strconv.FormatUint(currentEventCount, 10) + "][" + k + "]", fields[k]);
                }
            }

            // Host
            if len(hostname) > 0 {
                urlParams.Add("f[" + strconv.FormatUint(currentEventCount, 10) + "][host]", hostname);
            }

            // Increase current count
            currentEventCount++

            // Queue length
            var qLen = len(writeAhead)
            if qLen > 0 && currentEventCount < maxBulkSize {
                // There is more in the current queue, bulk request
                continue
            }

            // Assemble url
            var url string = ENDPOINT + "/push/bulk"

            // Make request
            if debugMode {
                log.Printf("Write ahead queue %d\n", qLen)
                log.Println(urlParams.Encode())
            }
            resp, err := httpclient.PostForm(url, urlParams)
            if err != nil {
                log.Printf("Error while forwarding data: %s\n", err)
            } else {
                defer resp.Body.Close()
            }

            // Done counter
            doneCounterMux.Lock()
            doneCounter += currentEventCount
            doneCounterMux.Unlock()

            // Reset event count
            currentEventCount = 0

            // Are we draining the system?
            if isDraining && doneCounter >= startCounter {
                // Flag the drained channel
                drained <- true
            }
        }
        log.Printf("Stopping backend writer")
    }()
}

// Timeout helper
func dialTimeout(network, addr string) (net.Conn, error) {
    return net.DialTimeout(network, addr, backendTimeout)
}

// Validate the token
func validateToken(t string) {
    if len(t) == 0 {
        log.Println("Please set a valid token with cloudpelican.SetToken(token string)")
    }
}<commit_msg>Hostname<commit_after>package cloudpelican

// @author Robin Verlangen
// @todo Support bulk index requests
// Tool for logging data to CloudPelican directly from Go

// Imports
import (
    "net"
    "net/http"
    "net/url"
    "log"
    "sync"
    "time"
    "strconv"
    "os"
)

// Settings
var ENDPOINT string = "https://app.cloudpelican.com/api"
var TOKEN string = ""
var backendTimeout = time.Duration(5 * time.Second)
var debugMode bool = false
var maxBulkSize uint64 = uint64(100)
var hostname string

// Monitor drain status
var startCounter uint64 = uint64(0)
var startCounterMux sync.Mutex
var doneCounter uint64 = uint64(0)
var doneCounterMux sync.Mutex
var isDraining bool = false
var drained = make(chan bool);

// Log queue
var writeAheadBufferSize int = 1000
var writeAhead chan map[string]string = make(chan map[string]string, writeAheadBufferSize)
var writeAheadInit bool
var dropOnFullWriteAheadBuffer bool = true

// Set token
func SetToken(t string) {
    // Validate before setting
    validateToken(t)

    // Store
    TOKEN = t
}

// Set endpoint
func SetEndpoint(e string) {
    // Store
    ENDPOINT = e
}

// Set timeout
func SetBackendTimeout(to time.Duration) {
    backendTimeout = to
}

// Debug
func SetDebugMode(b bool) {
    debugMode = b
}

// Drain
func Drain() {
    isDraining = true
    if startCounter > doneCounter {
        // Wait for signal
        <- drained
    }
}

// Write a message
func LogMessage(msg string) bool {
    // Create fields map
    params := make(map[string]string)
    params["__token__"] = TOKEN
    params["msg"] = msg

    // Push to channel
    return requestAsync(params)
}

// Request async
func requestAsync(params map[string]string) bool {
    // Check amount of open items in the channel, if the channel is full, return false and drop this message
    if dropOnFullWriteAheadBuffer {
        var lwa int = len(writeAhead)
        if lwa == writeAheadBufferSize {
            log.Printf("Write ahead buffer is full and contains %d items. Dropping current log message", lwa)
        }
    }

    // Add counter
    startCounterMux.Lock()
    startCounter++
    startCounterMux.Unlock()

    // Do we have to start a writer?
    if writeAheadInit == false {
        writeAheadInit = true
        backendWriter()
    }

    // Insert into channel
    writeAhead <- params

    // OK
    return true
}

// Get hostname of this system
func getHostname() string {
    // Hostname
    name, err := os.Hostname()
    if err != nil {
        return ""
    }
    return name
}

// Backend writer
func backendWriter() {
    hostname = getHostname()
    go func() {
        // Client
        transport := &http.Transport{
            Dial: func(netw, addr string) (net.Conn, error) {
                    deadline := time.Now().Add(backendTimeout)
                    c, err := net.DialTimeout(netw, addr, time.Second)
                    if err != nil {
                            return nil, err
                    }
                    c.SetDeadline(deadline)
                    return c, nil
            }}
        httpclient := &http.Client{Transport: transport}

        // Wait for messages
        var urlParams url.Values
        var currentEventCount uint64 = uint64(0)
        for {
            // Read from channel
            var fields map[string]string
            fields = <- writeAhead

            // Populate url params
            if currentEventCount == 0 {
                urlParams = url.Values{}
            }
            for k, _ := range fields {
                if k == "__token__" {
                    // Token
                    urlParams.Add("t", fields[k]);
                } else {
                    // Field
                    urlParams.Add("f[" + strconv.FormatUint(currentEventCount, 10) + "][" + k + "]", fields[k]);
                }
            }

            // Host
            if len(hostname) > 0 {
                urlParams.Add("f[" + strconv.FormatUint(currentEventCount, 10) + "][host]", hostname);
            }

            // Increase current count
            currentEventCount++

            // Queue length
            var qLen = len(writeAhead)
            if qLen > 0 && currentEventCount < maxBulkSize {
                // There is more in the current queue, bulk request
                continue
            }

            // Assemble url
            var url string = ENDPOINT + "/push/bulk"

            // Make request
            if debugMode {
                log.Printf("Write ahead queue %d\n", qLen)
                log.Println(urlParams.Encode())
            }
            resp, err := httpclient.PostForm(url, urlParams)
            if err != nil {
                log.Printf("Error while forwarding data: %s\n", err)
            } else {
                defer resp.Body.Close()
            }

            // Done counter
            doneCounterMux.Lock()
            doneCounter += currentEventCount
            doneCounterMux.Unlock()

            // Reset event count
            currentEventCount = 0

            // Are we draining the system?
            if isDraining && doneCounter >= startCounter {
                // Flag the drained channel
                drained <- true
            }
        }
        log.Printf("Stopping backend writer")
    }()
}

// Timeout helper
func dialTimeout(network, addr string) (net.Conn, error) {
    return net.DialTimeout(network, addr, backendTimeout)
}

// Validate the token
func validateToken(t string) {
    if len(t) == 0 {
        log.Println("Please set a valid token with cloudpelican.SetToken(token string)")
    }
}<|endoftext|>


<commit_before>/*
Copyright 2019 Google LLC

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package v1alpha1

import (
	"context"

	corev1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/equality"
	"knative.dev/pkg/apis"
)

func (current *Storage) Validate(ctx context.Context) *apis.FieldError {
	return current.Spec.Validate(ctx).ViaField("spec")
}

func (current *StorageSpec) Validate(ctx context.Context) *apis.FieldError {
	// TODO
	var errs *apis.FieldError

	// Sink [required]
	if err := validateRef(current.Sink); err != nil {
		errs = errs.Also(err.ViaField("sink"))
	}
	return errs
}

func validateRef(ref corev1.ObjectReference) *apis.FieldError {
	var errs *apis.FieldError

	if equality.Semantic.DeepEqual(ref, corev1.ObjectReference{}) {
		return apis.ErrMissingField(apis.CurrentField)
	}

	if ref.Name == "" {
		errs = errs.Also(apis.ErrMissingField("name"))
	}
	if ref.APIVersion == "" {
		errs = errs.Also(apis.ErrMissingField("apiVersion"))
	}
	if ref.Kind == "" {
		errs = errs.Also(apis.ErrMissingField("kind"))
	}
	return errs
}
<commit_msg>Storage validates a bucket is set.<commit_after>/*
Copyright 2019 Google LLC

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package v1alpha1

import (
	"context"

	corev1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/equality"
	"knative.dev/pkg/apis"
)

func (current *Storage) Validate(ctx context.Context) *apis.FieldError {
	return current.Spec.Validate(ctx).ViaField("spec")
}

func (current *StorageSpec) Validate(ctx context.Context) *apis.FieldError {
	// TODO
	var errs *apis.FieldError

	// Sink [required]
	if err := validateRef(current.Sink); err != nil {
		errs = errs.Also(err.ViaField("sink"))
	}

	// Bucket is required.
	if current.Bucket == "" {
		errs = errs.Also(apis.ErrMissingField("bucket"))
	}
	return errs
}

func validateRef(ref corev1.ObjectReference) *apis.FieldError {
	var errs *apis.FieldError

	if equality.Semantic.DeepEqual(ref, corev1.ObjectReference{}) {
		return apis.ErrMissingField(apis.CurrentField)
	}

	if ref.Name == "" {
		errs = errs.Also(apis.ErrMissingField("name"))
	}
	if ref.APIVersion == "" {
		errs = errs.Also(apis.ErrMissingField("apiVersion"))
	}
	if ref.Kind == "" {
		errs = errs.Also(apis.ErrMissingField("kind"))
	}
	return errs
}
<|endoftext|>


<commit_before>/*
Copyright 2017 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package validation

import (
	"testing"

	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/util/validation/field"
	"k8s.io/kops/pkg/apis/kops"
	"k8s.io/kops/upup/pkg/fi"
)

func s(v string) *string {
	return fi.String(v)
}
func TestValidateInstanceProfile(t *testing.T) {
	grid := []struct {
		Input          *kops.IAMProfileSpec
		ExpectedErrors []string
		ExpectedDetail string
	}{
		{
			Input: &kops.IAMProfileSpec{
				Profile: s("arn:aws:iam::123456789012:instance-profile/S3Access"),
			},
		},
		{
			Input: &kops.IAMProfileSpec{
				Profile: s("arn:aws:iam::123456789012:instance-profile/has/path/S3Access"),
			},
		},
		{
			Input: &kops.IAMProfileSpec{
				Profile: s("arn:aws-cn:iam::123456789012:instance-profile/has/path/S3Access"),
			},
		},
		{
			Input: &kops.IAMProfileSpec{
				Profile: s("arn:aws-us-gov:iam::123456789012:instance-profile/has/path/S3Access"),
			},
		},
		{
			Input: &kops.IAMProfileSpec{
				Profile: s("42"),
			},
			ExpectedErrors: []string{"Invalid value::iam.profile"},
			ExpectedDetail: "Instance Group IAM Instance Profile must be a valid aws arn such as arn:aws:iam::123456789012:instance-profile/KopsExampleRole",
		},
		{
			Input: &kops.IAMProfileSpec{
				Profile: s("arn:aws:iam::123456789012:group/division_abc/subdivision_xyz/product_A/Developers"),
			},
			ExpectedErrors: []string{"Invalid value::iam.profile"},
			ExpectedDetail: "Instance Group IAM Instance Profile must be a valid aws arn such as arn:aws:iam::123456789012:instance-profile/KopsExampleRole",
		},
	}

	for _, g := range grid {
		allErrs := validateInstanceProfile(g.Input, field.NewPath("iam"))
		testErrors(t, g.Input, allErrs, g.ExpectedErrors)

		if g.ExpectedDetail != "" {
			found := false
			for _, err := range allErrs {
				if err.Detail == g.ExpectedDetail {
					found = true
				}
			}
			if !found {
				for _, err := range allErrs {
					t.Logf("found detail: %q", err.Detail)
				}

				t.Errorf("did not find expected error %q", g.ExpectedDetail)
			}
		}
	}
}

func TestValidMasterInstanceGroup(t *testing.T) {
	grid := []struct {
		Cluster        *kops.Cluster
		IG             *kops.InstanceGroup
		ExpectedErrors int
		Description    string
	}{
		{
			Cluster: &kops.Cluster{
				Spec: kops.ClusterSpec{
					EtcdClusters: []*kops.EtcdClusterSpec{
						{
							Name: "main",
							Members: []*kops.EtcdMemberSpec{
								{
									Name:          "a",
									InstanceGroup: fi.String("eu-central-1a"),
								},
								{
									Name:          "b",
									InstanceGroup: fi.String("eu-central-1b"),
								},
								{
									Name:          "c",
									InstanceGroup: fi.String("eu-central-1c"),
								},
							},
						},
					},
				},
			},
			IG: &kops.InstanceGroup{
				ObjectMeta: v1.ObjectMeta{
					Name: "eu-central-1a",
				},
				Spec: kops.InstanceGroupSpec{
					Role: kops.InstanceGroupRoleMaster,
				},
			},
			ExpectedErrors: 0,
			Description:    "Valid instance group failed to validate",
		},
		{
			Cluster: &kops.Cluster{
				Spec: kops.ClusterSpec{
					EtcdClusters: []*kops.EtcdClusterSpec{
						{
							Name: "main",
							Members: []*kops.EtcdMemberSpec{
								{
									Name:          "a",
									InstanceGroup: fi.String("eu-central-1a"),
								},
								{
									Name:          "b",
									InstanceGroup: fi.String("eu-central-1b"),
								},
								{
									Name:          "c",
									InstanceGroup: fi.String("eu-central-1c"),
								},
							},
						},
					},
				},
			},
			IG: &kops.InstanceGroup{
				ObjectMeta: v1.ObjectMeta{
					Name: "eu-central-1d",
				},
				Spec: kops.InstanceGroupSpec{
					Role: kops.InstanceGroupRoleMaster,
				},
			},
			ExpectedErrors: 1,
			Description:    "Master IG without etcd member validated",
		},
	}

	for _, g := range grid {
		errList := ValidateMasterInstanceGroup(g.IG, g.Cluster)
		if len(errList) != g.ExpectedErrors {
			t.Error(g.Description)
		}
	}

}

func TestValidBootDevice(t *testing.T) {

	cluster := &kops.Cluster{
		Spec: kops.ClusterSpec{
			CloudProvider: "aws",
		},
	}
	grid := []struct {
		volumeType string
		expected   []string
	}{
		{
			volumeType: "gp2",
		},
		{
			volumeType: "io1",
		},
		{
			volumeType: "st1",
			expected:   []string{"Unsupported value::spec.rootVolumeType"},
		},
		{
			volumeType: "sc1",
			expected:   []string{"Unsupported value::spec.rootVolumeType"},
		},
	}

	for _, g := range grid {
		ig := &kops.InstanceGroup{
			ObjectMeta: v1.ObjectMeta{
				Name: "some-ig",
			},
			Spec: kops.InstanceGroupSpec{
				Role:           "Node",
				RootVolumeType: fi.String(g.volumeType),
			},
		}
		errs := CrossValidateInstanceGroup(ig, cluster)
		testErrors(t, g.volumeType, errs, g.expected)
	}
}
<commit_msg>fix new validation unit test<commit_after>/*
Copyright 2017 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package validation

import (
	"testing"

	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/util/validation/field"
	"k8s.io/kops/pkg/apis/kops"
	"k8s.io/kops/upup/pkg/fi"
)

func s(v string) *string {
	return fi.String(v)
}
func TestValidateInstanceProfile(t *testing.T) {
	grid := []struct {
		Input          *kops.IAMProfileSpec
		ExpectedErrors []string
		ExpectedDetail string
	}{
		{
			Input: &kops.IAMProfileSpec{
				Profile: s("arn:aws:iam::123456789012:instance-profile/S3Access"),
			},
		},
		{
			Input: &kops.IAMProfileSpec{
				Profile: s("arn:aws:iam::123456789012:instance-profile/has/path/S3Access"),
			},
		},
		{
			Input: &kops.IAMProfileSpec{
				Profile: s("arn:aws-cn:iam::123456789012:instance-profile/has/path/S3Access"),
			},
		},
		{
			Input: &kops.IAMProfileSpec{
				Profile: s("arn:aws-us-gov:iam::123456789012:instance-profile/has/path/S3Access"),
			},
		},
		{
			Input: &kops.IAMProfileSpec{
				Profile: s("42"),
			},
			ExpectedErrors: []string{"Invalid value::iam.profile"},
			ExpectedDetail: "Instance Group IAM Instance Profile must be a valid aws arn such as arn:aws:iam::123456789012:instance-profile/KopsExampleRole",
		},
		{
			Input: &kops.IAMProfileSpec{
				Profile: s("arn:aws:iam::123456789012:group/division_abc/subdivision_xyz/product_A/Developers"),
			},
			ExpectedErrors: []string{"Invalid value::iam.profile"},
			ExpectedDetail: "Instance Group IAM Instance Profile must be a valid aws arn such as arn:aws:iam::123456789012:instance-profile/KopsExampleRole",
		},
	}

	for _, g := range grid {
		allErrs := validateInstanceProfile(g.Input, field.NewPath("iam"))
		testErrors(t, g.Input, allErrs, g.ExpectedErrors)

		if g.ExpectedDetail != "" {
			found := false
			for _, err := range allErrs {
				if err.Detail == g.ExpectedDetail {
					found = true
				}
			}
			if !found {
				for _, err := range allErrs {
					t.Logf("found detail: %q", err.Detail)
				}

				t.Errorf("did not find expected error %q", g.ExpectedDetail)
			}
		}
	}
}

func TestValidMasterInstanceGroup(t *testing.T) {
	grid := []struct {
		Cluster        *kops.Cluster
		IG             *kops.InstanceGroup
		ExpectedErrors int
		Description    string
	}{
		{
			Cluster: &kops.Cluster{
				Spec: kops.ClusterSpec{
					EtcdClusters: []*kops.EtcdClusterSpec{
						{
							Name: "main",
							Members: []*kops.EtcdMemberSpec{
								{
									Name:          "a",
									InstanceGroup: fi.String("eu-central-1a"),
								},
								{
									Name:          "b",
									InstanceGroup: fi.String("eu-central-1b"),
								},
								{
									Name:          "c",
									InstanceGroup: fi.String("eu-central-1c"),
								},
							},
						},
					},
				},
			},
			IG: &kops.InstanceGroup{
				ObjectMeta: v1.ObjectMeta{
					Name: "eu-central-1a",
				},
				Spec: kops.InstanceGroupSpec{
					Role: kops.InstanceGroupRoleMaster,
				},
			},
			ExpectedErrors: 0,
			Description:    "Valid instance group failed to validate",
		},
		{
			Cluster: &kops.Cluster{
				Spec: kops.ClusterSpec{
					EtcdClusters: []*kops.EtcdClusterSpec{
						{
							Name: "main",
							Members: []*kops.EtcdMemberSpec{
								{
									Name:          "a",
									InstanceGroup: fi.String("eu-central-1a"),
								},
								{
									Name:          "b",
									InstanceGroup: fi.String("eu-central-1b"),
								},
								{
									Name:          "c",
									InstanceGroup: fi.String("eu-central-1c"),
								},
							},
						},
					},
				},
			},
			IG: &kops.InstanceGroup{
				ObjectMeta: v1.ObjectMeta{
					Name: "eu-central-1d",
				},
				Spec: kops.InstanceGroupSpec{
					Role: kops.InstanceGroupRoleMaster,
				},
			},
			ExpectedErrors: 1,
			Description:    "Master IG without etcd member validated",
		},
	}

	for _, g := range grid {
		errList := ValidateMasterInstanceGroup(g.IG, g.Cluster)
		if len(errList) != g.ExpectedErrors {
			t.Error(g.Description)
		}
	}

}

func TestValidBootDevice(t *testing.T) {

	cluster := &kops.Cluster{
		Spec: kops.ClusterSpec{
			CloudProvider: "aws",
		},
	}
	grid := []struct {
		volumeType string
		expected   []string
	}{
		{
			volumeType: "gp2",
		},
		{
			volumeType: "io1",
		},
		{
			volumeType: "st1",
			expected:   []string{"Unsupported value::spec.rootVolumeType"},
		},
		{
			volumeType: "sc1",
			expected:   []string{"Unsupported value::spec.rootVolumeType"},
		},
	}

	for _, g := range grid {
		ig := &kops.InstanceGroup{
			ObjectMeta: v1.ObjectMeta{
				Name: "some-ig",
			},
			Spec: kops.InstanceGroupSpec{
				Role:           "Node",
				RootVolumeType: fi.String(g.volumeType),
			},
		}
		errs := CrossValidateInstanceGroup(ig, cluster, nil)
		testErrors(t, g.volumeType, errs, g.expected)
	}
}
<|endoftext|>


<commit_before>// Package data provide simple CRUD operation on couchdb doc
package data

import (
	"fmt"
	"net/http"
	"strconv"

	"github.com/cozy/cozy-stack/couchdb"
	"github.com/cozy/cozy-stack/web/jsonapi"
	"github.com/cozy/cozy-stack/web/middlewares"
	"github.com/labstack/echo"
)

func validDoctype(next echo.HandlerFunc) echo.HandlerFunc {
	// TODO extends me to verificate characters allowed in db name.
	return func(c echo.Context) error {
		doctype := c.Param("doctype")
		if doctype == "" && c.Path() != "/data/" {
			return jsonapi.NewError(http.StatusBadRequest, "Invalid doctype '%s'", doctype)
		}
		c.Set("doctype", doctype)
		return next(c)
	}
}

// GetDoc get a doc by its type and id
func getDoc(c echo.Context) error {
	instance := middlewares.GetInstance(c)
	doctype := c.Get("doctype").(string)
	docid := c.Param("docid")

	if err := CheckReadable(c, doctype); err != nil {
		return err
	}

	if docid == "" {
		return dbStatus(c)
	}

	if docid[0] == '_' {
		return fmt.Errorf("Unsuported couchdb operation %s", docid)
	}

	revs := c.QueryParam("revs")
	if revs == "true" {
		return proxy(c, docid)
	}

	var out couchdb.JSONDoc
	err := couchdb.GetDoc(instance, doctype, docid, &out)
	if err != nil {
		return err
	}

	out.Type = doctype
	return c.JSON(http.StatusOK, out.ToMapWithType())
}

// CreateDoc create doc from the json passed as body
func createDoc(c echo.Context) error {
	doctype := c.Get("doctype").(string)
	instance := middlewares.GetInstance(c)

	doc := couchdb.JSONDoc{Type: doctype}
	if err := c.Bind(&doc.M); err != nil {
		return jsonapi.NewError(http.StatusBadRequest, err)
	}

	if err := CheckWritable(c, doctype); err != nil {
		return err
	}

	if doc.ID() != "" {
		return jsonapi.NewError(http.StatusBadRequest,
			"Cannot create a document with _id")
	}

	err := couchdb.CreateDoc(instance, doc)
	if err != nil {
		return err
	}

	return c.JSON(http.StatusCreated, echo.Map{
		"ok":   true,
		"id":   doc.ID(),
		"rev":  doc.Rev(),
		"type": doc.DocType(),
		"data": doc.ToMapWithType(),
	})
}

func updateDoc(c echo.Context) error {
	instance := middlewares.GetInstance(c)

	var doc couchdb.JSONDoc
	if err := c.Bind(&doc); err != nil {
		return jsonapi.NewError(http.StatusBadRequest, err)
	}

	doc.Type = c.Param("doctype")

	if err := CheckWritable(c, doc.Type); err != nil {
		return err
	}

	if (doc.ID() == "") != (doc.Rev() == "") {
		return jsonapi.NewError(http.StatusBadRequest,
			"You must either provide an _id and _rev in document (update) or neither (create with  fixed id).")
	}

	if doc.ID() != "" && doc.ID() != c.Param("docid") {
		return jsonapi.NewError(http.StatusBadRequest, "document _id doesnt match url")
	}

	var err error
	if doc.ID() == "" {
		doc.SetID(c.Param("docid"))
		err = couchdb.CreateNamedDoc(instance, doc)
	} else {
		err = couchdb.UpdateDoc(instance, doc)
	}

	if err != nil {
		return err
	}

	return c.JSON(http.StatusOK, echo.Map{
		"ok":   true,
		"id":   doc.ID(),
		"rev":  doc.Rev(),
		"type": doc.DocType(),
		"data": doc.ToMapWithType(),
	})
}

func deleteDoc(c echo.Context) error {
	instance := middlewares.GetInstance(c)
	doctype := c.Get("doctype").(string)
	docid := c.Param("docid")
	revHeader := c.Request().Header.Get("If-Match")
	revQuery := c.QueryParam("rev")
	rev := ""

	if revHeader != "" && revQuery != "" && revQuery != revHeader {
		return jsonapi.NewError(http.StatusBadRequest,
			"If-Match Header and rev query parameters mismatch")
	} else if revHeader != "" {
		rev = revHeader
	} else if revQuery != "" {
		rev = revQuery
	} else {
		return jsonapi.NewError(http.StatusBadRequest, "delete without revision")
	}

	if err := CheckWritable(c, doctype); err != nil {
		return err
	}

	tombrev, err := couchdb.Delete(instance, doctype, docid, rev)
	if err != nil {
		return err
	}

	return c.JSON(http.StatusOK, echo.Map{
		"ok":      true,
		"id":      docid,
		"rev":     tombrev,
		"type":    doctype,
		"deleted": true,
	})

}

func defineIndex(c echo.Context) error {
	instance := middlewares.GetInstance(c)
	doctype := c.Get("doctype").(string)
	var definitionRequest map[string]interface{}

	if err := c.Bind(&definitionRequest); err != nil {
		return jsonapi.NewError(http.StatusBadRequest, err)
	}

	if err := CheckWritable(c, doctype); err != nil {
		return err
	}

	result, err := couchdb.DefineIndexRaw(instance, doctype, &definitionRequest)
	if couchdb.IsNoDatabaseError(err) {
		if err = couchdb.CreateDB(instance, doctype); err == nil {
			result, err = couchdb.DefineIndexRaw(instance, doctype, &definitionRequest)
		}
	}
	if err != nil {
		return err
	}

	return c.JSON(http.StatusOK, result)
}

func findDocuments(c echo.Context) error {
	instance := middlewares.GetInstance(c)
	doctype := c.Get("doctype").(string)
	var findRequest map[string]interface{}

	if err := c.Bind(&findRequest); err != nil {
		return jsonapi.NewError(http.StatusBadRequest, err)
	}

	if err := CheckReadable(c, doctype); err != nil {
		return err
	}

	var results []couchdb.JSONDoc
	err := couchdb.FindDocsRaw(instance, doctype, &findRequest, &results)
	if err != nil {
		return err
	}

	return c.JSON(http.StatusOK, echo.Map{"docs": results})
}

var allowedChangesParams = map[string]bool{
	"feed":      true,
	"style":     true,
	"since":     true,
	"limit":     true,
	"timeout":   true,
	"heartbeat": true, // Pouchdb sends heartbeet even for non-continuous
}

func changesFeed(c echo.Context) error {
	instance := middlewares.GetInstance(c)

	// Drop a clear error for parameters not supported by stack
	for key := range c.QueryParams() {
		if !allowedChangesParams[key] {
			return jsonapi.NewError(http.StatusBadRequest, "Unsuported query parameter '%s'", key)
		}
	}

	feed, err := couchdb.ValidChangesMode(c.QueryParam("feed"))
	if err != nil {
		return jsonapi.NewError(http.StatusBadRequest, err)
	}

	feedStyle, err := couchdb.ValidChangesStyle(c.QueryParam("style"))
	if err != nil {
		return jsonapi.NewError(http.StatusBadRequest, err)
	}

	limitString := c.QueryParam("limit")
	limit := 0
	if limitString != "" {
		if limit, err = strconv.Atoi(limitString); err != nil {
			return jsonapi.NewError(http.StatusBadRequest, "Invalid limit value '%s'", err.Error())
		}
	}

	results, err := couchdb.GetChanges(instance, &couchdb.ChangesRequest{
		DocType: c.Get("doctype").(string),
		Feed:    feed,
		Style:   feedStyle,
		Since:   c.QueryParam("since"),
		Limit:   limit,
	})

	if err != nil {
		return err
	}

	return c.JSON(http.StatusOK, results)
}

func allDocs(c echo.Context) error {
	doctype := c.Get("doctype").(string)

	if err := CheckReadable(c, doctype); err != nil {
		return err
	}

	return proxy(c, "_all_docs")

}

func couchdbStyleErrorHandler(next echo.HandlerFunc) echo.HandlerFunc {
	return func(c echo.Context) error {
		err := next(c)
		if err == nil {
			return nil
		}

		if ce, ok := err.(*couchdb.Error); ok {
			return c.JSON(ce.StatusCode, ce.JSON())
		}

		if he, ok := err.(*echo.HTTPError); ok {
			return c.JSON(he.Code, echo.Map{"error": he.Error()})
		}

		if je, ok := err.(*jsonapi.Error); ok {
			return c.JSON(je.Status, echo.Map{"error": je.Title})
		}

		return c.JSON(http.StatusInternalServerError, echo.Map{
			"error": err.Error(),
		})
	}
}

// Routes sets the routing for the status service
func Routes(router *echo.Group) {
	router.Use(validDoctype)
	router.Use(couchdbStyleErrorHandler)

	replicationRoutes(router)

	// API Routes
	router.GET("/:doctype/:docid", getDoc)
	router.PUT("/:doctype/:docid", updateDoc)
	router.DELETE("/:doctype/:docid", deleteDoc)
	router.POST("/:doctype/", createDoc)
	router.POST("/:doctype/_all_docs", allDocs)
	router.POST("/:doctype/_index", defineIndex)
	router.POST("/:doctype/_find", findDocuments)
	// router.DELETE("/:doctype/:docid", DeleteDoc)
}
<commit_msg>allow replication of _design docs<commit_after>// Package data provide simple CRUD operation on couchdb doc
package data

import (
	"fmt"
	"net/http"
	"strconv"
	"strings"

	"github.com/cozy/cozy-stack/couchdb"
	"github.com/cozy/cozy-stack/web/jsonapi"
	"github.com/cozy/cozy-stack/web/middlewares"
	"github.com/labstack/echo"
)

func validDoctype(next echo.HandlerFunc) echo.HandlerFunc {
	// TODO extends me to verificate characters allowed in db name.
	return func(c echo.Context) error {
		doctype := c.Param("doctype")
		if doctype == "" && c.Path() != "/data/" {
			return jsonapi.NewError(http.StatusBadRequest, "Invalid doctype '%s'", doctype)
		}
		c.Set("doctype", doctype)
		return next(c)
	}
}

// GetDoc get a doc by its type and id
func getDoc(c echo.Context) error {
	instance := middlewares.GetInstance(c)
	doctype := c.Get("doctype").(string)
	docid := c.Param("docid")

	if err := CheckReadable(c, doctype); err != nil {
		return err
	}

	if docid == "" {
		return dbStatus(c)
	}

	if docid[0] == '_' && !strings.HasPrefix(docid, "_design") {
		return fmt.Errorf("Unsuported couchdb operation %s", docid)
	}

	revs := c.QueryParam("revs")
	if revs == "true" {
		return proxy(c, docid)
	}

	var out couchdb.JSONDoc
	err := couchdb.GetDoc(instance, doctype, docid, &out)
	if err != nil {
		return err
	}

	out.Type = doctype
	return c.JSON(http.StatusOK, out.ToMapWithType())
}

// CreateDoc create doc from the json passed as body
func createDoc(c echo.Context) error {
	doctype := c.Get("doctype").(string)
	instance := middlewares.GetInstance(c)

	doc := couchdb.JSONDoc{Type: doctype}
	if err := c.Bind(&doc.M); err != nil {
		return jsonapi.NewError(http.StatusBadRequest, err)
	}

	if err := CheckWritable(c, doctype); err != nil {
		return err
	}

	if doc.ID() != "" {
		return jsonapi.NewError(http.StatusBadRequest,
			"Cannot create a document with _id")
	}

	err := couchdb.CreateDoc(instance, doc)
	if err != nil {
		return err
	}

	return c.JSON(http.StatusCreated, echo.Map{
		"ok":   true,
		"id":   doc.ID(),
		"rev":  doc.Rev(),
		"type": doc.DocType(),
		"data": doc.ToMapWithType(),
	})
}

func updateDoc(c echo.Context) error {
	instance := middlewares.GetInstance(c)

	var doc couchdb.JSONDoc
	if err := c.Bind(&doc); err != nil {
		return jsonapi.NewError(http.StatusBadRequest, err)
	}

	doc.Type = c.Param("doctype")

	if err := CheckWritable(c, doc.Type); err != nil {
		return err
	}

	if (doc.ID() == "") != (doc.Rev() == "") {
		return jsonapi.NewError(http.StatusBadRequest,
			"You must either provide an _id and _rev in document (update) or neither (create with  fixed id).")
	}

	if doc.ID() != "" && doc.ID() != c.Param("docid") {
		return jsonapi.NewError(http.StatusBadRequest, "document _id doesnt match url")
	}

	var err error
	if doc.ID() == "" {
		doc.SetID(c.Param("docid"))
		err = couchdb.CreateNamedDoc(instance, doc)
	} else {
		err = couchdb.UpdateDoc(instance, doc)
	}

	if err != nil {
		return err
	}

	return c.JSON(http.StatusOK, echo.Map{
		"ok":   true,
		"id":   doc.ID(),
		"rev":  doc.Rev(),
		"type": doc.DocType(),
		"data": doc.ToMapWithType(),
	})
}

func deleteDoc(c echo.Context) error {
	instance := middlewares.GetInstance(c)
	doctype := c.Get("doctype").(string)
	docid := c.Param("docid")
	revHeader := c.Request().Header.Get("If-Match")
	revQuery := c.QueryParam("rev")
	rev := ""

	if revHeader != "" && revQuery != "" && revQuery != revHeader {
		return jsonapi.NewError(http.StatusBadRequest,
			"If-Match Header and rev query parameters mismatch")
	} else if revHeader != "" {
		rev = revHeader
	} else if revQuery != "" {
		rev = revQuery
	} else {
		return jsonapi.NewError(http.StatusBadRequest, "delete without revision")
	}

	if err := CheckWritable(c, doctype); err != nil {
		return err
	}

	tombrev, err := couchdb.Delete(instance, doctype, docid, rev)
	if err != nil {
		return err
	}

	return c.JSON(http.StatusOK, echo.Map{
		"ok":      true,
		"id":      docid,
		"rev":     tombrev,
		"type":    doctype,
		"deleted": true,
	})

}

func defineIndex(c echo.Context) error {
	instance := middlewares.GetInstance(c)
	doctype := c.Get("doctype").(string)
	var definitionRequest map[string]interface{}

	if err := c.Bind(&definitionRequest); err != nil {
		return jsonapi.NewError(http.StatusBadRequest, err)
	}

	if err := CheckWritable(c, doctype); err != nil {
		return err
	}

	result, err := couchdb.DefineIndexRaw(instance, doctype, &definitionRequest)
	if couchdb.IsNoDatabaseError(err) {
		if err = couchdb.CreateDB(instance, doctype); err == nil {
			result, err = couchdb.DefineIndexRaw(instance, doctype, &definitionRequest)
		}
	}
	if err != nil {
		return err
	}

	return c.JSON(http.StatusOK, result)
}

func findDocuments(c echo.Context) error {
	instance := middlewares.GetInstance(c)
	doctype := c.Get("doctype").(string)
	var findRequest map[string]interface{}

	if err := c.Bind(&findRequest); err != nil {
		return jsonapi.NewError(http.StatusBadRequest, err)
	}

	if err := CheckReadable(c, doctype); err != nil {
		return err
	}

	var results []couchdb.JSONDoc
	err := couchdb.FindDocsRaw(instance, doctype, &findRequest, &results)
	if err != nil {
		return err
	}

	return c.JSON(http.StatusOK, echo.Map{"docs": results})
}

var allowedChangesParams = map[string]bool{
	"feed":      true,
	"style":     true,
	"since":     true,
	"limit":     true,
	"timeout":   true,
	"heartbeat": true, // Pouchdb sends heartbeet even for non-continuous
}

func changesFeed(c echo.Context) error {
	instance := middlewares.GetInstance(c)

	// Drop a clear error for parameters not supported by stack
	for key := range c.QueryParams() {
		if !allowedChangesParams[key] {
			return jsonapi.NewError(http.StatusBadRequest, "Unsuported query parameter '%s'", key)
		}
	}

	feed, err := couchdb.ValidChangesMode(c.QueryParam("feed"))
	if err != nil {
		return jsonapi.NewError(http.StatusBadRequest, err)
	}

	feedStyle, err := couchdb.ValidChangesStyle(c.QueryParam("style"))
	if err != nil {
		return jsonapi.NewError(http.StatusBadRequest, err)
	}

	limitString := c.QueryParam("limit")
	limit := 0
	if limitString != "" {
		if limit, err = strconv.Atoi(limitString); err != nil {
			return jsonapi.NewError(http.StatusBadRequest, "Invalid limit value '%s'", err.Error())
		}
	}

	results, err := couchdb.GetChanges(instance, &couchdb.ChangesRequest{
		DocType: c.Get("doctype").(string),
		Feed:    feed,
		Style:   feedStyle,
		Since:   c.QueryParam("since"),
		Limit:   limit,
	})

	if err != nil {
		return err
	}

	return c.JSON(http.StatusOK, results)
}

func allDocs(c echo.Context) error {
	doctype := c.Get("doctype").(string)

	if err := CheckReadable(c, doctype); err != nil {
		return err
	}

	return proxy(c, "_all_docs")

}

func couchdbStyleErrorHandler(next echo.HandlerFunc) echo.HandlerFunc {
	return func(c echo.Context) error {
		err := next(c)
		if err == nil {
			return nil
		}

		if ce, ok := err.(*couchdb.Error); ok {
			return c.JSON(ce.StatusCode, ce.JSON())
		}

		if he, ok := err.(*echo.HTTPError); ok {
			return c.JSON(he.Code, echo.Map{"error": he.Error()})
		}

		if je, ok := err.(*jsonapi.Error); ok {
			return c.JSON(je.Status, echo.Map{"error": je.Title})
		}

		return c.JSON(http.StatusInternalServerError, echo.Map{
			"error": err.Error(),
		})
	}
}

// Routes sets the routing for the status service
func Routes(router *echo.Group) {
	router.Use(validDoctype)
	router.Use(couchdbStyleErrorHandler)

	replicationRoutes(router)

	// API Routes
	router.GET("/:doctype/:docid", getDoc)
	router.PUT("/:doctype/:docid", updateDoc)
	router.DELETE("/:doctype/:docid", deleteDoc)
	router.POST("/:doctype/", createDoc)
	router.POST("/:doctype/_all_docs", allDocs)
	router.POST("/:doctype/_index", defineIndex)
	router.POST("/:doctype/_find", findDocuments)
	// router.DELETE("/:doctype/:docid", DeleteDoc)
}
<|endoftext|>


<commit_before>package forgotpassword

import (
	"net/url"
	"path"
	"time"

	"github.com/skygeario/skygear-server/pkg/auth/dependency/audit"
	"github.com/skygeario/skygear-server/pkg/auth/dependency/hook"
	"github.com/skygeario/skygear-server/pkg/auth/dependency/principal/password"
	"github.com/skygeario/skygear-server/pkg/auth/dependency/urlprefix"
	"github.com/skygeario/skygear-server/pkg/auth/dependency/userprofile"
	"github.com/skygeario/skygear-server/pkg/auth/event"
	"github.com/skygeario/skygear-server/pkg/auth/model"
	taskspec "github.com/skygeario/skygear-server/pkg/auth/task/spec"
	"github.com/skygeario/skygear-server/pkg/core/async"
	"github.com/skygeario/skygear-server/pkg/core/auth/authinfo"
	"github.com/skygeario/skygear-server/pkg/core/auth/metadata"
	"github.com/skygeario/skygear-server/pkg/core/config"
	"github.com/skygeario/skygear-server/pkg/core/mail"
	"github.com/skygeario/skygear-server/pkg/core/sms"
	"github.com/skygeario/skygear-server/pkg/core/template"
	coretime "github.com/skygeario/skygear-server/pkg/core/time"
)

type Provider struct {
	AppName                     string
	EmailMessageConfiguration   config.EmailMessageConfiguration
	SMSMessageConfiguration     config.SMSMessageConfiguration
	ForgotPasswordConfiguration *config.ForgotPasswordConfiguration

	Store Store

	AuthInfoStore        authinfo.Store
	UserProfileStore     userprofile.Store
	PasswordAuthProvider password.Provider
	PasswordChecker      *audit.PasswordChecker
	HookProvider         hook.Provider
	TimeProvider         coretime.Provider
	URLPrefixProvider    urlprefix.Provider
	TemplateEngine       *template.Engine
	MailSender           mail.Sender
	SMSClient            sms.Client
	TaskQueue            async.Queue
}

// SendCode checks if loginID is an existing login ID.
// For each matched login ID, a code is generated.
// The code expires after a specific time.
// The code becomes invalid if it is consumed.
// Finally the code is sent to the login ID asynchronously.
func (p *Provider) SendCode(loginID string) (err error) {
	// TODO(forgotpassword): Test SendCode
	prins, err := p.PasswordAuthProvider.GetPrincipalsByLoginID("", loginID)
	if err != nil {
		return
	}

	for _, prin := range prins {
		email := p.PasswordAuthProvider.CheckLoginIDKeyType(prin.LoginIDKey, metadata.Email)
		phone := p.PasswordAuthProvider.CheckLoginIDKeyType(prin.LoginIDKey, metadata.Phone)

		if !email && !phone {
			continue
		}

		code, codeStr := p.newCode(prin)

		err = p.Store.Create(code)
		if err != nil {
			return
		}

		if email {
			err = p.sendEmail(prin.LoginID, codeStr)
			if err != nil {
				return
			}
		}

		if phone {
			err = p.sendSMS(prin.LoginID, codeStr)
			if err != nil {
				return
			}
		}
	}

	return
}

func (p *Provider) newCode(prin *password.Principal) (code *Code, codeStr string) {
	createdAt := p.TimeProvider.NowUTC()
	codeStr = GenerateCode()
	expireAt := createdAt.Add(time.Duration(p.ForgotPasswordConfiguration.ResetCodeLifetime) * time.Second)
	code = &Code{
		CodeHash:    HashCode(codeStr),
		PrincipalID: prin.ID,
		CreatedAt:   createdAt,
		ExpireAt:    expireAt,
		Consumed:    false,
	}
	return
}

func (p *Provider) sendEmail(email string, code string) (err error) {
	u := p.makeURL(code)

	data := map[string]interface{}{
		"appname": p.AppName,
		"email":   email,
		"code":    code,
		"link":    u.String(),
	}

	textBody, err := p.TemplateEngine.RenderTemplate(
		TemplateItemTypeForgotPasswordEmailTXT,
		data,
		template.ResolveOptions{},
	)
	if err != nil {
		return
	}

	htmlBody, err := p.TemplateEngine.RenderTemplate(
		TemplateItemTypeForgotPasswordEmailHTML,
		data,
		template.ResolveOptions{},
	)
	if err != nil {
		return
	}

	messageConfig := config.NewEmailMessageConfiguration(
		p.EmailMessageConfiguration,
		p.ForgotPasswordConfiguration.EmailMessage,
	)
	err = p.MailSender.Send(mail.SendOptions{
		MessageConfig: messageConfig,
		Recipient:     email,
		TextBody:      textBody,
		HTMLBody:      htmlBody,
	})
	if err != nil {
		return
	}

	return
}

func (p *Provider) sendSMS(phone string, code string) (err error) {
	u := p.makeURL(code)

	data := map[string]interface{}{
		"appname": p.AppName,
		"code":    code,
		"link":    u.String(),
	}

	body, err := p.TemplateEngine.RenderTemplate(
		TemplateItemTypeForgotPasswordSMSTXT,
		data,
		template.ResolveOptions{},
	)
	if err != nil {
		return
	}

	messageConfig := config.NewSMSMessageConfiguration(
		p.SMSMessageConfiguration,
		p.ForgotPasswordConfiguration.SMSMessage,
	)
	err = p.SMSClient.Send(sms.SendOptions{
		MessageConfig: messageConfig,
		To:            phone,
		Body:          body,
	})
	if err != nil {
		return
	}

	return
}

func (p *Provider) makeURL(code string) *url.URL {
	u := *p.URLPrefixProvider.Value()
	// /reset_password is an endpoint of Auth UI.
	u.Path = path.Join(u.Path, "reset_password")
	u.RawQuery = url.Values{
		"code": []string{code},
	}.Encode()
	return &u
}

// ResetPassword consumes code and reset password to newPassword.
// If the code is invalid, ErrInvalidCode is returned.
// If the code is found but expired, ErrExpiredCode is returned.
// if the code is found but used, ErrUsedCode is returned.
// Otherwise, the password is reset to newPassword.
// newPassword is checked against the password policy so
// password policy error may also be returned.
func (p *Provider) ResetPassword(codeStr string, newPassword string) (err error) {
	codeHash := HashCode(codeStr)
	code, err := p.Store.Get(codeHash)
	if err != nil {
		return
	}

	now := p.TimeProvider.NowUTC()
	if now.After(code.ExpireAt) {
		err = ErrExpiredCode
		return
	}
	if code.Consumed {
		err = ErrUsedCode
		return
	}

	code.Consumed = true
	err = p.Store.Update(code)
	if err != nil {
		return
	}

	prin, err := p.PasswordAuthProvider.GetPrincipalByID(code.PrincipalID)
	if err != nil {
		return
	}
	userID := prin.PrincipalUserID()

	resetPwdCtx := password.ResetPasswordRequestContext{
		PasswordChecker:      p.PasswordChecker,
		PasswordAuthProvider: p.PasswordAuthProvider,
	}

	err = resetPwdCtx.ExecuteWithUserID(newPassword, userID)
	if err != nil {
		return
	}

	var authInfo authinfo.AuthInfo
	err = p.AuthInfoStore.GetAuth(userID, &authInfo)
	if err != nil {
		return
	}

	userProfile, err := p.UserProfileStore.GetUserProfile(userID)
	if err != nil {
		return
	}

	user := model.NewUser(authInfo, userProfile)

	err = p.HookProvider.DispatchEvent(
		event.PasswordUpdateEvent{
			Reason: event.PasswordUpdateReasonResetPassword,
			User:   user,
		},
		&user,
	)
	if err != nil {
		return
	}

	p.TaskQueue.Enqueue(async.TaskSpec{
		Name: taskspec.PwHousekeeperTaskName,
		Param: taskspec.PwHousekeeperTaskParam{
			AuthID: user.ID,
		},
	})

	return nil
}
<commit_msg>Mark reset password code as consumed at the end<commit_after>package forgotpassword

import (
	"net/url"
	"path"
	"time"

	"github.com/skygeario/skygear-server/pkg/auth/dependency/audit"
	"github.com/skygeario/skygear-server/pkg/auth/dependency/hook"
	"github.com/skygeario/skygear-server/pkg/auth/dependency/principal/password"
	"github.com/skygeario/skygear-server/pkg/auth/dependency/urlprefix"
	"github.com/skygeario/skygear-server/pkg/auth/dependency/userprofile"
	"github.com/skygeario/skygear-server/pkg/auth/event"
	"github.com/skygeario/skygear-server/pkg/auth/model"
	taskspec "github.com/skygeario/skygear-server/pkg/auth/task/spec"
	"github.com/skygeario/skygear-server/pkg/core/async"
	"github.com/skygeario/skygear-server/pkg/core/auth/authinfo"
	"github.com/skygeario/skygear-server/pkg/core/auth/metadata"
	"github.com/skygeario/skygear-server/pkg/core/config"
	"github.com/skygeario/skygear-server/pkg/core/mail"
	"github.com/skygeario/skygear-server/pkg/core/sms"
	"github.com/skygeario/skygear-server/pkg/core/template"
	coretime "github.com/skygeario/skygear-server/pkg/core/time"
)

type Provider struct {
	AppName                     string
	EmailMessageConfiguration   config.EmailMessageConfiguration
	SMSMessageConfiguration     config.SMSMessageConfiguration
	ForgotPasswordConfiguration *config.ForgotPasswordConfiguration

	Store Store

	AuthInfoStore        authinfo.Store
	UserProfileStore     userprofile.Store
	PasswordAuthProvider password.Provider
	PasswordChecker      *audit.PasswordChecker
	HookProvider         hook.Provider
	TimeProvider         coretime.Provider
	URLPrefixProvider    urlprefix.Provider
	TemplateEngine       *template.Engine
	MailSender           mail.Sender
	SMSClient            sms.Client
	TaskQueue            async.Queue
}

// SendCode checks if loginID is an existing login ID.
// For each matched login ID, a code is generated.
// The code expires after a specific time.
// The code becomes invalid if it is consumed.
// Finally the code is sent to the login ID asynchronously.
func (p *Provider) SendCode(loginID string) (err error) {
	// TODO(forgotpassword): Test SendCode
	prins, err := p.PasswordAuthProvider.GetPrincipalsByLoginID("", loginID)
	if err != nil {
		return
	}

	for _, prin := range prins {
		email := p.PasswordAuthProvider.CheckLoginIDKeyType(prin.LoginIDKey, metadata.Email)
		phone := p.PasswordAuthProvider.CheckLoginIDKeyType(prin.LoginIDKey, metadata.Phone)

		if !email && !phone {
			continue
		}

		code, codeStr := p.newCode(prin)

		err = p.Store.Create(code)
		if err != nil {
			return
		}

		if email {
			err = p.sendEmail(prin.LoginID, codeStr)
			if err != nil {
				return
			}
		}

		if phone {
			err = p.sendSMS(prin.LoginID, codeStr)
			if err != nil {
				return
			}
		}
	}

	return
}

func (p *Provider) newCode(prin *password.Principal) (code *Code, codeStr string) {
	createdAt := p.TimeProvider.NowUTC()
	codeStr = GenerateCode()
	expireAt := createdAt.Add(time.Duration(p.ForgotPasswordConfiguration.ResetCodeLifetime) * time.Second)
	code = &Code{
		CodeHash:    HashCode(codeStr),
		PrincipalID: prin.ID,
		CreatedAt:   createdAt,
		ExpireAt:    expireAt,
		Consumed:    false,
	}
	return
}

func (p *Provider) sendEmail(email string, code string) (err error) {
	u := p.makeURL(code)

	data := map[string]interface{}{
		"appname": p.AppName,
		"email":   email,
		"code":    code,
		"link":    u.String(),
	}

	textBody, err := p.TemplateEngine.RenderTemplate(
		TemplateItemTypeForgotPasswordEmailTXT,
		data,
		template.ResolveOptions{},
	)
	if err != nil {
		return
	}

	htmlBody, err := p.TemplateEngine.RenderTemplate(
		TemplateItemTypeForgotPasswordEmailHTML,
		data,
		template.ResolveOptions{},
	)
	if err != nil {
		return
	}

	messageConfig := config.NewEmailMessageConfiguration(
		p.EmailMessageConfiguration,
		p.ForgotPasswordConfiguration.EmailMessage,
	)
	err = p.MailSender.Send(mail.SendOptions{
		MessageConfig: messageConfig,
		Recipient:     email,
		TextBody:      textBody,
		HTMLBody:      htmlBody,
	})
	if err != nil {
		return
	}

	return
}

func (p *Provider) sendSMS(phone string, code string) (err error) {
	u := p.makeURL(code)

	data := map[string]interface{}{
		"appname": p.AppName,
		"code":    code,
		"link":    u.String(),
	}

	body, err := p.TemplateEngine.RenderTemplate(
		TemplateItemTypeForgotPasswordSMSTXT,
		data,
		template.ResolveOptions{},
	)
	if err != nil {
		return
	}

	messageConfig := config.NewSMSMessageConfiguration(
		p.SMSMessageConfiguration,
		p.ForgotPasswordConfiguration.SMSMessage,
	)
	err = p.SMSClient.Send(sms.SendOptions{
		MessageConfig: messageConfig,
		To:            phone,
		Body:          body,
	})
	if err != nil {
		return
	}

	return
}

func (p *Provider) makeURL(code string) *url.URL {
	u := *p.URLPrefixProvider.Value()
	// /reset_password is an endpoint of Auth UI.
	u.Path = path.Join(u.Path, "reset_password")
	u.RawQuery = url.Values{
		"code": []string{code},
	}.Encode()
	return &u
}

// ResetPassword consumes code and reset password to newPassword.
// If the code is invalid, ErrInvalidCode is returned.
// If the code is found but expired, ErrExpiredCode is returned.
// if the code is found but used, ErrUsedCode is returned.
// Otherwise, the password is reset to newPassword.
// newPassword is checked against the password policy so
// password policy error may also be returned.
func (p *Provider) ResetPassword(codeStr string, newPassword string) (err error) {
	codeHash := HashCode(codeStr)
	code, err := p.Store.Get(codeHash)
	if err != nil {
		return
	}

	now := p.TimeProvider.NowUTC()
	if now.After(code.ExpireAt) {
		err = ErrExpiredCode
		return
	}
	if code.Consumed {
		err = ErrUsedCode
		return
	}

	prin, err := p.PasswordAuthProvider.GetPrincipalByID(code.PrincipalID)
	if err != nil {
		return
	}
	userID := prin.PrincipalUserID()

	resetPwdCtx := password.ResetPasswordRequestContext{
		PasswordChecker:      p.PasswordChecker,
		PasswordAuthProvider: p.PasswordAuthProvider,
	}

	err = resetPwdCtx.ExecuteWithUserID(newPassword, userID)
	if err != nil {
		return
	}

	var authInfo authinfo.AuthInfo
	err = p.AuthInfoStore.GetAuth(userID, &authInfo)
	if err != nil {
		return
	}

	userProfile, err := p.UserProfileStore.GetUserProfile(userID)
	if err != nil {
		return
	}

	user := model.NewUser(authInfo, userProfile)

	err = p.HookProvider.DispatchEvent(
		event.PasswordUpdateEvent{
			Reason: event.PasswordUpdateReasonResetPassword,
			User:   user,
		},
		&user,
	)
	if err != nil {
		return
	}

	// We have to mark the code as consumed at the end
	// because if we mark it at the beginning,
	// the code will be consumed if the new password violates
	// the password policy.
	code.Consumed = true
	err = p.Store.Update(code)
	if err != nil {
		return
	}

	p.TaskQueue.Enqueue(async.TaskSpec{
		Name: taskspec.PwHousekeeperTaskName,
		Param: taskspec.PwHousekeeperTaskParam{
			AuthID: user.ID,
		},
	})

	return nil
}
<|endoftext|>