diff --git a/client.go b/client.go index c058f36004..2b6b60f768 100644 --- a/client.go +++ b/client.go @@ -9,7 +9,6 @@ import ( "fmt" "io" "net" - "strconv" "strings" "sync" "sync/atomic" @@ -495,7 +494,7 @@ func (c *Client) Do(req *Request, resp *Response) error { hc := m[string(host)] if hc == nil { hc = &HostClient{ - Addr: addMissingPort(string(host), isTLS), + Addr: AddMissingPort(string(host), isTLS), Name: c.Name, NoDefaultUserAgentHeader: c.NoDefaultUserAgentHeader, Dial: c.Dial, @@ -1968,7 +1967,7 @@ func dialAddr(addr string, dial DialFunc, dialDualStack, isTLS bool, tlsConfig * } else { dial = Dial } - addr = addMissingPort(addr, isTLS) + addr = AddMissingPort(addr, isTLS) } conn, err := dial(addr) if err != nil { @@ -2006,16 +2005,33 @@ func (c *HostClient) getClientName() []byte { return clientName } -func addMissingPort(addr string, isTLS bool) string { - n := strings.Index(addr, ":") - if n >= 0 { +// AddMissingPort adds a port to a host if it is missing. +// A literal IPv6 address in hostport must be enclosed in square +// brackets, as in "[::1]:80", "[::1%lo0]:80". +func AddMissingPort(addr string, isTLS bool) string { + addrLen := len(addr) + if addrLen == 0 { return addr } - port := 80 + + isIp6 := addr[0] == '[' + if isIp6 { + // if the IPv6 has opening bracket but closing bracket is the last char then it doesn't have a port + isIp6WithoutPort := addr[addrLen-1] == ']' + if !isIp6WithoutPort { + return addr + } + } else { // IPv4 + columnPos := strings.LastIndexByte(addr, ':') + if columnPos > 0 { + return addr + } + } + port := ":80" if isTLS { - port = 443 + port = ":443" } - return net.JoinHostPort(addr, strconv.Itoa(port)) + return addr + port } // A wantConn records state about a wanted connection diff --git a/client_test.go b/client_test.go index 13da521033..b5c0b7f9ca 100644 --- a/client_test.go +++ b/client_test.go @@ -2971,3 +2971,63 @@ func TestHttpsRequestWithoutParsedURL(t *testing.T) { t.Fatal("https requests with IsTLS client must succeed") } } + +func Test_AddMissingPort(t *testing.T) { + type args struct { + addr string + isTLS bool + } + tests := []struct { + name string + args args + want string + }{ + { + args: args{"127.1", false}, // 127.1 is a short form of 127.0.0.1 + want: "127.1:80", + }, + { + args: args{"127.0.0.1", false}, + want: "127.0.0.1:80", + }, + { + args: args{"127.0.0.1", true}, + want: "127.0.0.1:443", + }, + { + args: args{"[::1]", false}, + want: "[::1]:80", + }, + { + args: args{"::1", false}, + want: "::1", // keep as is + }, + { + args: args{"[::1]", true}, + want: "[::1]:443", + }, + { + args: args{"127.0.0.1:8080", false}, + want: "127.0.0.1:8080", + }, + { + args: args{"127.0.0.1:8443", true}, + want: "127.0.0.1:8443", + }, + { + args: args{"[::1]:8080", false}, + want: "[::1]:8080", + }, + { + args: args{"[::1]:8443", true}, + want: "[::1]:8443", + }, + } + for _, tt := range tests { + t.Run(tt.want, func(t *testing.T) { + if got := AddMissingPort(tt.args.addr, tt.args.isTLS); got != tt.want { + t.Errorf("AddMissingPort() = %v, want %v", got, tt.want) + } + }) + } +}