From: Damien Neil Date: Wed, 19 Nov 2025 23:32:04 +0000 (-0800) Subject: net/url: permit colons in the host of postgresql:// URLs X-Git-Tag: go1.26rc1~222 X-Git-Url: http://www.git.cypherpunks.su/?a=commitdiff_plain;h=ff654ea1000af81bd08f24faf06c2113e8df001a;p=gostls13.git net/url: permit colons in the host of postgresql:// URLs PostgreSQL's postgresql:// URL scheme permits a comma-separated list of host:ports to appear in the host subcomponent: https://www.postgresql.org/docs/11/libpq-connect.html#LIBPQ-MULTIPLE-HOSTS While this is not compliant with RFC 3986, it's something we've accepted for a long time. Continue to accept colons in the host when the URL scheme is "postgresql". Fixes #75859 Change-Id: Iaa2e82b0be11d8e034e10dd7f4d6070039acfa2c Reviewed-on: https://go-review.googlesource.com/c/go/+/722300 Reviewed-by: Roland Shoemaker LUCI-TryBot-Result: Go LUCI Reviewed-by: Sean Liao --- diff --git a/src/net/url/url.go b/src/net/url/url.go index ca5ff9e3d7..6dc57eab85 100644 --- a/src/net/url/url.go +++ b/src/net/url/url.go @@ -489,7 +489,7 @@ func parse(rawURL string, viaRequest bool) (*URL, error) { if i := strings.Index(authority, "/"); i >= 0 { authority, rest = authority[:i], authority[i:] } - url.User, url.Host, err = parseAuthority(authority) + url.User, url.Host, err = parseAuthority(url.Scheme, authority) if err != nil { return nil, err } @@ -509,12 +509,12 @@ func parse(rawURL string, viaRequest bool) (*URL, error) { return url, nil } -func parseAuthority(authority string) (user *Userinfo, host string, err error) { +func parseAuthority(scheme, authority string) (user *Userinfo, host string, err error) { i := strings.LastIndex(authority, "@") if i < 0 { - host, err = parseHost(authority) + host, err = parseHost(scheme, authority) } else { - host, err = parseHost(authority[i+1:]) + host, err = parseHost(scheme, authority[i+1:]) } if err != nil { return nil, "", err @@ -546,7 +546,7 @@ func parseAuthority(authority string) (user *Userinfo, host string, err error) { // parseHost parses host as an authority without user // information. That is, as host[:port]. -func parseHost(host string) (string, error) { +func parseHost(scheme, host string) (string, error) { if openBracketIdx := strings.LastIndex(host, "["); openBracketIdx != -1 { // Parse an IP-Literal in RFC 3986 and RFC 6874. // E.g., "[fe80::1]", "[fe80::1%25en0]", "[fe80::1]:80". @@ -603,9 +603,22 @@ func parseHost(host string) (string, error) { } return "[" + unescapedHostname + "]" + unescapedColonPort, nil } else if i := strings.Index(host, ":"); i != -1 { - if j := strings.LastIndex(host, ":"); urlstrictcolons.Value() == "0" && j != i { - urlstrictcolons.IncNonDefault() - i = j + lastColon := strings.LastIndex(host, ":") + if lastColon != i { + if scheme == "postgresql" || scheme == "postgres" { + // PostgreSQL relies on non-RFC-3986 parsing to accept + // a comma-separated list of hosts (with optional ports) + // in the host subcomponent: + // https://www.postgresql.org/docs/11/libpq-connect.html#LIBPQ-MULTIPLE-HOSTS + // + // Since we historically permitted colons to appear in the host, + // continue to permit it for postgres:// URLs only. + // https://go.dev/issue/75223 + i = lastColon + } else if urlstrictcolons.Value() == "0" { + urlstrictcolons.IncNonDefault() + i = lastColon + } } colonPort := host[i:] if !validOptionalPort(colonPort) { diff --git a/src/net/url/url_test.go b/src/net/url/url_test.go index b601849ce5..a70b94b424 100644 --- a/src/net/url/url_test.go +++ b/src/net/url/url_test.go @@ -606,6 +606,26 @@ var urltests = []URLTest{ }, "mailto:?subject=hi", }, + // PostgreSQL URLs can include a comma-separated list of host:post hosts. + // https://go.dev/issue/75859 + { + "postgres://host1:1,host2:2,host3:3", + &URL{ + Scheme: "postgres", + Host: "host1:1,host2:2,host3:3", + Path: "", + }, + "postgres://host1:1,host2:2,host3:3", + }, + { + "postgresql://host1:1,host2:2,host3:3", + &URL{ + Scheme: "postgresql", + Host: "host1:1,host2:2,host3:3", + Path: "", + }, + "postgresql://host1:1,host2:2,host3:3", + }, } // more useful string for debugging than fmt's struct printer