diff --git a/src/net/ip_test.go b/src/net/ip_test.go index f765582cad..ad13388dd2 100644 --- a/src/net/ip_test.go +++ b/src/net/ip_test.go @@ -469,61 +469,77 @@ func TestNetworkNumberAndMask(t *testing.T) { } } -var splitJoinTests = []struct { - host string - port string - join string -}{ - {"www.google.com", "80", "www.google.com:80"}, - {"127.0.0.1", "1234", "127.0.0.1:1234"}, - {"::1", "80", "[::1]:80"}, - {"fe80::1%lo0", "80", "[fe80::1%lo0]:80"}, - {"localhost%lo0", "80", "[localhost%lo0]:80"}, - {"", "0", ":0"}, - - {"google.com", "https%foo", "google.com:https%foo"}, // Go 1.0 behavior - {"127.0.0.1", "", "127.0.0.1:"}, // Go 1.0 behavior - {"www.google.com", "", "www.google.com:"}, // Go 1.0 behavior -} - -var splitFailureTests = []struct { - hostPort string - err string -}{ - {"www.google.com", "missing port in address"}, - {"127.0.0.1", "missing port in address"}, - {"[::1]", "missing port in address"}, - {"[fe80::1%lo0]", "missing port in address"}, - {"[localhost%lo0]", "missing port in address"}, - {"localhost%lo0", "missing port in address"}, - - {"::1", "too many colons in address"}, - {"fe80::1%lo0", "too many colons in address"}, - {"fe80::1%lo0:80", "too many colons in address"}, - - {"localhost%lo0:80", "missing brackets in address"}, - - // Test cases that didn't fail in Go 1.0 - - {"[foo:bar]", "missing port in address"}, - {"[foo:bar]baz", "missing port in address"}, - {"[foo]bar:baz", "missing port in address"}, - - {"[foo]:[bar]:baz", "too many colons in address"}, - - {"[foo]:[bar]baz", "unexpected '[' in address"}, - {"foo[bar]:baz", "unexpected '[' in address"}, - - {"foo]bar:baz", "unexpected ']' in address"}, -} - func TestSplitHostPort(t *testing.T) { - for _, tt := range splitJoinTests { - if host, port, err := SplitHostPort(tt.join); host != tt.host || port != tt.port || err != nil { - t.Errorf("SplitHostPort(%q) = %q, %q, %v; want %q, %q, nil", tt.join, host, port, err, tt.host, tt.port) + for _, tt := range []struct { + hostPort string + host string + port string + }{ + // Host name + {"localhost:http", "localhost", "http"}, + {"localhost:80", "localhost", "80"}, + + // Go-specific host name with zone identifier + {"localhost%lo0:http", "localhost%lo0", "http"}, + {"localhost%lo0:80", "localhost%lo0", "80"}, + {"[localhost%lo0]:http", "localhost%lo0", "http"}, // Go 1 behavior + {"[localhost%lo0]:80", "localhost%lo0", "80"}, // Go 1 behavior + + // IP literal + {"127.0.0.1:http", "127.0.0.1", "http"}, + {"127.0.0.1:80", "127.0.0.1", "80"}, + {"[::1]:http", "::1", "http"}, + {"[::1]:80", "::1", "80"}, + + // IP literal with zone identifier + {"[::1%lo0]:http", "::1%lo0", "http"}, + {"[::1%lo0]:80", "::1%lo0", "80"}, + + // Go-specific wildcard for host name + {":http", "", "http"}, // Go 1 behavior + {":80", "", "80"}, // Go 1 behavior + + // Go-specific wildcard for service name or transport port number + {"golang.org:", "golang.org", ""}, // Go 1 behavior + {"127.0.0.1:", "127.0.0.1", ""}, // Go 1 behavior + {"[::1]:", "::1", ""}, // Go 1 behavior + + // Opaque service name + {"golang.org:https%foo", "golang.org", "https%foo"}, // Go 1 behavior + } { + if host, port, err := SplitHostPort(tt.hostPort); host != tt.host || port != tt.port || err != nil { + t.Errorf("SplitHostPort(%q) = %q, %q, %v; want %q, %q, nil", tt.hostPort, host, port, err, tt.host, tt.port) } } - for _, tt := range splitFailureTests { + + for _, tt := range []struct { + hostPort string + err string + }{ + {"golang.org", "missing port in address"}, + {"127.0.0.1", "missing port in address"}, + {"[::1]", "missing port in address"}, + {"[fe80::1%lo0]", "missing port in address"}, + {"[localhost%lo0]", "missing port in address"}, + {"localhost%lo0", "missing port in address"}, + + {"::1", "too many colons in address"}, + {"fe80::1%lo0", "too many colons in address"}, + {"fe80::1%lo0:80", "too many colons in address"}, + + // Test cases that didn't fail in Go 1 + + {"[foo:bar]", "missing port in address"}, + {"[foo:bar]baz", "missing port in address"}, + {"[foo]bar:baz", "missing port in address"}, + + {"[foo]:[bar]:baz", "too many colons in address"}, + + {"[foo]:[bar]baz", "unexpected '[' in address"}, + {"foo[bar]:baz", "unexpected '[' in address"}, + + {"foo]bar:baz", "unexpected ']' in address"}, + } { if host, port, err := SplitHostPort(tt.hostPort); err == nil { t.Errorf("SplitHostPort(%q) should have failed", tt.hostPort) } else { @@ -539,9 +555,43 @@ func TestSplitHostPort(t *testing.T) { } func TestJoinHostPort(t *testing.T) { - for _, tt := range splitJoinTests { - if join := JoinHostPort(tt.host, tt.port); join != tt.join { - t.Errorf("JoinHostPort(%q, %q) = %q; want %q", tt.host, tt.port, join, tt.join) + for _, tt := range []struct { + host string + port string + hostPort string + }{ + // Host name + {"localhost", "http", "localhost:http"}, + {"localhost", "80", "localhost:80"}, + + // Go-specific host name with zone identifier + {"localhost%lo0", "http", "localhost%lo0:http"}, + {"localhost%lo0", "80", "localhost%lo0:80"}, + + // IP literal + {"127.0.0.1", "http", "127.0.0.1:http"}, + {"127.0.0.1", "80", "127.0.0.1:80"}, + {"::1", "http", "[::1]:http"}, + {"::1", "80", "[::1]:80"}, + + // IP literal with zone identifier + {"::1%lo0", "http", "[::1%lo0]:http"}, + {"::1%lo0", "80", "[::1%lo0]:80"}, + + // Go-specific wildcard for host name + {"", "http", ":http"}, // Go 1 behavior + {"", "80", ":80"}, // Go 1 behavior + + // Go-specific wildcard for service name or transport port number + {"golang.org", "", "golang.org:"}, // Go 1 behavior + {"127.0.0.1", "", "127.0.0.1:"}, // Go 1 behavior + {"::1", "", "[::1]:"}, // Go 1 behavior + + // Opaque service name + {"golang.org", "https%foo", "golang.org:https%foo"}, // Go 1 behavior + } { + if hostPort := JoinHostPort(tt.host, tt.port); hostPort != tt.hostPort { + t.Errorf("JoinHostPort(%q, %q) = %q; want %q", tt.host, tt.port, hostPort, tt.hostPort) } } } diff --git a/src/net/ipsock.go b/src/net/ipsock.go index f1394a7ed8..9618c66440 100644 --- a/src/net/ipsock.go +++ b/src/net/ipsock.go @@ -107,10 +107,11 @@ func ipv6only(addr IPAddr) bool { } // SplitHostPort splits a network address of the form "host:port", -// "[host]:port" or "[ipv6-host%zone]:port" into host or -// ipv6-host%zone and port. A literal address or host name for IPv6 -// must be enclosed in square brackets, as in "[::1]:80", -// "[ipv6-host]:http" or "[ipv6-host%zone]:80". +// "host%zone:port", "[host]:port" or "[host%zone]:port" into host or +// host%zone and port. +// +// A literal IPv6 address in hostport must be enclosed in square +// brackets, as in "[::1]:80", "[::1%lo0]:80". func SplitHostPort(hostport string) (host, port string, err error) { const ( missingPort = "missing port in address" @@ -154,9 +155,6 @@ func SplitHostPort(hostport string) (host, port string, err error) { if byteIndex(host, ':') >= 0 { return addrErr(hostport, tooManyColons) } - if byteIndex(host, '%') >= 0 { - return addrErr(hostport, "missing brackets in address") - } } if byteIndex(hostport[j:], '[') >= 0 { return addrErr(hostport, "unexpected '[' in address") @@ -181,11 +179,12 @@ func splitHostZone(s string) (host, zone string) { } // JoinHostPort combines host and port into a network address of the -// form "host:port" or, if host contains a colon or a percent sign, -// "[host]:port". +// form "host:port" or "host%zone:port", if host is a literal IPv6 +// address, "[host]:port" or [host%zone]:port. func JoinHostPort(host, port string) string { - // If host has colons or a percent sign, have to bracket it. - if byteIndex(host, ':') >= 0 || byteIndex(host, '%') >= 0 { + // We assume that host is a literal IPv6 address if host has + // colons. + if byteIndex(host, ':') >= 0 { return "[" + host + "]:" + port } return host + ":" + port