author | Brad Fitzpatrick
<bradfitz@golang.org> 2016-09-12 01:12:11 UTC |
committer | Brad Fitzpatrick
<bradfitz@golang.org> 2016-09-12 18:43:32 UTC |
parent | 324491d706c63d58003f108b1c13ca4e477a6959 |
http2/transport.go | +15 | -6 |
lex/httplex/httplex.go | +39 | -0 |
lex/httplex/httplex_test.go | +18 | -0 |
diff --git a/http2/transport.go b/http2/transport.go index 47c66d9..7a8563d 100644 --- a/http2/transport.go +++ b/http2/transport.go @@ -26,6 +26,7 @@ import ( "time" "golang.org/x/net/http2/hpack" + "golang.org/x/net/idna" "golang.org/x/net/lex/httplex" ) @@ -285,14 +286,18 @@ func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) { // authorityAddr returns a given authority (a host/IP, or host:port / ip:port) // and returns a host:port. The port 443 is added if needed. func authorityAddr(scheme string, authority string) (addr string) { - if _, _, err := net.SplitHostPort(authority); err == nil { - return authority + host, port, err := net.SplitHostPort(authority) + if err != nil { // authority didn't have a port + port = "443" + if scheme == "http" { + port = "80" + } + host = authority } - port := "443" - if scheme == "http" { - port = "80" + if a, err := idna.ToASCII(host); err == nil { + host = a } - return net.JoinHostPort(authority, port) + return net.JoinHostPort(host, port) } // RoundTripOpt is like RoundTrip, but takes options. @@ -997,6 +1002,10 @@ func (cc *ClientConn) encodeHeaders(req *http.Request, addGzipHeader bool, trail if host == "" { host = req.URL.Host } + host, err := httplex.PunycodeHostPort(host) + if err != nil { + return nil, err + } var path string if req.Method != "CONNECT" { diff --git a/lex/httplex/httplex.go b/lex/httplex/httplex.go index bd0ec24..20f2b89 100644 --- a/lex/httplex/httplex.go +++ b/lex/httplex/httplex.go @@ -10,8 +10,11 @@ package httplex import ( + "net" "strings" "unicode/utf8" + + "golang.org/x/net/idna" ) var isTokenTable = [127]bool{ @@ -310,3 +313,39 @@ func ValidHeaderFieldValue(v string) bool { } return true } + +func isASCII(s string) bool { + for i := 0; i < len(s); i++ { + if s[i] >= utf8.RuneSelf { + return false + } + } + return true +} + +// PunycodeHostPort returns the IDNA Punycode version +// of the provided "host" or "host:port" string. +func PunycodeHostPort(v string) (string, error) { + if isASCII(v) { + return v, nil + } + + host, port, err := net.SplitHostPort(v) + if err != nil { + // The input 'v' argument was just a "host" argument, + // without a port. This error should not be returned + // to the caller. + host = v + port = "" + } + host, err = idna.ToASCII(host) + if err != nil { + // Non-UTF-8? Not representable in Punycode, in any + // case. + return "", err + } + if port == "" { + return host, nil + } + return net.JoinHostPort(host, port), nil +} diff --git a/lex/httplex/httplex_test.go b/lex/httplex/httplex_test.go index c4ace19..f47adc9 100644 --- a/lex/httplex/httplex_test.go +++ b/lex/httplex/httplex_test.go @@ -99,3 +99,21 @@ func TestHeaderValuesContainsToken(t *testing.T) { } } } + +func TestPunycodeHostPort(t *testing.T) { + tests := []struct { + in, want string + }{ + {"www.google.com", "www.google.com"}, + {"гофер.рф", "xn--c1ae0ajs.xn--p1ai"}, + {"bücher.de", "xn--bcher-kva.de"}, + {"bücher.de:8080", "xn--bcher-kva.de:8080"}, + {"[1::6]:8080", "[1::6]:8080"}, + } + for _, tt := range tests { + got, err := PunycodeHostPort(tt.in) + if tt.want != got || err != nil { + t.Errorf("PunycodeHostPort(%q) = %q, %v, want %q, nil", tt.in, got, err, tt.want) + } + } +}