From 466a463d94e6a47c591a66edc78ab77152063aa9 Mon Sep 17 00:00:00 2001 From: Damien Neil Date: Fri, 26 Jul 2024 13:23:09 -0700 Subject: [PATCH] net/http: don't write HEAD response body in ResponseWriter.ReadFrom Responses to HEAD requests don't have a body. The ResponseWriter automatically discards writes to the response body when responding to a HEAD request. ResponseWriter.ReadFrom was failing to discard writes under some circumstances; fix it to do so. Fixes #68609 Change-Id: I912f6b2b2a535df28ae37b875fcf15b10da1af2b Reviewed-on: https://go-review.googlesource.com/c/go/+/601475 Reviewed-by: Brad Fitzpatrick Auto-Submit: Damien Neil LUCI-TryBot-Result: Go LUCI Reviewed-by: Michael Knyszek --- src/net/http/serve_test.go | 30 +++++++++++++++++++++++++++++- src/net/http/server.go | 2 +- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/src/net/http/serve_test.go b/src/net/http/serve_test.go index cc485d3b89471c..4d71eb0498f5fd 100644 --- a/src/net/http/serve_test.go +++ b/src/net/http/serve_test.go @@ -1510,7 +1510,7 @@ func testHeadResponses(t *testing.T, mode testMode) { } // Also exercise the ReaderFrom path - _, err = io.Copy(w, strings.NewReader("789a")) + _, err = io.Copy(w, struct{ io.Reader }{strings.NewReader("789a")}) if err != nil { t.Errorf("Copy(ResponseWriter, ...): %v", err) } @@ -1537,6 +1537,34 @@ func testHeadResponses(t *testing.T, mode testMode) { } } +// Ensure ResponseWriter.ReadFrom doesn't write a body in response to a HEAD request. +// https://go.dev/issue/68609 +func TestHeadReaderFrom(t *testing.T) { run(t, testHeadReaderFrom, []testMode{http1Mode}) } +func testHeadReaderFrom(t *testing.T, mode testMode) { + // Body is large enough to exceed the content-sniffing length. + wantBody := strings.Repeat("a", 4096) + cst := newClientServerTest(t, mode, HandlerFunc(func(w ResponseWriter, r *Request) { + w.(io.ReaderFrom).ReadFrom(strings.NewReader(wantBody)) + })) + res, err := cst.c.Head(cst.ts.URL) + if err != nil { + t.Fatal(err) + } + res.Body.Close() + res, err = cst.c.Get(cst.ts.URL) + if err != nil { + t.Fatal(err) + } + gotBody, err := io.ReadAll(res.Body) + res.Body.Close() + if err != nil { + t.Fatal(err) + } + if string(gotBody) != wantBody { + t.Errorf("got unexpected body len=%v, want %v", len(gotBody), len(wantBody)) + } +} + func TestTLSHandshakeTimeout(t *testing.T) { run(t, testTLSHandshakeTimeout, []testMode{https1Mode, http2Mode}) } diff --git a/src/net/http/server.go b/src/net/http/server.go index 1ff72a04550c42..1eb0e502064f71 100644 --- a/src/net/http/server.go +++ b/src/net/http/server.go @@ -611,7 +611,7 @@ func (w *response) ReadFrom(src io.Reader) (n int64, err error) { w.cw.flush() // make sure Header is written; flush data to rwc // Now that cw has been flushed, its chunking field is guaranteed initialized. - if !w.cw.chunking && w.bodyAllowed() { + if !w.cw.chunking && w.bodyAllowed() && w.req.Method != "HEAD" { n0, err := rf.ReadFrom(src) n += n0 w.written += n0