Mikio Hara uploaded a change:
https://go-review.googlesource.com/2431
net/url: allow Parse, ParseRequestURI to parse ipv6 zone identifiers in URIs
Using IPv6 link-local addresses to make connections between on-link
nodes is useful for small distributed applications but it requires zone
identifiers to distinguish a correct IP link. It's the same for
transports using URI for destination discovery such as HTTP, WebSocket.
This CL allows both Parse and ParseRequestURI functions to parse a
literal IPv6 address followed by a zone identifier within a URI.
Fixes #6530.
Change-Id: I2936ea65c1446994770cf2ee2c28a1c73faaa0ca
---
M src/net/url/url.go
M src/net/url/url_test.go
2 files changed, 145 insertions(+), 12 deletions(-)
diff --git a/src/net/url/url.go b/src/net/url/url.go
index f167408..2862627 100644
--- a/src/net/url/url.go
+++ b/src/net/url/url.go
@@ -401,10 +401,6 @@
if err != nil {
goto Error
}
- if strings.Contains(url.Host, "%") {
- err = errors.New("hexadecimal escape in host")
- goto Error
- }
}
if url.Path, err = unescape(rest, encodePath); err != nil {
goto Error
@@ -418,26 +414,68 @@
func parseAuthority(authority string) (user *Userinfo, host string, err
error) {
i := strings.LastIndex(authority, "@")
if i < 0 {
- host = authority
- return
+ host, err = parseHost(authority)
+ } else {
+ host, err = parseHost(authority[i+1:])
}
- userinfo, host := authority[:i], authority[i+1:]
+ if err != nil {
+ return nil, "", err
+ }
+ if i < 0 {
+ return nil, host, nil
+ }
+ userinfo := authority[:i]
if strings.Index(userinfo, ":") < 0 {
if userinfo, err = unescape(userinfo, encodeUserPassword); err != nil {
- return
+ return nil, "", err
}
user = User(userinfo)
} else {
username, password := split(userinfo, ":", true)
if username, err = unescape(username, encodeUserPassword); err != nil {
- return
+ return nil, "", err
}
if password, err = unescape(password, encodeUserPassword); err != nil {
- return
+ return nil, "", err
}
user = UserPassword(username, password)
}
- return
+ return user, host, nil
+}
+
+// parseHost parses host as an authority without user information.
+func parseHost(host string) (string, error) {
+ if len(host) == 0 {
+ return host, nil
+ }
+ switch host[0] {
+ case '[': // IP-Literal in RFC 3986, RFC 6874
+ i := strings.Index(host[1:], `]`)
+ if i < 0 {
+ return "", errors.New("missing ']' in host")
+ }
+ j := strings.Index(host[1:1+i], `%25`)
+ switch {
+ case j < 0: // IPv6address in RFC 3986
+ if strings.Contains(host[1:1+i], `%`) {
+ return "", errors.New("hexadecimal escape in host")
+ }
+ default: // IPv6addrz in RFC 6874
+ if strings.Contains(host[1:1+j], `%`) {
+ return "", errors.New("hexadecimal escape in host")
+ }
+ }
+ default: // IPv4address or reg-name in RFC 3986
+ // Note that the reg-name is allowed to use the
+ // percent-encoded form in RFC but we don't use it for
+ // now to avoid messing up with the gap between
+ // allowed characters in URI and allowed characters in
+ // DNS.
+ if strings.Contains(host, `%`) {
+ return "", errors.New("hexadecimal escape in host")
+ }
+ }
+ return host, nil
}
// String reassembles the URL into a valid URL string.
diff --git a/src/net/url/url_test.go b/src/net/url/url_test.go
index d8b19d8..be25cca 100644
--- a/src/net/url/url_test.go
+++ b/src/net/url/url_test.go
@@ -289,6 +289,79 @@
},
"",
},
+ // host and port components of authority
+ {
+ "
http://192.168.0.1/",
+ &URL{
+ Scheme: "http",
+ Host: "192.168.0.1",
+ Path: "/",
+ },
+ "",
+ },
+ {
+ "
http://192.168.0.1:8080/",
+ &URL{
+ Scheme: "http",
+ Host: "
192.168.0.1:8080",
+ Path: "/",
+ },
+ "",
+ },
+ {
+ "http://[fe80::1]/",
+ &URL{
+ Scheme: "http",
+ Host: "[fe80::1]",
+ Path: "/",
+ },
+ "",
+ },
+ {
+ "http://[fe80::1]:8080/",
+ &URL{
+ Scheme: "http",
+ Host: "[fe80::1]:8080",
+ Path: "/",
+ },
+ "",
+ },
+ {
+ "http://[fe80::1%25en0]/",
+ &URL{
+ Scheme: "http",
+ Host: "[fe80::1%25en0]",
+ Path: "/",
+ },
+ "",
+ },
+ {
+ "http://[fe80::1%25en0]:8080/",
+ &URL{
+ Scheme: "http",
+ Host: "[fe80::1%25en0]:8080",
+ Path: "/",
+ },
+ "",
+ },
+ {
+ "http://[fe80::1%25%65%6e%30-._~]/",
+ &URL{
+ Scheme: "http",
+ Host: "[fe80::1%25%65%6e%30-._~]",
+ Path: "/",
+ },
+ "",
+ },
+ {
+ "http://[fe80::1%25%65%6e%30-._~]:8080/",
+ &URL{
+ Scheme: "http",
+ Host: "[fe80::1%25%65%6e%30-._~]:8080",
+ Path: "/",
+ },
+ "",
+ },
}
// more useful string for debugging than fmt's struct printer
@@ -358,9 +431,31 @@
{"/", true},
{pathThatLooksSchemeRelative, true},
{"//not.a.user@%66%6f%
6f.com/just/a/path/also", true},
+ {"*", true},
+ {"
http://192.168.0.1/", true},
+ {"
http://192.168.0.1:8080/", true},
+ {"http://[fe80::1]/", true},
+ {"http://[fe80::1]:8080/", true},
+ {"http://[fe80::1%25en0]/", true},
+ {"http://[fe80::1%25en0]:8080/", true},
+ {"http://[fe80::1%25%65%6e%30-._~]/", true},
+ {"http://[fe80::1%25%65%6e%30-._~]:8080/", true},
+
{"foo.html", false},
{"../dir/", false},
- {"*", true},
+ {"
http://192.168.0.%31/", false},
+ {"
http://192.168.0.%31:8080/", false},
+ {"http://[fe80::%31]/", false},
+ {"http://[fe80::%31]:8080/", false},
+ {"http://[fe80::%31%25en0]/", false},
+ {"http://[fe80::%31%25en0]:8080/", false},
+
+ // These two cases are valid as textunal representations as
+ // described in RFC 4007, but are not valid as address
+ // literals with IPv6 zone identifiers in URIs as described in
+ // RFC 6874.
+ {"http://[fe80::1%en0]/", false},
+ {"http://[fe80::1%en0]:8080/", false},
}
func TestParseRequestURI(t *testing.T) {
--
https://go-review.googlesource.com/2431