From 72521c8ab32e2950a874658bbc42bcc0c152c799 Mon Sep 17 00:00:00 2001 From: bensynapse <118375461+bensynapse@users.noreply.github.com> Date: Sat, 10 Oct 2026 06:19:56 +0300 Subject: [PATCH] fix: Reject unsigned integer overflow before conversion --- bareitem.go | 8 +++++-- item_test.go | 63 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 2 deletions(-) diff --git a/bareitem.go b/bareitem.go index 17b2c0b..147a756 100644 --- a/bareitem.go +++ b/bareitem.go @@ -65,8 +65,12 @@ func marshalBareItem(b *strings.Builder, v interface{}) error { case int, int8, int16, int32: return marshalInteger(b, reflect.ValueOf(v).Int()) case uint, uint8, uint16, uint32, uint64: - // Casting an uint64 to an int64 is possible because the maximum allowed value is 999,999,999,999,999 - return marshalInteger(b, int64(reflect.ValueOf(v).Uint())) + u := reflect.ValueOf(v).Uint() + if u > 999999999999999 { + return ErrNumberOutOfRange + } + + return marshalInteger(b, int64(u)) case float32, float64: return marshalDecimal(b, v.(float64)) case []byte: diff --git a/item_test.go b/item_test.go index c17f1d6..d50b64a 100644 --- a/item_test.go +++ b/item_test.go @@ -1,11 +1,74 @@ package httpsfv import ( + "errors" + "fmt" "reflect" + "strconv" "strings" "testing" ) +func TestMarshalUnsignedInteger(t *testing.T) { + t.Parallel() + + const maxInteger = uint64(999999999999999) + type testCase struct { + value interface{} + want string + err error + } + cases := []testCase{ + {uint(0), "0", nil}, + {uint8(255), "255", nil}, + {uint16(65535), "65535", nil}, + {uint32(4294967295), "4294967295", nil}, + {uint64(0), "0", nil}, + {maxInteger, "999999999999999", nil}, + {maxInteger + 1, "", ErrNumberOutOfRange}, + {uint64(1<<63 - 1), "", ErrNumberOutOfRange}, + {uint64(1 << 63), "", ErrNumberOutOfRange}, + {^uint64(0) - maxInteger, "", ErrNumberOutOfRange}, + {^uint64(0) - maxInteger + 1, "", ErrNumberOutOfRange}, + {^uint64(0) - 1, "", ErrNumberOutOfRange}, + {^uint64(0), "", ErrNumberOutOfRange}, + } + if strconv.IntSize == 64 { + cases = append(cases, testCase{^uint(0), "", ErrNumberOutOfRange}) + } else { + cases = append(cases, testCase{^uint(0), "4294967295", nil}) + } + + for _, c := range cases { + t.Run(fmt.Sprintf("%T/%v", c.value, c.value), func(t *testing.T) { + got, err := Marshal(NewItem(c.value)) + if !errors.Is(err, c.err) || got != c.want { + t.Errorf("Marshal() = %q, %v; want %q, %v", got, err, c.want, c.err) + } + }) + } +} + +func TestMarshalUnsignedIntegerOverflow(t *testing.T) { + t.Parallel() + + item := NewItem(^uint64(0)) + withParams := NewItem(Token("token")) + withParams.Params.Add("number", ^uint64(0)) + dictionary := NewDictionary() + dictionary.Add("number", item) + innerList := InnerList{Items: []Item{NewItem(42), item}, Params: NewParams()} + + for _, value := range []StructuredFieldValue{item, withParams, List{NewItem(42), item}, dictionary, innerList} { + t.Run(fmt.Sprintf("%T", value), func(t *testing.T) { + got, err := Marshal(value) + if !errors.Is(err, ErrNumberOutOfRange) || got != "" { + t.Errorf("Marshal() = %q, %v; want empty output and ErrNumberOutOfRange", got, err) + } + }) + } +} + func TestMarshalItem(t *testing.T) { t.Parallel()