diff --git a/bind.go b/bind.go index 149fe1d6..af8643ab 100644 --- a/bind.go +++ b/bind.go @@ -8,6 +8,7 @@ import ( "encoding/xml" "errors" "fmt" + "io" "mime/multipart" "net/http" "reflect" @@ -71,6 +72,22 @@ func (b *DefaultBinder) BindBody(c Context, i interface{}) (err error) { return } + // For unknown ContentLength (-1), check if body is actually empty + if req.ContentLength == -1 { + // Peek at the first byte to see if there's any content + var buf [1]byte + n, readErr := req.Body.Read(buf[:]) + if readErr != nil && readErr != io.EOF { + return NewHTTPError(http.StatusBadRequest, readErr.Error()).SetInternal(readErr) + } + if n == 0 { + // Body is empty, return without error + return + } + // There's content, put the byte back by creating a new reader + req.Body = io.NopCloser(io.MultiReader(strings.NewReader(string(buf[:n])), req.Body)) + } + // mediatype is found like `mime.ParseMediaType()` does it base, _, _ := strings.Cut(req.Header.Get(HeaderContentType), ";") mediatype := strings.TrimSpace(base) diff --git a/bind_test.go b/bind_test.go index 6aa0cce3..060845bd 100644 --- a/bind_test.go +++ b/bind_test.go @@ -1082,6 +1082,14 @@ func TestDefaultBinder_BindBody(t *testing.T) { givenContent: strings.NewReader(""), expect: &Node{ID: 0, Node: ""}, }, + { + name: "ok, POST with empty body and ContentLength -1 (Issue #2813)", + givenURL: "/api/real_node/endpoint?node=xxx", + givenMethod: http.MethodPost, + givenContent: strings.NewReader(""), + whenChunkedBody: true, // This sets ContentLength to -1 + expect: &Node{ID: 0, Node: ""}, + }, { name: "ok, JSON POST bind to struct with: path + query + chunked body", givenURL: "/api/real_node/endpoint?node=xxx",