Skip to content

Commit 24c8d99

Browse files
committed
net/http: disallow empty Content-Length header
The Content-Length must be a valid numeric value, empty values should not be accepted. See: https://www.rfc-editor.org/rfc/rfc9110.html#name-content-length Fixes #61679
1 parent be0e0b0 commit 24c8d99

File tree

4 files changed

+29
-15
lines changed

4 files changed

+29
-15
lines changed

src/internal/godebugs/table.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ var All = []Info{
3232
{Name: "http2client", Package: "net/http"},
3333
{Name: "http2debug", Package: "net/http", Opaque: true},
3434
{Name: "http2server", Package: "net/http"},
35+
{Name: "httplaxcontentlength", Package: "net/http"},
3536
{Name: "installgoroot", Package: "go/build"},
3637
{Name: "jstmpllitinterp", Package: "html/template"},
3738
//{Name: "multipartfiles", Package: "mime/multipart"},

src/net/http/response_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -883,6 +883,7 @@ func TestReadResponseErrors(t *testing.T) {
883883
}
884884

885885
errMultiCL := "message cannot contain multiple Content-Length headers"
886+
errEmptyCL := "invalid empty Content-Length"
886887

887888
tests := []testCase{
888889
{"", "", io.ErrUnexpectedEOF},
@@ -918,7 +919,7 @@ func TestReadResponseErrors(t *testing.T) {
918919
contentLength("200 OK", "Content-Length: 7\r\nContent-Length: 7\r\n\r\nGophers\r\n", nil),
919920
contentLength("201 OK", "Content-Length: 0\r\nContent-Length: 7\r\n\r\nGophers\r\n", errMultiCL),
920921
contentLength("300 OK", "Content-Length: 0\r\nContent-Length: 0 \r\n\r\nGophers\r\n", nil),
921-
contentLength("200 OK", "Content-Length:\r\nContent-Length:\r\n\r\nGophers\r\n", nil),
922+
contentLength("200 OK", "Content-Length:\r\nContent-Length:\r\n\r\nGophers\r\n", errEmptyCL),
922923
contentLength("206 OK", "Content-Length:\r\nContent-Length: 0 \r\nConnection: close\r\n\r\nGophers\r\n", errMultiCL),
923924

924925
// multiple content-length headers for 204 and 304 should still be checked

src/net/http/transfer.go

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"bytes"
1010
"errors"
1111
"fmt"
12+
"internal/godebug"
1213
"io"
1314
"net/http/httptrace"
1415
"net/http/internal"
@@ -527,7 +528,7 @@ func readTransfer(msg any, r *bufio.Reader) (err error) {
527528
return err
528529
}
529530
if isResponse && t.RequestMethod == "HEAD" {
530-
if n, err := parseContentLength(t.Header.get("Content-Length")); err != nil {
531+
if n, err := parseContentLength(t.Header["Content-Length"]); err != nil {
531532
return err
532533
} else {
533534
t.ContentLength = n
@@ -707,18 +708,15 @@ func fixLength(isResponse bool, status int, requestMethod string, header Header,
707708
return -1, nil
708709
}
709710

710-
// Logic based on Content-Length
711-
var cl string
712-
if len(contentLens) == 1 {
713-
cl = textproto.TrimString(contentLens[0])
714-
}
715-
if cl != "" {
716-
n, err := parseContentLength(cl)
711+
if len(contentLens) > 0 {
712+
// Logic based on Content-Length
713+
n, err := parseContentLength(contentLens)
717714
if err != nil {
718715
return -1, err
719716
}
720717
return n, nil
721718
}
719+
722720
header.Del("Content-Length")
723721

724722
if isRequest {
@@ -1038,19 +1036,29 @@ func (bl bodyLocked) Read(p []byte) (n int, err error) {
10381036
return bl.b.readLocked(p)
10391037
}
10401038

1039+
var laxContentLength = godebug.New("httplaxcontentlength")
1040+
10411041
// parseContentLength trims whitespace from s and returns -1 if no value
1042-
// is set, or the value if it's >= 0.
1043-
func parseContentLength(cl string) (int64, error) {
1044-
cl = textproto.TrimString(cl)
1045-
if cl == "" {
1042+
// is set otherwise the value if it's >= 0.
1043+
func parseContentLength(clHeaders []string) (int64, error) {
1044+
if len(clHeaders) == 0 {
10461045
return -1, nil
10471046
}
1047+
cl := textproto.TrimString(clHeaders[0])
1048+
1049+
// The Content-Length must be a valid numeric value.
1050+
// See: https://datatracker.ietf.org/doc/html/rfc2616/#section-14.13
1051+
if cl == "" {
1052+
if laxContentLength.Value() == "1" {
1053+
return -1, nil
1054+
}
1055+
return 0, badStringError("invalid empty Content-Length", cl)
1056+
}
10481057
n, err := strconv.ParseUint(cl, 10, 63)
10491058
if err != nil {
10501059
return 0, badStringError("bad Content-Length", cl)
10511060
}
10521061
return int64(n), nil
1053-
10541062
}
10551063

10561064
// finishAsyncByteRead finishes reading the 1-byte sniff

src/net/http/transfer_test.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,10 @@ func TestParseContentLength(t *testing.T) {
332332
cl string
333333
wantErr error
334334
}{
335+
{
336+
cl: "",
337+
wantErr: badStringError("invalid empty Content-Length", ""),
338+
},
335339
{
336340
cl: "3",
337341
wantErr: nil,
@@ -356,7 +360,7 @@ func TestParseContentLength(t *testing.T) {
356360
}
357361

358362
for _, tt := range tests {
359-
if _, gotErr := parseContentLength(tt.cl); !reflect.DeepEqual(gotErr, tt.wantErr) {
363+
if _, gotErr := parseContentLength([]string{tt.cl}); !reflect.DeepEqual(gotErr, tt.wantErr) {
360364
t.Errorf("%q:\n\tgot=%v\n\twant=%v", tt.cl, gotErr, tt.wantErr)
361365
}
362366
}

0 commit comments

Comments
 (0)