http2: do not sniff body if Content-Encoding is set Updates golang/go#31753 Change-Id: I2481ffcff6626c08ef32a02cffb3f108737fa87e Reviewed-on: https://go-review.googlesource.com/c/net/+/199841 Run-TryBot: Emmanuel Odeke <emm.odeke@gmail.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
diff --git a/http2/server.go b/http2/server.go index b7524ba..d2ba820 100644 --- a/http2/server.go +++ b/http2/server.go
@@ -2415,7 +2415,11 @@ clen = strconv.Itoa(len(p)) } _, hasContentType := rws.snapHeader["Content-Type"] - if !hasContentType && bodyAllowedForStatus(rws.status) && len(p) > 0 { + // If the Content-Encoding is non-blank, we shouldn't + // sniff the body. See Issue golang.org/issue/31753. + ce := rws.snapHeader.Get("Content-Encoding") + hasCE := len(ce) > 0 + if !hasCE && !hasContentType && bodyAllowedForStatus(rws.status) && len(p) > 0 { ctype = http.DetectContentType(p) } var date string
diff --git a/http2/server_test.go b/http2/server_test.go index 0b77919..7115a3e 100644 --- a/http2/server_test.go +++ b/http2/server_test.go
@@ -6,6 +6,8 @@ import ( "bytes" + "compress/gzip" + "compress/zlib" "context" "crypto/tls" "errors" @@ -3966,3 +3968,97 @@ t.Errorf("Read = %v, %v; want 0, non-nil", n, err) } } + +// Issue 31753: don't sniff when Content-Encoding is set +func TestContentEncodingNoSniffing(t *testing.T) { + type resp struct { + name string + body []byte + // setting Content-Encoding as an interface instead of a string + // directly, so as to differentiate between 3 states: + // unset, empty string "" and set string "foo/bar". + contentEncoding interface{} + wantContentType string + } + + resps := []*resp{ + { + name: "gzip content-encoding, gzipped", // don't sniff. + contentEncoding: "application/gzip", + wantContentType: "", + body: func() []byte { + buf := new(bytes.Buffer) + gzw := gzip.NewWriter(buf) + gzw.Write([]byte("doctype html><p>Hello</p>")) + gzw.Close() + return buf.Bytes() + }(), + }, + { + name: "zlib content-encoding, zlibbed", // don't sniff. + contentEncoding: "application/zlib", + wantContentType: "", + body: func() []byte { + buf := new(bytes.Buffer) + zw := zlib.NewWriter(buf) + zw.Write([]byte("doctype html><p>Hello</p>")) + zw.Close() + return buf.Bytes() + }(), + }, + { + name: "no content-encoding", // must sniff. + wantContentType: "application/x-gzip", + body: func() []byte { + buf := new(bytes.Buffer) + gzw := gzip.NewWriter(buf) + gzw.Write([]byte("doctype html><p>Hello</p>")) + gzw.Close() + return buf.Bytes() + }(), + }, + { + name: "phony content-encoding", // don't sniff. + contentEncoding: "foo/bar", + body: []byte("doctype html><p>Hello</p>"), + }, + { + name: "empty but set content-encoding", + contentEncoding: "", + wantContentType: "audio/mpeg", + body: []byte("ID3"), + }, + } + + tr := &Transport{TLSClientConfig: tlsConfigInsecure} + defer tr.CloseIdleConnections() + + for _, tt := range resps { + t.Run(tt.name, func(t *testing.T) { + st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) { + if tt.contentEncoding != nil { + w.Header().Set("Content-Encoding", tt.contentEncoding.(string)) + } + w.Write(tt.body) + }, optOnlyServer) + defer st.Close() + + req, _ := http.NewRequest("GET", st.ts.URL, nil) + res, err := tr.RoundTrip(req) + if err != nil { + t.Fatalf("Failed to fetch URL: %v", err) + } + defer res.Body.Close() + if g, w := res.Header.Get("Content-Encoding"), tt.contentEncoding; g != w { + if w != nil { // The case where contentEncoding was set explicitly. + t.Errorf("Content-Encoding mismatch\n\tgot: %q\n\twant: %q", g, w) + } else if g != "" { // "" should be the equivalent when the contentEncoding is unset. + t.Errorf("Unexpected Content-Encoding %q", g) + } + } + if g, w := res.Header.Get("Content-Type"), tt.wantContentType; g != w { + t.Errorf("Content-Type mismatch\n\tgot: %q\n\twant: %q", g, w) + } + }) + } +}