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