// Copyright 2014 Manu Martinez-Almeida. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. package binding import ( "encoding" "errors" "fmt" "mime/multipart" "reflect" "strconv" "strings" "time" "github.com/gin-gonic/gin/internal/bytesconv" "github.com/gin-gonic/gin/internal/json" ) var ( errUnknownType = errors.New("unknown type") // ErrConvertMapStringSlice can not convert to map[string][]string ErrConvertMapStringSlice = errors.New("can not convert to map slices of strings") // ErrConvertToMapString can not convert to map[string]string ErrConvertToMapString = errors.New("can not convert to map of strings") ) func mapURI(ptr any, m map[string][]string) error { return mapFormByTag(ptr, m, "uri") } func mapForm(ptr any, form map[string][]string) error { return mapFormByTag(ptr, form, "form") } func MapFormWithTag(ptr any, form map[string][]string, tag string) error { return mapFormByTag(ptr, form, tag) } var emptyField = reflect.StructField{} func mapFormByTag(ptr any, form map[string][]string, tag string) error { // Check if ptr is a map ptrVal := reflect.ValueOf(ptr) var pointed any if ptrVal.Kind() == reflect.Ptr { ptrVal = ptrVal.Elem() pointed = ptrVal.Interface() } if ptrVal.Kind() == reflect.Map && ptrVal.Type().Key().Kind() == reflect.String { if pointed != nil { ptr = pointed } return setFormMap(ptr, form) } return mappingByPtr(ptr, formSource(form), tag) } // setter tries to set value on a walking by fields of a struct type setter interface { TrySet(value reflect.Value, field reflect.StructField, key string, opt setOptions) (isSet bool, err error) } type formSource map[string][]string var _ setter = formSource(nil) // TrySet tries to set a value by request's form source (like map[string][]string) func (form formSource) TrySet(value reflect.Value, field reflect.StructField, tagValue string, opt setOptions) (isSet bool, err error) { return setByForm(value, field, form, tagValue, opt) } func mappingByPtr(ptr any, setter setter, tag string) error { _, err := mapping(reflect.ValueOf(ptr), emptyField, setter, tag) return err } func mapping(value reflect.Value, field reflect.StructField, setter setter, tag string) (bool, error) { if field.Tag.Get(tag) == "-" { // just ignoring this field return false, nil } vKind := value.Kind() if vKind == reflect.Ptr { var isNew bool vPtr := value if value.IsNil() { isNew = true vPtr = reflect.New(value.Type().Elem()) } isSet, err := mapping(vPtr.Elem(), field, setter, tag) if err != nil { return false, err } if isNew && isSet { value.Set(vPtr) } return isSet, nil } if vKind != reflect.Struct || !field.Anonymous { ok, err := tryToSetValue(value, field, setter, tag) if err != nil { return false, err } if ok { return true, nil } } if vKind == reflect.Struct { tValue := value.Type() var isSet bool for i := 0; i < value.NumField(); i++ { sf := tValue.Field(i) if sf.PkgPath != "" && !sf.Anonymous { // unexported continue } ok, err := mapping(value.Field(i), sf, setter, tag) if err != nil { return false, err } isSet = isSet || ok } return isSet, nil } return false, nil } type setOptions struct { isDefaultExists bool defaultValue string parser string } func tryToSetValue(value reflect.Value, field reflect.StructField, setter setter, tag string) (bool, error) { var tagValue string var setOpt setOptions tagValue = field.Tag.Get(tag) tagValue, opts := head(tagValue, ",") if tagValue == "" { // default value is FieldName tagValue = field.Name } if tagValue == "" { // when field is "emptyField" variable return false, nil } var opt string for len(opts) > 0 { opt, opts = head(opts, ",") if k, v := head(opt, "="); k == "default" { setOpt.isDefaultExists = true setOpt.defaultValue = v // convert semicolon-separated default values to csv-separated values for processing in setByForm if field.Type.Kind() == reflect.Slice || field.Type.Kind() == reflect.Array { cfTag := field.Tag.Get("collection_format") if cfTag == "" || cfTag == "multi" || cfTag == "csv" { setOpt.defaultValue = strings.ReplaceAll(v, ";", ",") } } } else if k, v = head(opt, "="); k == "parser" { setOpt.parser = v } } return setter.TrySet(value, field, tagValue, setOpt) } // BindUnmarshaler is the interface used to wrap the UnmarshalParam method. type BindUnmarshaler interface { // UnmarshalParam decodes and assigns a value from an form or query param. UnmarshalParam(param string) error } // trySetCustom tries to set a custom type value // If the value implements the BindUnmarshaler interface, it will be used to set the value, we will return `true` // to skip the default value setting. func trySetCustom(val string, value reflect.Value) (isSet bool, err error) { switch v := value.Addr().Interface().(type) { case BindUnmarshaler: return true, v.UnmarshalParam(val) } return false, nil } // trySetUsingParser tries to set a custom type value based on the presence of the "parser" tag on the field. // If the parser tag does not exist or does not match any of the supported parsers, gin will skip over this. func trySetUsingParser(val string, value reflect.Value, parser string) (isSet bool, err error) { switch parser { case "encoding.TextUnmarshaler": v, ok := value.Addr().Interface().(encoding.TextUnmarshaler) if !ok { return false, nil } return true, v.UnmarshalText([]byte(val)) } return false, nil } func trySplit(vs []string, field reflect.StructField) (newVs []string, err error) { cfTag := field.Tag.Get("collection_format") if cfTag == "" || cfTag == "multi" { return vs, nil } var sep string switch cfTag { case "csv": sep = "," case "ssv": sep = " " case "tsv": sep = "\t" case "pipes": sep = "|" default: return vs, fmt.Errorf("%s is not supported in the collection_format. (multi, csv, ssv, tsv, pipes)", cfTag) } totalLength := 0 for _, v := range vs { totalLength += strings.Count(v, sep) + 1 } newVs = make([]string, 0, totalLength) for _, v := range vs { newVs = append(newVs, strings.Split(v, sep)...) } return newVs, nil } func setByForm(value reflect.Value, field reflect.StructField, form map[string][]string, tagValue string, opt setOptions) (isSet bool, err error) { vs, ok := form[tagValue] if !ok && !opt.isDefaultExists { return false, nil } switch value.Kind() { case reflect.Slice: if !ok || len(vs) == 0 || (len(vs) > 0 && vs[0] == "") { vs = []string{opt.defaultValue} // pre-process the default value for multi if present cfTag := field.Tag.Get("collection_format") if cfTag == "" || cfTag == "multi" { vs = strings.Split(opt.defaultValue, ",") } } if ok, err = trySetUsingParser(vs[0], value, opt.parser); ok { return ok, err } else if ok, err = trySetCustom(vs[0], value); ok { return ok, err } if vs, err = trySplit(vs, field); err != nil { return false, err } return true, setSlice(vs, value, field, opt) case reflect.Array: if !ok || len(vs) == 0 || (len(vs) > 0 && vs[0] == "") { vs = []string{opt.defaultValue} // pre-process the default value for multi if present cfTag := field.Tag.Get("collection_format") if cfTag == "" || cfTag == "multi" { vs = strings.Split(opt.defaultValue, ",") } } if ok, err = trySetUsingParser(vs[0], value, opt.parser); ok { return ok, err } else if ok, err = trySetCustom(vs[0], value); ok { return ok, err } if vs, err = trySplit(vs, field); err != nil { return false, err } if len(vs) != value.Len() { return false, fmt.Errorf("%q is not valid value for %s", vs, value.Type().String()) } return true, setArray(vs, value, field, opt) default: var val string if !ok || len(vs) == 0 || (len(vs) > 0 && vs[0] == "") { val = opt.defaultValue } else if len(vs) > 0 { val = vs[0] } if ok, err = trySetUsingParser(val, value, opt.parser); ok { return ok, err } else if ok, err = trySetCustom(val, value); ok { return ok, err } return true, setWithProperType(val, value, field, opt) } } func setWithProperType(val string, value reflect.Value, field reflect.StructField, opt setOptions) error { // this if-check is required for parsing nested types like []MyId, where MyId is [12]byte if ok, err := trySetUsingParser(val, value, opt.parser); ok { return err } else if ok, err = trySetCustom(val, value); ok { return err } switch value.Kind() { case reflect.Int: return setIntField(val, 0, value) case reflect.Int8: return setIntField(val, 8, value) case reflect.Int16: return setIntField(val, 16, value) case reflect.Int32: return setIntField(val, 32, value) case reflect.Int64: switch value.Interface().(type) { case time.Duration: return setTimeDuration(val, value) } return setIntField(val, 64, value) case reflect.Uint: return setUintField(val, 0, value) case reflect.Uint8: return setUintField(val, 8, value) case reflect.Uint16: return setUintField(val, 16, value) case reflect.Uint32: return setUintField(val, 32, value) case reflect.Uint64: return setUintField(val, 64, value) case reflect.Bool: return setBoolField(val, value) case reflect.Float32: return setFloatField(val, 32, value) case reflect.Float64: return setFloatField(val, 64, value) case reflect.String: value.SetString(val) case reflect.Struct: switch value.Interface().(type) { case time.Time: return setTimeField(val, field, value) case multipart.FileHeader: return nil } return json.Unmarshal(bytesconv.StringToBytes(val), value.Addr().Interface()) case reflect.Map: return json.Unmarshal(bytesconv.StringToBytes(val), value.Addr().Interface()) case reflect.Ptr: if !value.Elem().IsValid() { value.Set(reflect.New(value.Type().Elem())) } return setWithProperType(val, value.Elem(), field, opt) default: return errUnknownType } return nil } func setIntField(val string, bitSize int, field reflect.Value) error { if val == "" { val = "0" } intVal, err := strconv.ParseInt(val, 10, bitSize) if err == nil { field.SetInt(intVal) } return err } func setUintField(val string, bitSize int, field reflect.Value) error { if val == "" { val = "0" } uintVal, err := strconv.ParseUint(val, 10, bitSize) if err == nil { field.SetUint(uintVal) } return err } func setBoolField(val string, field reflect.Value) error { if val == "" { val = "false" } boolVal, err := strconv.ParseBool(val) if err == nil { field.SetBool(boolVal) } return err } func setFloatField(val string, bitSize int, field reflect.Value) error { if val == "" { val = "0.0" } floatVal, err := strconv.ParseFloat(val, bitSize) if err == nil { field.SetFloat(floatVal) } return err } func setTimeField(val string, structField reflect.StructField, value reflect.Value) error { timeFormat := structField.Tag.Get("time_format") if timeFormat == "" { timeFormat = time.RFC3339 } switch tf := strings.ToLower(timeFormat); tf { case "unix", "unixmilli", "unixmicro", "unixnano": tv, err := strconv.ParseInt(val, 10, 64) if err != nil { return err } var t time.Time switch tf { case "unix": t = time.Unix(tv, 0) case "unixmilli": t = time.UnixMilli(tv) case "unixmicro": t = time.UnixMicro(tv) default: t = time.Unix(0, tv) } value.Set(reflect.ValueOf(t)) return nil } if val == "" { value.Set(reflect.ValueOf(time.Time{})) return nil } l := time.Local if isUTC, _ := strconv.ParseBool(structField.Tag.Get("time_utc")); isUTC { l = time.UTC } if locTag := structField.Tag.Get("time_location"); locTag != "" { loc, err := time.LoadLocation(locTag) if err != nil { return err } l = loc } t, err := time.ParseInLocation(timeFormat, val, l) if err != nil { return err } value.Set(reflect.ValueOf(t)) return nil } func setArray(vals []string, value reflect.Value, field reflect.StructField, opt setOptions) error { for i, s := range vals { err := setWithProperType(s, value.Index(i), field, opt) if err != nil { return err } } return nil } func setSlice(vals []string, value reflect.Value, field reflect.StructField, opt setOptions) error { slice := reflect.MakeSlice(value.Type(), len(vals), len(vals)) err := setArray(vals, slice, field, opt) if err != nil { return err } value.Set(slice) return nil } func setTimeDuration(val string, value reflect.Value) error { d, err := time.ParseDuration(val) if err != nil { return err } value.Set(reflect.ValueOf(d)) return nil } func head(str, sep string) (head string, tail string) { head, tail, _ = strings.Cut(str, sep) return head, tail } func setFormMap(ptr any, form map[string][]string) error { el := reflect.TypeOf(ptr).Elem() if el.Kind() == reflect.Slice { ptrMap, ok := ptr.(map[string][]string) if !ok { return ErrConvertMapStringSlice } for k, v := range form { ptrMap[k] = v } return nil } ptrMap, ok := ptr.(map[string]string) if !ok { return ErrConvertToMapString } for k, v := range form { ptrMap[k] = v[len(v)-1] // pick last } return nil }