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