package form import ( "bytes" "fmt" "net/url" "reflect" "strconv" "strings" "sync" ) const tagName = "form" // Appender is the interface implemented by types that can append themselves to // a collection of form values. // // This is usually something that shouldn't be used, but is needed in a few // places where authors deviated from norms while implementing various // parameters. type Appender interface { // AppendTo is invoked by the form package on any types found to implement // Appender so that they have a chance to encode themselves. Note that // AppendTo is called in addition to normal encoding, so other form tags on // the struct are still fair game. AppendTo(values *Values, keyParts []string) } // encoderFunc is used to encode any type from a request. // // A note about encodeZero: Since some types in the Stripe API are defaulted to // non-zero values, and Go defaults types to their zero values, any type that // has a Stripe API default of a non-zero value is defined as a Go pointer, // meaning nil defaults to the Stripe API non-zero value. To override this, a // check is made to see if the value is the zero-value for that type. If it is // and encodeZero is true, it's encoded. This is ignored as a parameter when // dealing with types like structs, where the decision cannot be made // preemptively. type encoderFunc func(values *Values, v reflect.Value, keyParts []string, encodeZero bool, options *formOptions) // field represents a single field found in a struct. It caches information // about that field so that we can make encoding faster. type field struct { formName string index int isAppender bool isPtr bool options *formOptions } type formOptions struct { // Empty indicates that a field's value should be emptied in that its value // should be an empty string. It's used to workaround the fact that an // empty string is a string's zero value and wouldn't normally be encoded. Empty bool // HighPrecision indicates that this field should be treated as a high // precision decimal, a decimal whose precision is important to the API and // which we want to encode as accurately as possible. // // All parameters are encoded using form encoding, so this of course // encodes a value to a string, but notably, these high precision fields // are sent back as strings in JSON, even though they might be surfaced as // floats in this library. // // This isn't a perfect abstraction because floats are not precise in // nature, and we might be better-advised to use a real high-precision data // type like `big.Float`. That said, we suspect that this will be an // adequate solution in the vast majority of cases and has a usability // benefit, so we've gone this route. HighPrecision bool } type structEncoder struct { fields []*field fieldEncs []encoderFunc } func (se *structEncoder) encode(values *Values, v reflect.Value, keyParts []string, _ bool, _ *formOptions) { for i, f := range se.fields { var fieldKeyParts []string fieldV := v.Field(f.index) // The wildcard on a form tag is a "special" value: it indicates a // struct field that we should recurse into, but for which no part // should be added to the key parts, meaning that its own subfields // will be named at the same level as with the fields of the // current structure. if f.formName == "*" { fieldKeyParts = keyParts } else { fieldKeyParts = append(keyParts, f.formName) } se.fieldEncs[i](values, fieldV, fieldKeyParts, f.isPtr, f.options) if f.isAppender && (!f.isPtr || !fieldV.IsNil()) { fieldV.Interface().(Appender).AppendTo(values, fieldKeyParts) } } } // --- // Strict enables strict mode wherein the package will panic on an AppendTo // function if it finds that a tag string was malformed. var Strict = false var encoderCache struct { m map[reflect.Type]encoderFunc mu sync.RWMutex // for coordinating concurrent operations on m } var structCache struct { m map[reflect.Type]*structEncoder mu sync.RWMutex // for coordinating concurrent operations on m } // AppendTo uses reflection to form encode into the given values collection // based off the form tags that it defines. func AppendTo(values *Values, i interface{}) { reflectValue(values, reflect.ValueOf(i), false, nil) } // AppendToPrefixed is the same as AppendTo, but it allows a slice of key parts // to be specified to prefix the form values. // // I was hoping not to have to expose this function, but I ended up needing it // for recipients. Recipients is going away, and when it does, we can probably // remove it again. func AppendToPrefixed(values *Values, i interface{}, keyParts []string) { reflectValue(values, reflect.ValueOf(i), false, keyParts) } // FormatKey takes a series of key parts that may be parameter keyParts, map keys, // or array indices and unifies them into a single key suitable for Stripe's // style of form encoding. func FormatKey(parts []string) string { if len(parts) < 1 { panic("Not allowed 0-length parts slice") } key := parts[0] for i := 1; i < len(parts); i++ { key += "[" + parts[i] + "]" } return key } // --- func boolEncoder(values *Values, v reflect.Value, keyParts []string, encodeZero bool, options *formOptions) { val := v.Bool() if !val && !encodeZero { return } if options != nil { switch { case options.Empty: values.Add(FormatKey(keyParts), "") } } else { values.Add(FormatKey(keyParts), strconv.FormatBool(val)) } } func buildArrayOrSliceEncoder(t reflect.Type) encoderFunc { // Gets an encoder for the type that the array or slice will hold elemF := getCachedOrBuildTypeEncoder(t.Elem()) return func(values *Values, v reflect.Value, keyParts []string, _ bool, options *formOptions) { // When encountering a slice that's been explicitly set (i.e. non-nil) // and which is of 0 length, we take this as an indication that the // user is trying to zero the API array. See the `additional_owners` // property under `legal_entity` on account for an example of somewhere // that this is useful. // // This only works for a slice (and not an array) because even a zeroed // array always has a fixed length. if t.Kind() == reflect.Slice && !v.IsNil() && v.Len() == 0 { values.Add(FormatKey(keyParts), "") return } var arrNames []string for i := 0; i < v.Len(); i++ { arrNames = append(keyParts, strconv.Itoa(i)) indexV := v.Index(i) elemF(values, indexV, arrNames, indexV.Kind() == reflect.Ptr, nil) if isAppender(indexV.Type()) && !indexV.IsNil() { indexV.Interface().(Appender).AppendTo(values, arrNames) } } } } func buildPtrEncoder(t reflect.Type) encoderFunc { // Gets an encoder for the type that the pointer wraps elemF := getCachedOrBuildTypeEncoder(t.Elem()) return func(values *Values, v reflect.Value, keyParts []string, _ bool, options *formOptions) { // We take a nil to mean that the property wasn't set, so ignore it in // the final encoding. if v.IsNil() { return } // Handle "zeroing" an array stored as a pointer to a slice. See // comment in `buildArrayOrSliceEncoder` above. if t.Elem().Kind() == reflect.Slice && v.Elem().Len() == 0 { values.Add(FormatKey(keyParts), "") return } // Otherwise, call into the appropriate encoder for the pointer's type. elemF(values, v.Elem(), keyParts, true, options) } } func buildStructEncoder(t reflect.Type) encoderFunc { se := getCachedOrBuildStructEncoder(t) return se.encode } func float32Encoder(values *Values, v reflect.Value, keyParts []string, encodeZero bool, options *formOptions) { val := v.Float() if val == 0.0 && !encodeZero { return } prec := 4 if options != nil && options.HighPrecision { // Special value that tells Go to format the float in as few required // digits as necessary for it to be successfully parsable from a string // back to the same original number. prec = -1 } values.Add(FormatKey(keyParts), strconv.FormatFloat(val, 'f', prec, 32)) } func float64Encoder(values *Values, v reflect.Value, keyParts []string, encodeZero bool, options *formOptions) { val := v.Float() if val == 0.0 && !encodeZero { return } prec := 4 if options != nil && options.HighPrecision { // Special value that tells Go to format the float in as few required // digits as necessary for it to be successfully parsable from a string // back to the same original number. prec = -1 } values.Add(FormatKey(keyParts), strconv.FormatFloat(val, 'f', prec, 64)) } func getCachedOrBuildStructEncoder(t reflect.Type) *structEncoder { // Just acquire a read lock when extracting a value (note that in Go, a map // cannot be read while it's also being written). structCache.mu.RLock() f := structCache.m[t] structCache.mu.RUnlock() if f != nil { return f } // We do the work to get the encoder without holding a lock. This could // result in duplicate work, but it will help us avoid a deadlock. Encoders // may be built and stored recursively in the cases of something like an // array or slice, so we need to make sure that this function is properly // re-entrant. f = makeStructEncoder(t) structCache.mu.Lock() defer structCache.mu.Unlock() if structCache.m == nil { structCache.m = make(map[reflect.Type]*structEncoder) } structCache.m[t] = f return f } // getCachedOrBuildTypeEncoder tries to get an encoderFunc for the type from // the cache, and falls back to building one if there wasn't a cached one // available. If an encoder is built, it's stored back to the cache. func getCachedOrBuildTypeEncoder(t reflect.Type) encoderFunc { // Just acquire a read lock when extracting a value (note that in Go, a map // cannot be read while it's also being written). encoderCache.mu.RLock() f := encoderCache.m[t] encoderCache.mu.RUnlock() if f != nil { return f } // We do the work to get the encoder without holding a lock. This could // result in duplicate work, but it will help us avoid a deadlock. Encoders // may be built and stored recursively in the cases of something like an // array or slice, so we need to make sure that this function is properly // re-entrant. f = makeTypeEncoder(t) encoderCache.mu.Lock() defer encoderCache.mu.Unlock() if encoderCache.m == nil { encoderCache.m = make(map[reflect.Type]encoderFunc) } encoderCache.m[t] = f return f } func intEncoder(values *Values, v reflect.Value, keyParts []string, encodeZero bool, options *formOptions) { val := v.Int() if val == 0 && !encodeZero { return } values.Add(FormatKey(keyParts), strconv.FormatInt(val, 10)) } func interfaceEncoder(values *Values, v reflect.Value, keyParts []string, encodeZero bool, _ *formOptions) { // interfaceEncoder never encodes a `nil`, but it will pass through an // `encodeZero` value into its chained encoder if v.IsNil() { return } reflectValue(values, v.Elem(), encodeZero, keyParts) } func isAppender(t reflect.Type) bool { return t.Implements(reflect.TypeOf((*Appender)(nil)).Elem()) } func mapEncoder(values *Values, v reflect.Value, keyParts []string, _ bool, _ *formOptions) { for _, keyVal := range v.MapKeys() { if Strict && keyVal.Kind() != reflect.String { panic("Don't support serializing maps with non-string keys") } // Unlike a property on a struct which will contain a zero value even // if never set, any value found in a map has been explicitly set, so // we always make an effort to encode them, even if a zero value // (that's why we pass through `true` here). reflectValue(values, v.MapIndex(keyVal), true, append(keyParts, keyVal.String())) } } func stringEncoder(values *Values, v reflect.Value, keyParts []string, encodeZero bool, options *formOptions) { val := v.String() if val == "" && !encodeZero { return } values.Add(FormatKey(keyParts), val) } func uintEncoder(values *Values, v reflect.Value, keyParts []string, encodeZero bool, options *formOptions) { val := v.Uint() if val == 0 && !encodeZero { return } values.Add(FormatKey(keyParts), strconv.FormatUint(val, 10)) } // reflectValue is roughly the shared entry point of any AppendTo functions. // It's also called recursively in cases where a precise type isn't yet known // and its encoding needs to be deferred down the chain; for example, when // encoding interface{} or the values in an array or map containing // interface{}. func reflectValue(values *Values, v reflect.Value, encodeZero bool, keyParts []string) { t := v.Type() f := getCachedOrBuildTypeEncoder(t) if f != nil { f(values, v, keyParts, encodeZero || v.Kind() == reflect.Ptr, nil) } if isAppender(t) { v.Interface().(Appender).AppendTo(values, keyParts) } } func makeStructEncoder(t reflect.Type) *structEncoder { // Don't specify capacity because we don't know how many fields are tagged with // `form` se := &structEncoder{} for i := 0; i < t.NumField(); i++ { reflectField := t.Field(i) tag := reflectField.Tag.Get(tagName) if Strict && tag == "" { panic(fmt.Sprintf( "All fields in structs to be form-encoded must have `form` tag; on: %s/%s "+ "(hint: use an explicit `form:\"-\"` if the field should not be encoded", t.Name(), reflectField.Name, )) } formName, options := parseTag(tag) // Like with encoding/json, a hyphen is an explicit way of saying // that this field should not be encoded if formName == "-" { continue } fldTyp := reflectField.Type fldKind := fldTyp.Kind() if Strict && options != nil { if options.Empty && fldKind != reflect.Bool { panic(fmt.Sprintf( "Cannot specify `empty` for non-boolean field; on: %s/%s", t.Name(), reflectField.Name, )) } var k reflect.Kind if fldKind == reflect.Ptr { k = fldTyp.Elem().Kind() } else { k = fldKind } fldIsFloat := k == reflect.Float32 || k == reflect.Float64 if options.HighPrecision && !fldIsFloat { panic(fmt.Sprintf( "Cannot specify `high_precision` for non-float field; on: %s/%s (%s)", t.Name(), reflectField.Name, fldTyp, )) } } se.fields = append(se.fields, &field{ formName: formName, index: i, isAppender: isAppender(fldTyp), isPtr: fldKind == reflect.Ptr, options: options, }) se.fieldEncs = append(se.fieldEncs, getCachedOrBuildTypeEncoder(fldTyp)) } return se } func makeTypeEncoder(t reflect.Type) encoderFunc { switch t.Kind() { case reflect.Array, reflect.Slice: return buildArrayOrSliceEncoder(t) case reflect.Bool: return boolEncoder case reflect.Float32: return float32Encoder case reflect.Float64: return float64Encoder case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: return intEncoder case reflect.Interface: return interfaceEncoder case reflect.Map: return mapEncoder case reflect.Ptr: return buildPtrEncoder(t) case reflect.String: return stringEncoder case reflect.Struct: return buildStructEncoder(t) case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: return uintEncoder } return nil } func parseTag(tag string) (string, *formOptions) { var options *formOptions parts := strings.Split(tag, ",") name := parts[0] for i := 1; i < len(parts); i++ { switch parts[i] { case "empty": if options == nil { options = &formOptions{} } options.Empty = true case "high_precision": if options == nil { options = &formOptions{} } options.HighPrecision = true default: if Strict { part := parts[i] if part == "" { part = "(empty)" } panic(fmt.Sprintf("Don't know how to handle form tag part: %s (tag: %s)", part, tag)) } } } return name, options } // --- // Values is a collection of values that can be submitted along with a // request that specifically allows for duplicate keys and encodes its entries // in the same order that they were added. type Values struct { values []formValue } // Add adds a key/value tuple to the form. func (f *Values) Add(key, val string) { f.values = append(f.values, formValue{key, val}) } // Encode encodes the keys and values into “URL encoded” form // ("bar=baz&foo=quux"). func (f *Values) Encode() string { var buf bytes.Buffer for _, v := range f.values { if buf.Len() > 0 { buf.WriteByte('&') } key := url.QueryEscape(v.Key) key = strings.Replace(key, "%5B", "[", -1) key = strings.Replace(key, "%5D", "]", -1) buf.WriteString(key) buf.WriteString("=") buf.WriteString(url.QueryEscape(v.Value)) } return buf.String() } // Empty returns true if no parameters have been set. func (f *Values) Empty() bool { return len(f.values) == 0 } // Set sets the first instance of a parameter for the given key to the given // value. If no parameters exist with the key, a new one is added. // // Note that Set is O(n) and may be quite slow for a very large parameter list. func (f *Values) Set(key, val string) { for i, v := range f.values { if v.Key == key { f.values[i].Value = val return } } f.Add(key, val) } // Get retrieves the list of values for the given key. If no values exist // for the key, nil will be returned. // // Note that Get is O(n) and may be quite slow for a very large parameter list. func (f *Values) Get(key string) []string { var results []string for i, v := range f.values { if v.Key == key { results = append(results, f.values[i].Value) } } return results } // ToValues converts an instance of Values into an instance of // url.Values. This can be useful in cases where it's useful to make an // unordered comparison of two sets of request values. // // Note that url.Values is incapable of representing certain Rack form types in // a cohesive way. For example, an array of maps in Rack is encoded with a // string like: // // arr[][foo]=foo0&arr[][bar]=bar0&arr[][foo]=foo1&arr[][bar]=bar1 // // Because url.Values is a map, values will be handled in a way that's grouped // by their key instead of in the order they were added. Therefore the above // may by encoded to something like (maps are unordered so the actual result is // somewhat non-deterministic): // // arr[][foo]=foo0&arr[][foo]=foo1&arr[][bar]=bar0&arr[][bar]=bar1 // // And thus result in an incorrect request to Stripe. func (f *Values) ToValues() url.Values { values := url.Values{} for _, v := range f.values { values.Add(v.Key, v.Value) } return values } // A key/value tuple for use in the Values type. type formValue struct { Key string Value string }