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
5package url
6
7import (
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
20type 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
26var 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://[email protected]/",
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%[email protected]/",
83		&URL{
84			Scheme: "ftp",
85			User:   User("john doe"),
86			Host:   "www.google.com",
87			Path:   "/",
88		},
89		"ftp://john%[email protected]/",
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:/[email protected]",
169		&URL{
170			Scheme:   "mailto",
171			Path:     "/[email protected]",
172			OmitHost: true,
173		},
174		"",
175	},
176	// non-authority
177	{
178		"mailto:[email protected]",
179		&URL{
180			Scheme: "mailto",
181			Opaque: "[email protected]",
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:[email protected]",
227		&URL{
228			Scheme: "http",
229			User:   UserPassword("user", "password"),
230			Host:   "google.com",
231		},
232		"http://user:[email protected]",
233	},
234	// unescaped @ in username should not confuse host
235	{
236		"http://j@ne:[email protected]",
237		&URL{
238			Scheme: "http",
239			User:   UserPassword("j@ne", "password"),
240			Host:   "google.com",
241		},
242		"http://j%40ne:[email protected]",
243	},
244	// unescaped @ in password should not confuse host
245	{
246		"http://jane:p@[email protected]",
247		&URL{
248			Scheme: "http",
249			User:   UserPassword("jane", "p@ssword"),
250			Host:   "google.com",
251		},
252		"http://jane:p%[email protected]",
253	},
254	{
255		"http://j@ne:[email protected]/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:[email protected]/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:[email protected]",
322		&URL{
323			Scheme: "mailto",
324			Opaque: "[email protected]",
325		},
326		"mailto:[email protected]",
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%[email protected]",
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
621func 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
633func 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
657func 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
670const pathThatLooksSchemeRelative = "//[email protected]/just/a/path"
671
672var 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
712func 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
731var 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
769func 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
793func 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:[email protected]/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://[email protected]/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://:[email protected]/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
860type EscapeTest struct {
861	in  string
862	out string
863	err error
864}
865
866var 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
934func 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
966var 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
994func 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
1009var 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
1047func 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
1068type EncodeQueryTest struct {
1069	m        Values
1070	expected string
1071}
1072
1073var encodeQueryTests = []EncodeQueryTest{
1074	{nil, ""},
1075	{Values{}, ""},
1076	{Values{"q": {"puppies"}, "oe": {"utf8"}}, "oe=utf8&q=puppies"},
1077	{Values{"q": {"dogs", "&", "7"}}, "q=dogs&q=%26&q=7"},
1078	{Values{
1079		"a": {"a1", "a2", "a3"},
1080		"b": {"b1", "b2", "b3"},
1081		"c": {"c1", "c2", "c3"},
1082	}, "a=a1&a=a2&a=a3&b=b1&b=b2&b=b3&c=c1&c=c2&c=c3"},
1083}
1084
1085func TestEncodeQuery(t *testing.T) {
1086	for _, tt := range encodeQueryTests {
1087		if q := tt.m.Encode(); q != tt.expected {
1088			t.Errorf(`EncodeQuery(%+v) = %q, want %q`, tt.m, q, tt.expected)
1089		}
1090	}
1091}
1092
1093var resolvePathTests = []struct {
1094	base, ref, expected string
1095}{
1096	{"a/b", ".", "/a/"},
1097	{"a/b", "c", "/a/c"},
1098	{"a/b", "..", "/"},
1099	{"a/", "..", "/"},
1100	{"a/", "../..", "/"},
1101	{"a/b/c", "..", "/a/"},
1102	{"a/b/c", "../d", "/a/d"},
1103	{"a/b/c", ".././d", "/a/d"},
1104	{"a/b", "./..", "/"},
1105	{"a/./b", ".", "/a/"},
1106	{"a/../", ".", "/"},
1107	{"a/.././b", "c", "/c"},
1108}
1109
1110func TestResolvePath(t *testing.T) {
1111	for _, test := range resolvePathTests {
1112		got := resolvePath(test.base, test.ref)
1113		if got != test.expected {
1114			t.Errorf("For %q + %q got %q; expected %q", test.base, test.ref, got, test.expected)
1115		}
1116	}
1117}
1118
1119func BenchmarkResolvePath(b *testing.B) {
1120	b.ReportAllocs()
1121	for i := 0; i < b.N; i++ {
1122		resolvePath("a/b/c", ".././d")
1123	}
1124}
1125
1126var resolveReferenceTests = []struct {
1127	base, rel, expected string
1128}{
1129	// Absolute URL references
1130	{"http://foo.com?a=b", "https://bar.com/", "https://bar.com/"},
1131	{"http://foo.com/", "https://bar.com/?a=b", "https://bar.com/?a=b"},
1132	{"http://foo.com/", "https://bar.com/?", "https://bar.com/?"},
1133	{"http://foo.com/bar", "mailto:[email protected]", "mailto:[email protected]"},
1134
1135	// Path-absolute references
1136	{"http://foo.com/bar", "/baz", "http://foo.com/baz"},
1137	{"http://foo.com/bar?a=b#f", "/baz", "http://foo.com/baz"},
1138	{"http://foo.com/bar?a=b", "/baz?", "http://foo.com/baz?"},
1139	{"http://foo.com/bar?a=b", "/baz?c=d", "http://foo.com/baz?c=d"},
1140
1141	// Multiple slashes
1142	{"http://foo.com/bar", "http://foo.com//baz", "http://foo.com//baz"},
1143	{"http://foo.com/bar", "http://foo.com///baz/quux", "http://foo.com///baz/quux"},
1144
1145	// Scheme-relative
1146	{"https://foo.com/bar?a=b", "//bar.com/quux", "https://bar.com/quux"},
1147
1148	// Path-relative references:
1149
1150	// ... current directory
1151	{"http://foo.com", ".", "http://foo.com/"},
1152	{"http://foo.com/bar", ".", "http://foo.com/"},
1153	{"http://foo.com/bar/", ".", "http://foo.com/bar/"},
1154
1155	// ... going down
1156	{"http://foo.com", "bar", "http://foo.com/bar"},
1157	{"http://foo.com/", "bar", "http://foo.com/bar"},
1158	{"http://foo.com/bar/baz", "quux", "http://foo.com/bar/quux"},
1159
1160	// ... going up
1161	{"http://foo.com/bar/baz", "../quux", "http://foo.com/quux"},
1162	{"http://foo.com/bar/baz", "../../../../../quux", "http://foo.com/quux"},
1163	{"http://foo.com/bar", "..", "http://foo.com/"},
1164	{"http://foo.com/bar/baz", "./..", "http://foo.com/"},
1165	// ".." in the middle (issue 3560)
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/./../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/./.././../tail", "http://foo.com/bar/quux/tail"},
1172	{"http://foo.com/bar/baz", "quux/./dotdot/dotdot/dotdot/./../../.././././tail", "http://foo.com/bar/quux/tail"},
1173	{"http://foo.com/bar/baz", "quux/./dotdot/../dotdot/../dot/./tail/..", "http://foo.com/bar/quux/dot/"},
1174
1175	// Remove any dot-segments prior to forming the target URI.
1176	// https://datatracker.ietf.org/doc/html/rfc3986#section-5.2.4
1177	{"http://foo.com/dot/./dotdot/../foo/bar", "../baz", "http://foo.com/dot/baz"},
1178
1179	// Triple dot isn't special
1180	{"http://foo.com/bar", "...", "http://foo.com/..."},
1181
1182	// Fragment
1183	{"http://foo.com/bar", ".#frag", "http://foo.com/#frag"},
1184	{"http://example.org/", "#!$&%27()*+,;=", "http://example.org/#!$&%27()*+,;="},
1185
1186	// Paths with escaping (issue 16947).
1187	{"http://foo.com/foo%2fbar/", "../baz", "http://foo.com/baz"},
1188	{"http://foo.com/1/2%2f/3%2f4/5", "../../a/b/c", "http://foo.com/1/a/b/c"},
1189	{"http://foo.com/1/2/3", "./a%2f../../b/..%2fc", "http://foo.com/1/2/b/..%2fc"},
1190	{"http://foo.com/1/2%2f/3%2f4/5", "./a%2f../b/../c", "http://foo.com/1/2%2f/3%2f4/a%2f../c"},
1191	{"http://foo.com/foo%20bar/", "../baz", "http://foo.com/baz"},
1192	{"http://foo.com/foo", "../bar%2fbaz", "http://foo.com/bar%2fbaz"},
1193	{"http://foo.com/foo%2dbar/", "./baz-quux", "http://foo.com/foo%2dbar/baz-quux"},
1194
1195	// RFC 3986: Normal Examples
1196	// https://datatracker.ietf.org/doc/html/rfc3986#section-5.4.1
1197	{"http://a/b/c/d;p?q", "g:h", "g:h"},
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/b/c/g/"},
1201	{"http://a/b/c/d;p?q", "/g", "http://a/g"},
1202	{"http://a/b/c/d;p?q", "//g", "http://g"},
1203	{"http://a/b/c/d;p?q", "?y", "http://a/b/c/d;p?y"},
1204	{"http://a/b/c/d;p?q", "g?y", "http://a/b/c/g?y"},
1205	{"http://a/b/c/d;p?q", "#s", "http://a/b/c/d;p?q#s"},
1206	{"http://a/b/c/d;p?q", "g#s", "http://a/b/c/g#s"},
1207	{"http://a/b/c/d;p?q", "g?y#s", "http://a/b/c/g?y#s"},
1208	{"http://a/b/c/d;p?q", ";x", "http://a/b/c/;x"},
1209	{"http://a/b/c/d;p?q", "g;x", "http://a/b/c/g;x"},
1210	{"http://a/b/c/d;p?q", "g;x?y#s", "http://a/b/c/g;x?y#s"},
1211	{"http://a/b/c/d;p?q", "", "http://a/b/c/d;p?q"},
1212	{"http://a/b/c/d;p?q", ".", "http://a/b/c/"},
1213	{"http://a/b/c/d;p?q", "./", "http://a/b/c/"},
1214	{"http://a/b/c/d;p?q", "..", "http://a/b/"},
1215	{"http://a/b/c/d;p?q", "../", "http://a/b/"},
1216	{"http://a/b/c/d;p?q", "../g", "http://a/b/g"},
1217	{"http://a/b/c/d;p?q", "../..", "http://a/"},
1218	{"http://a/b/c/d;p?q", "../../", "http://a/"},
1219	{"http://a/b/c/d;p?q", "../../g", "http://a/g"},
1220
1221	// RFC 3986: Abnormal Examples
1222	// https://datatracker.ietf.org/doc/html/rfc3986#section-5.4.2
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/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/c/..g"},
1231	{"http://a/b/c/d;p?q", "./../g", "http://a/b/g"},
1232	{"http://a/b/c/d;p?q", "./g/.", "http://a/b/c/g/"},
1233	{"http://a/b/c/d;p?q", "g/./h", "http://a/b/c/g/h"},
1234	{"http://a/b/c/d;p?q", "g/../h", "http://a/b/c/h"},
1235	{"http://a/b/c/d;p?q", "g;x=1/./y", "http://a/b/c/g;x=1/y"},
1236	{"http://a/b/c/d;p?q", "g;x=1/../y", "http://a/b/c/y"},
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?y/../x", "http://a/b/c/g?y/../x"},
1239	{"http://a/b/c/d;p?q", "g#s/./x", "http://a/b/c/g#s/./x"},
1240	{"http://a/b/c/d;p?q", "g#s/../x", "http://a/b/c/g#s/../x"},
1241
1242	// Extras.
1243	{"https://a/b/c/d;p?q", "//g?q", "https://g?q"},
1244	{"https://a/b/c/d;p?q", "//g#s", "https://g#s"},
1245	{"https://a/b/c/d;p?q", "//g/d/e/f?y#s", "https://g/d/e/f?y#s"},
1246	{"https://a/b/c/d;p#s", "?y", "https://a/b/c/d;p?y"},
1247	{"https://a/b/c/d;p?q#s", "?y", "https://a/b/c/d;p?y"},
1248
1249	// Empty path and query but with ForceQuery (issue 46033).
1250	{"https://a/b/c/d;p?q#s", "?", "https://a/b/c/d;p?"},
1251
1252	// Opaque URLs (issue 66084).
1253	{"https://foo.com/bar?a=b", "http:opaque", "http:opaque"},
1254	{"http:opaque?x=y#zzz", "https:/foo?a=b#frag", "https:/foo?a=b#frag"},
1255	{"http:opaque?x=y#zzz", "https:foo:bar", "https:foo:bar"},
1256	{"http:opaque?x=y#zzz", "https:bar/baz?a=b#frag", "https:bar/baz?a=b#frag"},
1257	{"http:opaque?x=y#zzz", "https://user@host:1234?a=b#frag", "https://user@host:1234?a=b#frag"},
1258	{"http:opaque?x=y#zzz", "?a=b#frag", "http:opaque?a=b#frag"},
1259}
1260
1261func TestResolveReference(t *testing.T) {
1262	mustParse := func(url string) *URL {
1263		u, err := Parse(url)
1264		if err != nil {
1265			t.Fatalf("Parse(%q) got err %v", url, err)
1266		}
1267		return u
1268	}
1269	opaque := &URL{Scheme: "scheme", Opaque: "opaque"}
1270	for _, test := range resolveReferenceTests {
1271		base := mustParse(test.base)
1272		rel := mustParse(test.rel)
1273		url := base.ResolveReference(rel)
1274		if got := url.String(); got != test.expected {
1275			t.Errorf("URL(%q).ResolveReference(%q)\ngot  %q\nwant %q", test.base, test.rel, got, test.expected)
1276		}
1277		// Ensure that new instances are returned.
1278		if base == url {
1279			t.Errorf("Expected URL.ResolveReference to return new URL instance.")
1280		}
1281		// Test the convenience wrapper too.
1282		url, err := base.Parse(test.rel)
1283		if err != nil {
1284			t.Errorf("URL(%q).Parse(%q) failed: %v", test.base, test.rel, err)
1285		} else if got := url.String(); got != test.expected {
1286			t.Errorf("URL(%q).Parse(%q)\ngot  %q\nwant %q", test.base, test.rel, got, test.expected)
1287		} else if base == url {
1288			// Ensure that new instances are returned for the wrapper too.
1289			t.Errorf("Expected URL.Parse to return new URL instance.")
1290		}
1291		// Ensure Opaque resets the URL.
1292		url = base.ResolveReference(opaque)
1293		if *url != *opaque {
1294			t.Errorf("ResolveReference failed to resolve opaque URL:\ngot  %#v\nwant %#v", url, opaque)
1295		}
1296		// Test the convenience wrapper with an opaque URL too.
1297		url, err = base.Parse("scheme:opaque")
1298		if err != nil {
1299			t.Errorf(`URL(%q).Parse("scheme:opaque") failed: %v`, test.base, err)
1300		} else if *url != *opaque {
1301			t.Errorf("Parse failed to resolve opaque URL:\ngot  %#v\nwant %#v", opaque, url)
1302		} else if base == url {
1303			// Ensure that new instances are returned, again.
1304			t.Errorf("Expected URL.Parse to return new URL instance.")
1305		}
1306	}
1307}
1308
1309func TestQueryValues(t *testing.T) {
1310	u, _ := Parse("http://x.com?foo=bar&bar=1&bar=2&baz")
1311	v := u.Query()
1312	if len(v) != 3 {
1313		t.Errorf("got %d keys in Query values, want 3", len(v))
1314	}
1315	if g, e := v.Get("foo"), "bar"; g != e {
1316		t.Errorf("Get(foo) = %q, want %q", g, e)
1317	}
1318	// Case sensitive:
1319	if g, e := v.Get("Foo"), ""; g != e {
1320		t.Errorf("Get(Foo) = %q, want %q", g, e)
1321	}
1322	if g, e := v.Get("bar"), "1"; g != e {
1323		t.Errorf("Get(bar) = %q, want %q", g, e)
1324	}
1325	if g, e := v.Get("baz"), ""; g != e {
1326		t.Errorf("Get(baz) = %q, want %q", g, e)
1327	}
1328	if h, e := v.Has("foo"), true; h != e {
1329		t.Errorf("Has(foo) = %t, want %t", h, e)
1330	}
1331	if h, e := v.Has("bar"), true; h != e {
1332		t.Errorf("Has(bar) = %t, want %t", h, e)
1333	}
1334	if h, e := v.Has("baz"), true; h != e {
1335		t.Errorf("Has(baz) = %t, want %t", h, e)
1336	}
1337	if h, e := v.Has("noexist"), false; h != e {
1338		t.Errorf("Has(noexist) = %t, want %t", h, e)
1339	}
1340	v.Del("bar")
1341	if g, e := v.Get("bar"), ""; g != e {
1342		t.Errorf("second Get(bar) = %q, want %q", g, e)
1343	}
1344}
1345
1346type parseTest struct {
1347	query string
1348	out   Values
1349	ok    bool
1350}
1351
1352var parseTests = []parseTest{
1353	{
1354		query: "a=1",
1355		out:   Values{"a": []string{"1"}},
1356		ok:    true,
1357	},
1358	{
1359		query: "a=1&b=2",
1360		out:   Values{"a": []string{"1"}, "b": []string{"2"}},
1361		ok:    true,
1362	},
1363	{
1364		query: "a=1&a=2&a=banana",
1365		out:   Values{"a": []string{"1", "2", "banana"}},
1366		ok:    true,
1367	},
1368	{
1369		query: "ascii=%3Ckey%3A+0x90%3E",
1370		out:   Values{"ascii": []string{"<key: 0x90>"}},
1371		ok:    true,
1372	}, {
1373		query: "a=1;b=2",
1374		out:   Values{},
1375		ok:    false,
1376	}, {
1377		query: "a;b=1",
1378		out:   Values{},
1379		ok:    false,
1380	}, {
1381		query: "a=%3B", // hex encoding for semicolon
1382		out:   Values{"a": []string{";"}},
1383		ok:    true,
1384	},
1385	{
1386		query: "a%3Bb=1",
1387		out:   Values{"a;b": []string{"1"}},
1388		ok:    true,
1389	},
1390	{
1391		query: "a=1&a=2;a=banana",
1392		out:   Values{"a": []string{"1"}},
1393		ok:    false,
1394	},
1395	{
1396		query: "a;b&c=1",
1397		out:   Values{"c": []string{"1"}},
1398		ok:    false,
1399	},
1400	{
1401		query: "a=1&b=2;a=3&c=4",
1402		out:   Values{"a": []string{"1"}, "c": []string{"4"}},
1403		ok:    false,
1404	},
1405	{
1406		query: "a=1&b=2;c=3",
1407		out:   Values{"a": []string{"1"}},
1408		ok:    false,
1409	},
1410	{
1411		query: ";",
1412		out:   Values{},
1413		ok:    false,
1414	},
1415	{
1416		query: "a=1;",
1417		out:   Values{},
1418		ok:    false,
1419	},
1420	{
1421		query: "a=1&;",
1422		out:   Values{"a": []string{"1"}},
1423		ok:    false,
1424	},
1425	{
1426		query: ";a=1&b=2",
1427		out:   Values{"b": []string{"2"}},
1428		ok:    false,
1429	},
1430	{
1431		query: "a=1&b=2;",
1432		out:   Values{"a": []string{"1"}},
1433		ok:    false,
1434	},
1435}
1436
1437func TestParseQuery(t *testing.T) {
1438	for _, test := range parseTests {
1439		t.Run(test.query, func(t *testing.T) {
1440			form, err := ParseQuery(test.query)
1441			if test.ok != (err == nil) {
1442				want := "<error>"
1443				if test.ok {
1444					want = "<nil>"
1445				}
1446				t.Errorf("Unexpected error: %v, want %v", err, want)
1447			}
1448			if len(form) != len(test.out) {
1449				t.Errorf("len(form) = %d, want %d", len(form), len(test.out))
1450			}
1451			for k, evs := range test.out {
1452				vs, ok := form[k]
1453				if !ok {
1454					t.Errorf("Missing key %q", k)
1455					continue
1456				}
1457				if len(vs) != len(evs) {
1458					t.Errorf("len(form[%q]) = %d, want %d", k, len(vs), len(evs))
1459					continue
1460				}
1461				for j, ev := range evs {
1462					if v := vs[j]; v != ev {
1463						t.Errorf("form[%q][%d] = %q, want %q", k, j, v, ev)
1464					}
1465				}
1466			}
1467		})
1468	}
1469}
1470
1471type RequestURITest struct {
1472	url *URL
1473	out string
1474}
1475
1476var requritests = []RequestURITest{
1477	{
1478		&URL{
1479			Scheme: "http",
1480			Host:   "example.com",
1481			Path:   "",
1482		},
1483		"/",
1484	},
1485	{
1486		&URL{
1487			Scheme: "http",
1488			Host:   "example.com",
1489			Path:   "/a b",
1490		},
1491		"/a%20b",
1492	},
1493	// golang.org/issue/4860 variant 1
1494	{
1495		&URL{
1496			Scheme: "http",
1497			Host:   "example.com",
1498			Opaque: "/%2F/%2F/",
1499		},
1500		"/%2F/%2F/",
1501	},
1502	// golang.org/issue/4860 variant 2
1503	{
1504		&URL{
1505			Scheme: "http",
1506			Host:   "example.com",
1507			Opaque: "//other.example.com/%2F/%2F/",
1508		},
1509		"http://other.example.com/%2F/%2F/",
1510	},
1511	// better fix for issue 4860
1512	{
1513		&URL{
1514			Scheme:  "http",
1515			Host:    "example.com",
1516			Path:    "/////",
1517			RawPath: "/%2F/%2F/",
1518		},
1519		"/%2F/%2F/",
1520	},
1521	{
1522		&URL{
1523			Scheme:  "http",
1524			Host:    "example.com",
1525			Path:    "/////",
1526			RawPath: "/WRONG/", // ignored because doesn't match Path
1527		},
1528		"/////",
1529	},
1530	{
1531		&URL{
1532			Scheme:   "http",
1533			Host:     "example.com",
1534			Path:     "/a b",
1535			RawQuery: "q=go+language",
1536		},
1537		"/a%20b?q=go+language",
1538	},
1539	{
1540		&URL{
1541			Scheme:   "http",
1542			Host:     "example.com",
1543			Path:     "/a b",
1544			RawPath:  "/a b", // ignored because invalid
1545			RawQuery: "q=go+language",
1546		},
1547		"/a%20b?q=go+language",
1548	},
1549	{
1550		&URL{
1551			Scheme:   "http",
1552			Host:     "example.com",
1553			Path:     "/a?b",
1554			RawPath:  "/a?b", // ignored because invalid
1555			RawQuery: "q=go+language",
1556		},
1557		"/a%3Fb?q=go+language",
1558	},
1559	{
1560		&URL{
1561			Scheme: "myschema",
1562			Opaque: "opaque",
1563		},
1564		"opaque",
1565	},
1566	{
1567		&URL{
1568			Scheme:   "myschema",
1569			Opaque:   "opaque",
1570			RawQuery: "q=go+language",
1571		},
1572		"opaque?q=go+language",
1573	},
1574	{
1575		&URL{
1576			Scheme: "http",
1577			Host:   "example.com",
1578			Path:   "//foo",
1579		},
1580		"//foo",
1581	},
1582	{
1583		&URL{
1584			Scheme:     "http",
1585			Host:       "example.com",
1586			Path:       "/foo",
1587			ForceQuery: true,
1588		},
1589		"/foo?",
1590	},
1591}
1592
1593func TestRequestURI(t *testing.T) {
1594	for _, tt := range requritests {
1595		s := tt.url.RequestURI()
1596		if s != tt.out {
1597			t.Errorf("%#v.RequestURI() == %q (expected %q)", tt.url, s, tt.out)
1598		}
1599	}
1600}
1601
1602func TestParseFailure(t *testing.T) {
1603	// Test that the first parse error is returned.
1604	const url = "%gh&%ij"
1605	_, err := ParseQuery(url)
1606	errStr := fmt.Sprint(err)
1607	if !strings.Contains(errStr, "%gh") {
1608		t.Errorf(`ParseQuery(%q) returned error %q, want something containing %q"`, url, errStr, "%gh")
1609	}
1610}
1611
1612func TestParseErrors(t *testing.T) {
1613	tests := []struct {
1614		in      string
1615		wantErr bool
1616	}{
1617		{"http://[::1]", false},
1618		{"http://[::1]:80", false},
1619		{"http://[::1]:namedport", true}, // rfc3986 3.2.3
1620		{"http://x:namedport", true},     // rfc3986 3.2.3
1621		{"http://[::1]/", false},
1622		{"http://[::1]a", true},
1623		{"http://[::1]%23", true},
1624		{"http://[::1%25en0]", false},    // valid zone id
1625		{"http://[::1]:", false},         // colon, but no port OK
1626		{"http://x:", false},             // colon, but no port OK
1627		{"http://[::1]:%38%30", true},    // not allowed: % encoding only for non-ASCII
1628		{"http://[::1%25%41]", false},    // RFC 6874 allows over-escaping in zone
1629		{"http://[%10::1]", true},        // no %xx escapes in IP address
1630		{"http://[::1]/%48", false},      // %xx in path is fine
1631		{"http://%41:8080/", true},       // not allowed: % encoding only for non-ASCII
1632		{"mysql://x@y(z:123)/foo", true}, // not well-formed per RFC 3986, golang.org/issue/33646
1633		{"mysql://x@y(1.2.3.4:123)/foo", true},
1634
1635		{" http://foo.com", true},  // invalid character in schema
1636		{"ht tp://foo.com", true},  // invalid character in schema
1637		{"ahttp://foo.com", false}, // valid schema characters
1638		{"1http://foo.com", true},  // invalid character in schema
1639
1640		{"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
1641		{"http://a b.com/", true},    // no space in host name please
1642		{"cache_object://foo", true}, // scheme cannot have _, relative path cannot have : in first segment
1643		{"cache_object:foo", true},
1644		{"cache_object:foo/bar", true},
1645		{"cache_object/:foo/bar", false},
1646	}
1647	for _, tt := range tests {
1648		u, err := Parse(tt.in)
1649		if tt.wantErr {
1650			if err == nil {
1651				t.Errorf("Parse(%q) = %#v; want an error", tt.in, u)
1652			}
1653			continue
1654		}
1655		if err != nil {
1656			t.Errorf("Parse(%q) = %v; want no error", tt.in, err)
1657		}
1658	}
1659}
1660
1661// Issue 11202
1662func TestStarRequest(t *testing.T) {
1663	u, err := Parse("*")
1664	if err != nil {
1665		t.Fatal(err)
1666	}
1667	if got, want := u.RequestURI(), "*"; got != want {
1668		t.Errorf("RequestURI = %q; want %q", got, want)
1669	}
1670}
1671
1672type shouldEscapeTest struct {
1673	in     byte
1674	mode   encoding
1675	escape bool
1676}
1677
1678var shouldEscapeTests = []shouldEscapeTest{
1679	// Unreserved characters (§2.3)
1680	{'a', encodePath, false},
1681	{'a', encodeUserPassword, false},
1682	{'a', encodeQueryComponent, false},
1683	{'a', encodeFragment, false},
1684	{'a', encodeHost, false},
1685	{'z', encodePath, false},
1686	{'A', encodePath, false},
1687	{'Z', encodePath, false},
1688	{'0', encodePath, false},
1689	{'9', encodePath, false},
1690	{'-', encodePath, false},
1691	{'-', encodeUserPassword, false},
1692	{'-', encodeQueryComponent, false},
1693	{'-', encodeFragment, false},
1694	{'.', encodePath, false},
1695	{'_', encodePath, false},
1696	{'~', encodePath, false},
1697
1698	// User information (§3.2.1)
1699	{':', encodeUserPassword, true},
1700	{'/', encodeUserPassword, true},
1701	{'?', encodeUserPassword, true},
1702	{'@', encodeUserPassword, true},
1703	{'$', encodeUserPassword, false},
1704	{'&', encodeUserPassword, false},
1705	{'+', encodeUserPassword, false},
1706	{',', encodeUserPassword, false},
1707	{';', encodeUserPassword, false},
1708	{'=', encodeUserPassword, false},
1709
1710	// Host (IP address, IPv6 address, registered name, port suffix; §3.2.2)
1711	{'!', encodeHost, false},
1712	{'$', encodeHost, false},
1713	{'&', encodeHost, false},
1714	{'\'', encodeHost, false},
1715	{'(', encodeHost, false},
1716	{')', encodeHost, false},
1717	{'*', encodeHost, false},
1718	{'+', encodeHost, false},
1719	{',', encodeHost, false},
1720	{';', encodeHost, false},
1721	{'=', encodeHost, false},
1722	{':', encodeHost, false},
1723	{'[', encodeHost, false},
1724	{']', encodeHost, false},
1725	{'0', encodeHost, false},
1726	{'9', encodeHost, false},
1727	{'A', encodeHost, false},
1728	{'z', encodeHost, false},
1729	{'_', encodeHost, false},
1730	{'-', encodeHost, false},
1731	{'.', encodeHost, false},
1732}
1733
1734func TestShouldEscape(t *testing.T) {
1735	for _, tt := range shouldEscapeTests {
1736		if shouldEscape(tt.in, tt.mode) != tt.escape {
1737			t.Errorf("shouldEscape(%q, %v) returned %v; expected %v", tt.in, tt.mode, !tt.escape, tt.escape)
1738		}
1739	}
1740}
1741
1742type timeoutError struct {
1743	timeout bool
1744}
1745
1746func (e *timeoutError) Error() string { return "timeout error" }
1747func (e *timeoutError) Timeout() bool { return e.timeout }
1748
1749type temporaryError struct {
1750	temporary bool
1751}
1752
1753func (e *temporaryError) Error() string   { return "temporary error" }
1754func (e *temporaryError) Temporary() bool { return e.temporary }
1755
1756type timeoutTemporaryError struct {
1757	timeoutError
1758	temporaryError
1759}
1760
1761func (e *timeoutTemporaryError) Error() string { return "timeout/temporary error" }
1762
1763var netErrorTests = []struct {
1764	err       error
1765	timeout   bool
1766	temporary bool
1767}{{
1768	err:       &Error{"Get", "http://google.com/", &timeoutError{timeout: true}},
1769	timeout:   true,
1770	temporary: false,
1771}, {
1772	err:       &Error{"Get", "http://google.com/", &timeoutError{timeout: false}},
1773	timeout:   false,
1774	temporary: false,
1775}, {
1776	err:       &Error{"Get", "http://google.com/", &temporaryError{temporary: true}},
1777	timeout:   false,
1778	temporary: true,
1779}, {
1780	err:       &Error{"Get", "http://google.com/", &temporaryError{temporary: false}},
1781	timeout:   false,
1782	temporary: false,
1783}, {
1784	err:       &Error{"Get", "http://google.com/", &timeoutTemporaryError{timeoutError{timeout: true}, temporaryError{temporary: true}}},
1785	timeout:   true,
1786	temporary: true,
1787}, {
1788	err:       &Error{"Get", "http://google.com/", &timeoutTemporaryError{timeoutError{timeout: false}, temporaryError{temporary: true}}},
1789	timeout:   false,
1790	temporary: true,
1791}, {
1792	err:       &Error{"Get", "http://google.com/", &timeoutTemporaryError{timeoutError{timeout: true}, temporaryError{temporary: false}}},
1793	timeout:   true,
1794	temporary: false,
1795}, {
1796	err:       &Error{"Get", "http://google.com/", &timeoutTemporaryError{timeoutError{timeout: false}, temporaryError{temporary: false}}},
1797	timeout:   false,
1798	temporary: false,
1799}, {
1800	err:       &Error{"Get", "http://google.com/", io.EOF},
1801	timeout:   false,
1802	temporary: false,
1803}}
1804
1805// Test that url.Error implements net.Error and that it forwards
1806func TestURLErrorImplementsNetError(t *testing.T) {
1807	for i, tt := range netErrorTests {
1808		err, ok := tt.err.(net.Error)
1809		if !ok {
1810			t.Errorf("%d: %T does not implement net.Error", i+1, tt.err)
1811			continue
1812		}
1813		if err.Timeout() != tt.timeout {
1814			t.Errorf("%d: err.Timeout(): got %v, want %v", i+1, err.Timeout(), tt.timeout)
1815			continue
1816		}
1817		if err.Temporary() != tt.temporary {
1818			t.Errorf("%d: err.Temporary(): got %v, want %v", i+1, err.Temporary(), tt.temporary)
1819		}
1820	}
1821}
1822
1823func TestURLHostnameAndPort(t *testing.T) {
1824	tests := []struct {
1825		in   string // URL.Host field
1826		host string
1827		port string
1828	}{
1829		{"foo.com:80", "foo.com", "80"},
1830		{"foo.com", "foo.com", ""},
1831		{"foo.com:", "foo.com", ""},
1832		{"FOO.COM", "FOO.COM", ""}, // no canonicalization
1833		{"1.2.3.4", "1.2.3.4", ""},
1834		{"1.2.3.4:80", "1.2.3.4", "80"},
1835		{"[1:2:3:4]", "1:2:3:4", ""},
1836		{"[1:2:3:4]:80", "1:2:3:4", "80"},
1837		{"[::1]:80", "::1", "80"},
1838		{"[::1]", "::1", ""},
1839		{"[::1]:", "::1", ""},
1840		{"localhost", "localhost", ""},
1841		{"localhost:443", "localhost", "443"},
1842		{"some.super.long.domain.example.org:8080", "some.super.long.domain.example.org", "8080"},
1843		{"[2001:0db8:85a3:0000:0000:8a2e:0370:7334]:17000", "2001:0db8:85a3:0000:0000:8a2e:0370:7334", "17000"},
1844		{"[2001:0db8:85a3:0000:0000:8a2e:0370:7334]", "2001:0db8:85a3:0000:0000:8a2e:0370:7334", ""},
1845
1846		// Ensure that even when not valid, Host is one of "Hostname",
1847		// "Hostname:Port", "[Hostname]" or "[Hostname]:Port".
1848		// See https://golang.org/issue/29098.
1849		{"[google.com]:80", "google.com", "80"},
1850		{"google.com]:80", "google.com]", "80"},
1851		{"google.com:80_invalid_port", "google.com:80_invalid_port", ""},
1852		{"[::1]extra]:80", "::1]extra", "80"},
1853		{"google.com]extra:extra", "google.com]extra:extra", ""},
1854	}
1855	for _, tt := range tests {
1856		u := &URL{Host: tt.in}
1857		host, port := u.Hostname(), u.Port()
1858		if host != tt.host {
1859			t.Errorf("Hostname for Host %q = %q; want %q", tt.in, host, tt.host)
1860		}
1861		if port != tt.port {
1862			t.Errorf("Port for Host %q = %q; want %q", tt.in, port, tt.port)
1863		}
1864	}
1865}
1866
1867var _ encodingPkg.BinaryMarshaler = (*URL)(nil)
1868var _ encodingPkg.BinaryUnmarshaler = (*URL)(nil)
1869
1870func TestJSON(t *testing.T) {
1871	u, err := Parse("https://www.google.com/x?y=z")
1872	if err != nil {
1873		t.Fatal(err)
1874	}
1875	js, err := json.Marshal(u)
1876	if err != nil {
1877		t.Fatal(err)
1878	}
1879
1880	// If only we could implement TextMarshaler/TextUnmarshaler,
1881	// this would work:
1882	//
1883	// if string(js) != strconv.Quote(u.String()) {
1884	// 	t.Errorf("json encoding: %s\nwant: %s\n", js, strconv.Quote(u.String()))
1885	// }
1886
1887	u1 := new(URL)
1888	err = json.Unmarshal(js, u1)
1889	if err != nil {
1890		t.Fatal(err)
1891	}
1892	if u1.String() != u.String() {
1893		t.Errorf("json decoded to: %s\nwant: %s\n", u1, u)
1894	}
1895}
1896
1897func TestGob(t *testing.T) {
1898	u, err := Parse("https://www.google.com/x?y=z")
1899	if err != nil {
1900		t.Fatal(err)
1901	}
1902	var w bytes.Buffer
1903	err = gob.NewEncoder(&w).Encode(u)
1904	if err != nil {
1905		t.Fatal(err)
1906	}
1907
1908	u1 := new(URL)
1909	err = gob.NewDecoder(&w).Decode(u1)
1910	if err != nil {
1911		t.Fatal(err)
1912	}
1913	if u1.String() != u.String() {
1914		t.Errorf("json decoded to: %s\nwant: %s\n", u1, u)
1915	}
1916}
1917
1918func TestNilUser(t *testing.T) {
1919	defer func() {
1920		if v := recover(); v != nil {
1921			t.Fatalf("unexpected panic: %v", v)
1922		}
1923	}()
1924
1925	u, err := Parse("http://foo.com/")
1926
1927	if err != nil {
1928		t.Fatalf("parse err: %v", err)
1929	}
1930
1931	if v := u.User.Username(); v != "" {
1932		t.Fatalf("expected empty username, got %s", v)
1933	}
1934
1935	if v, ok := u.User.Password(); v != "" || ok {
1936		t.Fatalf("expected empty password, got %s (%v)", v, ok)
1937	}
1938
1939	if v := u.User.String(); v != "" {
1940		t.Fatalf("expected empty string, got %s", v)
1941	}
1942}
1943
1944func TestInvalidUserPassword(t *testing.T) {
1945	_, err := Parse("http://user^:passwo^[email protected]/")
1946	if got, wantsub := fmt.Sprint(err), "net/url: invalid userinfo"; !strings.Contains(got, wantsub) {
1947		t.Errorf("error = %q; want substring %q", got, wantsub)
1948	}
1949}
1950
1951func TestRejectControlCharacters(t *testing.T) {
1952	tests := []string{
1953		"http://foo.com/?foo\nbar",
1954		"http\r://foo.com/",
1955		"http://foo\x7f.com/",
1956	}
1957	for _, s := range tests {
1958		_, err := Parse(s)
1959		const wantSub = "net/url: invalid control character in URL"
1960		if got := fmt.Sprint(err); !strings.Contains(got, wantSub) {
1961			t.Errorf("Parse(%q) error = %q; want substring %q", s, got, wantSub)
1962		}
1963	}
1964
1965	// But don't reject non-ASCII CTLs, at least for now:
1966	if _, err := Parse("http://foo.com/ctl\x80"); err != nil {
1967		t.Errorf("error parsing URL with non-ASCII control byte: %v", err)
1968	}
1969
1970}
1971
1972var escapeBenchmarks = []struct {
1973	unescaped string
1974	query     string
1975	path      string
1976}{
1977	{
1978		unescaped: "one two",
1979		query:     "one+two",
1980		path:      "one%20two",
1981	},
1982	{
1983		unescaped: "Фотки собак",
1984		query:     "%D0%A4%D0%BE%D1%82%D0%BA%D0%B8+%D1%81%D0%BE%D0%B1%D0%B0%D0%BA",
1985		path:      "%D0%A4%D0%BE%D1%82%D0%BA%D0%B8%20%D1%81%D0%BE%D0%B1%D0%B0%D0%BA",
1986	},
1987
1988	{
1989		unescaped: "shortrun(break)shortrun",
1990		query:     "shortrun%28break%29shortrun",
1991		path:      "shortrun%28break%29shortrun",
1992	},
1993
1994	{
1995		unescaped: "longerrunofcharacters(break)anotherlongerrunofcharacters",
1996		query:     "longerrunofcharacters%28break%29anotherlongerrunofcharacters",
1997		path:      "longerrunofcharacters%28break%29anotherlongerrunofcharacters",
1998	},
1999
2000	{
2001		unescaped: strings.Repeat("padded/with+various%characters?that=need$some@escaping+paddedsowebreak/256bytes", 4),
2002		query:     strings.Repeat("padded%2Fwith%2Bvarious%25characters%3Fthat%3Dneed%24some%40escaping%2Bpaddedsowebreak%2F256bytes", 4),
2003		path:      strings.Repeat("padded%2Fwith+various%25characters%3Fthat=need$some@escaping+paddedsowebreak%2F256bytes", 4),
2004	},
2005}
2006
2007func BenchmarkQueryEscape(b *testing.B) {
2008	for _, tc := range escapeBenchmarks {
2009		b.Run("", func(b *testing.B) {
2010			b.ReportAllocs()
2011			var g string
2012			for i := 0; i < b.N; i++ {
2013				g = QueryEscape(tc.unescaped)
2014			}
2015			b.StopTimer()
2016			if g != tc.query {
2017				b.Errorf("QueryEscape(%q) == %q, want %q", tc.unescaped, g, tc.query)
2018			}
2019
2020		})
2021	}
2022}
2023
2024func BenchmarkPathEscape(b *testing.B) {
2025	for _, tc := range escapeBenchmarks {
2026		b.Run("", func(b *testing.B) {
2027			b.ReportAllocs()
2028			var g string
2029			for i := 0; i < b.N; i++ {
2030				g = PathEscape(tc.unescaped)
2031			}
2032			b.StopTimer()
2033			if g != tc.path {
2034				b.Errorf("PathEscape(%q) == %q, want %q", tc.unescaped, g, tc.path)
2035			}
2036
2037		})
2038	}
2039}
2040
2041func BenchmarkQueryUnescape(b *testing.B) {
2042	for _, tc := range escapeBenchmarks {
2043		b.Run("", func(b *testing.B) {
2044			b.ReportAllocs()
2045			var g string
2046			for i := 0; i < b.N; i++ {
2047				g, _ = QueryUnescape(tc.query)
2048			}
2049			b.StopTimer()
2050			if g != tc.unescaped {
2051				b.Errorf("QueryUnescape(%q) == %q, want %q", tc.query, g, tc.unescaped)
2052			}
2053
2054		})
2055	}
2056}
2057
2058func BenchmarkPathUnescape(b *testing.B) {
2059	for _, tc := range escapeBenchmarks {
2060		b.Run("", func(b *testing.B) {
2061			b.ReportAllocs()
2062			var g string
2063			for i := 0; i < b.N; i++ {
2064				g, _ = PathUnescape(tc.path)
2065			}
2066			b.StopTimer()
2067			if g != tc.unescaped {
2068				b.Errorf("PathUnescape(%q) == %q, want %q", tc.path, g, tc.unescaped)
2069			}
2070
2071		})
2072	}
2073}
2074
2075func TestJoinPath(t *testing.T) {
2076	tests := []struct {
2077		base string
2078		elem []string
2079		out  string
2080	}{
2081		{
2082			base: "https://go.googlesource.com",
2083			elem: []string{"go"},
2084			out:  "https://go.googlesource.com/go",
2085		},
2086		{
2087			base: "https://go.googlesource.com/a/b/c",
2088			elem: []string{"../../../go"},
2089			out:  "https://go.googlesource.com/go",
2090		},
2091		{
2092			base: "https://go.googlesource.com/",
2093			elem: []string{"../go"},
2094			out:  "https://go.googlesource.com/go",
2095		},
2096		{
2097			base: "https://go.googlesource.com",
2098			elem: []string{"../go"},
2099			out:  "https://go.googlesource.com/go",
2100		},
2101		{
2102			base: "https://go.googlesource.com",
2103			elem: []string{"../go", "../../go", "../../../go"},
2104			out:  "https://go.googlesource.com/go",
2105		},
2106		{
2107			base: "https://go.googlesource.com/../go",
2108			elem: nil,
2109			out:  "https://go.googlesource.com/go",
2110		},
2111		{
2112			base: "https://go.googlesource.com/",
2113			elem: []string{"./go"},
2114			out:  "https://go.googlesource.com/go",
2115		},
2116		{
2117			base: "https://go.googlesource.com//",
2118			elem: []string{"/go"},
2119			out:  "https://go.googlesource.com/go",
2120		},
2121		{
2122			base: "https://go.googlesource.com//",
2123			elem: []string{"/go", "a", "b", "c"},
2124			out:  "https://go.googlesource.com/go/a/b/c",
2125		},
2126		{
2127			base: "http://[fe80::1%en0]:8080/",
2128			elem: []string{"/go"},
2129		},
2130		{
2131			base: "https://go.googlesource.com",
2132			elem: []string{"go/"},
2133			out:  "https://go.googlesource.com/go/",
2134		},
2135		{
2136			base: "https://go.googlesource.com",
2137			elem: []string{"go//"},
2138			out:  "https://go.googlesource.com/go/",
2139		},
2140		{
2141			base: "https://go.googlesource.com",
2142			elem: nil,
2143			out:  "https://go.googlesource.com/",
2144		},
2145		{
2146			base: "https://go.googlesource.com/",
2147			elem: nil,
2148			out:  "https://go.googlesource.com/",
2149		},
2150		{
2151			base: "https://go.googlesource.com/a%2fb",
2152			elem: []string{"c"},
2153			out:  "https://go.googlesource.com/a%2fb/c",
2154		},
2155		{
2156			base: "https://go.googlesource.com/a%2fb",
2157			elem: []string{"c%2fd"},
2158			out:  "https://go.googlesource.com/a%2fb/c%2fd",
2159		},
2160		{
2161			base: "https://go.googlesource.com/a/b",
2162			elem: []string{"/go"},
2163			out:  "https://go.googlesource.com/a/b/go",
2164		},
2165		{
2166			base: "/",
2167			elem: nil,
2168			out:  "/",
2169		},
2170		{
2171			base: "a",
2172			elem: nil,
2173			out:  "a",
2174		},
2175		{
2176			base: "a",
2177			elem: []string{"b"},
2178			out:  "a/b",
2179		},
2180		{
2181			base: "a",
2182			elem: []string{"../b"},
2183			out:  "b",
2184		},
2185		{
2186			base: "a",
2187			elem: []string{"../../b"},
2188			out:  "b",
2189		},
2190		{
2191			base: "",
2192			elem: []string{"a"},
2193			out:  "a",
2194		},
2195		{
2196			base: "",
2197			elem: []string{"../a"},
2198			out:  "a",
2199		},
2200	}
2201	for _, tt := range tests {
2202		wantErr := "nil"
2203		if tt.out == "" {
2204			wantErr = "non-nil error"
2205		}
2206		if out, err := JoinPath(tt.base, tt.elem...); out != tt.out || (err == nil) != (tt.out != "") {
2207			t.Errorf("JoinPath(%q, %q) = %q, %v, want %q, %v", tt.base, tt.elem, out, err, tt.out, wantErr)
2208		}
2209		var out string
2210		u, err := Parse(tt.base)
2211		if err == nil {
2212			u = u.JoinPath(tt.elem...)
2213			out = u.String()
2214		}
2215		if out != tt.out || (err == nil) != (tt.out != "") {
2216			t.Errorf("Parse(%q).JoinPath(%q) = %q, %v, want %q, %v", tt.base, tt.elem, out, err, tt.out, wantErr)
2217		}
2218	}
2219}
2220