Skip to content

Commit 0c28789

Browse files
committed
net/url: disallow raw IPv6 addresses in host
RFC 3986 requires square brackets around IPv6 addresses. Parse's acceptance of raw IPv6 addresses is non compliant, and complicates splitting out a port. This is a resubmission of CL 710176 after the revert in CL 711800, this time with a new urlstrictipv6 godebug to control the behavior. Fixes #31024 Fixes #75223 Change-Id: I4cbe5bb84266b3efe9c98cf4300421ddf1df7291 Reviewed-on: https://go-review.googlesource.com/c/go/+/712840 Reviewed-by: Junyang Shao <[email protected]> Reviewed-by: Damien Neil <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]>
1 parent 4e761b9 commit 0c28789

File tree

6 files changed

+49
-21
lines changed

6 files changed

+49
-21
lines changed

doc/godebug.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,11 @@ will fail early. The default value is `httpcookiemaxnum=3000`. Setting
163163
number of cookies. To avoid denial of service attacks, this setting and default
164164
was backported to Go 1.25.2 and Go 1.24.8.
165165

166+
Go 1.26 added a new `urlstrictcolons` setting that controls whether `net/url.Parse`
167+
allows malformed hostnames containing colons outside of a bracketed IPv6 address.
168+
The default `urlstrictcolons=1` rejects URLs such as `http://localhost:1:2` or `http://::1/`.
169+
Colons are permitted as part of a bracketed IPv6 address, such as `http://[::1]/`.
170+
166171
### Go 1.25
167172

168173
Go 1.25 added a new `decoratemappings` setting that controls whether the Go
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
[Parse] now rejects malformed URLs containing colons in the host subcomponent,
2+
such as `http://::1/` or `http://localhost:80:80/`.
3+
URLs containing bracketed IPv6 addresses, such as `http://[::1]/` are still accepted.
4+
The new GODEBUG=urlstrictcolons=0 setting restores the old behavior.

src/internal/godebugs/table.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ var All = []Info{
6767
{Name: "tlssha1", Package: "crypto/tls", Changed: 25, Old: "1"},
6868
{Name: "tlsunsafeekm", Package: "crypto/tls", Changed: 22, Old: "1"},
6969
{Name: "updatemaxprocs", Package: "runtime", Changed: 25, Old: "0"},
70+
{Name: "urlstrictcolons", Package: "net/url", Changed: 26, Old: "0"},
7071
{Name: "winreadlinkvolume", Package: "os", Changed: 23, Old: "0"},
7172
{Name: "winsymlink", Package: "os", Changed: 23, Old: "0"},
7273
{Name: "x509keypairleaf", Package: "crypto/tls", Changed: 23, Old: "0"},

src/net/url/url.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package url
1818
import (
1919
"errors"
2020
"fmt"
21+
"internal/godebug"
2122
"net/netip"
2223
"path"
2324
"slices"
@@ -26,6 +27,8 @@ import (
2627
_ "unsafe" // for linkname
2728
)
2829

30+
var urlstrictcolons = godebug.New("urlstrictcolons")
31+
2932
// Error reports an error and the operation and URL that caused it.
3033
type Error struct {
3134
Op string
@@ -599,7 +602,11 @@ func parseHost(host string) (string, error) {
599602
return "", errors.New("invalid IP-literal")
600603
}
601604
return "[" + unescapedHostname + "]" + unescapedColonPort, nil
602-
} else if i := strings.LastIndex(host, ":"); i != -1 {
605+
} else if i := strings.Index(host, ":"); i != -1 {
606+
if j := strings.LastIndex(host, ":"); urlstrictcolons.Value() == "0" && j != i {
607+
urlstrictcolons.IncNonDefault()
608+
i = j
609+
}
603610
colonPort := host[i:]
604611
if !validOptionalPort(colonPort) {
605612
return "", fmt.Errorf("invalid port %q after host", colonPort)

src/net/url/url_test.go

Lines changed: 26 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"io"
1414
"net"
1515
"reflect"
16+
"strconv"
1617
"strings"
1718
"testing"
1819
)
@@ -506,26 +507,6 @@ var urltests = []URLTest{
506507
},
507508
"",
508509
},
509-
{
510-
// Malformed IPv6 but still accepted.
511-
"http://2b01:e34:ef40:7730:8e70:5aff:fefe:edac:8080/foo",
512-
&URL{
513-
Scheme: "http",
514-
Host: "2b01:e34:ef40:7730:8e70:5aff:fefe:edac:8080",
515-
Path: "/foo",
516-
},
517-
"",
518-
},
519-
{
520-
// Malformed IPv6 but still accepted.
521-
"http://2b01:e34:ef40:7730:8e70:5aff:fefe:edac:/foo",
522-
&URL{
523-
Scheme: "http",
524-
Host: "2b01:e34:ef40:7730:8e70:5aff:fefe:edac:",
525-
Path: "/foo",
526-
},
527-
"",
528-
},
529510
{
530511
"http://[2b01:e34:ef40:7730:8e70:5aff:fefe:edac]:8080/foo",
531512
&URL{
@@ -735,6 +716,9 @@ var parseRequestURLTests = []struct {
735716
{"https://[0:0::test.com]:80", false},
736717
{"https://[2001:db8::test.com]", false},
737718
{"https://[test.com]", false},
719+
{"https://1:2:3:4:5:6:7:8", false},
720+
{"https://1:2:3:4:5:6:7:8:80", false},
721+
{"https://example.com:80:", false},
738722
}
739723

740724
func TestParseRequestURI(t *testing.T) {
@@ -2280,3 +2264,25 @@ func TestJoinPath(t *testing.T) {
22802264
}
22812265
}
22822266
}
2267+
2268+
func TestParseStrictIpv6(t *testing.T) {
2269+
t.Setenv("GODEBUG", "urlstrictcolons=0")
2270+
2271+
tests := []struct {
2272+
url string
2273+
}{
2274+
// Malformed URLs that used to parse.
2275+
{"https://1:2:3:4:5:6:7:8"},
2276+
{"https://1:2:3:4:5:6:7:8:80"},
2277+
{"https://example.com:80:"},
2278+
}
2279+
for i, tc := range tests {
2280+
t.Run(strconv.Itoa(i), func(t *testing.T) {
2281+
_, err := Parse(tc.url)
2282+
if err != nil {
2283+
t.Errorf("Parse(%q) error = %v, want nil", tc.url, err)
2284+
}
2285+
})
2286+
}
2287+
2288+
}

src/runtime/metrics/doc.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -399,6 +399,11 @@ Below is the full list of supported metrics, ordered lexicographically.
399399
The number of non-default behaviors executed by the runtime
400400
package due to a non-default GODEBUG=updatemaxprocs=... setting.
401401
402+
/godebug/non-default-behavior/urlstrictcolons:events
403+
The number of non-default behaviors executed by the net/url
404+
package due to a non-default GODEBUG=urlstrictcolons=...
405+
setting.
406+
402407
/godebug/non-default-behavior/winreadlinkvolume:events
403408
The number of non-default behaviors executed by the os package
404409
due to a non-default GODEBUG=winreadlinkvolume=... setting.

0 commit comments

Comments
 (0)