Skip to content

Commit 4741026

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 4741026

File tree

6 files changed

+42
-16
lines changed

6 files changed

+42
-16
lines changed

doc/godebug.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,12 @@ for example,
126126
see the [runtime documentation](/pkg/runtime#hdr-Environment_Variables)
127127
and the [go command documentation](/cmd/go#hdr-Build_and_test_caching).
128128

129+
### Go 1.22
130+
131+
Go 1.22 made it an error for a request or response read by a net/http
132+
client or server to have an empty Content-Length header.
133+
This behavior is controlled by the `httplaxcontentlength` setting.
134+
129135
### Go 1.21
130136

131137
Go 1.21 made it a run-time error to call `panic` with a nil interface value,

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", Changed: 22, Old: "1"},
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: 23 additions & 14 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,30 @@ func (bl bodyLocked) Read(p []byte) (n int, err error) {
10381036
return bl.b.readLocked(p)
10391037
}
10401038

1041-
// 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 == "" {
1039+
var laxContentLength = godebug.New("httplaxcontentlength")
1040+
1041+
// parseContentLength checks that the header is valid and then trims
1042+
// whitespace. It returns -1 if no value is set otherwise the value
1043+
// if it's >= 0.
1044+
func parseContentLength(clHeaders []string) (int64, error) {
1045+
if len(clHeaders) == 0 {
10461046
return -1, nil
10471047
}
1048+
cl := textproto.TrimString(clHeaders[0])
1049+
1050+
// The Content-Length must be a valid numeric value.
1051+
// See: https://datatracker.ietf.org/doc/html/rfc2616/#section-14.13
1052+
if cl == "" {
1053+
if laxContentLength.Value() == "1" {
1054+
return -1, nil
1055+
}
1056+
return 0, badStringError("invalid empty Content-Length", cl)
1057+
}
10481058
n, err := strconv.ParseUint(cl, 10, 63)
10491059
if err != nil {
10501060
return 0, badStringError("bad Content-Length", cl)
10511061
}
10521062
return int64(n), nil
1053-
10541063
}
10551064

10561065
// 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
}

src/runtime/metrics/doc.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,11 @@ Below is the full list of supported metrics, ordered lexicographically.
254254
The number of non-default behaviors executed by the net/http
255255
package due to a non-default GODEBUG=http2server=... setting.
256256
257+
/godebug/non-default-behavior/httplaxcontentlength:events
258+
The number of non-default behaviors executed by the net/http
259+
package due to a non-default GODEBUG=httplaxcontentlength=...
260+
setting.
261+
257262
/godebug/non-default-behavior/installgoroot:events
258263
The number of non-default behaviors executed by the go/build
259264
package due to a non-default GODEBUG=installgoroot=... setting.

0 commit comments

Comments
 (0)