163 lines
4.3 KiB
Go
163 lines
4.3 KiB
Go
package config
|
|
|
|
import (
|
|
"fmt"
|
|
"math"
|
|
"net"
|
|
"net/url"
|
|
"reflect"
|
|
"time"
|
|
"unicode/utf8"
|
|
)
|
|
|
|
const maxValueDepth = 128
|
|
|
|
var (
|
|
timeType = reflect.TypeFor[time.Time]()
|
|
durationType = reflect.TypeFor[time.Duration]()
|
|
ipType = reflect.TypeFor[net.IP]()
|
|
ipNetType = reflect.TypeFor[net.IPNet]()
|
|
urlType = reflect.TypeFor[url.URL]()
|
|
userInfoType = reflect.TypeFor[url.Userinfo]()
|
|
)
|
|
|
|
func atomicType(t reflect.Type) bool {
|
|
for t.Kind() == reflect.Pointer {
|
|
t = t.Elem()
|
|
}
|
|
return t == timeType || t == ipType || t == ipNetType || t == urlType
|
|
}
|
|
|
|
// copyValue validates the supported value graph and detaches mutable containers.
|
|
// The depth bound rejects cycles without retaining a global identity cache.
|
|
func copyValue(v any) (any, error) {
|
|
if v == nil {
|
|
return nil, nil
|
|
}
|
|
out, err := copyReflect(reflect.ValueOf(v), 0)
|
|
if err != nil {
|
|
return nil, wrapError(ErrTypeMismatch, err)
|
|
}
|
|
return out.Interface(), nil
|
|
}
|
|
|
|
func copyReflect(v reflect.Value, depth int) (reflect.Value, error) {
|
|
if depth > maxValueDepth {
|
|
return reflect.Value{}, fmt.Errorf("value nesting exceeds %d (possible cycle)", maxValueDepth)
|
|
}
|
|
if v.Type() == ipType && v.Len() != 0 && v.Interface().(net.IP).To16() == nil {
|
|
return reflect.Value{}, fmt.Errorf("invalid IP value")
|
|
}
|
|
if v.Type() == ipNetType {
|
|
n := v.Interface().(net.IPNet)
|
|
_, bits := n.Mask.Size()
|
|
if (len(n.IP) != 0 || len(n.Mask) != 0) && (n.IP.To16() == nil || bits == 0 || bits == 32 && n.IP.To4() == nil) {
|
|
return reflect.Value{}, fmt.Errorf("invalid CIDR value")
|
|
}
|
|
}
|
|
if v.Type() == timeType {
|
|
if _, err := v.Interface().(time.Time).MarshalText(); err != nil {
|
|
return reflect.Value{}, err
|
|
}
|
|
}
|
|
out := reflect.New(v.Type()).Elem()
|
|
switch v.Kind() {
|
|
case reflect.Interface, reflect.Pointer:
|
|
if v.IsNil() {
|
|
return out, nil
|
|
}
|
|
elem, err := copyReflect(v.Elem(), depth+1)
|
|
if err != nil {
|
|
return reflect.Value{}, err
|
|
}
|
|
if v.Kind() == reflect.Pointer {
|
|
out.Set(reflect.New(v.Type().Elem()))
|
|
out.Elem().Set(elem)
|
|
} else {
|
|
out.Set(elem)
|
|
}
|
|
case reflect.Map:
|
|
if v.Type().Key().Kind() != reflect.String {
|
|
return reflect.Value{}, fmt.Errorf("map keys must be strings")
|
|
}
|
|
if v.IsNil() {
|
|
return out, nil
|
|
}
|
|
out.Set(reflect.MakeMapWithSize(v.Type(), v.Len()))
|
|
it := v.MapRange()
|
|
for it.Next() {
|
|
if !utf8.ValidString(it.Key().String()) {
|
|
return reflect.Value{}, fmt.Errorf("invalid UTF-8 map key")
|
|
}
|
|
x, err := copyReflect(it.Value(), depth+1)
|
|
if err != nil {
|
|
return reflect.Value{}, err
|
|
}
|
|
out.SetMapIndex(it.Key(), x)
|
|
}
|
|
case reflect.Slice, reflect.Array:
|
|
if v.Kind() == reflect.Slice {
|
|
if v.IsNil() {
|
|
return out, nil
|
|
}
|
|
out.Set(reflect.MakeSlice(v.Type(), v.Len(), v.Len()))
|
|
}
|
|
for i := 0; i < v.Len(); i++ {
|
|
x, err := copyReflect(v.Index(i), depth+1)
|
|
if err != nil {
|
|
return reflect.Value{}, err
|
|
}
|
|
out.Index(i).Set(x)
|
|
}
|
|
case reflect.Struct:
|
|
// These standard-library values contain immutable private state.
|
|
if v.Type() == timeType || v.Type() == userInfoType {
|
|
out.Set(v)
|
|
return out, nil
|
|
}
|
|
exported := false
|
|
for i := 0; i < v.NumField(); i++ {
|
|
if !v.Type().Field(i).IsExported() {
|
|
continue
|
|
}
|
|
exported = true
|
|
x, err := copyReflect(v.Field(i), depth+1)
|
|
if err != nil {
|
|
return reflect.Value{}, err
|
|
}
|
|
out.Field(i).Set(x)
|
|
}
|
|
if !exported && v.NumField() != 0 {
|
|
return reflect.Value{}, fmt.Errorf("unsupported private state in %s", v.Type())
|
|
}
|
|
case reflect.String:
|
|
if len(v.String()) > MaxValueSize {
|
|
return reflect.Value{}, ErrValueSize
|
|
}
|
|
if !utf8.ValidString(v.String()) {
|
|
return reflect.Value{}, fmt.Errorf("invalid UTF-8 string")
|
|
}
|
|
out.Set(v)
|
|
case reflect.Float32, reflect.Float64:
|
|
if math.IsNaN(v.Float()) || math.IsInf(v.Float(), 0) {
|
|
return reflect.Value{}, fmt.Errorf("non-finite float")
|
|
}
|
|
out.Set(v)
|
|
case reflect.Bool, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
|
|
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
|
out.Set(v)
|
|
default:
|
|
return reflect.Value{}, fmt.Errorf("unsupported value type %s", v.Type())
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
// cloneOwned is used only on values already accepted by copyValue.
|
|
func cloneOwned(v any) any {
|
|
out, err := copyValue(v)
|
|
if err != nil {
|
|
panic("config: invalid internal value: " + err.Error())
|
|
}
|
|
return out
|
|
}
|