diff --git a/backend/internal/httpserver/binding.go b/backend/internal/httpserver/binding.go index 177d5d6e..74e7ffd7 100644 --- a/backend/internal/httpserver/binding.go +++ b/backend/internal/httpserver/binding.go @@ -33,17 +33,13 @@ func BindJSON(c *gin.Context, value any) error { // BindOptionalJSON accepts an empty body while normalizing valid input and classifying malformed JSON as invalid input func BindOptionalJSON(c *gin.Context, value any) error { - if c.Request.ContentLength == 0 { - return nil - } - if err := requireJSONContentType(c); err != nil { - return err - } - err := c.ShouldBindJSON(value) if errors.Is(err, io.EOF) { return nil } + if contentTypeErr := requireJSONContentType(c); contentTypeErr != nil { + return contentTypeErr + } if err = classifyBindingError(err); err != nil { return err } diff --git a/backend/internal/httpserver/binding_test.go b/backend/internal/httpserver/binding_test.go index b5ceed24..5780aa76 100644 --- a/backend/internal/httpserver/binding_test.go +++ b/backend/internal/httpserver/binding_test.go @@ -97,6 +97,32 @@ func TestBindJSONAcceptsStructuredJSONContentType(t *testing.T) { require.Equal(t, "admin", input.Name) } +func TestBindOptionalJSONDoesNotRelyOnContentLength(t *testing.T) { + for _, test := range []struct { + name string + contentLength int64 + }{ + {name: "zero", contentLength: 0}, + {name: "unknown", contentLength: -1}, + } { + t.Run(test.name, func(t *testing.T) { + gin.SetMode(gin.TestMode) + c, _ := gin.CreateTestContext(httptest.NewRecorder()) + c.Request = httptest.NewRequestWithContext(t.Context(), http.MethodPost, "/", strings.NewReader(`{"secret":"custom-secret"}`)) + c.Request.ContentLength = test.contentLength + c.Request.Header.Set("Content-Type", "application/json") + + var input struct { + Secret string `json:"secret"` + } + err := BindOptionalJSON(c, &input) + + require.NoError(t, err) + require.Equal(t, "custom-secret", input.Secret) + }) + } +} + func TestFormFileClassifiesMissingField(t *testing.T) { gin.SetMode(gin.TestMode) c, _ := gin.CreateTestContext(httptest.NewRecorder())