aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorEmmanuel T Odeke <emmanuel@orijtech.com>2026-02-18 17:03:37 -0500
committerGopher Robot <gobot@golang.org>2026-02-19 15:40:16 -0800
commit09c3cfbc208fbd3ee8a06885e7efa53cdd56be7a (patch)
tree998a773ef6bbc64302313c490fead8d88f191a85 /src
parent839cd82fa53ec481ffcd02e139b3d77c2724892e (diff)
downloadgo-09c3cfbc208fbd3ee8a06885e7efa53cdd56be7a.tar.xz
net/url: add (*URL).Clone
This change adds URL.Clone which creates a deep copy of the URL's fields including the .User and tests these. In a separate CL I shall send Values.Clone too. Updates #73450 Change-Id: Ifea4bfc4ddd0640247544ec111ec83bd9bbe9104 Reviewed-on: https://go-review.googlesource.com/c/go/+/746800 Reviewed-by: Nicholas Husin <husin@google.com> Auto-Submit: Emmanuel Odeke <emmanuel@orijtech.com> Reviewed-by: Damien Neil <dneil@google.com> Reviewed-by: Nicholas Husin <nsh@golang.org> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Auto-Submit: Damien Neil <dneil@google.com>
Diffstat (limited to 'src')
-rw-r--r--src/net/http/clone.go11
-rw-r--r--src/net/url/url.go13
-rw-r--r--src/net/url/url_test.go76
3 files changed, 90 insertions, 10 deletions
diff --git a/src/net/http/clone.go b/src/net/http/clone.go
index 0c2daf8552..7ea353a42f 100644
--- a/src/net/http/clone.go
+++ b/src/net/http/clone.go
@@ -39,16 +39,7 @@ func cloneURLValues(v url.Values) url.Values {
//
//go:linkname cloneURL
func cloneURL(u *url.URL) *url.URL {
- if u == nil {
- return nil
- }
- u2 := new(url.URL)
- *u2 = *u
- if u.User != nil {
- u2.User = new(url.Userinfo)
- *u2.User = *u.User
- }
- return u2
+ return u.Clone()
}
// cloneMultipartForm should be an internal detail,
diff --git a/src/net/url/url.go b/src/net/url/url.go
index 202957a3a2..6f0e1efca1 100644
--- a/src/net/url/url.go
+++ b/src/net/url/url.go
@@ -1322,3 +1322,16 @@ func JoinPath(base string, elem ...string) (result string, err error) {
}
return res.String(), nil
}
+
+// Clone creates a deep copy of the fields of the subject [URL].
+func (u *URL) Clone() *URL {
+ if u == nil {
+ return nil
+ }
+
+ uc := new(*u)
+ if u.User != nil {
+ uc.User = new(*u.User)
+ }
+ return uc
+}
diff --git a/src/net/url/url_test.go b/src/net/url/url_test.go
index 9ba2a1231d..1645c9a882 100644
--- a/src/net/url/url_test.go
+++ b/src/net/url/url_test.go
@@ -10,6 +10,7 @@ import (
"encoding/gob"
"encoding/json"
"fmt"
+ "internal/diff"
"io"
"net"
"reflect"
@@ -2361,3 +2362,78 @@ func TestParseStrictIpv6(t *testing.T) {
}
}
+
+func TestURLClone(t *testing.T) {
+ tests := []struct {
+ name string
+ in *URL
+ }{
+ {"nil", nil},
+ {"zero value", &URL{}},
+ {
+ "Populated but nil .User",
+ &URL{
+ User: nil,
+ Host: "foo",
+ Path: "/path",
+ RawQuery: "a=b",
+ },
+ },
+ {
+ "non-nil .User",
+ &URL{
+ User: User("user"),
+ Host: "foo",
+ Path: "/path",
+ RawQuery: "a=b",
+ },
+ },
+ {
+ "non-nil .User: user and password set",
+ &URL{
+ User: UserPassword("user", "password"),
+ Host: "foo",
+ Path: "/path",
+ RawQuery: "a=b",
+ },
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ // 1. The cloned URL must always deep equal the input, but never the same pointer.
+ cloned := tt.in.Clone()
+ if !reflect.DeepEqual(tt.in, cloned) {
+ t.Fatalf("Differing values\n%s",
+ diff.Diff("original", []byte(tt.in.String()), "cloned", []byte(cloned.String())))
+ }
+ if tt.in == nil {
+ return
+ }
+
+ // Ensure that their pointer values are not the same.
+ if tt.in == cloned {
+ t.Fatalf("URL: same pointer returned: %p", cloned)
+ }
+
+ // 2. Test out malleability of URL fields.
+ cloned.Scheme = "https"
+ if cloned.Scheme == tt.in.Scheme {
+ t.Error("Inconsistent state: cloned.scheme changed and reflected in the input's scheme")
+ }
+ if reflect.DeepEqual(tt.in, cloned) {
+ t.Fatal("Inconsistent state: cloned and input are somehow the same")
+ }
+
+ // 3. Ensure that the .User object deep equals but not the same pointer.
+ if !reflect.DeepEqual(tt.in.User, cloned.User) {
+ t.Fatalf("Differing .User\n%s",
+ diff.Diff("original", []byte(tt.in.String()), "cloned", []byte(cloned.String())))
+ }
+ bothNil := tt.in.User == nil && cloned.User == nil
+ if !bothNil && tt.in.User == cloned.User {
+ t.Fatalf(".User: same pointer returned: %p", cloned.User)
+ }
+ })
+ }
+}