Skip to content

Commit 617c93c

Browse files
committed
net/url: reject space in host; do not escape < > " in host
Host names in URLs must not use %-escaping for ASCII bytes, per RFC 3986. url.Parse has historically allowed spaces and < > " in the URL host. In Go 1.5, URL's String method started escaping those, but then Parse would rejects the escaped form. This CL is an attempt at some consistency between Parse and String as far as the accepted host characters and the encoding of host characters, so that if Parse succeeds, then Parse -> String -> Parse also succeeds. Allowing space seems like a mistake, so reject that in Parse. (Similarly, reject \t, \x01, and so on, all of which were being allowed.) Allowing < > " doesn't seem awful, so continue to do that, and go back to the Go 1.4 behavior of not escaping them in String. Fixes #11302. Change-Id: I0bf65b874cd936598f20694574364352a5abbe5f Reviewed-on: https://go-review.googlesource.com/17387 Reviewed-by: Brad Fitzpatrick <[email protected]> Run-TryBot: Russ Cox <[email protected]> TryBot-Result: Gobot Gobot <[email protected]>
1 parent a4fd325 commit 617c93c

File tree

3 files changed

+29
-3
lines changed

3 files changed

+29
-3
lines changed

src/net/http/request_test.go

+3-1
Original file line numberDiff line numberDiff line change
@@ -539,10 +539,12 @@ func TestRequestWriteBufferedWriter(t *testing.T) {
539539

540540
func TestRequestBadHost(t *testing.T) {
541541
got := []string{}
542-
req, err := NewRequest("GET", "http://foo.com with spaces/after", nil)
542+
req, err := NewRequest("GET", "http://foo/after", nil)
543543
if err != nil {
544544
t.Fatal(err)
545545
}
546+
req.Host = "foo.com with spaces"
547+
req.URL.Host = "foo.com with spaces"
546548
req.Write(logWrites{t, &got})
547549
want := []string{
548550
"GET /after HTTP/1.1\r\n",

src/net/url/url.go

+15-2
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,12 @@ func (e EscapeError) Error() string {
8383
return "invalid URL escape " + strconv.Quote(string(e))
8484
}
8585

86+
type InvalidHostError string
87+
88+
func (e InvalidHostError) Error() string {
89+
return "invalid character " + strconv.Quote(string(e)) + " in host name"
90+
}
91+
8692
// Return true if the specified character should be escaped when
8793
// appearing in a URL string, according to RFC 3986.
8894
//
@@ -99,9 +105,13 @@ func shouldEscape(c byte, mode encoding) bool {
99105
// sub-delims = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "="
100106
// as part of reg-name.
101107
// We add : because we include :port as part of host.
102-
// We add [ ] because we include [ipv6]:port as part of host
108+
// We add [ ] because we include [ipv6]:port as part of host.
109+
// We add < > because they're the only characters left that
110+
// we could possibly allow, and Parse will reject them if we
111+
// escape them (because hosts can't use %-encoding for
112+
// ASCII bytes).
103113
switch c {
104-
case '!', '$', '&', '\'', '(', ')', '*', '+', ',', ';', '=', ':', '[', ']':
114+
case '!', '$', '&', '\'', '(', ')', '*', '+', ',', ';', '=', ':', '[', ']', '<', '>', '"':
105115
return false
106116
}
107117
}
@@ -193,6 +203,9 @@ func unescape(s string, mode encoding) (string, error) {
193203
hasPlus = mode == encodeQueryComponent
194204
i++
195205
default:
206+
if (mode == encodeHost || mode == encodeZone) && s[i] < 0x80 && shouldEscape(s[i], mode) {
207+
return "", InvalidHostError(s[i : i+1])
208+
}
196209
i++
197210
}
198211
}

src/net/url/url_test.go

+11
Original file line numberDiff line numberDiff line change
@@ -521,6 +521,16 @@ var urltests = []URLTest{
521521
},
522522
"",
523523
},
524+
// test that we can reparse the host names we accept.
525+
{
526+
"myscheme://authority<\"hi\">/foo",
527+
&URL{
528+
Scheme: "myscheme",
529+
Host: "authority<\"hi\">",
530+
Path: "/foo",
531+
},
532+
"",
533+
},
524534
}
525535

526536
// more useful string for debugging than fmt's struct printer
@@ -1239,6 +1249,7 @@ func TestParseAuthority(t *testing.T) {
12391249
{"mysql://x@y(1.2.3.4:123)/foo", false},
12401250
{"mysql://x@y([2001:db8::1]:123)/foo", false},
12411251
{"http://[]%20%48%54%54%50%2f%31%2e%31%0a%4d%79%48%65%61%64%65%72%3a%20%31%32%33%0a%0a/", true}, // golang.org/issue/11208
1252+
{"http://a b.com/", true}, // no space in host name please
12421253
}
12431254
for _, tt := range tests {
12441255
u, err := Parse(tt.in)

0 commit comments

Comments
 (0)