mirror of https://github.com/golang/go.git
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
This commit is contained in:
parent
162469b3cf
commit
932e46b55b
|
|
@ -134,6 +134,10 @@ The default is tlsmaxrsasize=8192, limiting RSA to 8192-bit keys. To avoid
|
||||||
denial of service attacks, this setting and default was backported to Go
|
denial of service attacks, this setting and default was backported to Go
|
||||||
1.19.13, Go 1.20.8, and Go 1.21.1.
|
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
|
||||||
|
|
||||||
Go 1.21 made it a run-time error to call `panic` with a nil interface value,
|
Go 1.21 made it a run-time error to call `panic` with a nil interface value,
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ var All = []Info{
|
||||||
{Name: "http2client", Package: "net/http"},
|
{Name: "http2client", Package: "net/http"},
|
||||||
{Name: "http2debug", Package: "net/http", Opaque: true},
|
{Name: "http2debug", Package: "net/http", Opaque: true},
|
||||||
{Name: "http2server", Package: "net/http"},
|
{Name: "http2server", Package: "net/http"},
|
||||||
|
{Name: "httplaxcontentlength", Package: "net/http", Changed: 22, Old: "1"},
|
||||||
{Name: "installgoroot", Package: "go/build"},
|
{Name: "installgoroot", Package: "go/build"},
|
||||||
{Name: "jstmpllitinterp", Package: "html/template"},
|
{Name: "jstmpllitinterp", Package: "html/template"},
|
||||||
//{Name: "multipartfiles", Package: "mime/multipart"},
|
//{Name: "multipartfiles", Package: "mime/multipart"},
|
||||||
|
|
|
||||||
|
|
@ -883,6 +883,7 @@ func TestReadResponseErrors(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
errMultiCL := "message cannot contain multiple Content-Length headers"
|
errMultiCL := "message cannot contain multiple Content-Length headers"
|
||||||
|
errEmptyCL := "invalid empty Content-Length"
|
||||||
|
|
||||||
tests := []testCase{
|
tests := []testCase{
|
||||||
{"", "", io.ErrUnexpectedEOF},
|
{"", "", io.ErrUnexpectedEOF},
|
||||||
|
|
@ -918,7 +919,7 @@ func TestReadResponseErrors(t *testing.T) {
|
||||||
contentLength("200 OK", "Content-Length: 7\r\nContent-Length: 7\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("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("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),
|
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
|
// multiple content-length headers for 204 and 304 should still be checked
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"internal/godebug"
|
||||||
"io"
|
"io"
|
||||||
"net/http/httptrace"
|
"net/http/httptrace"
|
||||||
"net/http/internal"
|
"net/http/internal"
|
||||||
|
|
@ -527,7 +528,7 @@ func readTransfer(msg any, r *bufio.Reader) (err error) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if isResponse && t.RequestMethod == "HEAD" {
|
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
|
return err
|
||||||
} else {
|
} else {
|
||||||
t.ContentLength = n
|
t.ContentLength = n
|
||||||
|
|
@ -707,18 +708,15 @@ func fixLength(isResponse bool, status int, requestMethod string, header Header,
|
||||||
return -1, nil
|
return -1, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(contentLens) > 0 {
|
||||||
// Logic based on Content-Length
|
// Logic based on Content-Length
|
||||||
var cl string
|
n, err := parseContentLength(contentLens)
|
||||||
if len(contentLens) == 1 {
|
|
||||||
cl = textproto.TrimString(contentLens[0])
|
|
||||||
}
|
|
||||||
if cl != "" {
|
|
||||||
n, err := parseContentLength(cl)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return -1, err
|
return -1, err
|
||||||
}
|
}
|
||||||
return n, nil
|
return n, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
header.Del("Content-Length")
|
header.Del("Content-Length")
|
||||||
|
|
||||||
if isRequest {
|
if isRequest {
|
||||||
|
|
@ -1038,19 +1036,31 @@ func (bl bodyLocked) Read(p []byte) (n int, err error) {
|
||||||
return bl.b.readLocked(p)
|
return bl.b.readLocked(p)
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseContentLength trims whitespace from s and returns -1 if no value
|
var laxContentLength = godebug.New("httplaxcontentlength")
|
||||||
// is set, or the value if it's >= 0.
|
|
||||||
func parseContentLength(cl string) (int64, error) {
|
// parseContentLength checks that the header is valid and then trims
|
||||||
cl = textproto.TrimString(cl)
|
// whitespace. It returns -1 if no value is set otherwise the value
|
||||||
if cl == "" {
|
// if it's >= 0.
|
||||||
|
func parseContentLength(clHeaders []string) (int64, error) {
|
||||||
|
if len(clHeaders) == 0 {
|
||||||
return -1, nil
|
return -1, nil
|
||||||
}
|
}
|
||||||
|
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)
|
n, err := strconv.ParseUint(cl, 10, 63)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, badStringError("bad Content-Length", cl)
|
return 0, badStringError("bad Content-Length", cl)
|
||||||
}
|
}
|
||||||
return int64(n), nil
|
return int64(n), nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// finishAsyncByteRead finishes reading the 1-byte sniff
|
// finishAsyncByteRead finishes reading the 1-byte sniff
|
||||||
|
|
|
||||||
|
|
@ -332,6 +332,10 @@ func TestParseContentLength(t *testing.T) {
|
||||||
cl string
|
cl string
|
||||||
wantErr error
|
wantErr error
|
||||||
}{
|
}{
|
||||||
|
{
|
||||||
|
cl: "",
|
||||||
|
wantErr: badStringError("invalid empty Content-Length", ""),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
cl: "3",
|
cl: "3",
|
||||||
wantErr: nil,
|
wantErr: nil,
|
||||||
|
|
@ -356,7 +360,7 @@ func TestParseContentLength(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -254,6 +254,11 @@ Below is the full list of supported metrics, ordered lexicographically.
|
||||||
The number of non-default behaviors executed by the net/http
|
The number of non-default behaviors executed by the net/http
|
||||||
package due to a non-default GODEBUG=http2server=... setting.
|
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
|
/godebug/non-default-behavior/installgoroot:events
|
||||||
The number of non-default behaviors executed by the go/build
|
The number of non-default behaviors executed by the go/build
|
||||||
package due to a non-default GODEBUG=installgoroot=... setting.
|
package due to a non-default GODEBUG=installgoroot=... setting.
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue