]> Cypherpunks repositories - gostls13.git/commitdiff
net/http: don't write HEAD response body in ResponseWriter.ReadFrom
authorDamien Neil <dneil@google.com>
Fri, 26 Jul 2024 20:23:09 +0000 (13:23 -0700)
committerGopher Robot <gobot@golang.org>
Mon, 29 Jul 2024 21:29:55 +0000 (21:29 +0000)
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 <bradfitz@golang.org>
Auto-Submit: Damien Neil <dneil@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
src/net/http/serve_test.go
src/net/http/server.go

index cc485d3b89471c5b1ffbec44bea2a9409da4d22c..4d71eb0498f5fdbb191a86e23a77cb63d06c5ff0 100644 (file)
@@ -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})
 }
index 1ff72a04550c42b1fdb0579a6b2ae373cbb2dadf..1eb0e502064f71487a64ff569e9abe758a784f0d 100644 (file)
@@ -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