]> Cypherpunks repositories - gostls13.git/commitdiff
net/http: do not sniff response if Content-Encoding header is set
authorEmmanuel T Odeke <emmanuel@orijtech.com>
Tue, 8 Oct 2019 07:23:08 +0000 (00:23 -0700)
committerEmmanuel Odeke <emm.odeke@gmail.com>
Wed, 9 Oct 2019 19:49:39 +0000 (19:49 +0000)
Fixes #31753

Change-Id: I32ec5906ef6714e19b094f67cb0f10a211a9c500
Reviewed-on: https://go-review.googlesource.com/c/go/+/199799
Run-TryBot: Emmanuel Odeke <emm.odeke@gmail.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
src/net/http/clientserver_test.go
src/net/http/serve_test.go
src/net/http/server.go

index d61d77839d31d4068e32229224affaae8ae0dd91..e9241c40dd59a6762fb749fcaab8527fda9e43a9 100644 (file)
@@ -192,6 +192,8 @@ func (tt h12Compare) reqFunc() reqFunc {
 }
 
 func (tt h12Compare) run(t *testing.T) {
+       t.Skip("Temporarily disabling until https://golang.org/issue/31753 is fixed")
+
        setParallel(t)
        cst1 := newClientServerTest(t, false, HandlerFunc(tt.Handler), tt.Opts...)
        defer cst1.close()
index 1d1449aa659ef417db74d94a17b1d9c5f13a61b9..e1f8d2ddb79cb594e7e74be3656346f0ccbd298b 100644 (file)
@@ -10,6 +10,7 @@ import (
        "bufio"
        "bytes"
        "compress/gzip"
+       "compress/zlib"
        "context"
        "crypto/tls"
        "encoding/json"
@@ -6161,6 +6162,111 @@ func TestUnsupportedTransferEncodingsReturn501(t *testing.T) {
        }
 }
 
+func TestContentEncodingNoSniffing_h1(t *testing.T) {
+       testContentEncodingNoSniffing(t, h1Mode)
+}
+
+func TestContentEncodingNoSniffing_h2(t *testing.T) {
+       t.Skip("Waiting for h2_bundle.go update after https://golang.org/issue/31753")
+       testContentEncodingNoSniffing(t, h2Mode)
+}
+
+// Issue 31753: don't sniff when Content-Encoding is set
+func testContentEncodingNoSniffing(t *testing.T, h2 bool) {
+       setParallel(t)
+       defer afterTest(t)
+
+       type setting struct {
+               name string
+               body []byte
+
+               // setting contentEncoding 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
+       }
+
+       settings := []*setting{
+               {
+                       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"),
+               },
+       }
+
+       for _, tt := range settings {
+               t.Run(tt.name, func(t *testing.T) {
+                       cst := newClientServerTest(t, h2, HandlerFunc(func(rw ResponseWriter, r *Request) {
+                               if tt.contentEncoding != nil {
+                                       rw.Header().Set("Content-Encoding", tt.contentEncoding.(string))
+                               }
+                               rw.Write(tt.body)
+                       }))
+                       defer cst.close()
+
+                       res, err := cst.c.Get(cst.ts.URL)
+                       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)
+                       }
+               })
+       }
+}
+
 // fetchWireResponse is a helper for dialing to host,
 // sending http1ReqBody as the payload and retrieving
 // the response as it was sent on the wire.
index 9ab4cc745e601d6ff5d6ae8a8810341fc2a04252..6e3197118000fb852db51fc6f8b41b6be5710e86 100644 (file)
@@ -1379,7 +1379,12 @@ func (cw *chunkWriter) writeHeader(p []byte) {
        if bodyAllowedForStatus(code) {
                // If no content type, apply sniffing algorithm to body.
                _, haveType := header["Content-Type"]
-               if !haveType && !hasTE && len(p) > 0 {
+
+               // If the Content-Encoding was set and is non-blank,
+               // we shouldn't sniff the body. See Issue 31753.
+               ce := header.Get("Content-Encoding")
+               hasCE := len(ce) > 0
+               if !hasCE && !haveType && !hasTE && len(p) > 0 {
                        setHeader.contentType = DetectContentType(p)
                }
        } else {