// 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 ( "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 } 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, ";", ",") } } } } 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 } 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. (csv, ssv, 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 { 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 = 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) case reflect.Array: if !ok { 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 = 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) default: var val string if !ok { val = opt.defaultValue } if len(vs) > 0 { val = vs[0] if val == "" { val = opt.defaultValue } } if ok, err := trySetCustom(val, value); ok { return ok, err } return true, setWithProperType(val, value, field) } } func setWithProperType(val string, value reflect.Value, field reflect.StructField) error { 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) 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 } if val == "" { value.Set(reflect.ValueOf(time.Time{})) return nil } switch tf := strings.ToLower(timeFormat); tf { case "unix", "unixnano": tv, err := strconv.ParseInt(val, 10, 64) if err != nil { return err } d := time.Duration(1) if tf == "unixnano" { d = time.Second } t := time.Unix(tv/int64(d), tv%int64(d)) value.Set(reflect.ValueOf(t)) 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) error { for i, s := range vals { err := setWithProperType(s, value.Index(i), field) if err != nil { return err } } return nil } func setSlice(vals []string, value reflect.Value, field reflect.StructField) error { slice := reflect.MakeSlice(value.Type(), len(vals), len(vals)) err := setArray(vals, slice, field) 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 }