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 <shaojunyang@google.com>
Reviewed-by: Damien Neil <dneil@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
number of cookies. To avoid denial of service attacks, this setting and default
was backported to Go 1.25.2 and Go 1.24.8.
+Go 1.26 added a new `urlstrictcolons` setting that controls whether `net/url.Parse`
+allows malformed hostnames containing colons outside of a bracketed IPv6 address.
+The default `urlstrictcolons=1` rejects URLs such as `http://localhost:1:2` or `http://::1/`.
+Colons are permitted as part of a bracketed IPv6 address, such as `http://[::1]/`.
+
### Go 1.25
Go 1.25 added a new `decoratemappings` setting that controls whether the Go
--- /dev/null
+[Parse] now rejects malformed URLs containing colons in the host subcomponent,
+such as `http://::1/` or `http://localhost:80:80/`.
+URLs containing bracketed IPv6 addresses, such as `http://[::1]/` are still accepted.
+The new GODEBUG=urlstrictcolons=0 setting restores the old behavior.
{Name: "tlssha1", Package: "crypto/tls", Changed: 25, Old: "1"},
{Name: "tlsunsafeekm", Package: "crypto/tls", Changed: 22, Old: "1"},
{Name: "updatemaxprocs", Package: "runtime", Changed: 25, Old: "0"},
+ {Name: "urlstrictcolons", Package: "net/url", Changed: 26, Old: "0"},
{Name: "winreadlinkvolume", Package: "os", Changed: 23, Old: "0"},
{Name: "winsymlink", Package: "os", Changed: 23, Old: "0"},
{Name: "x509keypairleaf", Package: "crypto/tls", Changed: 23, Old: "0"},
import (
"errors"
"fmt"
+ "internal/godebug"
"net/netip"
"path"
"slices"
_ "unsafe" // for linkname
)
+var urlstrictcolons = godebug.New("urlstrictcolons")
+
// Error reports an error and the operation and URL that caused it.
type Error struct {
Op string
return "", errors.New("invalid IP-literal")
}
return "[" + unescapedHostname + "]" + unescapedColonPort, nil
- } else if i := strings.LastIndex(host, ":"); i != -1 {
+ } else if i := strings.Index(host, ":"); i != -1 {
+ if j := strings.LastIndex(host, ":"); urlstrictcolons.Value() == "0" && j != i {
+ urlstrictcolons.IncNonDefault()
+ i = j
+ }
colonPort := host[i:]
if !validOptionalPort(colonPort) {
return "", fmt.Errorf("invalid port %q after host", colonPort)
"io"
"net"
"reflect"
+ "strconv"
"strings"
"testing"
)
},
"",
},
- {
- // Malformed IPv6 but still accepted.
- "http://2b01:e34:ef40:7730:8e70:5aff:fefe:edac:8080/foo",
- &URL{
- Scheme: "http",
- Host: "2b01:e34:ef40:7730:8e70:5aff:fefe:edac:8080",
- Path: "/foo",
- },
- "",
- },
- {
- // Malformed IPv6 but still accepted.
- "http://2b01:e34:ef40:7730:8e70:5aff:fefe:edac:/foo",
- &URL{
- Scheme: "http",
- Host: "2b01:e34:ef40:7730:8e70:5aff:fefe:edac:",
- Path: "/foo",
- },
- "",
- },
{
"http://[2b01:e34:ef40:7730:8e70:5aff:fefe:edac]:8080/foo",
&URL{
{"https://[0:0::test.com]:80", false},
{"https://[2001:db8::test.com]", false},
{"https://[test.com]", false},
+ {"https://1:2:3:4:5:6:7:8", false},
+ {"https://1:2:3:4:5:6:7:8:80", false},
+ {"https://example.com:80:", false},
}
func TestParseRequestURI(t *testing.T) {
}
}
}
+
+func TestParseStrictIpv6(t *testing.T) {
+ t.Setenv("GODEBUG", "urlstrictcolons=0")
+
+ tests := []struct {
+ url string
+ }{
+ // Malformed URLs that used to parse.
+ {"https://1:2:3:4:5:6:7:8"},
+ {"https://1:2:3:4:5:6:7:8:80"},
+ {"https://example.com:80:"},
+ }
+ for i, tc := range tests {
+ t.Run(strconv.Itoa(i), func(t *testing.T) {
+ _, err := Parse(tc.url)
+ if err != nil {
+ t.Errorf("Parse(%q) error = %v, want nil", tc.url, err)
+ }
+ })
+ }
+
+}
The number of non-default behaviors executed by the runtime
package due to a non-default GODEBUG=updatemaxprocs=... setting.
+ /godebug/non-default-behavior/urlstrictcolons:events
+ The number of non-default behaviors executed by the net/url
+ package due to a non-default GODEBUG=urlstrictcolons=...
+ setting.
+
/godebug/non-default-behavior/winreadlinkvolume:events
The number of non-default behaviors executed by the os package
due to a non-default GODEBUG=winreadlinkvolume=... setting.