Skip to content

Commit 9969c72

Browse files
committed
net/http: fix Transport panic with nil Request.Header
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 #34878 Change-Id: Id19dea2272948c8dd10883b18ea7f7b8b33ea8eb Reviewed-on: https://go-review.googlesource.com/c/go/+/200977 Run-TryBot: Emmanuel Odeke <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Brad Fitzpatrick <[email protected]>
1 parent dab199c commit 9969c72

File tree

4 files changed

+72
-2
lines changed

4 files changed

+72
-2
lines changed

src/net/http/client.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,7 @@ func send(ireq *Request, rt RoundTripper, deadline time.Time) (resp *Response, d
240240
username := u.Username()
241241
password, _ := u.Password()
242242
forkReq()
243-
req.Header = ireq.Header.Clone()
243+
req.Header = cloneOrMakeHeader(ireq.Header)
244244
req.Header.Set("Authorization", "Basic "+basicAuth(username, password))
245245
}
246246

@@ -719,7 +719,7 @@ func (c *Client) makeHeadersCopier(ireq *Request) func(*Request) {
719719
// The headers to copy are from the very initial request.
720720
// We use a closured callback to keep a reference to these original headers.
721721
var (
722-
ireqhdr = ireq.Header.Clone()
722+
ireqhdr = cloneOrMakeHeader(ireq.Header)
723723
icookies map[string][]*Cookie
724724
)
725725
if c.Jar != nil && ireq.Header.Get("Cookie") != "" {

src/net/http/clone.go

+10
Original file line numberDiff line numberDiff line change
@@ -62,3 +62,13 @@ func cloneMultipartFileHeader(fh *multipart.FileHeader) *multipart.FileHeader {
6262
fh2.Header = textproto.MIMEHeader(Header(fh.Header).Clone())
6363
return fh2
6464
}
65+
66+
// cloneOrMakeHeader invokes Header.Clone but if the
67+
// result is nil, it'll instead make and return a non-nil Header.
68+
func cloneOrMakeHeader(hdr Header) Header {
69+
clone := hdr.Clone()
70+
if clone == nil {
71+
clone = make(Header)
72+
}
73+
return clone
74+
}

src/net/http/header_test.go

+32
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ package http
77
import (
88
"bytes"
99
"internal/race"
10+
"reflect"
1011
"runtime"
1112
"testing"
1213
"time"
@@ -219,3 +220,34 @@ func TestHeaderWriteSubsetAllocs(t *testing.T) {
219220
t.Errorf("allocs = %g; want 0", n)
220221
}
221222
}
223+
224+
// Issue 34878: test that every call to
225+
// cloneOrMakeHeader never returns a nil Header.
226+
func TestCloneOrMakeHeader(t *testing.T) {
227+
tests := []struct {
228+
name string
229+
in, want Header
230+
}{
231+
{"nil", nil, Header{}},
232+
{"empty", Header{}, Header{}},
233+
{
234+
name: "non-empty",
235+
in: Header{"foo": {"bar"}},
236+
want: Header{"foo": {"bar"}},
237+
},
238+
}
239+
240+
for _, tt := range tests {
241+
t.Run(tt.name, func(t *testing.T) {
242+
got := cloneOrMakeHeader(tt.in)
243+
if got == nil {
244+
t.Fatal("unexpected nil Header")
245+
}
246+
if !reflect.DeepEqual(got, tt.want) {
247+
t.Fatalf("Got: %#v\nWant: %#v", got, tt.want)
248+
}
249+
got.Add("A", "B")
250+
got.Get("A")
251+
})
252+
}
253+
}

src/net/http/request_test.go

+28
Original file line numberDiff line numberDiff line change
@@ -826,6 +826,34 @@ func TestWithContextDeepCopiesURL(t *testing.T) {
826826
}
827827
}
828828

829+
func TestNoPanicOnRoundTripWithBasicAuth_h1(t *testing.T) {
830+
testNoPanicWithBasicAuth(t, h1Mode)
831+
}
832+
833+
func TestNoPanicOnRoundTripWithBasicAuth_h2(t *testing.T) {
834+
testNoPanicWithBasicAuth(t, h2Mode)
835+
}
836+
837+
// Issue 34878: verify we don't panic when including basic auth (Go 1.13 regression)
838+
func testNoPanicWithBasicAuth(t *testing.T, h2 bool) {
839+
defer afterTest(t)
840+
cst := newClientServerTest(t, h2, HandlerFunc(func(w ResponseWriter, r *Request) {}))
841+
defer cst.close()
842+
843+
u, err := url.Parse(cst.ts.URL)
844+
if err != nil {
845+
t.Fatal(err)
846+
}
847+
u.User = url.UserPassword("foo", "bar")
848+
req := &Request{
849+
URL: u,
850+
Method: "GET",
851+
}
852+
if _, err := cst.c.Do(req); err != nil {
853+
t.Fatalf("Unexpected error: %v", err)
854+
}
855+
}
856+
829857
// verify that NewRequest sets Request.GetBody and that it works
830858
func TestNewRequestGetBody(t *testing.T) {
831859
tests := []struct {

0 commit comments

Comments
 (0)