From 44b7c21cbf19450f38b337eb6b6fe4f6496fb5b3 Mon Sep 17 00:00:00 2001 From: Michael Fraenkel Date: Sun, 22 Oct 2017 13:59:00 -0400 Subject: [PATCH] http2: Discard data reads on HEAD requests If a server returns a DATA frame while procesing a HEAD request, the client will discard the data. Fixes golang/go#22376 Change-Id: Ief9c17ddfe51cc17f7f6326c87330ac9d8b9d3ff Reviewed-on: https://go-review.googlesource.com/72551 Run-TryBot: Tom Bergan TryBot-Result: Gobot Gobot Reviewed-by: Tom Bergan Reviewed-on: https://go-review.googlesource.com/88655 Run-TryBot: Andrew Bonventre Reviewed-by: Brad Fitzpatrick --- http2/transport.go | 8 ++++++ http2/transport_test.go | 54 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+) diff --git a/http2/transport.go b/http2/transport.go index 850d7ae0..b6a5c7dd 100644 --- a/http2/transport.go +++ b/http2/transport.go @@ -1703,6 +1703,14 @@ func (rl *clientConnReadLoop) processData(f *DataFrame) error { return nil } if f.Length > 0 { + if cs.req.Method == "HEAD" && len(data) > 0 { + cc.logf("protocol error: received DATA on a HEAD request") + rl.endStreamError(cs, StreamError{ + StreamID: f.StreamID, + Code: ErrCodeProtocol, + }) + return nil + } // Check connection-level flow control. cc.mu.Lock() if cs.inflow.available() >= int32(f.Length) { diff --git a/http2/transport_test.go b/http2/transport_test.go index 15dfa073..eff21030 100644 --- a/http2/transport_test.go +++ b/http2/transport_test.go @@ -2026,6 +2026,60 @@ func TestTransportReadHeadResponse(t *testing.T) { ct.run() } +func TestTransportReadHeadResponseWithBody(t *testing.T) { + response := "redirecting to /elsewhere" + ct := newClientTester(t) + clientDone := make(chan struct{}) + ct.client = func() error { + defer close(clientDone) + req, _ := http.NewRequest("HEAD", "https://dummy.tld/", nil) + res, err := ct.tr.RoundTrip(req) + if err != nil { + return err + } + if res.ContentLength != int64(len(response)) { + return fmt.Errorf("Content-Length = %d; want %d", res.ContentLength, len(response)) + } + slurp, err := ioutil.ReadAll(res.Body) + if err != nil { + return fmt.Errorf("ReadAll: %v", err) + } + if len(slurp) > 0 { + return fmt.Errorf("Unexpected non-empty ReadAll body: %q", slurp) + } + return nil + } + ct.server = func() error { + ct.greet() + for { + f, err := ct.fr.ReadFrame() + if err != nil { + t.Logf("ReadFrame: %v", err) + return nil + } + hf, ok := f.(*HeadersFrame) + if !ok { + continue + } + var buf bytes.Buffer + enc := hpack.NewEncoder(&buf) + enc.WriteField(hpack.HeaderField{Name: ":status", Value: "200"}) + enc.WriteField(hpack.HeaderField{Name: "content-length", Value: strconv.Itoa(len(response))}) + ct.fr.WriteHeaders(HeadersFrameParam{ + StreamID: hf.StreamID, + EndHeaders: true, + EndStream: false, + BlockFragment: buf.Bytes(), + }) + ct.fr.WriteData(hf.StreamID, true, []byte(response)) + + <-clientDone + return nil + } + } + ct.run() +} + type neverEnding byte func (b neverEnding) Read(p []byte) (int, error) {