diff options
Diffstat (limited to 'src/net/url')
-rw-r--r-- | src/net/url/url.go | 41 | ||||
-rw-r--r-- | src/net/url/url_test.go | 8 |
2 files changed, 47 insertions, 2 deletions
diff --git a/src/net/url/url.go b/src/net/url/url.go index f362958edd..7beaef1ba6 100644 --- a/src/net/url/url.go +++ b/src/net/url/url.go @@ -14,9 +14,10 @@ import ( "errors" "fmt" "path" - "sort" + "slices" "strconv" "strings" + _ "unsafe" // for linkname ) // Error reports an error and the operation and URL that caused it. @@ -677,6 +678,16 @@ func parseHost(host string) (string, error) { // - setPath("/foo%2fbar") will set Path="/foo/bar" and RawPath="/foo%2fbar" // setPath will return an error only if the provided path contains an invalid // escaping. +// +// setPath should be an internal detail, +// but widely used packages access it using linkname. +// Notable members of the hall of shame include: +// - github.com/sagernet/sing +// +// Do not remove or change the type signature. +// See go.dev/issue/67401. +// +//go:linkname badSetPath net/url.(*URL).setPath func (u *URL) setPath(p string) error { path, err := unescape(p, encodePath) if err != nil { @@ -692,6 +703,9 @@ func (u *URL) setPath(p string) error { return nil } +// for linkname because we cannot linkname methods directly +func badSetPath(*URL, string) error + // EscapedPath returns the escaped form of u.Path. // In general there are multiple possible escaped forms of any path. // EscapedPath returns u.RawPath when it is a valid escaping of u.Path. @@ -814,6 +828,22 @@ func validOptionalPort(port string) bool { // - if u.Fragment is empty, #fragment is omitted. func (u *URL) String() string { var buf strings.Builder + + n := len(u.Scheme) + if u.Opaque != "" { + n += len(u.Opaque) + } else { + if !u.OmitHost && (u.Scheme != "" || u.Host != "" || u.User != nil) { + username := u.User.Username() + password, _ := u.User.Password() + n += len(username) + len(password) + len(u.Host) + } + n += len(u.Path) + } + n += len(u.RawQuery) + len(u.RawFragment) + n += len(":" + "//" + "//" + ":" + "@" + "/" + "./" + "?" + "#") + buf.Grow(n) + if u.Scheme != "" { buf.WriteString(u.Scheme) buf.WriteByte(':') @@ -978,7 +1008,7 @@ func (v Values) Encode() string { for k := range v { keys = append(keys, k) } - sort.Strings(keys) + slices.Sort(keys) for _, k := range keys { vs := v[k] keyEscaped := QueryEscape(k) @@ -1108,6 +1138,13 @@ func (u *URL) ResolveReference(ref *URL) *URL { url.RawFragment = u.RawFragment } } + if ref.Path == "" && u.Opaque != "" { + url.Opaque = u.Opaque + url.User = nil + url.Host = "" + url.Path = "" + return &url + } // The "abs_path" or "rel_path" cases. url.Host = u.Host url.User = u.User diff --git a/src/net/url/url_test.go b/src/net/url/url_test.go index 4aa20bb95f..68219c3df1 100644 --- a/src/net/url/url_test.go +++ b/src/net/url/url_test.go @@ -1248,6 +1248,14 @@ var resolveReferenceTests = []struct { // Empty path and query but with ForceQuery (issue 46033). {"https://a/b/c/d;p?q#s", "?", "https://a/b/c/d;p?"}, + + // Opaque URLs (issue 66084). + {"https://foo.com/bar?a=b", "http:opaque", "http:opaque"}, + {"http:opaque?x=y#zzz", "https:/foo?a=b#frag", "https:/foo?a=b#frag"}, + {"http:opaque?x=y#zzz", "https:foo:bar", "https:foo:bar"}, + {"http:opaque?x=y#zzz", "https:bar/baz?a=b#frag", "https:bar/baz?a=b#frag"}, + {"http:opaque?x=y#zzz", "https://user@host:1234?a=b#frag", "https://user@host:1234?a=b#frag"}, + {"http:opaque?x=y#zzz", "?a=b#frag", "http:opaque?a=b#frag"}, } func TestResolveReference(t *testing.T) { |