Source file src/net/url/url_test.go

     1  // Copyright 2009 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package url
     6  
     7  import (
     8  	"bytes"
     9  	encodingPkg "encoding"
    10  	"encoding/gob"
    11  	"encoding/json"
    12  	"fmt"
    13  	"internal/diff"
    14  	"io"
    15  	"maps"
    16  	"net"
    17  	"reflect"
    18  	"slices"
    19  	"strconv"
    20  	"strings"
    21  	"testing"
    22  )
    23  
    24  type URLTest struct {
    25  	in        string // empty means this URL cannot be generated by parsing
    26  	out       *URL   // expected parse
    27  	roundtrip string // expected result of reserializing the URL; empty means same as "in".
    28  }
    29  
    30  var urltests = []URLTest{
    31  	// no path
    32  	{
    33  		"http://www.google.com",
    34  		&URL{
    35  			Scheme: "http",
    36  			Host:   "www.google.com",
    37  		},
    38  		"",
    39  	},
    40  	// path
    41  	{
    42  		"http://www.google.com/",
    43  		&URL{
    44  			Scheme: "http",
    45  			Host:   "www.google.com",
    46  			Path:   "/",
    47  		},
    48  		"",
    49  	},
    50  	// path with hex escaping
    51  	{
    52  		"http://www.google.com/file%20one%26two",
    53  		&URL{
    54  			Scheme:  "http",
    55  			Host:    "www.google.com",
    56  			Path:    "/file one&two",
    57  			RawPath: "/file%20one%26two",
    58  		},
    59  		"",
    60  	},
    61  	// fragment with hex escaping
    62  	{
    63  		"http://www.google.com/#file%20one%26two",
    64  		&URL{
    65  			Scheme:      "http",
    66  			Host:        "www.google.com",
    67  			Path:        "/",
    68  			Fragment:    "file one&two",
    69  			RawFragment: "file%20one%26two",
    70  		},
    71  		"",
    72  	},
    73  	// user
    74  	{
    75  		"ftp://webmaster@www.google.com/",
    76  		&URL{
    77  			Scheme: "ftp",
    78  			User:   User("webmaster"),
    79  			Host:   "www.google.com",
    80  			Path:   "/",
    81  		},
    82  		"",
    83  	},
    84  	// escape sequence in username
    85  	{
    86  		"ftp://john%20doe@www.google.com/",
    87  		&URL{
    88  			Scheme: "ftp",
    89  			User:   User("john doe"),
    90  			Host:   "www.google.com",
    91  			Path:   "/",
    92  		},
    93  		"ftp://john%20doe@www.google.com/",
    94  	},
    95  	// empty query
    96  	{
    97  		"http://www.google.com/?",
    98  		&URL{
    99  			Scheme:     "http",
   100  			Host:       "www.google.com",
   101  			Path:       "/",
   102  			ForceQuery: true,
   103  		},
   104  		"",
   105  	},
   106  	// query ending in question mark (Issue 14573)
   107  	{
   108  		"http://www.google.com/?foo=bar?",
   109  		&URL{
   110  			Scheme:   "http",
   111  			Host:     "www.google.com",
   112  			Path:     "/",
   113  			RawQuery: "foo=bar?",
   114  		},
   115  		"",
   116  	},
   117  	// query
   118  	{
   119  		"http://www.google.com/?q=go+language",
   120  		&URL{
   121  			Scheme:   "http",
   122  			Host:     "www.google.com",
   123  			Path:     "/",
   124  			RawQuery: "q=go+language",
   125  		},
   126  		"",
   127  	},
   128  	// query with hex escaping: NOT parsed
   129  	{
   130  		"http://www.google.com/?q=go%20language",
   131  		&URL{
   132  			Scheme:   "http",
   133  			Host:     "www.google.com",
   134  			Path:     "/",
   135  			RawQuery: "q=go%20language",
   136  		},
   137  		"",
   138  	},
   139  	// %20 outside query
   140  	{
   141  		"http://www.google.com/a%20b?q=c+d",
   142  		&URL{
   143  			Scheme:   "http",
   144  			Host:     "www.google.com",
   145  			Path:     "/a b",
   146  			RawQuery: "q=c+d",
   147  		},
   148  		"",
   149  	},
   150  	// path without leading /, so no parsing
   151  	{
   152  		"http:www.google.com/?q=go+language",
   153  		&URL{
   154  			Scheme:   "http",
   155  			Opaque:   "www.google.com/",
   156  			RawQuery: "q=go+language",
   157  		},
   158  		"http:www.google.com/?q=go+language",
   159  	},
   160  	// path without leading /, so no parsing
   161  	{
   162  		"http:%2f%2fwww.google.com/?q=go+language",
   163  		&URL{
   164  			Scheme:   "http",
   165  			Opaque:   "%2f%2fwww.google.com/",
   166  			RawQuery: "q=go+language",
   167  		},
   168  		"http:%2f%2fwww.google.com/?q=go+language",
   169  	},
   170  	// non-authority with path; see golang.org/issue/46059
   171  	{
   172  		"mailto:/webmaster@golang.org",
   173  		&URL{
   174  			Scheme:   "mailto",
   175  			Path:     "/webmaster@golang.org",
   176  			OmitHost: true,
   177  		},
   178  		"",
   179  	},
   180  	// non-authority
   181  	{
   182  		"mailto:webmaster@golang.org",
   183  		&URL{
   184  			Scheme: "mailto",
   185  			Opaque: "webmaster@golang.org",
   186  		},
   187  		"",
   188  	},
   189  	// unescaped :// in query should not create a scheme
   190  	{
   191  		"/foo?query=http://bad",
   192  		&URL{
   193  			Path:     "/foo",
   194  			RawQuery: "query=http://bad",
   195  		},
   196  		"",
   197  	},
   198  	// leading // without scheme should create an authority
   199  	{
   200  		"//foo",
   201  		&URL{
   202  			Host: "foo",
   203  		},
   204  		"",
   205  	},
   206  	// leading // without scheme, with userinfo, path, and query
   207  	{
   208  		"//user@foo/path?a=b",
   209  		&URL{
   210  			User:     User("user"),
   211  			Host:     "foo",
   212  			Path:     "/path",
   213  			RawQuery: "a=b",
   214  		},
   215  		"",
   216  	},
   217  	// Three leading slashes isn't an authority, but doesn't return an error.
   218  	// (We can't return an error, as this code is also used via
   219  	// ServeHTTP -> ReadRequest -> Parse, which is arguably a
   220  	// different URL parsing context, but currently shares the
   221  	// same codepath)
   222  	{
   223  		"///threeslashes",
   224  		&URL{
   225  			Path: "///threeslashes",
   226  		},
   227  		"",
   228  	},
   229  	{
   230  		"http://user:password@google.com",
   231  		&URL{
   232  			Scheme: "http",
   233  			User:   UserPassword("user", "password"),
   234  			Host:   "google.com",
   235  		},
   236  		"http://user:password@google.com",
   237  	},
   238  	// unescaped @ in username should not confuse host
   239  	{
   240  		"http://j@ne:password@google.com",
   241  		&URL{
   242  			Scheme: "http",
   243  			User:   UserPassword("j@ne", "password"),
   244  			Host:   "google.com",
   245  		},
   246  		"http://j%40ne:password@google.com",
   247  	},
   248  	// unescaped @ in password should not confuse host
   249  	{
   250  		"http://jane:p@ssword@google.com",
   251  		&URL{
   252  			Scheme: "http",
   253  			User:   UserPassword("jane", "p@ssword"),
   254  			Host:   "google.com",
   255  		},
   256  		"http://jane:p%40ssword@google.com",
   257  	},
   258  	{
   259  		"http://j@ne:password@google.com/p@th?q=@go",
   260  		&URL{
   261  			Scheme:   "http",
   262  			User:     UserPassword("j@ne", "password"),
   263  			Host:     "google.com",
   264  			Path:     "/p@th",
   265  			RawQuery: "q=@go",
   266  		},
   267  		"http://j%40ne:password@google.com/p@th?q=@go",
   268  	},
   269  	{
   270  		"http://www.google.com/?q=go+language#foo",
   271  		&URL{
   272  			Scheme:   "http",
   273  			Host:     "www.google.com",
   274  			Path:     "/",
   275  			RawQuery: "q=go+language",
   276  			Fragment: "foo",
   277  		},
   278  		"",
   279  	},
   280  	{
   281  		"http://www.google.com/?q=go+language#foo&bar",
   282  		&URL{
   283  			Scheme:   "http",
   284  			Host:     "www.google.com",
   285  			Path:     "/",
   286  			RawQuery: "q=go+language",
   287  			Fragment: "foo&bar",
   288  		},
   289  		"http://www.google.com/?q=go+language#foo&bar",
   290  	},
   291  	{
   292  		"http://www.google.com/?q=go+language#foo%26bar",
   293  		&URL{
   294  			Scheme:      "http",
   295  			Host:        "www.google.com",
   296  			Path:        "/",
   297  			RawQuery:    "q=go+language",
   298  			Fragment:    "foo&bar",
   299  			RawFragment: "foo%26bar",
   300  		},
   301  		"http://www.google.com/?q=go+language#foo%26bar",
   302  	},
   303  	{
   304  		"file:///home/adg/rabbits",
   305  		&URL{
   306  			Scheme: "file",
   307  			Host:   "",
   308  			Path:   "/home/adg/rabbits",
   309  		},
   310  		"file:///home/adg/rabbits",
   311  	},
   312  	// "Windows" paths are no exception to the rule.
   313  	// See golang.org/issue/6027, especially comment #9.
   314  	{
   315  		"file:///C:/FooBar/Baz.txt",
   316  		&URL{
   317  			Scheme: "file",
   318  			Host:   "",
   319  			Path:   "/C:/FooBar/Baz.txt",
   320  		},
   321  		"file:///C:/FooBar/Baz.txt",
   322  	},
   323  	// case-insensitive scheme
   324  	{
   325  		"MaIlTo:webmaster@golang.org",
   326  		&URL{
   327  			Scheme: "mailto",
   328  			Opaque: "webmaster@golang.org",
   329  		},
   330  		"mailto:webmaster@golang.org",
   331  	},
   332  	// Relative path
   333  	{
   334  		"a/b/c",
   335  		&URL{
   336  			Path: "a/b/c",
   337  		},
   338  		"a/b/c",
   339  	},
   340  	// escaped '?' in username and password
   341  	{
   342  		"http://%3Fam:pa%3Fsword@google.com",
   343  		&URL{
   344  			Scheme: "http",
   345  			User:   UserPassword("?am", "pa?sword"),
   346  			Host:   "google.com",
   347  		},
   348  		"",
   349  	},
   350  	// host subcomponent; IPv4 address in RFC 3986
   351  	{
   352  		"http://192.168.0.1/",
   353  		&URL{
   354  			Scheme: "http",
   355  			Host:   "192.168.0.1",
   356  			Path:   "/",
   357  		},
   358  		"",
   359  	},
   360  	// host and port subcomponents; IPv4 address in RFC 3986
   361  	{
   362  		"http://192.168.0.1:8080/",
   363  		&URL{
   364  			Scheme: "http",
   365  			Host:   "192.168.0.1:8080",
   366  			Path:   "/",
   367  		},
   368  		"",
   369  	},
   370  	// host subcomponent; IPv6 address in RFC 3986
   371  	{
   372  		"http://[fe80::1]/",
   373  		&URL{
   374  			Scheme: "http",
   375  			Host:   "[fe80::1]",
   376  			Path:   "/",
   377  		},
   378  		"",
   379  	},
   380  	// host and port subcomponents; IPv6 address in RFC 3986
   381  	{
   382  		"http://[fe80::1]:8080/",
   383  		&URL{
   384  			Scheme: "http",
   385  			Host:   "[fe80::1]:8080",
   386  			Path:   "/",
   387  		},
   388  		"",
   389  	},
   390  	// valid IPv6 host with port and path
   391  	{
   392  		"https://[2001:db8::1]:8443/test/path",
   393  		&URL{
   394  			Scheme: "https",
   395  			Host:   "[2001:db8::1]:8443",
   396  			Path:   "/test/path",
   397  		},
   398  		"",
   399  	},
   400  	// host subcomponent; IPv6 address with zone identifier in RFC 6874
   401  	{
   402  		"http://[fe80::1%25en0]/", // alphanum zone identifier
   403  		&URL{
   404  			Scheme: "http",
   405  			Host:   "[fe80::1%en0]",
   406  			Path:   "/",
   407  		},
   408  		"",
   409  	},
   410  	// host and port subcomponents; IPv6 address with zone identifier in RFC 6874
   411  	{
   412  		"http://[fe80::1%25en0]:8080/", // alphanum zone identifier
   413  		&URL{
   414  			Scheme: "http",
   415  			Host:   "[fe80::1%en0]:8080",
   416  			Path:   "/",
   417  		},
   418  		"",
   419  	},
   420  	// host subcomponent; IPv6 address with zone identifier in RFC 6874
   421  	{
   422  		"http://[fe80::1%25%65%6e%301-._~]/", // percent-encoded+unreserved zone identifier
   423  		&URL{
   424  			Scheme: "http",
   425  			Host:   "[fe80::1%en01-._~]",
   426  			Path:   "/",
   427  		},
   428  		"http://[fe80::1%25en01-._~]/",
   429  	},
   430  	// host and port subcomponents; IPv6 address with zone identifier in RFC 6874
   431  	{
   432  		"http://[fe80::1%25%65%6e%301-._~]:8080/", // percent-encoded+unreserved zone identifier
   433  		&URL{
   434  			Scheme: "http",
   435  			Host:   "[fe80::1%en01-._~]:8080",
   436  			Path:   "/",
   437  		},
   438  		"http://[fe80::1%25en01-._~]:8080/",
   439  	},
   440  	// alternate escapings of path survive round trip
   441  	{
   442  		"http://rest.rsc.io/foo%2fbar/baz%2Fquux?alt=media",
   443  		&URL{
   444  			Scheme:   "http",
   445  			Host:     "rest.rsc.io",
   446  			Path:     "/foo/bar/baz/quux",
   447  			RawPath:  "/foo%2fbar/baz%2Fquux",
   448  			RawQuery: "alt=media",
   449  		},
   450  		"",
   451  	},
   452  	// issue 12036
   453  	{
   454  		"mysql://a,b,c/bar",
   455  		&URL{
   456  			Scheme: "mysql",
   457  			Host:   "a,b,c",
   458  			Path:   "/bar",
   459  		},
   460  		"",
   461  	},
   462  	// worst case host, still round trips
   463  	{
   464  		"scheme://!$&'()*+,;=hello!:1/path",
   465  		&URL{
   466  			Scheme: "scheme",
   467  			Host:   "!$&'()*+,;=hello!:1",
   468  			Path:   "/path",
   469  		},
   470  		"",
   471  	},
   472  	// worst case path, still round trips
   473  	{
   474  		"http://host/!$&'()*+,;=:@[hello]",
   475  		&URL{
   476  			Scheme:  "http",
   477  			Host:    "host",
   478  			Path:    "/!$&'()*+,;=:@[hello]",
   479  			RawPath: "/!$&'()*+,;=:@[hello]",
   480  		},
   481  		"",
   482  	},
   483  	// golang.org/issue/5684
   484  	{
   485  		"http://example.com/oid/[order_id]",
   486  		&URL{
   487  			Scheme:  "http",
   488  			Host:    "example.com",
   489  			Path:    "/oid/[order_id]",
   490  			RawPath: "/oid/[order_id]",
   491  		},
   492  		"",
   493  	},
   494  	// golang.org/issue/12200 (colon with empty port)
   495  	{
   496  		"http://192.168.0.2:8080/foo",
   497  		&URL{
   498  			Scheme: "http",
   499  			Host:   "192.168.0.2:8080",
   500  			Path:   "/foo",
   501  		},
   502  		"",
   503  	},
   504  	{
   505  		"http://192.168.0.2:/foo",
   506  		&URL{
   507  			Scheme: "http",
   508  			Host:   "192.168.0.2:",
   509  			Path:   "/foo",
   510  		},
   511  		"",
   512  	},
   513  	{
   514  		"http://[2b01:e34:ef40:7730:8e70:5aff:fefe:edac]:8080/foo",
   515  		&URL{
   516  			Scheme: "http",
   517  			Host:   "[2b01:e34:ef40:7730:8e70:5aff:fefe:edac]:8080",
   518  			Path:   "/foo",
   519  		},
   520  		"",
   521  	},
   522  	{
   523  		"http://[2b01:e34:ef40:7730:8e70:5aff:fefe:edac]:/foo",
   524  		&URL{
   525  			Scheme: "http",
   526  			Host:   "[2b01:e34:ef40:7730:8e70:5aff:fefe:edac]:",
   527  			Path:   "/foo",
   528  		},
   529  		"",
   530  	},
   531  	// golang.org/issue/7991 and golang.org/issue/12719 (non-ascii %-encoded in host)
   532  	{
   533  		"http://hello.世界.com/foo",
   534  		&URL{
   535  			Scheme: "http",
   536  			Host:   "hello.世界.com",
   537  			Path:   "/foo",
   538  		},
   539  		"http://hello.%E4%B8%96%E7%95%8C.com/foo",
   540  	},
   541  	{
   542  		"http://hello.%e4%b8%96%e7%95%8c.com/foo",
   543  		&URL{
   544  			Scheme: "http",
   545  			Host:   "hello.世界.com",
   546  			Path:   "/foo",
   547  		},
   548  		"http://hello.%E4%B8%96%E7%95%8C.com/foo",
   549  	},
   550  	{
   551  		"http://hello.%E4%B8%96%E7%95%8C.com/foo",
   552  		&URL{
   553  			Scheme: "http",
   554  			Host:   "hello.世界.com",
   555  			Path:   "/foo",
   556  		},
   557  		"",
   558  	},
   559  	// golang.org/issue/10433 (path beginning with //)
   560  	{
   561  		"http://example.com//foo",
   562  		&URL{
   563  			Scheme: "http",
   564  			Host:   "example.com",
   565  			Path:   "//foo",
   566  		},
   567  		"",
   568  	},
   569  	// test that we can reparse the host names we accept.
   570  	{
   571  		"myscheme://authority<\"hi\">/foo",
   572  		&URL{
   573  			Scheme: "myscheme",
   574  			Host:   "authority<\"hi\">",
   575  			Path:   "/foo",
   576  		},
   577  		"",
   578  	},
   579  	// spaces in hosts are disallowed but escaped spaces in IPv6 scope IDs are grudgingly OK.
   580  	// This happens on Windows.
   581  	// golang.org/issue/14002
   582  	{
   583  		"tcp://[2020::2020:20:2020:2020%25Windows%20Loves%20Spaces]:2020",
   584  		&URL{
   585  			Scheme: "tcp",
   586  			Host:   "[2020::2020:20:2020:2020%Windows Loves Spaces]:2020",
   587  		},
   588  		"",
   589  	},
   590  	// test we can roundtrip magnet url
   591  	// fix issue https://golang.org/issue/20054
   592  	{
   593  		"magnet:?xt=urn:btih:c12fe1c06bba254a9dc9f519b335aa7c1367a88a&dn",
   594  		&URL{
   595  			Scheme:   "magnet",
   596  			Host:     "",
   597  			Path:     "",
   598  			RawQuery: "xt=urn:btih:c12fe1c06bba254a9dc9f519b335aa7c1367a88a&dn",
   599  		},
   600  		"magnet:?xt=urn:btih:c12fe1c06bba254a9dc9f519b335aa7c1367a88a&dn",
   601  	},
   602  	{
   603  		"mailto:?subject=hi",
   604  		&URL{
   605  			Scheme:   "mailto",
   606  			Host:     "",
   607  			Path:     "",
   608  			RawQuery: "subject=hi",
   609  		},
   610  		"mailto:?subject=hi",
   611  	},
   612  	// PostgreSQL URLs can include a comma-separated list of host:post hosts.
   613  	// https://go.dev/issue/75859
   614  	{
   615  		"postgres://host1:1,host2:2,host3:3",
   616  		&URL{
   617  			Scheme: "postgres",
   618  			Host:   "host1:1,host2:2,host3:3",
   619  			Path:   "",
   620  		},
   621  		"postgres://host1:1,host2:2,host3:3",
   622  	},
   623  	{
   624  		"postgresql://host1:1,host2:2,host3:3",
   625  		&URL{
   626  			Scheme: "postgresql",
   627  			Host:   "host1:1,host2:2,host3:3",
   628  			Path:   "",
   629  		},
   630  		"postgresql://host1:1,host2:2,host3:3",
   631  	},
   632  	// Mongodb URLs can include a comma-separated list of host:post hosts.
   633  	{
   634  		"mongodb://user:password@host1:1,host2:2,host3:3",
   635  		&URL{
   636  			Scheme: "mongodb",
   637  			User:   UserPassword("user", "password"),
   638  			Host:   "host1:1,host2:2,host3:3",
   639  			Path:   "",
   640  		},
   641  		"",
   642  	},
   643  	{
   644  		"mongodb+srv://user:password@host1:1,host2:2,host3:3",
   645  		&URL{
   646  			Scheme: "mongodb+srv",
   647  			User:   UserPassword("user", "password"),
   648  			Host:   "host1:1,host2:2,host3:3",
   649  			Path:   "",
   650  		},
   651  		"",
   652  	},
   653  	// OmitHost with a Path starting with //
   654  	{
   655  		"",
   656  		&URL{
   657  			Scheme:   "http",
   658  			OmitHost: true,
   659  			Path:     "//host/path",
   660  		},
   661  		"http:%2F/host/path",
   662  	},
   663  }
   664  
   665  // more useful string for debugging than fmt's struct printer
   666  func ufmt(u *URL) string {
   667  	var user, pass any
   668  	if u.User != nil {
   669  		user = u.User.Username()
   670  		if p, ok := u.User.Password(); ok {
   671  			pass = p
   672  		}
   673  	}
   674  	return fmt.Sprintf("opaque=%q, scheme=%q, user=%#v, pass=%#v, host=%q, path=%q, rawpath=%q, rawq=%q, frag=%q, rawfrag=%q, forcequery=%v, omithost=%t",
   675  		u.Opaque, u.Scheme, user, pass, u.Host, u.Path, u.RawPath, u.RawQuery, u.Fragment, u.RawFragment, u.ForceQuery, u.OmitHost)
   676  }
   677  
   678  func BenchmarkString(b *testing.B) {
   679  	b.StopTimer()
   680  	b.ReportAllocs()
   681  	for _, tt := range urltests {
   682  		if tt.in == "" {
   683  			continue
   684  		}
   685  		u, err := Parse(tt.in)
   686  		if err != nil {
   687  			b.Errorf("Parse(%q) returned error %s", tt.in, err)
   688  			continue
   689  		}
   690  		if tt.roundtrip == "" {
   691  			continue
   692  		}
   693  		b.StartTimer()
   694  		var g string
   695  		for i := 0; i < b.N; i++ {
   696  			g = u.String()
   697  		}
   698  		b.StopTimer()
   699  		if w := tt.roundtrip; b.N > 0 && g != w {
   700  			b.Errorf("Parse(%q).String() == %q, want %q", tt.in, g, w)
   701  		}
   702  	}
   703  }
   704  
   705  func TestParse(t *testing.T) {
   706  	for _, tt := range urltests {
   707  		if tt.in == "" {
   708  			continue
   709  		}
   710  		u, err := Parse(tt.in)
   711  		if err != nil {
   712  			t.Errorf("Parse(%q) returned error %v", tt.in, err)
   713  			continue
   714  		}
   715  		if !reflect.DeepEqual(u, tt.out) {
   716  			t.Errorf("Parse(%q):\n\tgot  %v\n\twant %v\n", tt.in, ufmt(u), ufmt(tt.out))
   717  		}
   718  	}
   719  }
   720  
   721  const pathThatLooksSchemeRelative = "//not.a.user@not.a.host/just/a/path"
   722  
   723  var parseRequestURLTests = []struct {
   724  	url           string
   725  	expectedValid bool
   726  }{
   727  	{"http://foo.com", true},
   728  	{"http://foo.com/", true},
   729  	{"http://foo.com/path", true},
   730  	{"/", true},
   731  	{pathThatLooksSchemeRelative, true},
   732  	{"//not.a.user@%66%6f%6f.com/just/a/path/also", true},
   733  	{"*", true},
   734  	{"http://192.168.0.1/", true},
   735  	{"http://192.168.0.1:8080/", true},
   736  	{"http://[fe80::1]/", true},
   737  	{"http://[fe80::1]:8080/", true},
   738  
   739  	// Tests exercising RFC 6874 compliance:
   740  	{"http://[fe80::1%25en0]/", true},                 // with alphanum zone identifier
   741  	{"http://[fe80::1%25en0]:8080/", true},            // with alphanum zone identifier
   742  	{"http://[fe80::1%25%65%6e%301-._~]/", true},      // with percent-encoded+unreserved zone identifier
   743  	{"http://[fe80::1%25%65%6e%301-._~]:8080/", true}, // with percent-encoded+unreserved zone identifier
   744  
   745  	{"foo.html", false},
   746  	{"../dir/", false},
   747  	{" http://foo.com", false},
   748  	{"http://192.168.0.%31/", false},
   749  	{"http://192.168.0.%31:8080/", false},
   750  	{"http://[fe80::%31]/", false},
   751  	{"http://[fe80::%31]:8080/", false},
   752  	{"http://[fe80::%31%25en0]/", false},
   753  	{"http://[fe80::%31%25en0]:8080/", false},
   754  
   755  	// These two cases are valid as textual representations as
   756  	// described in RFC 4007, but are not valid as address
   757  	// literals with IPv6 zone identifiers in URIs as described in
   758  	// RFC 6874.
   759  	{"http://[fe80::1%en0]/", false},
   760  	{"http://[fe80::1%en0]:8080/", false},
   761  
   762  	// Tests exercising RFC 3986 compliance
   763  	{"https://[1:2:3:4:5:6:7:8]", true},             // full IPv6 address
   764  	{"https://[2001:db8::a:b:c:d]", true},           // compressed IPv6 address
   765  	{"https://[fe80::1%25eth0]", true},              // link-local address with zone ID (interface name)
   766  	{"https://[fe80::abc:def%254]", true},           // link-local address with zone ID (interface index)
   767  	{"https://[2001:db8::1]/path", true},            // compressed IPv6 address with path
   768  	{"https://[fe80::1%25eth0]/path?query=1", true}, // link-local with zone, path, and query
   769  
   770  	{"https://[::ffff:192.0.2.1]", true},
   771  	{"https://[:1] ", false},
   772  	{"https://[1:2:3:4:5:6:7:8:9]", false},
   773  	{"https://[1::1::1]", false},
   774  	{"https://[1:2:3:]", false},
   775  	{"https://[ffff::127.0.0.4000]", false},
   776  	{"https://[0:0::test.com]:80", false},
   777  	{"https://[2001:db8::test.com]", false},
   778  	{"https://[test.com]", false},
   779  	{"https://1:2:3:4:5:6:7:8", false},
   780  	{"https://1:2:3:4:5:6:7:8:80", false},
   781  	{"https://example.com:80:", false},
   782  }
   783  
   784  func TestParseRequestURI(t *testing.T) {
   785  	for _, test := range parseRequestURLTests {
   786  		_, err := ParseRequestURI(test.url)
   787  		if test.expectedValid && err != nil {
   788  			t.Errorf("ParseRequestURI(%q) gave err %v; want no error", test.url, err)
   789  		} else if !test.expectedValid && err == nil {
   790  			t.Errorf("ParseRequestURI(%q) gave nil error; want some error", test.url)
   791  		}
   792  	}
   793  
   794  	url, err := ParseRequestURI(pathThatLooksSchemeRelative)
   795  	if err != nil {
   796  		t.Fatalf("Unexpected error %v", err)
   797  	}
   798  	if url.Path != pathThatLooksSchemeRelative {
   799  		t.Errorf("ParseRequestURI path:\ngot  %q\nwant %q", url.Path, pathThatLooksSchemeRelative)
   800  	}
   801  }
   802  
   803  var stringURLTests = []struct {
   804  	url  URL
   805  	want string
   806  }{
   807  	// No leading slash on path should prepend slash on String() call
   808  	{
   809  		url: URL{
   810  			Scheme: "http",
   811  			Host:   "www.google.com",
   812  			Path:   "search",
   813  		},
   814  		want: "http://www.google.com/search",
   815  	},
   816  	// Relative path with first element containing ":" should be prepended with "./", golang.org/issue/17184
   817  	{
   818  		url: URL{
   819  			Path: "this:that",
   820  		},
   821  		want: "./this:that",
   822  	},
   823  	// Relative path with second element containing ":" should not be prepended with "./"
   824  	{
   825  		url: URL{
   826  			Path: "here/this:that",
   827  		},
   828  		want: "here/this:that",
   829  	},
   830  	// Non-relative path with first element containing ":" should not be prepended with "./"
   831  	{
   832  		url: URL{
   833  			Scheme: "http",
   834  			Host:   "www.google.com",
   835  			Path:   "this:that",
   836  		},
   837  		want: "http://www.google.com/this:that",
   838  	},
   839  }
   840  
   841  func TestURLString(t *testing.T) {
   842  	for _, tt := range urltests {
   843  		u := tt.out
   844  		if tt.in != "" {
   845  			var err error
   846  			u, err = Parse(tt.in)
   847  			if err != nil {
   848  				t.Errorf("Parse(%q) returned error %s", tt.in, err)
   849  				continue
   850  			}
   851  		}
   852  		expected := tt.in
   853  		if tt.roundtrip != "" {
   854  			expected = tt.roundtrip
   855  		}
   856  		s := u.String()
   857  		if s != expected {
   858  			t.Errorf("Parse(%q).String() == %q (expected %q)", tt.in, s, expected)
   859  		}
   860  	}
   861  
   862  	for _, tt := range stringURLTests {
   863  		if got := tt.url.String(); got != tt.want {
   864  			t.Errorf("%+v.String() = %q; want %q", tt.url, got, tt.want)
   865  		}
   866  	}
   867  }
   868  
   869  func TestURLRedacted(t *testing.T) {
   870  	cases := []struct {
   871  		name string
   872  		url  *URL
   873  		want string
   874  	}{
   875  		{
   876  			name: "non-blank Password",
   877  			url: &URL{
   878  				Scheme: "http",
   879  				Host:   "host.tld",
   880  				Path:   "this:that",
   881  				User:   UserPassword("user", "password"),
   882  			},
   883  			want: "http://user:xxxxx@host.tld/this:that",
   884  		},
   885  		{
   886  			name: "blank Password",
   887  			url: &URL{
   888  				Scheme: "http",
   889  				Host:   "host.tld",
   890  				Path:   "this:that",
   891  				User:   User("user"),
   892  			},
   893  			want: "http://user@host.tld/this:that",
   894  		},
   895  		{
   896  			name: "nil User",
   897  			url: &URL{
   898  				Scheme: "http",
   899  				Host:   "host.tld",
   900  				Path:   "this:that",
   901  				User:   UserPassword("", "password"),
   902  			},
   903  			want: "http://:xxxxx@host.tld/this:that",
   904  		},
   905  		{
   906  			name: "blank Username, blank Password",
   907  			url: &URL{
   908  				Scheme: "http",
   909  				Host:   "host.tld",
   910  				Path:   "this:that",
   911  			},
   912  			want: "http://host.tld/this:that",
   913  		},
   914  		{
   915  			name: "empty URL",
   916  			url:  &URL{},
   917  			want: "",
   918  		},
   919  		{
   920  			name: "nil URL",
   921  			url:  nil,
   922  			want: "",
   923  		},
   924  	}
   925  
   926  	for _, tt := range cases {
   927  		t.Run(tt.name, func(t *testing.T) {
   928  			if g, w := tt.url.Redacted(), tt.want; g != w {
   929  				t.Fatalf("got: %q\nwant: %q", g, w)
   930  			}
   931  		})
   932  	}
   933  }
   934  
   935  type EscapeTest struct {
   936  	in  string
   937  	out string
   938  	err error
   939  }
   940  
   941  var unescapeTests = []EscapeTest{
   942  	{
   943  		"",
   944  		"",
   945  		nil,
   946  	},
   947  	{
   948  		"abc",
   949  		"abc",
   950  		nil,
   951  	},
   952  	{
   953  		"1%41",
   954  		"1A",
   955  		nil,
   956  	},
   957  	{
   958  		"1%41%42%43",
   959  		"1ABC",
   960  		nil,
   961  	},
   962  	{
   963  		"%4a",
   964  		"J",
   965  		nil,
   966  	},
   967  	{
   968  		"%6F",
   969  		"o",
   970  		nil,
   971  	},
   972  	{
   973  		"%", // not enough characters after %
   974  		"",
   975  		EscapeError("%"),
   976  	},
   977  	{
   978  		"%a", // not enough characters after %
   979  		"",
   980  		EscapeError("%a"),
   981  	},
   982  	{
   983  		"%1", // not enough characters after %
   984  		"",
   985  		EscapeError("%1"),
   986  	},
   987  	{
   988  		"123%45%6", // not enough characters after %
   989  		"",
   990  		EscapeError("%6"),
   991  	},
   992  	{
   993  		"%zzzzz", // invalid hex digits
   994  		"",
   995  		EscapeError("%zz"),
   996  	},
   997  	{
   998  		"a+b",
   999  		"a b",
  1000  		nil,
  1001  	},
  1002  	{
  1003  		"a%20b",
  1004  		"a b",
  1005  		nil,
  1006  	},
  1007  }
  1008  
  1009  func TestUnescape(t *testing.T) {
  1010  	for _, tt := range unescapeTests {
  1011  		actual, err := QueryUnescape(tt.in)
  1012  		if actual != tt.out || (err != nil) != (tt.err != nil) {
  1013  			t.Errorf("QueryUnescape(%q) = %q, %s; want %q, %s", tt.in, actual, err, tt.out, tt.err)
  1014  		}
  1015  
  1016  		in := tt.in
  1017  		out := tt.out
  1018  		if strings.Contains(tt.in, "+") {
  1019  			in = strings.ReplaceAll(tt.in, "+", "%20")
  1020  			actual, err := PathUnescape(in)
  1021  			if actual != tt.out || (err != nil) != (tt.err != nil) {
  1022  				t.Errorf("PathUnescape(%q) = %q, %s; want %q, %s", in, actual, err, tt.out, tt.err)
  1023  			}
  1024  			if tt.err == nil {
  1025  				s, err := QueryUnescape(strings.ReplaceAll(tt.in, "+", "XXX"))
  1026  				if err != nil {
  1027  					continue
  1028  				}
  1029  				in = tt.in
  1030  				out = strings.ReplaceAll(s, "XXX", "+")
  1031  			}
  1032  		}
  1033  
  1034  		actual, err = PathUnescape(in)
  1035  		if actual != out || (err != nil) != (tt.err != nil) {
  1036  			t.Errorf("PathUnescape(%q) = %q, %s; want %q, %s", in, actual, err, out, tt.err)
  1037  		}
  1038  	}
  1039  }
  1040  
  1041  var queryEscapeTests = []EscapeTest{
  1042  	{
  1043  		"",
  1044  		"",
  1045  		nil,
  1046  	},
  1047  	{
  1048  		"abc",
  1049  		"abc",
  1050  		nil,
  1051  	},
  1052  	{
  1053  		"one two",
  1054  		"one+two",
  1055  		nil,
  1056  	},
  1057  	{
  1058  		"10%",
  1059  		"10%25",
  1060  		nil,
  1061  	},
  1062  	{
  1063  		" ?&=#+%!<>#\"{}|\\^[]`☺\t:/@$'()*,;",
  1064  		"+%3F%26%3D%23%2B%25%21%3C%3E%23%22%7B%7D%7C%5C%5E%5B%5D%60%E2%98%BA%09%3A%2F%40%24%27%28%29%2A%2C%3B",
  1065  		nil,
  1066  	},
  1067  }
  1068  
  1069  func TestQueryEscape(t *testing.T) {
  1070  	for _, tt := range queryEscapeTests {
  1071  		actual := QueryEscape(tt.in)
  1072  		if tt.out != actual {
  1073  			t.Errorf("QueryEscape(%q) = %q, want %q", tt.in, actual, tt.out)
  1074  		}
  1075  
  1076  		// for bonus points, verify that escape:unescape is an identity.
  1077  		roundtrip, err := QueryUnescape(actual)
  1078  		if roundtrip != tt.in || err != nil {
  1079  			t.Errorf("QueryUnescape(%q) = %q, %s; want %q, %s", actual, roundtrip, err, tt.in, "[no error]")
  1080  		}
  1081  	}
  1082  }
  1083  
  1084  var pathEscapeTests = []EscapeTest{
  1085  	{
  1086  		"",
  1087  		"",
  1088  		nil,
  1089  	},
  1090  	{
  1091  		"abc",
  1092  		"abc",
  1093  		nil,
  1094  	},
  1095  	{
  1096  		"abc+def",
  1097  		"abc+def",
  1098  		nil,
  1099  	},
  1100  	{
  1101  		"a/b",
  1102  		"a%2Fb",
  1103  		nil,
  1104  	},
  1105  	{
  1106  		"one two",
  1107  		"one%20two",
  1108  		nil,
  1109  	},
  1110  	{
  1111  		"10%",
  1112  		"10%25",
  1113  		nil,
  1114  	},
  1115  	{
  1116  		" ?&=#+%!<>#\"{}|\\^[]`☺\t:/@$'()*,;",
  1117  		"%20%3F&=%23+%25%21%3C%3E%23%22%7B%7D%7C%5C%5E%5B%5D%60%E2%98%BA%09:%2F@$%27%28%29%2A%2C%3B",
  1118  		nil,
  1119  	},
  1120  }
  1121  
  1122  func TestPathEscape(t *testing.T) {
  1123  	for _, tt := range pathEscapeTests {
  1124  		actual := PathEscape(tt.in)
  1125  		if tt.out != actual {
  1126  			t.Errorf("PathEscape(%q) = %q, want %q", tt.in, actual, tt.out)
  1127  		}
  1128  
  1129  		// for bonus points, verify that escape:unescape is an identity.
  1130  		roundtrip, err := PathUnescape(actual)
  1131  		if roundtrip != tt.in || err != nil {
  1132  			t.Errorf("PathUnescape(%q) = %q, %s; want %q, %s", actual, roundtrip, err, tt.in, "[no error]")
  1133  		}
  1134  	}
  1135  }
  1136  
  1137  //var userinfoTests = []UserinfoTest{
  1138  //	{"user", "password", "user:password"},
  1139  //	{"foo:bar", "~!@#$%^&*()_+{}|[]\\-=`:;'\"<>?,./",
  1140  //		"foo%3Abar:~!%40%23$%25%5E&*()_+%7B%7D%7C%5B%5D%5C-=%60%3A;'%22%3C%3E?,.%2F"},
  1141  //}
  1142  
  1143  type EncodeQueryTest struct {
  1144  	m        Values
  1145  	expected string
  1146  }
  1147  
  1148  var encodeQueryTests = []EncodeQueryTest{
  1149  	{nil, ""},
  1150  	{Values{}, ""},
  1151  	{Values{"q": {"puppies"}, "oe": {"utf8"}}, "oe=utf8&q=puppies"},
  1152  	{Values{"q": {"dogs", "&", "7"}}, "q=dogs&q=%26&q=7"},
  1153  	{Values{
  1154  		"a": {"a1", "a2", "a3"},
  1155  		"b": {"b1", "b2", "b3"},
  1156  		"c": {"c1", "c2", "c3"},
  1157  	}, "a=a1&a=a2&a=a3&b=b1&b=b2&b=b3&c=c1&c=c2&c=c3"},
  1158  	{Values{
  1159  		"a": {"a"},
  1160  		"b": {"b"},
  1161  		"c": {"c"},
  1162  		"d": {"d"},
  1163  		"e": {"e"},
  1164  		"f": {"f"},
  1165  		"g": {"g"},
  1166  		"h": {"h"},
  1167  		"i": {"i"},
  1168  	}, "a=a&b=b&c=c&d=d&e=e&f=f&g=g&h=h&i=i"},
  1169  }
  1170  
  1171  func TestEncodeQuery(t *testing.T) {
  1172  	for _, tt := range encodeQueryTests {
  1173  		if q := tt.m.Encode(); q != tt.expected {
  1174  			t.Errorf(`EncodeQuery(%+v) = %q, want %q`, tt.m, q, tt.expected)
  1175  		}
  1176  	}
  1177  }
  1178  
  1179  func BenchmarkEncodeQuery(b *testing.B) {
  1180  	for _, tt := range encodeQueryTests {
  1181  		b.Run(tt.expected, func(b *testing.B) {
  1182  			b.ReportAllocs()
  1183  			for b.Loop() {
  1184  				tt.m.Encode()
  1185  			}
  1186  		})
  1187  	}
  1188  }
  1189  
  1190  var resolvePathTests = []struct {
  1191  	base, ref, expected string
  1192  }{
  1193  	{"a/b", ".", "/a/"},
  1194  	{"a/b", "c", "/a/c"},
  1195  	{"a/b", "..", "/"},
  1196  	{"a/", "..", "/"},
  1197  	{"a/", "../..", "/"},
  1198  	{"a/b/c", "..", "/a/"},
  1199  	{"a/b/c", "../d", "/a/d"},
  1200  	{"a/b/c", ".././d", "/a/d"},
  1201  	{"a/b", "./..", "/"},
  1202  	{"a/./b", ".", "/a/"},
  1203  	{"a/../", ".", "/"},
  1204  	{"a/.././b", "c", "/c"},
  1205  }
  1206  
  1207  func TestResolvePath(t *testing.T) {
  1208  	for _, test := range resolvePathTests {
  1209  		got := resolvePath(test.base, test.ref)
  1210  		if got != test.expected {
  1211  			t.Errorf("For %q + %q got %q; expected %q", test.base, test.ref, got, test.expected)
  1212  		}
  1213  	}
  1214  }
  1215  
  1216  func BenchmarkResolvePath(b *testing.B) {
  1217  	b.ReportAllocs()
  1218  	for i := 0; i < b.N; i++ {
  1219  		resolvePath("a/b/c", ".././d")
  1220  	}
  1221  }
  1222  
  1223  var resolveReferenceTests = []struct {
  1224  	base, rel, expected string
  1225  }{
  1226  	// Absolute URL references
  1227  	{"http://foo.com?a=b", "https://bar.com/", "https://bar.com/"},
  1228  	{"http://foo.com/", "https://bar.com/?a=b", "https://bar.com/?a=b"},
  1229  	{"http://foo.com/", "https://bar.com/?", "https://bar.com/?"},
  1230  	{"http://foo.com/bar", "mailto:foo@example.com", "mailto:foo@example.com"},
  1231  
  1232  	// Path-absolute references
  1233  	{"http://foo.com/bar", "/baz", "http://foo.com/baz"},
  1234  	{"http://foo.com/bar?a=b#f", "/baz", "http://foo.com/baz"},
  1235  	{"http://foo.com/bar?a=b", "/baz?", "http://foo.com/baz?"},
  1236  	{"http://foo.com/bar?a=b", "/baz?c=d", "http://foo.com/baz?c=d"},
  1237  
  1238  	// Multiple slashes
  1239  	{"http://foo.com/bar", "http://foo.com//baz", "http://foo.com//baz"},
  1240  	{"http://foo.com/bar", "http://foo.com///baz/quux", "http://foo.com///baz/quux"},
  1241  
  1242  	// Scheme-relative
  1243  	{"https://foo.com/bar?a=b", "//bar.com/quux", "https://bar.com/quux"},
  1244  
  1245  	// Path-relative references:
  1246  
  1247  	// ... current directory
  1248  	{"http://foo.com", ".", "http://foo.com/"},
  1249  	{"http://foo.com/bar", ".", "http://foo.com/"},
  1250  	{"http://foo.com/bar/", ".", "http://foo.com/bar/"},
  1251  
  1252  	// ... going down
  1253  	{"http://foo.com", "bar", "http://foo.com/bar"},
  1254  	{"http://foo.com/", "bar", "http://foo.com/bar"},
  1255  	{"http://foo.com/bar/baz", "quux", "http://foo.com/bar/quux"},
  1256  	{"http://foo.com/bar/baz/", "quux", "http://foo.com/bar/baz/quux"},
  1257  
  1258  	// ... going up
  1259  	{"http://foo.com/bar/baz", "../quux", "http://foo.com/quux"},
  1260  	{"http://foo.com/bar/baz", "../../../../../quux", "http://foo.com/quux"},
  1261  	{"http://foo.com/bar", "..", "http://foo.com/"},
  1262  	{"http://foo.com/bar/baz", "./..", "http://foo.com/"},
  1263  	// ".." in the middle (issue 3560)
  1264  	{"http://foo.com/bar/baz", "quux/dotdot/../tail", "http://foo.com/bar/quux/tail"},
  1265  	{"http://foo.com/bar/baz", "quux/./dotdot/../tail", "http://foo.com/bar/quux/tail"},
  1266  	{"http://foo.com/bar/baz", "quux/./dotdot/.././tail", "http://foo.com/bar/quux/tail"},
  1267  	{"http://foo.com/bar/baz", "quux/./dotdot/./../tail", "http://foo.com/bar/quux/tail"},
  1268  	{"http://foo.com/bar/baz", "quux/./dotdot/dotdot/././../../tail", "http://foo.com/bar/quux/tail"},
  1269  	{"http://foo.com/bar/baz", "quux/./dotdot/dotdot/./.././../tail", "http://foo.com/bar/quux/tail"},
  1270  	{"http://foo.com/bar/baz", "quux/./dotdot/dotdot/dotdot/./../../.././././tail", "http://foo.com/bar/quux/tail"},
  1271  	{"http://foo.com/bar/baz", "quux/./dotdot/../dotdot/../dot/./tail/..", "http://foo.com/bar/quux/dot/"},
  1272  
  1273  	// Remove any dot-segments prior to forming the target URI.
  1274  	// https://datatracker.ietf.org/doc/html/rfc3986#section-5.2.4
  1275  	{"http://foo.com/dot/./dotdot/../foo/bar", "../baz", "http://foo.com/dot/baz"},
  1276  
  1277  	// Triple dot isn't special
  1278  	{"http://foo.com/bar", "...", "http://foo.com/..."},
  1279  
  1280  	// Fragment
  1281  	{"http://foo.com/bar", ".#frag", "http://foo.com/#frag"},
  1282  	{"http://example.org/", "#!$&%27()*+,;=", "http://example.org/#!$&%27()*+,;="},
  1283  
  1284  	// Paths with escaping (issue 16947).
  1285  	{"http://foo.com/foo%2fbar/", "../baz", "http://foo.com/baz"},
  1286  	{"http://foo.com/1/2%2f/3%2f4/5", "../../a/b/c", "http://foo.com/1/a/b/c"},
  1287  	{"http://foo.com/1/2/3", "./a%2f../../b/..%2fc", "http://foo.com/1/2/b/..%2fc"},
  1288  	{"http://foo.com/1/2%2f/3%2f4/5", "./a%2f../b/../c", "http://foo.com/1/2%2f/3%2f4/a%2f../c"},
  1289  	{"http://foo.com/foo%20bar/", "../baz", "http://foo.com/baz"},
  1290  	{"http://foo.com/foo", "../bar%2fbaz", "http://foo.com/bar%2fbaz"},
  1291  	{"http://foo.com/foo%2dbar/", "./baz-quux", "http://foo.com/foo%2dbar/baz-quux"},
  1292  
  1293  	// RFC 3986: Normal Examples
  1294  	// https://datatracker.ietf.org/doc/html/rfc3986#section-5.4.1
  1295  	{"http://a/b/c/d;p?q", "g:h", "g:h"},
  1296  	{"http://a/b/c/d;p?q", "g", "http://a/b/c/g"},
  1297  	{"http://a/b/c/d;p?q", "./g", "http://a/b/c/g"},
  1298  	{"http://a/b/c/d;p?q", "g/", "http://a/b/c/g/"},
  1299  	{"http://a/b/c/d;p?q", "/g", "http://a/g"},
  1300  	{"http://a/b/c/d;p?q", "//g", "http://g"},
  1301  	{"http://a/b/c/d;p?q", "?y", "http://a/b/c/d;p?y"},
  1302  	{"http://a/b/c/d;p?q", "g?y", "http://a/b/c/g?y"},
  1303  	{"http://a/b/c/d;p?q", "#s", "http://a/b/c/d;p?q#s"},
  1304  	{"http://a/b/c/d;p?q", "g#s", "http://a/b/c/g#s"},
  1305  	{"http://a/b/c/d;p?q", "g?y#s", "http://a/b/c/g?y#s"},
  1306  	{"http://a/b/c/d;p?q", ";x", "http://a/b/c/;x"},
  1307  	{"http://a/b/c/d;p?q", "g;x", "http://a/b/c/g;x"},
  1308  	{"http://a/b/c/d;p?q", "g;x?y#s", "http://a/b/c/g;x?y#s"},
  1309  	{"http://a/b/c/d;p?q", "", "http://a/b/c/d;p?q"},
  1310  	{"http://a/b/c/d;p?q", ".", "http://a/b/c/"},
  1311  	{"http://a/b/c/d;p?q", "./", "http://a/b/c/"},
  1312  	{"http://a/b/c/d;p?q", "..", "http://a/b/"},
  1313  	{"http://a/b/c/d;p?q", "../", "http://a/b/"},
  1314  	{"http://a/b/c/d;p?q", "../g", "http://a/b/g"},
  1315  	{"http://a/b/c/d;p?q", "../..", "http://a/"},
  1316  	{"http://a/b/c/d;p?q", "../../", "http://a/"},
  1317  	{"http://a/b/c/d;p?q", "../../g", "http://a/g"},
  1318  
  1319  	// RFC 3986: Abnormal Examples
  1320  	// https://datatracker.ietf.org/doc/html/rfc3986#section-5.4.2
  1321  	{"http://a/b/c/d;p?q", "../../../g", "http://a/g"},
  1322  	{"http://a/b/c/d;p?q", "../../../../g", "http://a/g"},
  1323  	{"http://a/b/c/d;p?q", "/./g", "http://a/g"},
  1324  	{"http://a/b/c/d;p?q", "/../g", "http://a/g"},
  1325  	{"http://a/b/c/d;p?q", "g.", "http://a/b/c/g."},
  1326  	{"http://a/b/c/d;p?q", ".g", "http://a/b/c/.g"},
  1327  	{"http://a/b/c/d;p?q", "g..", "http://a/b/c/g.."},
  1328  	{"http://a/b/c/d;p?q", "..g", "http://a/b/c/..g"},
  1329  	{"http://a/b/c/d;p?q", "./../g", "http://a/b/g"},
  1330  	{"http://a/b/c/d;p?q", "./g/.", "http://a/b/c/g/"},
  1331  	{"http://a/b/c/d;p?q", "g/./h", "http://a/b/c/g/h"},
  1332  	{"http://a/b/c/d;p?q", "g/../h", "http://a/b/c/h"},
  1333  	{"http://a/b/c/d;p?q", "g;x=1/./y", "http://a/b/c/g;x=1/y"},
  1334  	{"http://a/b/c/d;p?q", "g;x=1/../y", "http://a/b/c/y"},
  1335  	{"http://a/b/c/d;p?q", "g?y/./x", "http://a/b/c/g?y/./x"},
  1336  	{"http://a/b/c/d;p?q", "g?y/../x", "http://a/b/c/g?y/../x"},
  1337  	{"http://a/b/c/d;p?q", "g#s/./x", "http://a/b/c/g#s/./x"},
  1338  	{"http://a/b/c/d;p?q", "g#s/../x", "http://a/b/c/g#s/../x"},
  1339  
  1340  	// Extras.
  1341  	{"https://a/b/c/d;p?q", "//g?q", "https://g?q"},
  1342  	{"https://a/b/c/d;p?q", "//g#s", "https://g#s"},
  1343  	{"https://a/b/c/d;p?q", "//g/d/e/f?y#s", "https://g/d/e/f?y#s"},
  1344  	{"https://a/b/c/d;p#s", "?y", "https://a/b/c/d;p?y"},
  1345  	{"https://a/b/c/d;p?q#s", "?y", "https://a/b/c/d;p?y"},
  1346  
  1347  	// Empty path and query but with ForceQuery (issue 46033).
  1348  	{"https://a/b/c/d;p?q#s", "?", "https://a/b/c/d;p?"},
  1349  
  1350  	// Opaque URLs (issue 66084).
  1351  	{"https://foo.com/bar?a=b", "http:opaque", "http:opaque"},
  1352  	{"http:opaque?x=y#zzz", "https:/foo?a=b#frag", "https:/foo?a=b#frag"},
  1353  	{"http:opaque?x=y#zzz", "https:foo:bar", "https:foo:bar"},
  1354  	{"http:opaque?x=y#zzz", "https:bar/baz?a=b#frag", "https:bar/baz?a=b#frag"},
  1355  	{"http:opaque?x=y#zzz", "https://user@host:1234?a=b#frag", "https://user@host:1234?a=b#frag"},
  1356  	{"http:opaque?x=y#zzz", "?a=b#frag", "http:opaque?a=b#frag"},
  1357  }
  1358  
  1359  func TestResolveReference(t *testing.T) {
  1360  	mustParse := func(url string) *URL {
  1361  		u, err := Parse(url)
  1362  		if err != nil {
  1363  			t.Fatalf("Parse(%q) got err %v", url, err)
  1364  		}
  1365  		return u
  1366  	}
  1367  	opaque := &URL{Scheme: "scheme", Opaque: "opaque"}
  1368  	for _, test := range resolveReferenceTests {
  1369  		base := mustParse(test.base)
  1370  		rel := mustParse(test.rel)
  1371  		url := base.ResolveReference(rel)
  1372  		if got := url.String(); got != test.expected {
  1373  			t.Errorf("URL(%q).ResolveReference(%q)\ngot  %q\nwant %q", test.base, test.rel, got, test.expected)
  1374  		}
  1375  		// Ensure that new instances are returned.
  1376  		if base == url {
  1377  			t.Errorf("Expected URL.ResolveReference to return new URL instance.")
  1378  		}
  1379  		// Test the convenience wrapper too.
  1380  		url, err := base.Parse(test.rel)
  1381  		if err != nil {
  1382  			t.Errorf("URL(%q).Parse(%q) failed: %v", test.base, test.rel, err)
  1383  		} else if got := url.String(); got != test.expected {
  1384  			t.Errorf("URL(%q).Parse(%q)\ngot  %q\nwant %q", test.base, test.rel, got, test.expected)
  1385  		} else if base == url {
  1386  			// Ensure that new instances are returned for the wrapper too.
  1387  			t.Errorf("Expected URL.Parse to return new URL instance.")
  1388  		}
  1389  		// Ensure Opaque resets the URL.
  1390  		url = base.ResolveReference(opaque)
  1391  		if *url != *opaque {
  1392  			t.Errorf("ResolveReference failed to resolve opaque URL:\ngot  %#v\nwant %#v", url, opaque)
  1393  		}
  1394  		// Test the convenience wrapper with an opaque URL too.
  1395  		url, err = base.Parse("scheme:opaque")
  1396  		if err != nil {
  1397  			t.Errorf(`URL(%q).Parse("scheme:opaque") failed: %v`, test.base, err)
  1398  		} else if *url != *opaque {
  1399  			t.Errorf("Parse failed to resolve opaque URL:\ngot  %#v\nwant %#v", opaque, url)
  1400  		} else if base == url {
  1401  			// Ensure that new instances are returned, again.
  1402  			t.Errorf("Expected URL.Parse to return new URL instance.")
  1403  		}
  1404  	}
  1405  }
  1406  
  1407  func TestQueryValues(t *testing.T) {
  1408  	u, _ := Parse("http://x.com?foo=bar&bar=1&bar=2&baz")
  1409  	v := u.Query()
  1410  	if len(v) != 3 {
  1411  		t.Errorf("got %d keys in Query values, want 3", len(v))
  1412  	}
  1413  	if g, e := v.Get("foo"), "bar"; g != e {
  1414  		t.Errorf("Get(foo) = %q, want %q", g, e)
  1415  	}
  1416  	// Case sensitive:
  1417  	if g, e := v.Get("Foo"), ""; g != e {
  1418  		t.Errorf("Get(Foo) = %q, want %q", g, e)
  1419  	}
  1420  	if g, e := v.Get("bar"), "1"; g != e {
  1421  		t.Errorf("Get(bar) = %q, want %q", g, e)
  1422  	}
  1423  	if g, e := v.Get("baz"), ""; g != e {
  1424  		t.Errorf("Get(baz) = %q, want %q", g, e)
  1425  	}
  1426  	if h, e := v.Has("foo"), true; h != e {
  1427  		t.Errorf("Has(foo) = %t, want %t", h, e)
  1428  	}
  1429  	if h, e := v.Has("bar"), true; h != e {
  1430  		t.Errorf("Has(bar) = %t, want %t", h, e)
  1431  	}
  1432  	if h, e := v.Has("baz"), true; h != e {
  1433  		t.Errorf("Has(baz) = %t, want %t", h, e)
  1434  	}
  1435  	if h, e := v.Has("noexist"), false; h != e {
  1436  		t.Errorf("Has(noexist) = %t, want %t", h, e)
  1437  	}
  1438  	v.Del("bar")
  1439  	if g, e := v.Get("bar"), ""; g != e {
  1440  		t.Errorf("second Get(bar) = %q, want %q", g, e)
  1441  	}
  1442  }
  1443  
  1444  type parseTest struct {
  1445  	query string
  1446  	out   Values
  1447  	ok    bool
  1448  }
  1449  
  1450  var parseTests = []parseTest{
  1451  	{
  1452  		query: "a=1",
  1453  		out:   Values{"a": []string{"1"}},
  1454  		ok:    true,
  1455  	},
  1456  	{
  1457  		query: "a=1&b=2",
  1458  		out:   Values{"a": []string{"1"}, "b": []string{"2"}},
  1459  		ok:    true,
  1460  	},
  1461  	{
  1462  		query: "a=1&a=2&a=banana",
  1463  		out:   Values{"a": []string{"1", "2", "banana"}},
  1464  		ok:    true,
  1465  	},
  1466  	{
  1467  		query: "ascii=%3Ckey%3A+0x90%3E",
  1468  		out:   Values{"ascii": []string{"<key: 0x90>"}},
  1469  		ok:    true,
  1470  	}, {
  1471  		query: "a=1;b=2",
  1472  		out:   Values{},
  1473  		ok:    false,
  1474  	}, {
  1475  		query: "a;b=1",
  1476  		out:   Values{},
  1477  		ok:    false,
  1478  	}, {
  1479  		query: "a=%3B", // hex encoding for semicolon
  1480  		out:   Values{"a": []string{";"}},
  1481  		ok:    true,
  1482  	},
  1483  	{
  1484  		query: "a%3Bb=1",
  1485  		out:   Values{"a;b": []string{"1"}},
  1486  		ok:    true,
  1487  	},
  1488  	{
  1489  		query: "a=1&a=2;a=banana",
  1490  		out:   Values{"a": []string{"1"}},
  1491  		ok:    false,
  1492  	},
  1493  	{
  1494  		query: "a;b&c=1",
  1495  		out:   Values{"c": []string{"1"}},
  1496  		ok:    false,
  1497  	},
  1498  	{
  1499  		query: "a=1&b=2;a=3&c=4",
  1500  		out:   Values{"a": []string{"1"}, "c": []string{"4"}},
  1501  		ok:    false,
  1502  	},
  1503  	{
  1504  		query: "a=1&b=2;c=3",
  1505  		out:   Values{"a": []string{"1"}},
  1506  		ok:    false,
  1507  	},
  1508  	{
  1509  		query: ";",
  1510  		out:   Values{},
  1511  		ok:    false,
  1512  	},
  1513  	{
  1514  		query: "a=1;",
  1515  		out:   Values{},
  1516  		ok:    false,
  1517  	},
  1518  	{
  1519  		query: "a=1&;",
  1520  		out:   Values{"a": []string{"1"}},
  1521  		ok:    false,
  1522  	},
  1523  	{
  1524  		query: ";a=1&b=2",
  1525  		out:   Values{"b": []string{"2"}},
  1526  		ok:    false,
  1527  	},
  1528  	{
  1529  		query: "a=1&b=2;",
  1530  		out:   Values{"a": []string{"1"}},
  1531  		ok:    false,
  1532  	},
  1533  }
  1534  
  1535  func TestParseQuery(t *testing.T) {
  1536  	for _, test := range parseTests {
  1537  		t.Run(test.query, func(t *testing.T) {
  1538  			form, err := ParseQuery(test.query)
  1539  			if test.ok != (err == nil) {
  1540  				want := "<error>"
  1541  				if test.ok {
  1542  					want = "<nil>"
  1543  				}
  1544  				t.Errorf("Unexpected error: %v, want %v", err, want)
  1545  			}
  1546  			if len(form) != len(test.out) {
  1547  				t.Errorf("len(form) = %d, want %d", len(form), len(test.out))
  1548  			}
  1549  			for k, evs := range test.out {
  1550  				vs, ok := form[k]
  1551  				if !ok {
  1552  					t.Errorf("Missing key %q", k)
  1553  					continue
  1554  				}
  1555  				if len(vs) != len(evs) {
  1556  					t.Errorf("len(form[%q]) = %d, want %d", k, len(vs), len(evs))
  1557  					continue
  1558  				}
  1559  				for j, ev := range evs {
  1560  					if v := vs[j]; v != ev {
  1561  						t.Errorf("form[%q][%d] = %q, want %q", k, j, v, ev)
  1562  					}
  1563  				}
  1564  			}
  1565  		})
  1566  	}
  1567  }
  1568  
  1569  func TestParseQueryLimits(t *testing.T) {
  1570  	for _, test := range []struct {
  1571  		params  int
  1572  		godebug string
  1573  		wantErr bool
  1574  	}{{
  1575  		params:  10,
  1576  		wantErr: false,
  1577  	}, {
  1578  		params:  defaultMaxParams,
  1579  		wantErr: false,
  1580  	}, {
  1581  		params:  defaultMaxParams + 1,
  1582  		wantErr: true,
  1583  	}, {
  1584  		params:  10,
  1585  		godebug: "urlmaxqueryparams=9",
  1586  		wantErr: true,
  1587  	}, {
  1588  		params:  defaultMaxParams + 1,
  1589  		godebug: "urlmaxqueryparams=0",
  1590  		wantErr: false,
  1591  	}} {
  1592  		t.Setenv("GODEBUG", test.godebug)
  1593  		want := Values{}
  1594  		var b strings.Builder
  1595  		for i := range test.params {
  1596  			if i > 0 {
  1597  				b.WriteString("&")
  1598  			}
  1599  			p := fmt.Sprintf("p%v", i)
  1600  			b.WriteString(p)
  1601  			want[p] = []string{""}
  1602  		}
  1603  		query := b.String()
  1604  		got, err := ParseQuery(query)
  1605  		if gotErr, wantErr := err != nil, test.wantErr; gotErr != wantErr {
  1606  			t.Errorf("GODEBUG=%v ParseQuery(%v params) = %v, want error: %v", test.godebug, test.params, err, wantErr)
  1607  		}
  1608  		if err != nil {
  1609  			continue
  1610  		}
  1611  		if got, want := len(got), test.params; got != want {
  1612  			t.Errorf("GODEBUG=%v ParseQuery(%v params): got %v params, want %v", test.godebug, test.params, got, want)
  1613  		}
  1614  	}
  1615  }
  1616  
  1617  type RequestURITest struct {
  1618  	url *URL
  1619  	out string
  1620  }
  1621  
  1622  var requritests = []RequestURITest{
  1623  	{
  1624  		&URL{
  1625  			Scheme: "http",
  1626  			Host:   "example.com",
  1627  			Path:   "",
  1628  		},
  1629  		"/",
  1630  	},
  1631  	{
  1632  		&URL{
  1633  			Scheme: "http",
  1634  			Host:   "example.com",
  1635  			Path:   "/a b",
  1636  		},
  1637  		"/a%20b",
  1638  	},
  1639  	// golang.org/issue/4860 variant 1
  1640  	{
  1641  		&URL{
  1642  			Scheme: "http",
  1643  			Host:   "example.com",
  1644  			Opaque: "/%2F/%2F/",
  1645  		},
  1646  		"/%2F/%2F/",
  1647  	},
  1648  	// golang.org/issue/4860 variant 2
  1649  	{
  1650  		&URL{
  1651  			Scheme: "http",
  1652  			Host:   "example.com",
  1653  			Opaque: "//other.example.com/%2F/%2F/",
  1654  		},
  1655  		"http://other.example.com/%2F/%2F/",
  1656  	},
  1657  	// better fix for issue 4860
  1658  	{
  1659  		&URL{
  1660  			Scheme:  "http",
  1661  			Host:    "example.com",
  1662  			Path:    "/////",
  1663  			RawPath: "/%2F/%2F/",
  1664  		},
  1665  		"/%2F/%2F/",
  1666  	},
  1667  	{
  1668  		&URL{
  1669  			Scheme:  "http",
  1670  			Host:    "example.com",
  1671  			Path:    "/////",
  1672  			RawPath: "/WRONG/", // ignored because doesn't match Path
  1673  		},
  1674  		"/////",
  1675  	},
  1676  	{
  1677  		&URL{
  1678  			Scheme:   "http",
  1679  			Host:     "example.com",
  1680  			Path:     "/a b",
  1681  			RawQuery: "q=go+language",
  1682  		},
  1683  		"/a%20b?q=go+language",
  1684  	},
  1685  	{
  1686  		&URL{
  1687  			Scheme:   "http",
  1688  			Host:     "example.com",
  1689  			Path:     "/a b",
  1690  			RawPath:  "/a b", // ignored because invalid
  1691  			RawQuery: "q=go+language",
  1692  		},
  1693  		"/a%20b?q=go+language",
  1694  	},
  1695  	{
  1696  		&URL{
  1697  			Scheme:   "http",
  1698  			Host:     "example.com",
  1699  			Path:     "/a?b",
  1700  			RawPath:  "/a?b", // ignored because invalid
  1701  			RawQuery: "q=go+language",
  1702  		},
  1703  		"/a%3Fb?q=go+language",
  1704  	},
  1705  	{
  1706  		&URL{
  1707  			Scheme: "myschema",
  1708  			Opaque: "opaque",
  1709  		},
  1710  		"opaque",
  1711  	},
  1712  	{
  1713  		&URL{
  1714  			Scheme:   "myschema",
  1715  			Opaque:   "opaque",
  1716  			RawQuery: "q=go+language",
  1717  		},
  1718  		"opaque?q=go+language",
  1719  	},
  1720  	{
  1721  		&URL{
  1722  			Scheme: "http",
  1723  			Host:   "example.com",
  1724  			Path:   "//foo",
  1725  		},
  1726  		"//foo",
  1727  	},
  1728  	{
  1729  		&URL{
  1730  			Scheme:     "http",
  1731  			Host:       "example.com",
  1732  			Path:       "/foo",
  1733  			ForceQuery: true,
  1734  		},
  1735  		"/foo?",
  1736  	},
  1737  }
  1738  
  1739  func TestRequestURI(t *testing.T) {
  1740  	for _, tt := range requritests {
  1741  		s := tt.url.RequestURI()
  1742  		if s != tt.out {
  1743  			t.Errorf("%#v.RequestURI() == %q (expected %q)", tt.url, s, tt.out)
  1744  		}
  1745  	}
  1746  }
  1747  
  1748  func TestParseFailure(t *testing.T) {
  1749  	// Test that the first parse error is returned.
  1750  	const url = "%gh&%ij"
  1751  	_, err := ParseQuery(url)
  1752  	errStr := fmt.Sprint(err)
  1753  	if !strings.Contains(errStr, "%gh") {
  1754  		t.Errorf(`ParseQuery(%q) returned error %q, want something containing %q"`, url, errStr, "%gh")
  1755  	}
  1756  }
  1757  
  1758  func TestParseErrors(t *testing.T) {
  1759  	tests := []struct {
  1760  		in      string
  1761  		wantErr bool
  1762  	}{
  1763  		{"http://[::1]", false},
  1764  		{"http://[::1]:80", false},
  1765  		{"http://[::1]:namedport", true}, // rfc3986 3.2.3
  1766  		{"http://x:namedport", true},     // rfc3986 3.2.3
  1767  		{"http://[::1]/", false},
  1768  		{"http://[::1]a", true},
  1769  		{"http://[::1]%23", true},
  1770  		{"http://[::1%25en0]", false},    // valid zone id
  1771  		{"http://[::1]:", false},         // colon, but no port OK
  1772  		{"http://x:", false},             // colon, but no port OK
  1773  		{"http://[::1]:%38%30", true},    // not allowed: % encoding only for non-ASCII
  1774  		{"http://[::1%25%41]", false},    // RFC 6874 allows over-escaping in zone
  1775  		{"http://[%10::1]", true},        // no %xx escapes in IP address
  1776  		{"http://[::1]/%48", false},      // %xx in path is fine
  1777  		{"http://%41:8080/", true},       // not allowed: % encoding only for non-ASCII
  1778  		{"mysql://x@y(z:123)/foo", true}, // not well-formed per RFC 3986, golang.org/issue/33646
  1779  		{"mysql://x@y(1.2.3.4:123)/foo", true},
  1780  
  1781  		{" http://foo.com", true},  // invalid character in schema
  1782  		{"ht tp://foo.com", true},  // invalid character in schema
  1783  		{"ahttp://foo.com", false}, // valid schema characters
  1784  		{"1http://foo.com", true},  // invalid character in schema
  1785  
  1786  		{"http://[]%20%48%54%54%50%2f%31%2e%31%0a%4d%79%48%65%61%64%65%72%3a%20%31%32%33%0a%0a/", true}, // golang.org/issue/11208
  1787  		{"http://a b.com/", true},    // no space in host name please
  1788  		{"cache_object://foo", true}, // scheme cannot have _, relative path cannot have : in first segment
  1789  		{"cache_object:foo", true},
  1790  		{"cache_object:foo/bar", true},
  1791  		{"cache_object/:foo/bar", false},
  1792  
  1793  		{"http://[192.168.0.1]/", true},              // IPv4 in brackets
  1794  		{"http://[192.168.0.1]:8080/", true},         // IPv4 in brackets with port
  1795  		{"http://[::ffff:192.168.0.1]/", false},      // IPv4-mapped IPv6 in brackets
  1796  		{"http://[::ffff:192.168.0.1000]/", true},    // Out of range IPv4-mapped IPv6 in brackets
  1797  		{"http://[::ffff:192.168.0.1]:8080/", false}, // IPv4-mapped IPv6 in brackets with port
  1798  		{"http://[::ffff:c0a8:1]/", false},           // IPv4-mapped IPv6 in brackets (hex)
  1799  		{"http://[not-an-ip]/", true},                // invalid IP string in brackets
  1800  		{"http://[fe80::1%foo]/", true},              // invalid zone format in brackets
  1801  		{"http://[fe80::1", true},                    // missing closing bracket
  1802  		{"http://fe80::1]/", true},                   // missing opening bracket
  1803  		{"http://[test.com]/", true},                 // domain name in brackets
  1804  		{"http://example.com[::1]", true},            // IPv6 literal doesn't start with '['
  1805  		{"http://example.com[::1", true},
  1806  		{"http://[::1", true},
  1807  		{"http://.[::1]", true},
  1808  		{"http:// [::1]", true},
  1809  		{"hxxp://mathepqo[.]serveftp(.)com:9059", true},
  1810  	}
  1811  	for _, tt := range tests {
  1812  		u, err := Parse(tt.in)
  1813  		if tt.wantErr {
  1814  			if err == nil {
  1815  				t.Errorf("Parse(%q) = %#v; want an error", tt.in, u)
  1816  			}
  1817  			continue
  1818  		}
  1819  		if err != nil {
  1820  			t.Errorf("Parse(%q) = %v; want no error", tt.in, err)
  1821  		}
  1822  	}
  1823  }
  1824  
  1825  // Issue 11202
  1826  func TestStarRequest(t *testing.T) {
  1827  	u, err := Parse("*")
  1828  	if err != nil {
  1829  		t.Fatal(err)
  1830  	}
  1831  	if got, want := u.RequestURI(), "*"; got != want {
  1832  		t.Errorf("RequestURI = %q; want %q", got, want)
  1833  	}
  1834  }
  1835  
  1836  type shouldEscapeTest struct {
  1837  	in     byte
  1838  	mode   encoding
  1839  	escape bool
  1840  }
  1841  
  1842  var shouldEscapeTests = []shouldEscapeTest{
  1843  	// Unreserved characters (§2.3)
  1844  	{'a', encodePath, false},
  1845  	{'a', encodeUserPassword, false},
  1846  	{'a', encodeQueryComponent, false},
  1847  	{'a', encodeFragment, false},
  1848  	{'a', encodeHost, false},
  1849  	{'z', encodePath, false},
  1850  	{'A', encodePath, false},
  1851  	{'Z', encodePath, false},
  1852  	{'0', encodePath, false},
  1853  	{'9', encodePath, false},
  1854  	{'-', encodePath, false},
  1855  	{'-', encodeUserPassword, false},
  1856  	{'-', encodeQueryComponent, false},
  1857  	{'-', encodeFragment, false},
  1858  	{'.', encodePath, false},
  1859  	{'_', encodePath, false},
  1860  	{'~', encodePath, false},
  1861  
  1862  	// User information (§3.2.1)
  1863  	{':', encodeUserPassword, true},
  1864  	{'/', encodeUserPassword, true},
  1865  	{'?', encodeUserPassword, true},
  1866  	{'@', encodeUserPassword, true},
  1867  	{'$', encodeUserPassword, false},
  1868  	{'&', encodeUserPassword, false},
  1869  	{'+', encodeUserPassword, false},
  1870  	{',', encodeUserPassword, false},
  1871  	{';', encodeUserPassword, false},
  1872  	{'=', encodeUserPassword, false},
  1873  
  1874  	// Host (IP address, IPv6 address, registered name, port suffix; §3.2.2)
  1875  	{'!', encodeHost, false},
  1876  	{'$', encodeHost, false},
  1877  	{'&', encodeHost, false},
  1878  	{'\'', encodeHost, false},
  1879  	{'(', encodeHost, false},
  1880  	{')', encodeHost, false},
  1881  	{'*', encodeHost, false},
  1882  	{'+', encodeHost, false},
  1883  	{',', encodeHost, false},
  1884  	{';', encodeHost, false},
  1885  	{'=', encodeHost, false},
  1886  	{':', encodeHost, false},
  1887  	{'[', encodeHost, false},
  1888  	{']', encodeHost, false},
  1889  	{'0', encodeHost, false},
  1890  	{'9', encodeHost, false},
  1891  	{'A', encodeHost, false},
  1892  	{'z', encodeHost, false},
  1893  	{'_', encodeHost, false},
  1894  	{'-', encodeHost, false},
  1895  	{'.', encodeHost, false},
  1896  }
  1897  
  1898  func TestShouldEscape(t *testing.T) {
  1899  	for _, tt := range shouldEscapeTests {
  1900  		if shouldEscape(tt.in, tt.mode) != tt.escape {
  1901  			t.Errorf("shouldEscape(%q, %v) returned %v; expected %v", tt.in, tt.mode, !tt.escape, tt.escape)
  1902  		}
  1903  	}
  1904  }
  1905  
  1906  type timeoutError struct {
  1907  	timeout bool
  1908  }
  1909  
  1910  func (e *timeoutError) Error() string { return "timeout error" }
  1911  func (e *timeoutError) Timeout() bool { return e.timeout }
  1912  
  1913  type temporaryError struct {
  1914  	temporary bool
  1915  }
  1916  
  1917  func (e *temporaryError) Error() string   { return "temporary error" }
  1918  func (e *temporaryError) Temporary() bool { return e.temporary }
  1919  
  1920  type timeoutTemporaryError struct {
  1921  	timeoutError
  1922  	temporaryError
  1923  }
  1924  
  1925  func (e *timeoutTemporaryError) Error() string { return "timeout/temporary error" }
  1926  
  1927  var netErrorTests = []struct {
  1928  	err       error
  1929  	timeout   bool
  1930  	temporary bool
  1931  }{{
  1932  	err:       &Error{"Get", "http://google.com/", &timeoutError{timeout: true}},
  1933  	timeout:   true,
  1934  	temporary: false,
  1935  }, {
  1936  	err:       &Error{"Get", "http://google.com/", &timeoutError{timeout: false}},
  1937  	timeout:   false,
  1938  	temporary: false,
  1939  }, {
  1940  	err:       &Error{"Get", "http://google.com/", &temporaryError{temporary: true}},
  1941  	timeout:   false,
  1942  	temporary: true,
  1943  }, {
  1944  	err:       &Error{"Get", "http://google.com/", &temporaryError{temporary: false}},
  1945  	timeout:   false,
  1946  	temporary: false,
  1947  }, {
  1948  	err:       &Error{"Get", "http://google.com/", &timeoutTemporaryError{timeoutError{timeout: true}, temporaryError{temporary: true}}},
  1949  	timeout:   true,
  1950  	temporary: true,
  1951  }, {
  1952  	err:       &Error{"Get", "http://google.com/", &timeoutTemporaryError{timeoutError{timeout: false}, temporaryError{temporary: true}}},
  1953  	timeout:   false,
  1954  	temporary: true,
  1955  }, {
  1956  	err:       &Error{"Get", "http://google.com/", &timeoutTemporaryError{timeoutError{timeout: true}, temporaryError{temporary: false}}},
  1957  	timeout:   true,
  1958  	temporary: false,
  1959  }, {
  1960  	err:       &Error{"Get", "http://google.com/", &timeoutTemporaryError{timeoutError{timeout: false}, temporaryError{temporary: false}}},
  1961  	timeout:   false,
  1962  	temporary: false,
  1963  }, {
  1964  	err:       &Error{"Get", "http://google.com/", io.EOF},
  1965  	timeout:   false,
  1966  	temporary: false,
  1967  }}
  1968  
  1969  // Test that url.Error implements net.Error and that it forwards
  1970  func TestURLErrorImplementsNetError(t *testing.T) {
  1971  	for i, tt := range netErrorTests {
  1972  		err, ok := tt.err.(net.Error)
  1973  		if !ok {
  1974  			t.Errorf("%d: %T does not implement net.Error", i+1, tt.err)
  1975  			continue
  1976  		}
  1977  		if err.Timeout() != tt.timeout {
  1978  			t.Errorf("%d: err.Timeout(): got %v, want %v", i+1, err.Timeout(), tt.timeout)
  1979  			continue
  1980  		}
  1981  		if err.Temporary() != tt.temporary {
  1982  			t.Errorf("%d: err.Temporary(): got %v, want %v", i+1, err.Temporary(), tt.temporary)
  1983  		}
  1984  	}
  1985  }
  1986  
  1987  func TestURLHostnameAndPort(t *testing.T) {
  1988  	tests := []struct {
  1989  		in   string // URL.Host field
  1990  		host string
  1991  		port string
  1992  	}{
  1993  		{"foo.com:80", "foo.com", "80"},
  1994  		{"foo.com", "foo.com", ""},
  1995  		{"foo.com:", "foo.com", ""},
  1996  		{"FOO.COM", "FOO.COM", ""}, // no canonicalization
  1997  		{"1.2.3.4", "1.2.3.4", ""},
  1998  		{"1.2.3.4:80", "1.2.3.4", "80"},
  1999  		{"[1:2:3:4]", "1:2:3:4", ""},
  2000  		{"[1:2:3:4]:80", "1:2:3:4", "80"},
  2001  		{"[::1]:80", "::1", "80"},
  2002  		{"[::1]", "::1", ""},
  2003  		{"[::1]:", "::1", ""},
  2004  		{"localhost", "localhost", ""},
  2005  		{"localhost:443", "localhost", "443"},
  2006  		{"some.super.long.domain.example.org:8080", "some.super.long.domain.example.org", "8080"},
  2007  		{"[2001:0db8:85a3:0000:0000:8a2e:0370:7334]:17000", "2001:0db8:85a3:0000:0000:8a2e:0370:7334", "17000"},
  2008  		{"[2001:0db8:85a3:0000:0000:8a2e:0370:7334]", "2001:0db8:85a3:0000:0000:8a2e:0370:7334", ""},
  2009  
  2010  		// Ensure that even when not valid, Host is one of "Hostname",
  2011  		// "Hostname:Port", "[Hostname]" or "[Hostname]:Port".
  2012  		// See https://golang.org/issue/29098.
  2013  		{"[google.com]:80", "google.com", "80"},
  2014  		{"google.com]:80", "google.com]", "80"},
  2015  		{"google.com:80_invalid_port", "google.com:80_invalid_port", ""},
  2016  		{"[::1]extra]:80", "::1]extra", "80"},
  2017  		{"google.com]extra:extra", "google.com]extra:extra", ""},
  2018  	}
  2019  	for _, tt := range tests {
  2020  		u := &URL{Host: tt.in}
  2021  		host, port := u.Hostname(), u.Port()
  2022  		if host != tt.host {
  2023  			t.Errorf("Hostname for Host %q = %q; want %q", tt.in, host, tt.host)
  2024  		}
  2025  		if port != tt.port {
  2026  			t.Errorf("Port for Host %q = %q; want %q", tt.in, port, tt.port)
  2027  		}
  2028  	}
  2029  }
  2030  
  2031  var _ encodingPkg.BinaryMarshaler = (*URL)(nil)
  2032  var _ encodingPkg.BinaryUnmarshaler = (*URL)(nil)
  2033  var _ encodingPkg.BinaryAppender = (*URL)(nil)
  2034  
  2035  func TestJSON(t *testing.T) {
  2036  	u, err := Parse("https://www.google.com/x?y=z")
  2037  	if err != nil {
  2038  		t.Fatal(err)
  2039  	}
  2040  	js, err := json.Marshal(u)
  2041  	if err != nil {
  2042  		t.Fatal(err)
  2043  	}
  2044  
  2045  	// If only we could implement TextMarshaler/TextUnmarshaler,
  2046  	// this would work:
  2047  	//
  2048  	// if string(js) != strconv.Quote(u.String()) {
  2049  	// 	t.Errorf("json encoding: %s\nwant: %s\n", js, strconv.Quote(u.String()))
  2050  	// }
  2051  
  2052  	u1 := new(URL)
  2053  	err = json.Unmarshal(js, u1)
  2054  	if err != nil {
  2055  		t.Fatal(err)
  2056  	}
  2057  	if u1.String() != u.String() {
  2058  		t.Errorf("json decoded to: %s\nwant: %s\n", u1, u)
  2059  	}
  2060  }
  2061  
  2062  func TestGob(t *testing.T) {
  2063  	u, err := Parse("https://www.google.com/x?y=z")
  2064  	if err != nil {
  2065  		t.Fatal(err)
  2066  	}
  2067  	var w bytes.Buffer
  2068  	err = gob.NewEncoder(&w).Encode(u)
  2069  	if err != nil {
  2070  		t.Fatal(err)
  2071  	}
  2072  
  2073  	u1 := new(URL)
  2074  	err = gob.NewDecoder(&w).Decode(u1)
  2075  	if err != nil {
  2076  		t.Fatal(err)
  2077  	}
  2078  	if u1.String() != u.String() {
  2079  		t.Errorf("json decoded to: %s\nwant: %s\n", u1, u)
  2080  	}
  2081  }
  2082  
  2083  func TestNilUser(t *testing.T) {
  2084  	defer func() {
  2085  		if v := recover(); v != nil {
  2086  			t.Fatalf("unexpected panic: %v", v)
  2087  		}
  2088  	}()
  2089  
  2090  	u, err := Parse("http://foo.com/")
  2091  
  2092  	if err != nil {
  2093  		t.Fatalf("parse err: %v", err)
  2094  	}
  2095  
  2096  	if v := u.User.Username(); v != "" {
  2097  		t.Fatalf("expected empty username, got %s", v)
  2098  	}
  2099  
  2100  	if v, ok := u.User.Password(); v != "" || ok {
  2101  		t.Fatalf("expected empty password, got %s (%v)", v, ok)
  2102  	}
  2103  
  2104  	if v := u.User.String(); v != "" {
  2105  		t.Fatalf("expected empty string, got %s", v)
  2106  	}
  2107  }
  2108  
  2109  func TestInvalidUserPassword(t *testing.T) {
  2110  	_, err := Parse("http://user^:passwo^rd@foo.com/")
  2111  	if got, wantsub := fmt.Sprint(err), "net/url: invalid userinfo"; !strings.Contains(got, wantsub) {
  2112  		t.Errorf("error = %q; want substring %q", got, wantsub)
  2113  	}
  2114  }
  2115  
  2116  func TestRejectControlCharacters(t *testing.T) {
  2117  	tests := []string{
  2118  		"http://foo.com/?foo\nbar",
  2119  		"http\r://foo.com/",
  2120  		"http://foo\x7f.com/",
  2121  	}
  2122  	for _, s := range tests {
  2123  		_, err := Parse(s)
  2124  		const wantSub = "net/url: invalid control character in URL"
  2125  		if got := fmt.Sprint(err); !strings.Contains(got, wantSub) {
  2126  			t.Errorf("Parse(%q) error = %q; want substring %q", s, got, wantSub)
  2127  		}
  2128  	}
  2129  
  2130  	// But don't reject non-ASCII CTLs, at least for now:
  2131  	if _, err := Parse("http://foo.com/ctl\x80"); err != nil {
  2132  		t.Errorf("error parsing URL with non-ASCII control byte: %v", err)
  2133  	}
  2134  
  2135  }
  2136  
  2137  var escapeBenchmarks = []struct {
  2138  	unescaped string
  2139  	query     string
  2140  	path      string
  2141  }{
  2142  	{
  2143  		unescaped: "one two",
  2144  		query:     "one+two",
  2145  		path:      "one%20two",
  2146  	},
  2147  	{
  2148  		unescaped: "Фотки собак",
  2149  		query:     "%D0%A4%D0%BE%D1%82%D0%BA%D0%B8+%D1%81%D0%BE%D0%B1%D0%B0%D0%BA",
  2150  		path:      "%D0%A4%D0%BE%D1%82%D0%BA%D0%B8%20%D1%81%D0%BE%D0%B1%D0%B0%D0%BA",
  2151  	},
  2152  
  2153  	{
  2154  		unescaped: "shortrun(break)shortrun",
  2155  		query:     "shortrun%28break%29shortrun",
  2156  		path:      "shortrun%28break%29shortrun",
  2157  	},
  2158  
  2159  	{
  2160  		unescaped: "longerrunofcharacters(break)anotherlongerrunofcharacters",
  2161  		query:     "longerrunofcharacters%28break%29anotherlongerrunofcharacters",
  2162  		path:      "longerrunofcharacters%28break%29anotherlongerrunofcharacters",
  2163  	},
  2164  
  2165  	{
  2166  		unescaped: strings.Repeat("padded/with+various%characters?that=need$some@escaping+paddedsowebreak/256bytes", 4),
  2167  		query:     strings.Repeat("padded%2Fwith%2Bvarious%25characters%3Fthat%3Dneed%24some%40escaping%2Bpaddedsowebreak%2F256bytes", 4),
  2168  		path:      strings.Repeat("padded%2Fwith+various%25characters%3Fthat=need$some@escaping+paddedsowebreak%2F256bytes", 4),
  2169  	},
  2170  }
  2171  
  2172  func BenchmarkQueryEscape(b *testing.B) {
  2173  	for _, tc := range escapeBenchmarks {
  2174  		b.Run("", func(b *testing.B) {
  2175  			b.ReportAllocs()
  2176  			var g string
  2177  			for i := 0; i < b.N; i++ {
  2178  				g = QueryEscape(tc.unescaped)
  2179  			}
  2180  			b.StopTimer()
  2181  			if g != tc.query {
  2182  				b.Errorf("QueryEscape(%q) == %q, want %q", tc.unescaped, g, tc.query)
  2183  			}
  2184  
  2185  		})
  2186  	}
  2187  }
  2188  
  2189  func BenchmarkPathEscape(b *testing.B) {
  2190  	for _, tc := range escapeBenchmarks {
  2191  		b.Run("", func(b *testing.B) {
  2192  			b.ReportAllocs()
  2193  			var g string
  2194  			for i := 0; i < b.N; i++ {
  2195  				g = PathEscape(tc.unescaped)
  2196  			}
  2197  			b.StopTimer()
  2198  			if g != tc.path {
  2199  				b.Errorf("PathEscape(%q) == %q, want %q", tc.unescaped, g, tc.path)
  2200  			}
  2201  
  2202  		})
  2203  	}
  2204  }
  2205  
  2206  func BenchmarkQueryUnescape(b *testing.B) {
  2207  	for _, tc := range escapeBenchmarks {
  2208  		b.Run("", func(b *testing.B) {
  2209  			b.ReportAllocs()
  2210  			var g string
  2211  			for i := 0; i < b.N; i++ {
  2212  				g, _ = QueryUnescape(tc.query)
  2213  			}
  2214  			b.StopTimer()
  2215  			if g != tc.unescaped {
  2216  				b.Errorf("QueryUnescape(%q) == %q, want %q", tc.query, g, tc.unescaped)
  2217  			}
  2218  
  2219  		})
  2220  	}
  2221  }
  2222  
  2223  func BenchmarkPathUnescape(b *testing.B) {
  2224  	for _, tc := range escapeBenchmarks {
  2225  		b.Run("", func(b *testing.B) {
  2226  			b.ReportAllocs()
  2227  			var g string
  2228  			for i := 0; i < b.N; i++ {
  2229  				g, _ = PathUnescape(tc.path)
  2230  			}
  2231  			b.StopTimer()
  2232  			if g != tc.unescaped {
  2233  				b.Errorf("PathUnescape(%q) == %q, want %q", tc.path, g, tc.unescaped)
  2234  			}
  2235  
  2236  		})
  2237  	}
  2238  }
  2239  
  2240  func TestJoinPath(t *testing.T) {
  2241  	tests := []struct {
  2242  		base string
  2243  		elem []string
  2244  		out  string
  2245  	}{
  2246  		{
  2247  			base: "https://go.googlesource.com",
  2248  			elem: []string{"go"},
  2249  			out:  "https://go.googlesource.com/go",
  2250  		},
  2251  		{
  2252  			base: "https://go.googlesource.com/a/b/c",
  2253  			elem: []string{"../../../go"},
  2254  			out:  "https://go.googlesource.com/go",
  2255  		},
  2256  		{
  2257  			base: "https://go.googlesource.com/",
  2258  			elem: []string{"../go"},
  2259  			out:  "https://go.googlesource.com/go",
  2260  		},
  2261  		{
  2262  			base: "https://go.googlesource.com",
  2263  			elem: []string{"../go", "../../go", "../../../go"},
  2264  			out:  "https://go.googlesource.com/go",
  2265  		},
  2266  		{
  2267  			base: "https://go.googlesource.com/../go",
  2268  			elem: nil,
  2269  			out:  "https://go.googlesource.com/go",
  2270  		},
  2271  		{
  2272  			base: "https://go.googlesource.com/",
  2273  			elem: []string{"./go"},
  2274  			out:  "https://go.googlesource.com/go",
  2275  		},
  2276  		{
  2277  			base: "https://go.googlesource.com//",
  2278  			elem: []string{"/go"},
  2279  			out:  "https://go.googlesource.com/go",
  2280  		},
  2281  		{
  2282  			base: "https://go.googlesource.com//",
  2283  			elem: []string{"/go", "a", "b", "c"},
  2284  			out:  "https://go.googlesource.com/go/a/b/c",
  2285  		},
  2286  		{
  2287  			base: "http://[fe80::1%en0]:8080/",
  2288  			elem: []string{"/go"},
  2289  		},
  2290  		{
  2291  			base: "https://go.googlesource.com",
  2292  			elem: []string{"go/"},
  2293  			out:  "https://go.googlesource.com/go/",
  2294  		},
  2295  		{
  2296  			base: "https://go.googlesource.com",
  2297  			elem: []string{"go//"},
  2298  			out:  "https://go.googlesource.com/go/",
  2299  		},
  2300  		{
  2301  			base: "https://go.googlesource.com",
  2302  			elem: nil,
  2303  			out:  "https://go.googlesource.com/",
  2304  		},
  2305  		{
  2306  			base: "https://go.googlesource.com/",
  2307  			elem: nil,
  2308  			out:  "https://go.googlesource.com/",
  2309  		},
  2310  		{
  2311  			base: "https://go.googlesource.com/a%2fb",
  2312  			elem: []string{"c"},
  2313  			out:  "https://go.googlesource.com/a%2fb/c",
  2314  		},
  2315  		{
  2316  			base: "https://go.googlesource.com/a%2fb",
  2317  			elem: []string{"c%2fd"},
  2318  			out:  "https://go.googlesource.com/a%2fb/c%2fd",
  2319  		},
  2320  		{
  2321  			base: "https://go.googlesource.com/a/b",
  2322  			elem: []string{"/go"},
  2323  			out:  "https://go.googlesource.com/a/b/go",
  2324  		},
  2325  		{
  2326  			base: "https://go.googlesource.com/",
  2327  			elem: []string{"100%"},
  2328  		},
  2329  		{
  2330  			base: "/",
  2331  			elem: nil,
  2332  			out:  "/",
  2333  		},
  2334  		{
  2335  			base: "a",
  2336  			elem: nil,
  2337  			out:  "a",
  2338  		},
  2339  		{
  2340  			base: "a",
  2341  			elem: []string{"b"},
  2342  			out:  "a/b",
  2343  		},
  2344  		{
  2345  			base: "a",
  2346  			elem: []string{"../b"},
  2347  			out:  "b",
  2348  		},
  2349  		{
  2350  			base: "a",
  2351  			elem: []string{"../../b"},
  2352  			out:  "b",
  2353  		},
  2354  		{
  2355  			base: "",
  2356  			elem: []string{"a"},
  2357  			out:  "a",
  2358  		},
  2359  		{
  2360  			base: "",
  2361  			elem: []string{"../a"},
  2362  			out:  "a",
  2363  		},
  2364  	}
  2365  	for _, tt := range tests {
  2366  		wantErr := "nil"
  2367  		if tt.out == "" {
  2368  			wantErr = "non-nil error"
  2369  		}
  2370  		out, err := JoinPath(tt.base, tt.elem...)
  2371  		if out != tt.out || (err == nil) != (tt.out != "") {
  2372  			t.Errorf("JoinPath(%q, %q) = %q, %v, want %q, %v", tt.base, tt.elem, out, err, tt.out, wantErr)
  2373  		}
  2374  
  2375  		u, err := Parse(tt.base)
  2376  		if err != nil {
  2377  			if tt.out != "" {
  2378  				t.Errorf("Parse(%q) = %v", tt.base, err)
  2379  			}
  2380  			continue
  2381  		}
  2382  		if tt.out == "" {
  2383  			// URL.JoinPath doesn't return an error, so leave it unchanged
  2384  			tt.out = tt.base
  2385  		}
  2386  		out = u.JoinPath(tt.elem...).String()
  2387  		if out != tt.out {
  2388  			t.Errorf("Parse(%q).JoinPath(%q) = %q, want %q", tt.base, tt.elem, out, tt.out)
  2389  		}
  2390  	}
  2391  }
  2392  
  2393  func TestParseStrictIpv6(t *testing.T) {
  2394  	t.Setenv("GODEBUG", "urlstrictcolons=0")
  2395  
  2396  	tests := []struct {
  2397  		url string
  2398  	}{
  2399  		// Malformed URLs that used to parse.
  2400  		{"https://1:2:3:4:5:6:7:8"},
  2401  		{"https://1:2:3:4:5:6:7:8:80"},
  2402  		{"https://example.com:80:"},
  2403  	}
  2404  	for i, tc := range tests {
  2405  		t.Run(strconv.Itoa(i), func(t *testing.T) {
  2406  			_, err := Parse(tc.url)
  2407  			if err != nil {
  2408  				t.Errorf("Parse(%q) error = %v, want nil", tc.url, err)
  2409  			}
  2410  		})
  2411  	}
  2412  
  2413  }
  2414  
  2415  func TestURLClone(t *testing.T) {
  2416  	tests := []struct {
  2417  		name string
  2418  		in   *URL
  2419  	}{
  2420  		{"nil", nil},
  2421  		{"zero value", &URL{}},
  2422  		{
  2423  			"Populated but nil .User",
  2424  			&URL{
  2425  				User:     nil,
  2426  				Host:     "foo",
  2427  				Path:     "/path",
  2428  				RawQuery: "a=b",
  2429  			},
  2430  		},
  2431  		{
  2432  			"non-nil .User",
  2433  			&URL{
  2434  				User:     User("user"),
  2435  				Host:     "foo",
  2436  				Path:     "/path",
  2437  				RawQuery: "a=b",
  2438  			},
  2439  		},
  2440  		{
  2441  			"non-nil .User: user and password set",
  2442  			&URL{
  2443  				User:     UserPassword("user", "password"),
  2444  				Host:     "foo",
  2445  				Path:     "/path",
  2446  				RawQuery: "a=b",
  2447  			},
  2448  		},
  2449  	}
  2450  
  2451  	for _, tt := range tests {
  2452  		t.Run(tt.name, func(t *testing.T) {
  2453  			// 1. The cloned URL must always deep equal the input, but never the same pointer.
  2454  			cloned := tt.in.Clone()
  2455  			if !reflect.DeepEqual(tt.in, cloned) {
  2456  				t.Fatalf("Differing values\n%s",
  2457  					diff.Diff("original", []byte(tt.in.String()), "cloned", []byte(cloned.String())))
  2458  			}
  2459  			if tt.in == nil {
  2460  				return
  2461  			}
  2462  
  2463  			// Ensure that their pointer values are not the same.
  2464  			if tt.in == cloned {
  2465  				t.Fatalf("URL: same pointer returned: %p", cloned)
  2466  			}
  2467  
  2468  			// 2. Test out malleability of URL fields.
  2469  			cloned.Scheme = "https"
  2470  			if cloned.Scheme == tt.in.Scheme {
  2471  				t.Error("Inconsistent state: cloned.scheme changed and reflected in the input's scheme")
  2472  			}
  2473  			if reflect.DeepEqual(tt.in, cloned) {
  2474  				t.Fatal("Inconsistent state: cloned and input are somehow the same")
  2475  			}
  2476  
  2477  			// 3. Ensure that the .User object deep equals but not the same pointer.
  2478  			if !reflect.DeepEqual(tt.in.User, cloned.User) {
  2479  				t.Fatalf("Differing .User\n%s",
  2480  					diff.Diff("original", []byte(tt.in.String()), "cloned", []byte(cloned.String())))
  2481  			}
  2482  			bothNil := tt.in.User == nil && cloned.User == nil
  2483  			if !bothNil && tt.in.User == cloned.User {
  2484  				t.Fatalf(".User: same pointer returned: %p", cloned.User)
  2485  			}
  2486  		})
  2487  	}
  2488  }
  2489  
  2490  func TestValuesClone(t *testing.T) {
  2491  	tests := []struct {
  2492  		name string
  2493  		in   Values
  2494  	}{
  2495  		{"nil", nil},
  2496  		{"empty", Values{}},
  2497  		{"1 key, nil values", Values{"1": nil}},
  2498  		{"1 key, no values", Values{"1": {}}},
  2499  		{"1 key, some values", Values{"1": {"a", "b"}}},
  2500  		{"multiple keys, diverse values", Values{"1": {"a", "b"}, "X": nil, "B": {"abcdefghi"}}},
  2501  	}
  2502  
  2503  	for _, tt := range tests {
  2504  		t.Run(tt.name, func(t *testing.T) {
  2505  			// The cloned map must always deep equal the input.
  2506  			cloned1 := tt.in.Clone()
  2507  			if !reflect.DeepEqual(tt.in, cloned1) {
  2508  				t.Fatal("reflect.DeepEqual failed")
  2509  			}
  2510  
  2511  			if cloned1 == nil && tt.in == nil {
  2512  				return
  2513  			}
  2514  			if len(cloned1) == 0 && len(tt.in) == 0 && (cloned1 == nil || tt.in == nil) {
  2515  				t.Fatalf("Inconsistency: both have len=0, yet not both nil\nCloned: %#v\nOriginal: %#v\n", cloned1, tt.in)
  2516  			}
  2517  			// Test out malleability of values.
  2518  			cloned1["XXXXXXXXXXX"] = []string{"a", "b"}
  2519  			if reflect.DeepEqual(tt.in, cloned1) {
  2520  				t.Fatal("Inconsistent state: cloned and input are somehow the same")
  2521  			}
  2522  
  2523  			// Ensure that we can correctly invoke some methods like .Add
  2524  			cloned2 := tt.in.Clone()
  2525  			if !reflect.DeepEqual(tt.in, cloned2) {
  2526  				t.Fatal("reflect.DeepEqual failed")
  2527  			}
  2528  			cloned2.Add("a", "A")
  2529  			if !cloned2.Has("a") {
  2530  				t.Error("Cloned doesn't have the desired key: a")
  2531  			}
  2532  			if !cloned2.Has("a") {
  2533  				t.Error("Cloned doesn't have the desired key: a")
  2534  			}
  2535  			// Assert that any changes to the clone did not change the original.
  2536  			if reflect.DeepEqual(tt.in, cloned2) {
  2537  				t.Fatal("reflect.DeepEqual unexpectedly passed after modify cloned")
  2538  			}
  2539  			cloned2.Del("a")
  2540  			// Assert that reverting the clone's changes bring it back to original state.
  2541  			if !reflect.DeepEqual(tt.in, cloned2) {
  2542  				t.Fatal("reflect.DeepEqual failed")
  2543  			}
  2544  
  2545  			cloned3 := tt.in.Clone()
  2546  			clonedKeys := slices.Collect(maps.Keys(cloned3))
  2547  			if len(clonedKeys) == 0 {
  2548  				return
  2549  			}
  2550  			key0 := clonedKeys[0]
  2551  			// Test modifying the actual slice.
  2552  			if len(cloned3[key0]) == 0 {
  2553  				cloned3[key0] = append(cloned3[key0], "golang")
  2554  			} else {
  2555  				cloned3[key0][0] = "directly modified"
  2556  				if got, want := cloned3.Get(key0), "directly modified"; got != want {
  2557  					t.Errorf("Get failed:\n\tGot:  %q\n\tWant: %q", got, want)
  2558  				}
  2559  			}
  2560  			if reflect.DeepEqual(tt.in, cloned3) {
  2561  				t.Fatal("reflect.DeepEqual unexpectedly passed after modify cloned")
  2562  			}
  2563  
  2564  			// Try out also with .Set.
  2565  			cloned4 := tt.in.Clone()
  2566  			if !reflect.DeepEqual(tt.in, cloned4) {
  2567  				t.Fatal("reflect.DeepEqual failed")
  2568  			}
  2569  			cloned4.Set(key0, "good night")
  2570  			if reflect.DeepEqual(tt.in, cloned4) {
  2571  				t.Fatal("reflect.DeepEqual unexpectedly passed after modify cloned")
  2572  			}
  2573  			if got, want := cloned4.Get(key0), "good night"; got != want {
  2574  				t.Errorf("Get failed:\n\tGot:  %q\n\tWant: %q", got, want)
  2575  			}
  2576  		})
  2577  	}
  2578  }
  2579  

View as plain text