Skip to content

Commit e24a628

Browse files
committed
net/http: do not sniff response if Content-Encoding header is set
Fixes #31753 Change-Id: I32ec5906ef6714e19b094f67cb0f10a211a9c500 Reviewed-on: https://go-review.googlesource.com/c/go/+/199799 Run-TryBot: Emmanuel Odeke <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Brad Fitzpatrick <[email protected]>
1 parent 6cba4db commit e24a628

File tree

3 files changed

+114
-1
lines changed

3 files changed

+114
-1
lines changed

src/net/http/clientserver_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,8 @@ func (tt h12Compare) reqFunc() reqFunc {
192192
}
193193

194194
func (tt h12Compare) run(t *testing.T) {
195+
t.Skip("Temporarily disabling until https://golang.org/issue/31753 is fixed")
196+
195197
setParallel(t)
196198
cst1 := newClientServerTest(t, false, HandlerFunc(tt.Handler), tt.Opts...)
197199
defer cst1.close()

src/net/http/serve_test.go

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"bufio"
1111
"bytes"
1212
"compress/gzip"
13+
"compress/zlib"
1314
"context"
1415
"crypto/tls"
1516
"encoding/json"
@@ -6161,6 +6162,111 @@ func TestUnsupportedTransferEncodingsReturn501(t *testing.T) {
61616162
}
61626163
}
61636164

6165+
func TestContentEncodingNoSniffing_h1(t *testing.T) {
6166+
testContentEncodingNoSniffing(t, h1Mode)
6167+
}
6168+
6169+
func TestContentEncodingNoSniffing_h2(t *testing.T) {
6170+
t.Skip("Waiting for h2_bundle.go update after https://golang.org/issue/31753")
6171+
testContentEncodingNoSniffing(t, h2Mode)
6172+
}
6173+
6174+
// Issue 31753: don't sniff when Content-Encoding is set
6175+
func testContentEncodingNoSniffing(t *testing.T, h2 bool) {
6176+
setParallel(t)
6177+
defer afterTest(t)
6178+
6179+
type setting struct {
6180+
name string
6181+
body []byte
6182+
6183+
// setting contentEncoding as an interface instead of a string
6184+
// directly, so as to differentiate between 3 states:
6185+
// unset, empty string "" and set string "foo/bar".
6186+
contentEncoding interface{}
6187+
wantContentType string
6188+
}
6189+
6190+
settings := []*setting{
6191+
{
6192+
name: "gzip content-encoding, gzipped", // don't sniff.
6193+
contentEncoding: "application/gzip",
6194+
wantContentType: "",
6195+
body: func() []byte {
6196+
buf := new(bytes.Buffer)
6197+
gzw := gzip.NewWriter(buf)
6198+
gzw.Write([]byte("doctype html><p>Hello</p>"))
6199+
gzw.Close()
6200+
return buf.Bytes()
6201+
}(),
6202+
},
6203+
{
6204+
name: "zlib content-encoding, zlibbed", // don't sniff.
6205+
contentEncoding: "application/zlib",
6206+
wantContentType: "",
6207+
body: func() []byte {
6208+
buf := new(bytes.Buffer)
6209+
zw := zlib.NewWriter(buf)
6210+
zw.Write([]byte("doctype html><p>Hello</p>"))
6211+
zw.Close()
6212+
return buf.Bytes()
6213+
}(),
6214+
},
6215+
{
6216+
name: "no content-encoding", // must sniff.
6217+
wantContentType: "application/x-gzip",
6218+
body: func() []byte {
6219+
buf := new(bytes.Buffer)
6220+
gzw := gzip.NewWriter(buf)
6221+
gzw.Write([]byte("doctype html><p>Hello</p>"))
6222+
gzw.Close()
6223+
return buf.Bytes()
6224+
}(),
6225+
},
6226+
{
6227+
name: "phony content-encoding", // don't sniff.
6228+
contentEncoding: "foo/bar",
6229+
body: []byte("doctype html><p>Hello</p>"),
6230+
},
6231+
{
6232+
name: "empty but set content-encoding",
6233+
contentEncoding: "",
6234+
wantContentType: "audio/mpeg",
6235+
body: []byte("ID3"),
6236+
},
6237+
}
6238+
6239+
for _, tt := range settings {
6240+
t.Run(tt.name, func(t *testing.T) {
6241+
cst := newClientServerTest(t, h2, HandlerFunc(func(rw ResponseWriter, r *Request) {
6242+
if tt.contentEncoding != nil {
6243+
rw.Header().Set("Content-Encoding", tt.contentEncoding.(string))
6244+
}
6245+
rw.Write(tt.body)
6246+
}))
6247+
defer cst.close()
6248+
6249+
res, err := cst.c.Get(cst.ts.URL)
6250+
if err != nil {
6251+
t.Fatalf("Failed to fetch URL: %v", err)
6252+
}
6253+
defer res.Body.Close()
6254+
6255+
if g, w := res.Header.Get("Content-Encoding"), tt.contentEncoding; g != w {
6256+
if w != nil { // The case where contentEncoding was set explicitly.
6257+
t.Errorf("Content-Encoding mismatch\n\tgot: %q\n\twant: %q", g, w)
6258+
} else if g != "" { // "" should be the equivalent when the contentEncoding is unset.
6259+
t.Errorf("Unexpected Content-Encoding %q", g)
6260+
}
6261+
}
6262+
6263+
if g, w := res.Header.Get("Content-Type"), tt.wantContentType; g != w {
6264+
t.Errorf("Content-Type mismatch\n\tgot: %q\n\twant: %q", g, w)
6265+
}
6266+
})
6267+
}
6268+
}
6269+
61646270
// fetchWireResponse is a helper for dialing to host,
61656271
// sending http1ReqBody as the payload and retrieving
61666272
// the response as it was sent on the wire.

src/net/http/server.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1379,7 +1379,12 @@ func (cw *chunkWriter) writeHeader(p []byte) {
13791379
if bodyAllowedForStatus(code) {
13801380
// If no content type, apply sniffing algorithm to body.
13811381
_, haveType := header["Content-Type"]
1382-
if !haveType && !hasTE && len(p) > 0 {
1382+
1383+
// If the Content-Encoding was set and is non-blank,
1384+
// we shouldn't sniff the body. See Issue 31753.
1385+
ce := header.Get("Content-Encoding")
1386+
hasCE := len(ce) > 0
1387+
if !hasCE && !haveType && !hasTE && len(p) > 0 {
13831388
setHeader.contentType = DetectContentType(p)
13841389
}
13851390
} else {

0 commit comments

Comments
 (0)