diff --git a/internal/web/runtime/remote_decode.go b/internal/web/runtime/remote_decode.go index ec4c92c78..d4d9e599a 100644 --- a/internal/web/runtime/remote_decode.go +++ b/internal/web/runtime/remote_decode.go @@ -23,20 +23,36 @@ const ( var errRemoteListTooLong = json_util.ErrListTooLong // decodeCappedValues unmarshals raw into v after checking it holds at most maxValues -// JSON values in all, nested ones included. +// JSON values in all, nested ones included; object keys and closing brackets are not values. func decodeCappedValues(raw json.RawMessage, v any, maxValues int) error { dec := json.NewDecoder(bytes.NewReader(raw)) - for count := 0; ; count++ { + // One frame per open container; an object's strings alternate between key and value. + type frame struct{ object, wantKey bool } + var open []frame + count := 0 + for { tok, err := dec.Token() if err != nil { break } - if delim, ok := tok.(json.Delim); ok && (delim == ']' || delim == '}') { + delim, isDelim := tok.(json.Delim) + if isDelim && (delim == ']' || delim == '}') { + open = open[:len(open)-1] continue } - if count > maxValues { + if top := len(open) - 1; top >= 0 && open[top].object { + if open[top].wantKey { + open[top].wantKey = false + continue + } + open[top].wantKey = true + } + if count++; count > maxValues { return fmt.Errorf("%w (%d values)", errRemoteListTooLong, maxValues) } + if isDelim { + open = append(open, frame{object: delim == '{', wantKey: delim == '{'}) + } } return json.Unmarshal(raw, v) } diff --git a/internal/web/runtime/remote_test.go b/internal/web/runtime/remote_test.go index 1720514a1..5fee05759 100644 --- a/internal/web/runtime/remote_test.go +++ b/internal/web/runtime/remote_test.go @@ -554,3 +554,17 @@ func TestRemoteForgetInboundDropsEveryTrace(t *testing.T) { t.Fatalf("left behind: pushedFP=%v ids=%v aliases=%v", r.pushedFP, r.remoteIDByTag, r.adoptedAliases) } } + +// The cap is documented in JSON values; counting keys and closing brackets as well refused a +// node's whole online report at well under the stated number of values. +func TestDecodeCappedValuesCountsValuesOnly(t *testing.T) { + var got map[string][]string + // One object, two arrays and three strings: six values, though fourteen tokens. + raw := json.RawMessage(`{"guid-a":["x","y"],"guid-b":["z"]}`) + if err := decodeCappedValues(raw, &got, 6); err != nil { + t.Fatalf("six values under a cap of six: %v", err) + } + if err := decodeCappedValues(raw, &got, 5); !errors.Is(err, errRemoteListTooLong) { + t.Fatalf("six values under a cap of five: err = %v, want errRemoteListTooLong", err) + } +}