// Package jsonvalue validates and defensively copies bounded JSON-compatible // value trees used by configuration, request, and prepared-state boundaries. package jsonvalue import ( "encoding/json" "fmt" "math" "reflect" "sort" ) const ( maxContainerDepth = 100 maxProducedNodes = 100_000 ) type visit struct { typ reflect.Type ptr uintptr } type traversalState struct { active map[visit]struct{} producedNodes int } // Copy validates and deeply copies a JSON-compatible value while preserving // compatible concrete map, slice, array, scalar, and number types. It rejects // cycles and values that exceed the package's traversal limits. func Copy(src any) (any, error) { return copyValue(reflect.ValueOf(src), "value", newTraversalState(), true, 0) } // CopyMap validates and deeply copies an extra-parameter map while preserving // compatible concrete map, slice, array, scalar, and number types. It rejects // empty object keys, cycles, and values that exceed the package's traversal // limits. func CopyMap(src map[string]any) (map[string]any, error) { if src == nil { return nil, nil } copied, err := copyValue(reflect.ValueOf(src), "extra_params", newTraversalState(), false, 0) if err != nil { return nil, err } out, ok := copied.(map[string]any) if !ok { return nil, fmt.Errorf("extra_params: expected object") } return out, nil } func copyValue( value reflect.Value, path string, state *traversalState, allowEmptyMapKeys bool, containerDepth int, ) (any, error) { resolved, cleanup, isNull, err := state.resolveIndirection(value, path) if err != nil { return nil, err } defer cleanup() if isNull { if err := state.produceNode(path); err != nil { return nil, err } return nil, nil } value = resolved if !value.CanInterface() { return nil, fmt.Errorf("%s: value cannot be copied", path) } if number, ok := value.Interface().(json.Number); ok { if !validJSONNumber(number) { return nil, fmt.Errorf("%s: invalid JSON number", path) } if err := state.produceNode(path); err != nil { return nil, err } return number, nil } switch value.Kind() { case reflect.Bool, reflect.String: if err := state.produceNode(path); err != nil { return nil, err } return value.Interface(), nil case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: if err := state.produceNode(path); err != nil { return nil, err } return value.Interface(), nil case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: if err := state.produceNode(path); err != nil { return nil, err } return value.Interface(), nil case reflect.Float32, reflect.Float64: number := value.Float() if math.IsNaN(number) || math.IsInf(number, 0) { return nil, fmt.Errorf("%s: floating-point value must be finite", path) } if err := state.produceNode(path); err != nil { return nil, err } return value.Interface(), nil case reflect.Map: if value.IsNil() { if err := state.produceNode(path); err != nil { return nil, err } return nil, nil } nextDepth, err := state.enterContainer(path, containerDepth) if err != nil { return nil, err } return copyMapValue(value, path, state, allowEmptyMapKeys, nextDepth) case reflect.Slice: if value.IsNil() { if err := state.produceNode(path); err != nil { return nil, err } return nil, nil } nextDepth, err := state.enterContainer(path, containerDepth) if err != nil { return nil, err } return copySequenceValue(value, path, state, allowEmptyMapKeys, nextDepth) case reflect.Array: nextDepth, err := state.enterContainer(path, containerDepth) if err != nil { return nil, err } return copySequenceValue(value, path, state, allowEmptyMapKeys, nextDepth) default: return nil, fmt.Errorf("%s: unsupported JSON value type %s", path, value.Type()) } } func copyMapValue( value reflect.Value, path string, state *traversalState, allowEmptyMapKeys bool, containerDepth int, ) (any, error) { if value.Type().Key().Kind() != reflect.String { return nil, fmt.Errorf("%s: map key type %s is not supported", path, value.Type().Key()) } if err := state.produceNode(path); err != nil { return nil, err } if err := state.ensureChildCapacity(path, value.Len()); err != nil { return nil, err } current := visit{typ: value.Type(), ptr: value.Pointer()} if _, ok := state.active[current]; ok { return nil, fmt.Errorf("%s: cyclic value is not supported", path) } state.active[current] = struct{}{} defer delete(state.active, current) keys := value.MapKeys() sort.Slice(keys, func(i, j int) bool { return keys[i].String() < keys[j].String() }) type entry struct { key reflect.Value name string value any } entries := make([]entry, 0, len(keys)) preserveType := true elementType := value.Type().Elem() for _, key := range keys { name := key.String() if name == "" && !allowEmptyMapKeys { return nil, fmt.Errorf("%s: map key must not be empty", path) } copied, err := copyValue( value.MapIndex(key), path+"."+name, state, allowEmptyMapKeys, containerDepth, ) if err != nil { return nil, err } entries = append(entries, entry{key: key, name: name, value: copied}) if copied == nil { if !canAssignNil(elementType) { preserveType = false } continue } if !reflect.TypeOf(copied).AssignableTo(elementType) { preserveType = false } } if preserveType { out := reflect.MakeMapWithSize(value.Type(), len(entries)) for _, entry := range entries { if entry.value == nil { out.SetMapIndex(entry.key, reflect.Zero(elementType)) continue } out.SetMapIndex(entry.key, reflect.ValueOf(entry.value)) } return out.Interface(), nil } out := make(map[string]any, len(entries)) for _, entry := range entries { out[entry.name] = entry.value } return out, nil } func copySequenceValue( value reflect.Value, path string, state *traversalState, allowEmptyMapKeys bool, containerDepth int, ) (any, error) { if err := state.produceNode(path); err != nil { return nil, err } if err := state.ensureChildCapacity(path, value.Len()); err != nil { return nil, err } var current visit if value.Kind() == reflect.Slice { current = visit{typ: value.Type(), ptr: value.Pointer()} if _, ok := state.active[current]; ok { return nil, fmt.Errorf("%s: cyclic value is not supported", path) } state.active[current] = struct{}{} defer delete(state.active, current) } values := make([]any, value.Len()) preserveType := true elementType := value.Type().Elem() for i := 0; i < value.Len(); i++ { copied, err := copyValue( value.Index(i), fmt.Sprintf("%s[%d]", path, i), state, allowEmptyMapKeys, containerDepth, ) if err != nil { return nil, err } values[i] = copied if copied == nil { if !canAssignNil(elementType) { preserveType = false } continue } if !reflect.TypeOf(copied).AssignableTo(elementType) { preserveType = false } } if preserveType { out := reflect.New(value.Type()).Elem() if value.Kind() == reflect.Slice { out = reflect.MakeSlice(value.Type(), value.Len(), value.Len()) } for i, copied := range values { if copied == nil { out.Index(i).Set(reflect.Zero(elementType)) continue } out.Index(i).Set(reflect.ValueOf(copied)) } return out.Interface(), nil } out := make([]any, len(values)) copy(out, values) return out, nil } func validJSONNumber(number json.Number) bool { var parsed json.Number if err := json.Unmarshal([]byte(number.String()), &parsed); err != nil { return false } return parsed.String() == number.String() } func newTraversalState() *traversalState { return &traversalState{active: make(map[visit]struct{})} } func (state *traversalState) produceNode(path string) error { if state.producedNodes >= maxProducedNodes { return fmt.Errorf("%s: JSON value work limit exceeded", path) } state.producedNodes++ return nil } func (state *traversalState) enterContainer(path string, depth int) (int, error) { depth++ if depth > maxContainerDepth { return 0, fmt.Errorf("%s: JSON container depth limit exceeded", path) } return depth, nil } func (state *traversalState) ensureChildCapacity(path string, count int) error { if count > maxProducedNodes-state.producedNodes { return fmt.Errorf("%s: JSON value work limit exceeded", path) } return nil } func (state *traversalState) resolveIndirection( value reflect.Value, path string, ) (reflect.Value, func(), bool, error) { var visits []visit cleanup := func() { for _, current := range visits { delete(state.active, current) } } for value.IsValid() && (value.Kind() == reflect.Interface || value.Kind() == reflect.Pointer) { if value.IsNil() { return reflect.Value{}, cleanup, true, nil } if value.Kind() == reflect.Pointer { current := visit{typ: value.Type(), ptr: value.Pointer()} if _, ok := state.active[current]; ok { cleanup() return reflect.Value{}, nil, false, fmt.Errorf("%s: cyclic value is not supported", path) } state.active[current] = struct{}{} visits = append(visits, current) } value = value.Elem() } if !value.IsValid() { return reflect.Value{}, cleanup, true, nil } return value, cleanup, false, nil } func canAssignNil(typ reflect.Type) bool { switch typ.Kind() { case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Pointer, reflect.Slice: return true default: return false } }