1
0
mirror of https://github.com/golang/go synced 2024-11-14 23:40:35 -07:00

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 <mknyszek@google.com>
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
This commit is contained in:
Damien Neil 2024-07-26 15:34:03 -07:00
parent aa97a012b4
commit fad6390f38
2 changed files with 143 additions and 6 deletions

View File

@ -21,9 +21,10 @@ import (
) )
type respTest struct { type respTest struct {
Raw string Raw string
Resp Response RawOut string
Body string Resp Response
Body string
} }
func dummyReq(method string) *Request { func dummyReq(method string) *Request {
@ -42,6 +43,11 @@ var respTests = []respTest{
"\r\n" + "\r\n" +
"Body here\n", "Body here\n",
"HTTP/1.0 200 OK\r\n" +
"Connection: close\r\n" +
"\r\n" +
"Body here\n",
Response{ Response{
Status: "200 OK", Status: "200 OK",
StatusCode: 200, StatusCode: 200,
@ -66,6 +72,11 @@ var respTests = []respTest{
"\r\n" + "\r\n" +
"Body here\n", "Body here\n",
"HTTP/1.1 200 OK\r\n" +
"Connection: close\r\n" +
"\r\n" +
"Body here\n",
Response{ Response{
Status: "200 OK", Status: "200 OK",
StatusCode: 200, StatusCode: 200,
@ -87,6 +98,9 @@ var respTests = []respTest{
"\r\n" + "\r\n" +
"Body should not be read!\n", "Body should not be read!\n",
"HTTP/1.1 204 No Content\r\n" +
"\r\n",
Response{ Response{
Status: "204 No Content", Status: "204 No Content",
StatusCode: 204, StatusCode: 204,
@ -110,6 +124,12 @@ var respTests = []respTest{
"\r\n" + "\r\n" +
"Body here\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{ Response{
Status: "200 OK", Status: "200 OK",
StatusCode: 200, StatusCode: 200,
@ -140,6 +160,14 @@ var respTests = []respTest{
"0\r\n" + "0\r\n" +
"\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{ Response{
Status: "200 OK", Status: "200 OK",
StatusCode: 200, StatusCode: 200,
@ -165,6 +193,12 @@ var respTests = []respTest{
"\r\n" + "\r\n" +
"Body here\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{ Response{
Status: "200 OK", Status: "200 OK",
StatusCode: 200, StatusCode: 200,
@ -195,6 +229,14 @@ var respTests = []respTest{
"0\r\n" + "0\r\n" +
"\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{ Response{
Status: "200 OK", Status: "200 OK",
StatusCode: 200, StatusCode: 200,
@ -217,6 +259,10 @@ var respTests = []respTest{
"Transfer-Encoding: chunked\r\n" + "Transfer-Encoding: chunked\r\n" +
"\r\n", "\r\n",
"HTTP/1.1 200 OK\r\n" +
"Transfer-Encoding: chunked\r\n" +
"\r\n",
Response{ Response{
Status: "200 OK", Status: "200 OK",
StatusCode: 200, StatusCode: 200,
@ -239,6 +285,11 @@ var respTests = []respTest{
"Content-Length: 256\r\n" + "Content-Length: 256\r\n" +
"\r\n", "\r\n",
"HTTP/1.0 200 OK\r\n" +
"Connection: close\r\n" +
"Content-Length: 256\r\n" +
"\r\n",
Response{ Response{
Status: "200 OK", Status: "200 OK",
StatusCode: 200, StatusCode: 200,
@ -261,6 +312,10 @@ var respTests = []respTest{
"Content-Length: 256\r\n" + "Content-Length: 256\r\n" +
"\r\n", "\r\n",
"HTTP/1.1 200 OK\r\n" +
"Content-Length: 256\r\n" +
"\r\n",
Response{ Response{
Status: "200 OK", Status: "200 OK",
StatusCode: 200, StatusCode: 200,
@ -282,6 +337,10 @@ var respTests = []respTest{
"HTTP/1.0 200 OK\r\n" + "HTTP/1.0 200 OK\r\n" +
"\r\n", "\r\n",
"HTTP/1.0 200 OK\r\n" +
"Connection: close\r\n" +
"\r\n",
Response{ Response{
Status: "200 OK", Status: "200 OK",
StatusCode: 200, StatusCode: 200,
@ -304,6 +363,10 @@ var respTests = []respTest{
"Content-Length: 0\r\n" + "Content-Length: 0\r\n" +
"\r\n", "\r\n",
"HTTP/1.1 200 OK\r\n" +
"Content-Length: 0\r\n" +
"\r\n",
Response{ Response{
Status: "200 OK", Status: "200 OK",
StatusCode: 200, StatusCode: 200,
@ -325,6 +388,11 @@ var respTests = []respTest{
// (permitted by RFC 7230, section 3.1.2) // (permitted by RFC 7230, section 3.1.2)
{ {
"HTTP/1.0 303 \r\n\r\n", "HTTP/1.0 303 \r\n\r\n",
"HTTP/1.0 303 \r\n" +
"Connection: close\r\n" +
"\r\n",
Response{ Response{
Status: "303 ", Status: "303 ",
StatusCode: 303, StatusCode: 303,
@ -344,6 +412,11 @@ var respTests = []respTest{
// (not permitted by RFC 7230, but we'll accept it anyway) // (not permitted by RFC 7230, but we'll accept it anyway)
{ {
"HTTP/1.0 303\r\n\r\n", "HTTP/1.0 303\r\n\r\n",
"HTTP/1.0 303 303\r\n" +
"Connection: close\r\n" +
"\r\n",
Response{ Response{
Status: "303", Status: "303",
StatusCode: 303, StatusCode: 303,
@ -366,6 +439,13 @@ Connection: close
Content-Type: multipart/byteranges; boundary=18a75608c8f47cef Content-Type: multipart/byteranges; boundary=18a75608c8f47cef
some body`, 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{ Response{
Status: "206 Partial Content", Status: "206 Partial Content",
StatusCode: 206, StatusCode: 206,
@ -390,6 +470,11 @@ some body`,
"\r\n" + "\r\n" +
"Body here\n", "Body here\n",
"HTTP/1.0 200 OK\r\n" +
"Connection: close\r\n" +
"\r\n" +
"Body here\n",
Response{ Response{
Status: "200 OK", Status: "200 OK",
StatusCode: 200, StatusCode: 200,
@ -415,6 +500,14 @@ some body`,
"Content-Length: 6\r\n\r\n" + "Content-Length: 6\r\n\r\n" +
"foobar", "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{ Response{
Status: "206 Partial Content", Status: "206 Partial Content",
StatusCode: 206, StatusCode: 206,
@ -441,6 +534,11 @@ some body`,
"Connection: keep-alive, close\r\n" + "Connection: keep-alive, close\r\n" +
"\r\n", "\r\n",
"HTTP/1.1 200 OK\r\n" +
"Connection: close\r\n" +
"Content-Length: 256\r\n" +
"\r\n",
Response{ Response{
Status: "200 OK", Status: "200 OK",
StatusCode: 200, StatusCode: 200,
@ -467,6 +565,11 @@ some body`,
"Connection: close\r\n" + "Connection: close\r\n" +
"\r\n", "\r\n",
"HTTP/1.1 200 OK\r\n" +
"Connection: close\r\n" +
"Content-Length: 256\r\n" +
"\r\n",
Response{ Response{
Status: "200 OK", Status: "200 OK",
StatusCode: 200, StatusCode: 200,
@ -493,6 +596,11 @@ some body`,
"\r\n" + "\r\n" +
"Body here\n", "Body here\n",
"HTTP/1.0 200 OK\r\n" +
"Connection: close\r\n" +
"\r\n" +
"Body here\n",
Response{ Response{
Status: "200 OK", Status: "200 OK",
StatusCode: 200, StatusCode: 200,
@ -517,6 +625,12 @@ some body`,
"\r\n" + "\r\n" +
"Body here\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{ Response{
Status: "200 OK", Status: "200 OK",
StatusCode: 200, StatusCode: 200,
@ -541,6 +655,14 @@ some body`,
"Connection: keep-alive\r\n" + "Connection: keep-alive\r\n" +
"Keep-Alive: timeout=7200\r\n\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", "\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{ Response{
Status: "200 OK", Status: "200 OK",
StatusCode: 200, StatusCode: 200,
@ -566,6 +688,14 @@ some body`,
"Content-type: text/html\r\n" + "Content-type: text/html\r\n" +
"WWW-Authenticate: Basic realm=\"\"\r\n\r\n" + "WWW-Authenticate: Basic realm=\"\"\r\n\r\n" +
"Your Authentication failed.\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{ Response{
Status: "401 Unauthorized", Status: "401 Unauthorized",
StatusCode: 401, StatusCode: 401,
@ -619,11 +749,18 @@ func TestWriteResponse(t *testing.T) {
t.Errorf("#%d: %v", i, err) t.Errorf("#%d: %v", i, err)
continue continue
} }
err = resp.Write(io.Discard) var buf bytes.Buffer
err = resp.Write(&buf)
if err != nil { if err != nil {
t.Errorf("#%d: %v", i, err) t.Errorf("#%d: %v", i, err)
continue 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"))
}
} }
} }

View File

@ -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 // 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-level optimizations in the event that the body is an
// *os.File. // *os.File.
if t.Body != nil { if !t.ResponseToHEAD && t.Body != nil {
var body = t.unwrapBody() var body = t.unwrapBody()
if chunked(t.TransferEncoding) { if chunked(t.TransferEncoding) {
if bw, ok := w.(*bufio.Writer); ok && !t.IsResponse { 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) t.ContentLength, ncopy)
} }
if chunked(t.TransferEncoding) { if !t.ResponseToHEAD && chunked(t.TransferEncoding) {
// Write Trailer header // Write Trailer header
if t.Trailer != nil { if t.Trailer != nil {
if err := t.Trailer.Write(w); err != nil { if err := t.Trailer.Write(w); err != nil {