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
Change-Id: Icbcd933087fe5e50199b62ff34c58bf92a09d3d4
GitHub-Last-Rev:
932e46b55b54d5f2050453bcaa50e9476c8559fd
GitHub-Pull-Request: golang/go#61865
Reviewed-on: https://go-review.googlesource.com/c/go/+/517336
Reviewed-by: Damien Neil <dneil@google.com>
Auto-Submit: Bryan Mills <bcmills@google.com>
Reviewed-by: Bryan Mills <bcmills@google.com>
Run-TryBot: Damien Neil <dneil@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
denial of service attacks, this setting and default was backported to Go
1.19.13, Go 1.20.8, and Go 1.21.1.
denial of service attacks, this setting and default was backported to Go
1.19.13, Go 1.20.8, and Go 1.21.1.
+Go 1.22 made it an error for a request or response read by a net/http
+client or server to have an empty Content-Length header.
+This behavior is controlled by the `httplaxcontentlength` setting.
+
### Go 1.21
Go 1.21 made it a run-time error to call `panic` with a nil interface value,
### Go 1.21
Go 1.21 made it a run-time error to call `panic` with a nil interface value,
{Name: "http2client", Package: "net/http"},
{Name: "http2debug", Package: "net/http", Opaque: true},
{Name: "http2server", Package: "net/http"},
{Name: "http2client", Package: "net/http"},
{Name: "http2debug", Package: "net/http", Opaque: true},
{Name: "http2server", Package: "net/http"},
+ {Name: "httplaxcontentlength", Package: "net/http", Changed: 22, Old: "1"},
{Name: "installgoroot", Package: "go/build"},
{Name: "jstmpllitinterp", Package: "html/template"},
//{Name: "multipartfiles", Package: "mime/multipart"},
{Name: "installgoroot", Package: "go/build"},
{Name: "jstmpllitinterp", Package: "html/template"},
//{Name: "multipartfiles", Package: "mime/multipart"},
}
errMultiCL := "message cannot contain multiple Content-Length headers"
}
errMultiCL := "message cannot contain multiple Content-Length headers"
+ errEmptyCL := "invalid empty Content-Length"
tests := []testCase{
{"", "", io.ErrUnexpectedEOF},
tests := []testCase{
{"", "", io.ErrUnexpectedEOF},
contentLength("200 OK", "Content-Length: 7\r\nContent-Length: 7\r\n\r\nGophers\r\n", nil),
contentLength("201 OK", "Content-Length: 0\r\nContent-Length: 7\r\n\r\nGophers\r\n", errMultiCL),
contentLength("300 OK", "Content-Length: 0\r\nContent-Length: 0 \r\n\r\nGophers\r\n", nil),
contentLength("200 OK", "Content-Length: 7\r\nContent-Length: 7\r\n\r\nGophers\r\n", nil),
contentLength("201 OK", "Content-Length: 0\r\nContent-Length: 7\r\n\r\nGophers\r\n", errMultiCL),
contentLength("300 OK", "Content-Length: 0\r\nContent-Length: 0 \r\n\r\nGophers\r\n", nil),
- contentLength("200 OK", "Content-Length:\r\nContent-Length:\r\n\r\nGophers\r\n", nil),
+ contentLength("200 OK", "Content-Length:\r\nContent-Length:\r\n\r\nGophers\r\n", errEmptyCL),
contentLength("206 OK", "Content-Length:\r\nContent-Length: 0 \r\nConnection: close\r\n\r\nGophers\r\n", errMultiCL),
// multiple content-length headers for 204 and 304 should still be checked
contentLength("206 OK", "Content-Length:\r\nContent-Length: 0 \r\nConnection: close\r\n\r\nGophers\r\n", errMultiCL),
// multiple content-length headers for 204 and 304 should still be checked
"io"
"net/http/httptrace"
"net/http/internal"
"io"
"net/http/httptrace"
"net/http/internal"
return err
}
if isResponse && t.RequestMethod == "HEAD" {
return err
}
if isResponse && t.RequestMethod == "HEAD" {
- if n, err := parseContentLength(t.Header.get("Content-Length")); err != nil {
+ if n, err := parseContentLength(t.Header["Content-Length"]); err != nil {
return err
} else {
t.ContentLength = n
return err
} else {
t.ContentLength = n
- // Logic based on Content-Length
- var cl string
- if len(contentLens) == 1 {
- cl = textproto.TrimString(contentLens[0])
- }
- if cl != "" {
- n, err := parseContentLength(cl)
+ if len(contentLens) > 0 {
+ // Logic based on Content-Length
+ n, err := parseContentLength(contentLens)
if err != nil {
return -1, err
}
return n, nil
}
if err != nil {
return -1, err
}
return n, nil
}
header.Del("Content-Length")
if isRequest {
header.Del("Content-Length")
if isRequest {
return bl.b.readLocked(p)
}
return bl.b.readLocked(p)
}
-// parseContentLength trims whitespace from s and returns -1 if no value
-// is set, or the value if it's >= 0.
-func parseContentLength(cl string) (int64, error) {
- cl = textproto.TrimString(cl)
- if cl == "" {
+var laxContentLength = godebug.New("httplaxcontentlength")
+
+// parseContentLength checks that the header is valid and then trims
+// whitespace. It returns -1 if no value is set otherwise the value
+// if it's >= 0.
+func parseContentLength(clHeaders []string) (int64, error) {
+ if len(clHeaders) == 0 {
+ cl := textproto.TrimString(clHeaders[0])
+
+ // The Content-Length must be a valid numeric value.
+ // See: https://datatracker.ietf.org/doc/html/rfc2616/#section-14.13
+ if cl == "" {
+ if laxContentLength.Value() == "1" {
+ laxContentLength.IncNonDefault()
+ return -1, nil
+ }
+ return 0, badStringError("invalid empty Content-Length", cl)
+ }
n, err := strconv.ParseUint(cl, 10, 63)
if err != nil {
return 0, badStringError("bad Content-Length", cl)
}
return int64(n), nil
n, err := strconv.ParseUint(cl, 10, 63)
if err != nil {
return 0, badStringError("bad Content-Length", cl)
}
return int64(n), nil
}
// finishAsyncByteRead finishes reading the 1-byte sniff
}
// finishAsyncByteRead finishes reading the 1-byte sniff
cl string
wantErr error
}{
cl string
wantErr error
}{
+ {
+ cl: "",
+ wantErr: badStringError("invalid empty Content-Length", ""),
+ },
}
for _, tt := range tests {
}
for _, tt := range tests {
- if _, gotErr := parseContentLength(tt.cl); !reflect.DeepEqual(gotErr, tt.wantErr) {
+ if _, gotErr := parseContentLength([]string{tt.cl}); !reflect.DeepEqual(gotErr, tt.wantErr) {
t.Errorf("%q:\n\tgot=%v\n\twant=%v", tt.cl, gotErr, tt.wantErr)
}
}
t.Errorf("%q:\n\tgot=%v\n\twant=%v", tt.cl, gotErr, tt.wantErr)
}
}
The number of non-default behaviors executed by the net/http
package due to a non-default GODEBUG=http2server=... setting.
The number of non-default behaviors executed by the net/http
package due to a non-default GODEBUG=http2server=... setting.
+ /godebug/non-default-behavior/httplaxcontentlength:events
+ The number of non-default behaviors executed by the net/http
+ package due to a non-default GODEBUG=httplaxcontentlength=...
+ setting.
+
/godebug/non-default-behavior/installgoroot:events
The number of non-default behaviors executed by the go/build
package due to a non-default GODEBUG=installgoroot=... setting.
/godebug/non-default-behavior/installgoroot:events
The number of non-default behaviors executed by the go/build
package due to a non-default GODEBUG=installgoroot=... setting.