From fad6390f38eb96e4e053452ae21a88260cbcf202 Mon Sep 17 00:00:00 2001 From: Damien Neil Date: Fri, 26 Jul 2024 15:34:03 -0700 Subject: [PATCH] net/http: don't write body for HEAD responses in Response.Write Fixes #62015 Change-Id: I88c5427f85e740d5b956942bb1c2727dac2935ea Reviewed-on: https://go-review.googlesource.com/c/go/+/601238 Reviewed-by: Michael Knyszek Reviewed-by: Brad Fitzpatrick LUCI-TryBot-Result: Go LUCI --- src/net/http/response_test.go | 145 +++++++++++++++++++++++++++++++++- src/net/http/transfer.go | 4 +- 2 files changed, 143 insertions(+), 6 deletions(-) diff --git a/src/net/http/response_test.go b/src/net/http/response_test.go index a63aac95ac..3ccbb9b0f2 100644 --- a/src/net/http/response_test.go +++ b/src/net/http/response_test.go @@ -21,9 +21,10 @@ import ( ) type respTest struct { - Raw string - Resp Response - Body string + Raw string + RawOut string + Resp Response + Body string } func dummyReq(method string) *Request { @@ -42,6 +43,11 @@ var respTests = []respTest{ "\r\n" + "Body here\n", + "HTTP/1.0 200 OK\r\n" + + "Connection: close\r\n" + + "\r\n" + + "Body here\n", + Response{ Status: "200 OK", StatusCode: 200, @@ -66,6 +72,11 @@ var respTests = []respTest{ "\r\n" + "Body here\n", + "HTTP/1.1 200 OK\r\n" + + "Connection: close\r\n" + + "\r\n" + + "Body here\n", + Response{ Status: "200 OK", StatusCode: 200, @@ -87,6 +98,9 @@ var respTests = []respTest{ "\r\n" + "Body should not be read!\n", + "HTTP/1.1 204 No Content\r\n" + + "\r\n", + Response{ Status: "204 No Content", StatusCode: 204, @@ -110,6 +124,12 @@ var respTests = []respTest{ "\r\n" + "Body here\n", + "HTTP/1.0 200 OK\r\n" + + "Content-Length: 10\r\n" + + "Connection: close\r\n" + + "\r\n" + + "Body here\n", + Response{ Status: "200 OK", StatusCode: 200, @@ -140,6 +160,14 @@ var respTests = []respTest{ "0\r\n" + "\r\n", + "HTTP/1.1 200 OK\r\n" + + "Transfer-Encoding: chunked\r\n" + + "\r\n" + + "13\r\n" + + "Body here\ncontinued\r\n" + + "0\r\n" + + "\r\n", + Response{ Status: "200 OK", StatusCode: 200, @@ -165,6 +193,12 @@ var respTests = []respTest{ "\r\n" + "Body here\n", + "HTTP/1.0 200 OK\r\n" + + "Content-Length: 10\r\n" + + "Connection: close\r\n" + + "\r\n" + + "Body here\n", + Response{ Status: "200 OK", StatusCode: 200, @@ -195,6 +229,14 @@ var respTests = []respTest{ "0\r\n" + "\r\n", + "HTTP/1.1 200 OK\r\n" + + "Transfer-Encoding: chunked\r\n" + + "\r\n" + + "a\r\n" + + "Body here\n\r\n" + + "0\r\n" + + "\r\n", + Response{ Status: "200 OK", StatusCode: 200, @@ -217,6 +259,10 @@ var respTests = []respTest{ "Transfer-Encoding: chunked\r\n" + "\r\n", + "HTTP/1.1 200 OK\r\n" + + "Transfer-Encoding: chunked\r\n" + + "\r\n", + Response{ Status: "200 OK", StatusCode: 200, @@ -239,6 +285,11 @@ var respTests = []respTest{ "Content-Length: 256\r\n" + "\r\n", + "HTTP/1.0 200 OK\r\n" + + "Connection: close\r\n" + + "Content-Length: 256\r\n" + + "\r\n", + Response{ Status: "200 OK", StatusCode: 200, @@ -261,6 +312,10 @@ var respTests = []respTest{ "Content-Length: 256\r\n" + "\r\n", + "HTTP/1.1 200 OK\r\n" + + "Content-Length: 256\r\n" + + "\r\n", + Response{ Status: "200 OK", StatusCode: 200, @@ -282,6 +337,10 @@ var respTests = []respTest{ "HTTP/1.0 200 OK\r\n" + "\r\n", + "HTTP/1.0 200 OK\r\n" + + "Connection: close\r\n" + + "\r\n", + Response{ Status: "200 OK", StatusCode: 200, @@ -304,6 +363,10 @@ var respTests = []respTest{ "Content-Length: 0\r\n" + "\r\n", + "HTTP/1.1 200 OK\r\n" + + "Content-Length: 0\r\n" + + "\r\n", + Response{ Status: "200 OK", StatusCode: 200, @@ -325,6 +388,11 @@ var respTests = []respTest{ // (permitted by RFC 7230, section 3.1.2) { "HTTP/1.0 303 \r\n\r\n", + + "HTTP/1.0 303 \r\n" + + "Connection: close\r\n" + + "\r\n", + Response{ Status: "303 ", StatusCode: 303, @@ -344,6 +412,11 @@ var respTests = []respTest{ // (not permitted by RFC 7230, but we'll accept it anyway) { "HTTP/1.0 303\r\n\r\n", + + "HTTP/1.0 303 303\r\n" + + "Connection: close\r\n" + + "\r\n", + Response{ Status: "303", StatusCode: 303, @@ -366,6 +439,13 @@ Connection: close Content-Type: multipart/byteranges; boundary=18a75608c8f47cef some body`, + + "HTTP/1.1 206 Partial Content\r\n" + + "Connection: close\r\n" + + "Content-Type: multipart/byteranges; boundary=18a75608c8f47cef\r\n" + + "\r\n" + + "some body", + Response{ Status: "206 Partial Content", StatusCode: 206, @@ -390,6 +470,11 @@ some body`, "\r\n" + "Body here\n", + "HTTP/1.0 200 OK\r\n" + + "Connection: close\r\n" + + "\r\n" + + "Body here\n", + Response{ Status: "200 OK", StatusCode: 200, @@ -415,6 +500,14 @@ some body`, "Content-Length: 6\r\n\r\n" + "foobar", + "HTTP/1.1 206 Partial Content\r\n" + + "Content-Length: 6\r\n" + + "Accept-Ranges: bytes\r\n" + + "Content-Range: bytes 0-5/1862\r\n" + + "Content-Type: text/plain; charset=utf-8\r\n" + + "\r\n" + + "foobar", + Response{ Status: "206 Partial Content", StatusCode: 206, @@ -441,6 +534,11 @@ some body`, "Connection: keep-alive, close\r\n" + "\r\n", + "HTTP/1.1 200 OK\r\n" + + "Connection: close\r\n" + + "Content-Length: 256\r\n" + + "\r\n", + Response{ Status: "200 OK", StatusCode: 200, @@ -467,6 +565,11 @@ some body`, "Connection: close\r\n" + "\r\n", + "HTTP/1.1 200 OK\r\n" + + "Connection: close\r\n" + + "Content-Length: 256\r\n" + + "\r\n", + Response{ Status: "200 OK", StatusCode: 200, @@ -493,6 +596,11 @@ some body`, "\r\n" + "Body here\n", + "HTTP/1.0 200 OK\r\n" + + "Connection: close\r\n" + + "\r\n" + + "Body here\n", + Response{ Status: "200 OK", StatusCode: 200, @@ -517,6 +625,12 @@ some body`, "\r\n" + "Body here\n", + "HTTP/1.0 200 OK\r\n" + + "Connection: close\r\n" + + "Content-Length: 10\r\n" + + "\r\n" + + "Body here\n", + Response{ Status: "200 OK", StatusCode: 200, @@ -541,6 +655,14 @@ some body`, "Connection: keep-alive\r\n" + "Keep-Alive: timeout=7200\r\n\r\n" + "\x1f\x8b\b\x00\x00\x00\x00\x00\x00\x00s\xf3\xf7\a\x00\xab'\xd4\x1a\x03\x00\x00\x00", + + "HTTP/1.1 200 OK\r\n" + + "Content-Length: 23\r\n" + + "Connection: keep-alive\r\n" + + "Content-Encoding: gzip\r\n" + + "Keep-Alive: timeout=7200\r\n\r\n" + + "\x1f\x8b\b\x00\x00\x00\x00\x00\x00\x00s\xf3\xf7\a\x00\xab'\xd4\x1a\x03\x00\x00\x00", + Response{ Status: "200 OK", StatusCode: 200, @@ -566,6 +688,14 @@ some body`, "Content-type: text/html\r\n" + "WWW-Authenticate: Basic realm=\"\"\r\n\r\n" + "Your Authentication failed.\r\n", + + "HTTP/1.0 401 Unauthorized\r\n" + + "Connection: close\r\n" + + "Content-Type: text/html\r\n" + + "Www-Authenticate: Basic realm=\"\"\r\n" + + "\r\n" + + "Your Authentication failed.\r\n", + Response{ Status: "401 Unauthorized", StatusCode: 401, @@ -619,11 +749,18 @@ func TestWriteResponse(t *testing.T) { t.Errorf("#%d: %v", i, err) continue } - err = resp.Write(io.Discard) + var buf bytes.Buffer + err = resp.Write(&buf) if err != nil { t.Errorf("#%d: %v", i, err) continue } + if got, want := buf.String(), tt.RawOut; got != want { + t.Errorf("#%d: response differs; got:\n----\n%v\n----\nwant:\n----\n%v\n----\n", + i, + strings.ReplaceAll(got, "\r", "\\r"), + strings.ReplaceAll(want, "\r", "\\r")) + } } } diff --git a/src/net/http/transfer.go b/src/net/http/transfer.go index 5a3c6ceff5..f7eef6475e 100644 --- a/src/net/http/transfer.go +++ b/src/net/http/transfer.go @@ -350,7 +350,7 @@ func (t *transferWriter) writeBody(w io.Writer) (err error) { // nopCloser or readTrackingBody. This is to ensure that we can take advantage of // OS-level optimizations in the event that the body is an // *os.File. - if t.Body != nil { + if !t.ResponseToHEAD && t.Body != nil { var body = t.unwrapBody() if chunked(t.TransferEncoding) { if bw, ok := w.(*bufio.Writer); ok && !t.IsResponse { @@ -392,7 +392,7 @@ func (t *transferWriter) writeBody(w io.Writer) (err error) { t.ContentLength, ncopy) } - if chunked(t.TransferEncoding) { + if !t.ResponseToHEAD && chunked(t.TransferEncoding) { // Write Trailer header if t.Trailer != nil { if err := t.Trailer.Write(w); err != nil {