Browse code

api/server/httputils: add Uint32Value utility

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>

Sebastiaan van Stijn authored on 2024/10/15 03:18:22
Showing 2 changed files
... ...
@@ -29,6 +29,29 @@ func BoolValueOrDefault(r *http.Request, k string, d bool) bool {
29 29
 	return BoolValue(r, k)
30 30
 }
31 31
 
32
+// Uint32Value parses a form value into an uint32 type. It returns an error
33
+// if the field is not set, empty, incorrectly formatted, or out of range.
34
+func Uint32Value(r *http.Request, field string) (uint32, error) {
35
+	// strconv.ParseUint returns an "strconv.ErrSyntax" for negative values,
36
+	// not an "out of range". Strip the prefix before parsing, and use it
37
+	// later to detect valid, but negative values.
38
+	v, isNeg := strings.CutPrefix(r.Form.Get(field), "-")
39
+	if v == "" || v[0] == '+' {
40
+		// Fast-path for invalid values.
41
+		return 0, strconv.ErrSyntax
42
+	}
43
+
44
+	i, err := strconv.ParseUint(v, 10, 32)
45
+	if err != nil {
46
+		// Unwrap to remove the 'strconv.ParseUint: parsing "some-invalid-value":' prefix.
47
+		return 0, errors.Unwrap(err)
48
+	}
49
+	if isNeg {
50
+		return 0, strconv.ErrRange
51
+	}
52
+	return uint32(i), nil
53
+}
54
+
32 55
 // Int64ValueOrZero parses a form value into an int64 type.
33 56
 // It returns 0 if the parsing fails.
34 57
 func Int64ValueOrZero(r *http.Request, k string) int64 {
... ...
@@ -1,8 +1,10 @@
1 1
 package httputils // import "github.com/docker/docker/api/server/httputils"
2 2
 
3 3
 import (
4
+	"math"
4 5
 	"net/http"
5 6
 	"net/url"
7
+	"strconv"
6 8
 	"testing"
7 9
 
8 10
 	"github.com/docker/docker/errdefs"
... ...
@@ -109,6 +111,58 @@ func TestInt64ValueOrDefaultWithError(t *testing.T) {
109 109
 	}
110 110
 }
111 111
 
112
+func TestUint32Value(t *testing.T) {
113
+	const valueNotSet = "unset"
114
+
115
+	tests := []struct {
116
+		value       string
117
+		expected    uint32
118
+		expectedErr error
119
+	}{
120
+		{
121
+			value:    "0",
122
+			expected: 0,
123
+		},
124
+		{
125
+			value:    strconv.FormatUint(math.MaxUint32, 10),
126
+			expected: math.MaxUint32,
127
+		},
128
+		{
129
+			value:       valueNotSet,
130
+			expectedErr: strconv.ErrSyntax,
131
+		},
132
+		{
133
+			value:       "",
134
+			expectedErr: strconv.ErrSyntax,
135
+		},
136
+		{
137
+			value:       "-1",
138
+			expectedErr: strconv.ErrRange,
139
+		},
140
+		{
141
+			value:       "4294967296", // MaxUint32+1
142
+			expectedErr: strconv.ErrRange,
143
+		},
144
+		{
145
+			value:       "not-a-number",
146
+			expectedErr: strconv.ErrSyntax,
147
+		},
148
+	}
149
+	for _, tc := range tests {
150
+		tc := tc
151
+		t.Run(tc.value, func(t *testing.T) {
152
+			r, _ := http.NewRequest(http.MethodPost, "", nil)
153
+			r.Form = url.Values{}
154
+			if tc.value != valueNotSet {
155
+				r.Form.Set("field", tc.value)
156
+			}
157
+			out, err := Uint32Value(r, "field")
158
+			assert.Check(t, is.Equal(tc.expected, out))
159
+			assert.Check(t, is.ErrorIs(err, tc.expectedErr))
160
+		})
161
+	}
162
+}
163
+
112 164
 func TestDecodePlatform(t *testing.T) {
113 165
 	tests := []struct {
114 166
 		doc          string