]> Cypherpunks repositories - gostls13.git/commitdiff
[release-branch.go1.13] net/http: fix Transport panic with nil Request.Header
authorEmmanuel T Odeke <emmanuel@orijtech.com>
Sun, 13 Oct 2019 22:07:06 +0000 (15:07 -0700)
committerBrad Fitzpatrick <bradfitz@golang.org>
Mon, 14 Oct 2019 18:28:36 +0000 (18:28 +0000)
For Go 1.13 we introduced Header.Clone and it returns
nil if a nil Header is cloned. Unfortunately, though,
this exported Header.Clone nil behavior differed from
the old Go 1.12 and earlier internal header clone
behavior which always returned non-nil Headers.
This CL fixes the places where that distinction mattered.

Fixes #34882

Change-Id: Id19dea2272948c8dd10883b18ea7f7b8b33ea8eb
Reviewed-on: https://go-review.googlesource.com/c/go/+/200977
Run-TryBot: Emmanuel Odeke <emm.odeke@gmail.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
(cherry picked from commit 9969c720800302c63147720da5507633133bd4a6)
Reviewed-on: https://go-review.googlesource.com/c/go/+/201040

src/net/http/client.go
src/net/http/clone.go
src/net/http/header_test.go
src/net/http/request_test.go

index 65a9d51cc6b179b944b78690db2e4fb3900476d2..20496d2f0ef9cbbae802c0961e9bf800a7954446 100644 (file)
@@ -238,7 +238,7 @@ func send(ireq *Request, rt RoundTripper, deadline time.Time) (resp *Response, d
                username := u.Username()
                password, _ := u.Password()
                forkReq()
-               req.Header = ireq.Header.Clone()
+               req.Header = cloneOrMakeHeader(ireq.Header)
                req.Header.Set("Authorization", "Basic "+basicAuth(username, password))
        }
 
@@ -668,7 +668,7 @@ func (c *Client) makeHeadersCopier(ireq *Request) func(*Request) {
        // The headers to copy are from the very initial request.
        // We use a closured callback to keep a reference to these original headers.
        var (
-               ireqhdr  = ireq.Header.Clone()
+               ireqhdr  = cloneOrMakeHeader(ireq.Header)
                icookies map[string][]*Cookie
        )
        if c.Jar != nil && ireq.Header.Get("Cookie") != "" {
index 5f2784d28089d2758cbf06aa0484e8a988ad625d..3a3375bff7164163d39753f4bbf1edb9e87f87cd 100644 (file)
@@ -62,3 +62,13 @@ func cloneMultipartFileHeader(fh *multipart.FileHeader) *multipart.FileHeader {
        fh2.Header = textproto.MIMEHeader(Header(fh.Header).Clone())
        return fh2
 }
+
+// cloneOrMakeHeader invokes Header.Clone but if the
+// result is nil, it'll instead make and return a non-nil Header.
+func cloneOrMakeHeader(hdr Header) Header {
+       clone := hdr.Clone()
+       if clone == nil {
+               clone = make(Header)
+       }
+       return clone
+}
index 51fcab103b42472fd4280d438d63d93b507c1d6e..47893629194b6a6dedde343b06c73ac0db976f34 100644 (file)
@@ -7,6 +7,7 @@ package http
 import (
        "bytes"
        "internal/race"
+       "reflect"
        "runtime"
        "testing"
        "time"
@@ -219,3 +220,34 @@ func TestHeaderWriteSubsetAllocs(t *testing.T) {
                t.Errorf("allocs = %g; want 0", n)
        }
 }
+
+// Issue 34878: test that every call to
+// cloneOrMakeHeader never returns a nil Header.
+func TestCloneOrMakeHeader(t *testing.T) {
+       tests := []struct {
+               name     string
+               in, want Header
+       }{
+               {"nil", nil, Header{}},
+               {"empty", Header{}, Header{}},
+               {
+                       name: "non-empty",
+                       in:   Header{"foo": {"bar"}},
+                       want: Header{"foo": {"bar"}},
+               },
+       }
+
+       for _, tt := range tests {
+               t.Run(tt.name, func(t *testing.T) {
+                       got := cloneOrMakeHeader(tt.in)
+                       if got == nil {
+                               t.Fatal("unexpected nil Header")
+                       }
+                       if !reflect.DeepEqual(got, tt.want) {
+                               t.Fatalf("Got:  %#v\nWant: %#v", got, tt.want)
+                       }
+                       got.Add("A", "B")
+                       got.Get("A")
+               })
+       }
+}
index b072f958024b903f79f052a8fc471f092d415cc8..bb06d922f0fb9e6cfe75c3a6f3319d2e65ae6d2f 100644 (file)
@@ -826,6 +826,34 @@ func TestWithContextDeepCopiesURL(t *testing.T) {
        }
 }
 
+func TestNoPanicOnRoundTripWithBasicAuth_h1(t *testing.T) {
+       testNoPanicWithBasicAuth(t, h1Mode)
+}
+
+func TestNoPanicOnRoundTripWithBasicAuth_h2(t *testing.T) {
+       testNoPanicWithBasicAuth(t, h2Mode)
+}
+
+// Issue 34878: verify we don't panic when including basic auth (Go 1.13 regression)
+func testNoPanicWithBasicAuth(t *testing.T, h2 bool) {
+       defer afterTest(t)
+       cst := newClientServerTest(t, h2, HandlerFunc(func(w ResponseWriter, r *Request) {}))
+       defer cst.close()
+
+       u, err := url.Parse(cst.ts.URL)
+       if err != nil {
+               t.Fatal(err)
+       }
+       u.User = url.UserPassword("foo", "bar")
+       req := &Request{
+               URL:    u,
+               Method: "GET",
+       }
+       if _, err := cst.c.Do(req); err != nil {
+               t.Fatalf("Unexpected error: %v", err)
+       }
+}
+
 // verify that NewRequest sets Request.GetBody and that it works
 func TestNewRequestGetBody(t *testing.T) {
        tests := []struct {