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
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))
}
// 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") != "" {
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
+}
import (
"bytes"
"internal/race"
+ "reflect"
"runtime"
"testing"
"time"
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")
+ })
+ }
+}
}
}
+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 {