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.Run("Simple", func(b *testing.B) {
1218 for i := 0; i < b.N; i++ {
1219 resolvePath("a/b/c", ".././d")
1220 }
1221 })
1222 b.Run("Deep", func(b *testing.B) {
1223 base := strings.Repeat("a/", 100) + "b"
1224 ref := "c"
1225 b.ResetTimer()
1226 for b.Loop() {
1227 resolvePath(base, ref)
1228 }
1229 })
1230 b.Run("Backtrack", func(b *testing.B) {
1231 base := strings.Repeat("a/", 100) + "b"
1232 ref := strings.Repeat("../", 50) + "c"
1233 b.ResetTimer()
1234 for b.Loop() {
1235 resolvePath(base, ref)
1236 }
1237 })
1238 }
1239
1240 var resolveReferenceTests = []struct {
1241 base, rel, expected string
1242 }{
1243
1244 {"http://foo.com?a=b", "https://bar.com/", "https://bar.com/"},
1245 {"http://foo.com/", "https://bar.com/?a=b", "https://bar.com/?a=b"},
1246 {"http://foo.com/", "https://bar.com/?", "https://bar.com/?"},
1247 {"http://foo.com/bar", "mailto:foo@example.com", "mailto:foo@example.com"},
1248
1249
1250 {"http://foo.com/bar", "/baz", "http://foo.com/baz"},
1251 {"http://foo.com/bar?a=b#f", "/baz", "http://foo.com/baz"},
1252 {"http://foo.com/bar?a=b", "/baz?", "http://foo.com/baz?"},
1253 {"http://foo.com/bar?a=b", "/baz?c=d", "http://foo.com/baz?c=d"},
1254
1255
1256 {"http://foo.com/bar", "http://foo.com//baz", "http://foo.com//baz"},
1257 {"http://foo.com/bar", "http://foo.com///baz/quux", "http://foo.com///baz/quux"},
1258
1259
1260 {"https://foo.com/bar?a=b", "//bar.com/quux", "https://bar.com/quux"},
1261
1262
1263
1264
1265 {"http://foo.com", ".", "http://foo.com/"},
1266 {"http://foo.com/bar", ".", "http://foo.com/"},
1267 {"http://foo.com/bar/", ".", "http://foo.com/bar/"},
1268
1269
1270 {"http://foo.com", "bar", "http://foo.com/bar"},
1271 {"http://foo.com/", "bar", "http://foo.com/bar"},
1272 {"http://foo.com/bar/baz", "quux", "http://foo.com/bar/quux"},
1273 {"http://foo.com/bar/baz/", "quux", "http://foo.com/bar/baz/quux"},
1274
1275
1276 {"http://foo.com/bar/baz", "../quux", "http://foo.com/quux"},
1277 {"http://foo.com/bar/baz", "../../../../../quux", "http://foo.com/quux"},
1278 {"http://foo.com/bar", "..", "http://foo.com/"},
1279 {"http://foo.com/bar/baz", "./..", "http://foo.com/"},
1280
1281 {"http://foo.com/bar/baz", "quux/dotdot/../tail", "http://foo.com/bar/quux/tail"},
1282 {"http://foo.com/bar/baz", "quux/./dotdot/../tail", "http://foo.com/bar/quux/tail"},
1283 {"http://foo.com/bar/baz", "quux/./dotdot/.././tail", "http://foo.com/bar/quux/tail"},
1284 {"http://foo.com/bar/baz", "quux/./dotdot/./../tail", "http://foo.com/bar/quux/tail"},
1285 {"http://foo.com/bar/baz", "quux/./dotdot/dotdot/././../../tail", "http://foo.com/bar/quux/tail"},
1286 {"http://foo.com/bar/baz", "quux/./dotdot/dotdot/./.././../tail", "http://foo.com/bar/quux/tail"},
1287 {"http://foo.com/bar/baz", "quux/./dotdot/dotdot/dotdot/./../../.././././tail", "http://foo.com/bar/quux/tail"},
1288 {"http://foo.com/bar/baz", "quux/./dotdot/../dotdot/../dot/./tail/..", "http://foo.com/bar/quux/dot/"},
1289
1290
1291
1292 {"http://foo.com/dot/./dotdot/../foo/bar", "../baz", "http://foo.com/dot/baz"},
1293
1294
1295 {"http://foo.com/bar", "...", "http://foo.com/..."},
1296
1297
1298 {"http://foo.com/bar", ".#frag", "http://foo.com/#frag"},
1299 {"http://example.org/", "#!$&%27()*+,;=", "http://example.org/#!$&%27()*+,;="},
1300
1301
1302 {"http://foo.com/foo%2fbar/", "../baz", "http://foo.com/baz"},
1303 {"http://foo.com/1/2%2f/3%2f4/5", "../../a/b/c", "http://foo.com/1/a/b/c"},
1304 {"http://foo.com/1/2/3", "./a%2f../../b/..%2fc", "http://foo.com/1/2/b/..%2fc"},
1305 {"http://foo.com/1/2%2f/3%2f4/5", "./a%2f../b/../c", "http://foo.com/1/2%2f/3%2f4/a%2f../c"},
1306 {"http://foo.com/foo%20bar/", "../baz", "http://foo.com/baz"},
1307 {"http://foo.com/foo", "../bar%2fbaz", "http://foo.com/bar%2fbaz"},
1308 {"http://foo.com/foo%2dbar/", "./baz-quux", "http://foo.com/foo%2dbar/baz-quux"},
1309
1310
1311
1312 {"http://a/b/c/d;p?q", "g:h", "g:h"},
1313 {"http://a/b/c/d;p?q", "g", "http://a/b/c/g"},
1314 {"http://a/b/c/d;p?q", "./g", "http://a/b/c/g"},
1315 {"http://a/b/c/d;p?q", "g/", "http://a/b/c/g/"},
1316 {"http://a/b/c/d;p?q", "/g", "http://a/g"},
1317 {"http://a/b/c/d;p?q", "//g", "http://g"},
1318 {"http://a/b/c/d;p?q", "?y", "http://a/b/c/d;p?y"},
1319 {"http://a/b/c/d;p?q", "g?y", "http://a/b/c/g?y"},
1320 {"http://a/b/c/d;p?q", "#s", "http://a/b/c/d;p?q#s"},
1321 {"http://a/b/c/d;p?q", "g#s", "http://a/b/c/g#s"},
1322 {"http://a/b/c/d;p?q", "g?y#s", "http://a/b/c/g?y#s"},
1323 {"http://a/b/c/d;p?q", ";x", "http://a/b/c/;x"},
1324 {"http://a/b/c/d;p?q", "g;x", "http://a/b/c/g;x"},
1325 {"http://a/b/c/d;p?q", "g;x?y#s", "http://a/b/c/g;x?y#s"},
1326 {"http://a/b/c/d;p?q", "", "http://a/b/c/d;p?q"},
1327 {"http://a/b/c/d;p?q", ".", "http://a/b/c/"},
1328 {"http://a/b/c/d;p?q", "./", "http://a/b/c/"},
1329 {"http://a/b/c/d;p?q", "..", "http://a/b/"},
1330 {"http://a/b/c/d;p?q", "../", "http://a/b/"},
1331 {"http://a/b/c/d;p?q", "../g", "http://a/b/g"},
1332 {"http://a/b/c/d;p?q", "../..", "http://a/"},
1333 {"http://a/b/c/d;p?q", "../../", "http://a/"},
1334 {"http://a/b/c/d;p?q", "../../g", "http://a/g"},
1335
1336
1337
1338 {"http://a/b/c/d;p?q", "../../../g", "http://a/g"},
1339 {"http://a/b/c/d;p?q", "../../../../g", "http://a/g"},
1340 {"http://a/b/c/d;p?q", "/./g", "http://a/g"},
1341 {"http://a/b/c/d;p?q", "/../g", "http://a/g"},
1342 {"http://a/b/c/d;p?q", "g.", "http://a/b/c/g."},
1343 {"http://a/b/c/d;p?q", ".g", "http://a/b/c/.g"},
1344 {"http://a/b/c/d;p?q", "g..", "http://a/b/c/g.."},
1345 {"http://a/b/c/d;p?q", "..g", "http://a/b/c/..g"},
1346 {"http://a/b/c/d;p?q", "./../g", "http://a/b/g"},
1347 {"http://a/b/c/d;p?q", "./g/.", "http://a/b/c/g/"},
1348 {"http://a/b/c/d;p?q", "g/./h", "http://a/b/c/g/h"},
1349 {"http://a/b/c/d;p?q", "g/../h", "http://a/b/c/h"},
1350 {"http://a/b/c/d;p?q", "g;x=1/./y", "http://a/b/c/g;x=1/y"},
1351 {"http://a/b/c/d;p?q", "g;x=1/../y", "http://a/b/c/y"},
1352 {"http://a/b/c/d;p?q", "g?y/./x", "http://a/b/c/g?y/./x"},
1353 {"http://a/b/c/d;p?q", "g?y/../x", "http://a/b/c/g?y/../x"},
1354 {"http://a/b/c/d;p?q", "g#s/./x", "http://a/b/c/g#s/./x"},
1355 {"http://a/b/c/d;p?q", "g#s/../x", "http://a/b/c/g#s/../x"},
1356
1357
1358 {"https://a/b/c/d;p?q", "//g?q", "https://g?q"},
1359 {"https://a/b/c/d;p?q", "//g#s", "https://g#s"},
1360 {"https://a/b/c/d;p?q", "//g/d/e/f?y#s", "https://g/d/e/f?y#s"},
1361 {"https://a/b/c/d;p#s", "?y", "https://a/b/c/d;p?y"},
1362 {"https://a/b/c/d;p?q#s", "?y", "https://a/b/c/d;p?y"},
1363
1364
1365 {"https://a/b/c/d;p?q#s", "?", "https://a/b/c/d;p?"},
1366
1367
1368 {"https://foo.com/bar?a=b", "http:opaque", "http:opaque"},
1369 {"http:opaque?x=y#zzz", "https:/foo?a=b#frag", "https:/foo?a=b#frag"},
1370 {"http:opaque?x=y#zzz", "https:foo:bar", "https:foo:bar"},
1371 {"http:opaque?x=y#zzz", "https:bar/baz?a=b#frag", "https:bar/baz?a=b#frag"},
1372 {"http:opaque?x=y#zzz", "https://user@host:1234?a=b#frag", "https://user@host:1234?a=b#frag"},
1373 {"http:opaque?x=y#zzz", "?a=b#frag", "http:opaque?a=b#frag"},
1374 }
1375
1376 func TestResolveReference(t *testing.T) {
1377 mustParse := func(url string) *URL {
1378 u, err := Parse(url)
1379 if err != nil {
1380 t.Fatalf("Parse(%q) got err %v", url, err)
1381 }
1382 return u
1383 }
1384 opaque := &URL{Scheme: "scheme", Opaque: "opaque"}
1385 for _, test := range resolveReferenceTests {
1386 base := mustParse(test.base)
1387 rel := mustParse(test.rel)
1388 url := base.ResolveReference(rel)
1389 if got := url.String(); got != test.expected {
1390 t.Errorf("URL(%q).ResolveReference(%q)\ngot %q\nwant %q", test.base, test.rel, got, test.expected)
1391 }
1392
1393 if base == url {
1394 t.Errorf("Expected URL.ResolveReference to return new URL instance.")
1395 }
1396
1397 url, err := base.Parse(test.rel)
1398 if err != nil {
1399 t.Errorf("URL(%q).Parse(%q) failed: %v", test.base, test.rel, err)
1400 } else if got := url.String(); got != test.expected {
1401 t.Errorf("URL(%q).Parse(%q)\ngot %q\nwant %q", test.base, test.rel, got, test.expected)
1402 } else if base == url {
1403
1404 t.Errorf("Expected URL.Parse to return new URL instance.")
1405 }
1406
1407 url = base.ResolveReference(opaque)
1408 if *url != *opaque {
1409 t.Errorf("ResolveReference failed to resolve opaque URL:\ngot %#v\nwant %#v", url, opaque)
1410 }
1411
1412 url, err = base.Parse("scheme:opaque")
1413 if err != nil {
1414 t.Errorf(`URL(%q).Parse("scheme:opaque") failed: %v`, test.base, err)
1415 } else if *url != *opaque {
1416 t.Errorf("Parse failed to resolve opaque URL:\ngot %#v\nwant %#v", opaque, url)
1417 } else if base == url {
1418
1419 t.Errorf("Expected URL.Parse to return new URL instance.")
1420 }
1421 }
1422 }
1423
1424 func TestQueryValues(t *testing.T) {
1425 u, _ := Parse("http://x.com?foo=bar&bar=1&bar=2&baz")
1426 v := u.Query()
1427 if len(v) != 3 {
1428 t.Errorf("got %d keys in Query values, want 3", len(v))
1429 }
1430 if g, e := v.Get("foo"), "bar"; g != e {
1431 t.Errorf("Get(foo) = %q, want %q", g, e)
1432 }
1433
1434 if g, e := v.Get("Foo"), ""; g != e {
1435 t.Errorf("Get(Foo) = %q, want %q", g, e)
1436 }
1437 if g, e := v.Get("bar"), "1"; g != e {
1438 t.Errorf("Get(bar) = %q, want %q", g, e)
1439 }
1440 if g, e := v.Get("baz"), ""; g != e {
1441 t.Errorf("Get(baz) = %q, want %q", g, e)
1442 }
1443 if h, e := v.Has("foo"), true; h != e {
1444 t.Errorf("Has(foo) = %t, want %t", h, e)
1445 }
1446 if h, e := v.Has("bar"), true; h != e {
1447 t.Errorf("Has(bar) = %t, want %t", h, e)
1448 }
1449 if h, e := v.Has("baz"), true; h != e {
1450 t.Errorf("Has(baz) = %t, want %t", h, e)
1451 }
1452 if h, e := v.Has("noexist"), false; h != e {
1453 t.Errorf("Has(noexist) = %t, want %t", h, e)
1454 }
1455 v.Del("bar")
1456 if g, e := v.Get("bar"), ""; g != e {
1457 t.Errorf("second Get(bar) = %q, want %q", g, e)
1458 }
1459 }
1460
1461 type parseTest struct {
1462 query string
1463 out Values
1464 ok bool
1465 }
1466
1467 var parseTests = []parseTest{
1468 {
1469 query: "a=1",
1470 out: Values{"a": []string{"1"}},
1471 ok: true,
1472 },
1473 {
1474 query: "a=1&b=2",
1475 out: Values{"a": []string{"1"}, "b": []string{"2"}},
1476 ok: true,
1477 },
1478 {
1479 query: "a=1&a=2&a=banana",
1480 out: Values{"a": []string{"1", "2", "banana"}},
1481 ok: true,
1482 },
1483 {
1484 query: "ascii=%3Ckey%3A+0x90%3E",
1485 out: Values{"ascii": []string{"<key: 0x90>"}},
1486 ok: true,
1487 }, {
1488 query: "a=1;b=2",
1489 out: Values{},
1490 ok: false,
1491 }, {
1492 query: "a;b=1",
1493 out: Values{},
1494 ok: false,
1495 }, {
1496 query: "a=%3B",
1497 out: Values{"a": []string{";"}},
1498 ok: true,
1499 },
1500 {
1501 query: "a%3Bb=1",
1502 out: Values{"a;b": []string{"1"}},
1503 ok: true,
1504 },
1505 {
1506 query: "a=1&a=2;a=banana",
1507 out: Values{"a": []string{"1"}},
1508 ok: false,
1509 },
1510 {
1511 query: "a;b&c=1",
1512 out: Values{"c": []string{"1"}},
1513 ok: false,
1514 },
1515 {
1516 query: "a=1&b=2;a=3&c=4",
1517 out: Values{"a": []string{"1"}, "c": []string{"4"}},
1518 ok: false,
1519 },
1520 {
1521 query: "a=1&b=2;c=3",
1522 out: Values{"a": []string{"1"}},
1523 ok: false,
1524 },
1525 {
1526 query: ";",
1527 out: Values{},
1528 ok: false,
1529 },
1530 {
1531 query: "a=1;",
1532 out: Values{},
1533 ok: false,
1534 },
1535 {
1536 query: "a=1&;",
1537 out: Values{"a": []string{"1"}},
1538 ok: false,
1539 },
1540 {
1541 query: ";a=1&b=2",
1542 out: Values{"b": []string{"2"}},
1543 ok: false,
1544 },
1545 {
1546 query: "a=1&b=2;",
1547 out: Values{"a": []string{"1"}},
1548 ok: false,
1549 },
1550 }
1551
1552 func TestParseQuery(t *testing.T) {
1553 for _, test := range parseTests {
1554 t.Run(test.query, func(t *testing.T) {
1555 form, err := ParseQuery(test.query)
1556 if test.ok != (err == nil) {
1557 want := "<error>"
1558 if test.ok {
1559 want = "<nil>"
1560 }
1561 t.Errorf("Unexpected error: %v, want %v", err, want)
1562 }
1563 if len(form) != len(test.out) {
1564 t.Errorf("len(form) = %d, want %d", len(form), len(test.out))
1565 }
1566 for k, evs := range test.out {
1567 vs, ok := form[k]
1568 if !ok {
1569 t.Errorf("Missing key %q", k)
1570 continue
1571 }
1572 if len(vs) != len(evs) {
1573 t.Errorf("len(form[%q]) = %d, want %d", k, len(vs), len(evs))
1574 continue
1575 }
1576 for j, ev := range evs {
1577 if v := vs[j]; v != ev {
1578 t.Errorf("form[%q][%d] = %q, want %q", k, j, v, ev)
1579 }
1580 }
1581 }
1582 })
1583 }
1584 }
1585
1586 func TestParseQueryLimits(t *testing.T) {
1587 for _, test := range []struct {
1588 params int
1589 godebug string
1590 wantErr bool
1591 }{{
1592 params: 10,
1593 wantErr: false,
1594 }, {
1595 params: defaultMaxParams,
1596 wantErr: false,
1597 }, {
1598 params: defaultMaxParams + 1,
1599 wantErr: true,
1600 }, {
1601 params: 10,
1602 godebug: "urlmaxqueryparams=9",
1603 wantErr: true,
1604 }, {
1605 params: defaultMaxParams + 1,
1606 godebug: "urlmaxqueryparams=0",
1607 wantErr: false,
1608 }} {
1609 t.Setenv("GODEBUG", test.godebug)
1610 want := Values{}
1611 var b strings.Builder
1612 for i := range test.params {
1613 if i > 0 {
1614 b.WriteString("&")
1615 }
1616 p := fmt.Sprintf("p%v", i)
1617 b.WriteString(p)
1618 want[p] = []string{""}
1619 }
1620 query := b.String()
1621 got, err := ParseQuery(query)
1622 if gotErr, wantErr := err != nil, test.wantErr; gotErr != wantErr {
1623 t.Errorf("GODEBUG=%v ParseQuery(%v params) = %v, want error: %v", test.godebug, test.params, err, wantErr)
1624 }
1625 if err != nil {
1626 continue
1627 }
1628 if got, want := len(got), test.params; got != want {
1629 t.Errorf("GODEBUG=%v ParseQuery(%v params): got %v params, want %v", test.godebug, test.params, got, want)
1630 }
1631 }
1632 }
1633
1634 type RequestURITest struct {
1635 url *URL
1636 out string
1637 }
1638
1639 var requritests = []RequestURITest{
1640 {
1641 &URL{
1642 Scheme: "http",
1643 Host: "example.com",
1644 Path: "",
1645 },
1646 "/",
1647 },
1648 {
1649 &URL{
1650 Scheme: "http",
1651 Host: "example.com",
1652 Path: "/a b",
1653 },
1654 "/a%20b",
1655 },
1656
1657 {
1658 &URL{
1659 Scheme: "http",
1660 Host: "example.com",
1661 Opaque: "/%2F/%2F/",
1662 },
1663 "/%2F/%2F/",
1664 },
1665
1666 {
1667 &URL{
1668 Scheme: "http",
1669 Host: "example.com",
1670 Opaque: "//other.example.com/%2F/%2F/",
1671 },
1672 "http://other.example.com/%2F/%2F/",
1673 },
1674
1675 {
1676 &URL{
1677 Scheme: "http",
1678 Host: "example.com",
1679 Path: "/////",
1680 RawPath: "/%2F/%2F/",
1681 },
1682 "/%2F/%2F/",
1683 },
1684 {
1685 &URL{
1686 Scheme: "http",
1687 Host: "example.com",
1688 Path: "/////",
1689 RawPath: "/WRONG/",
1690 },
1691 "/////",
1692 },
1693 {
1694 &URL{
1695 Scheme: "http",
1696 Host: "example.com",
1697 Path: "/a b",
1698 RawQuery: "q=go+language",
1699 },
1700 "/a%20b?q=go+language",
1701 },
1702 {
1703 &URL{
1704 Scheme: "http",
1705 Host: "example.com",
1706 Path: "/a b",
1707 RawPath: "/a b",
1708 RawQuery: "q=go+language",
1709 },
1710 "/a%20b?q=go+language",
1711 },
1712 {
1713 &URL{
1714 Scheme: "http",
1715 Host: "example.com",
1716 Path: "/a?b",
1717 RawPath: "/a?b",
1718 RawQuery: "q=go+language",
1719 },
1720 "/a%3Fb?q=go+language",
1721 },
1722 {
1723 &URL{
1724 Scheme: "myschema",
1725 Opaque: "opaque",
1726 },
1727 "opaque",
1728 },
1729 {
1730 &URL{
1731 Scheme: "myschema",
1732 Opaque: "opaque",
1733 RawQuery: "q=go+language",
1734 },
1735 "opaque?q=go+language",
1736 },
1737 {
1738 &URL{
1739 Scheme: "http",
1740 Host: "example.com",
1741 Path: "//foo",
1742 },
1743 "//foo",
1744 },
1745 {
1746 &URL{
1747 Scheme: "http",
1748 Host: "example.com",
1749 Path: "/foo",
1750 ForceQuery: true,
1751 },
1752 "/foo?",
1753 },
1754 }
1755
1756 func TestRequestURI(t *testing.T) {
1757 for _, tt := range requritests {
1758 s := tt.url.RequestURI()
1759 if s != tt.out {
1760 t.Errorf("%#v.RequestURI() == %q (expected %q)", tt.url, s, tt.out)
1761 }
1762 }
1763 }
1764
1765 func TestParseFailure(t *testing.T) {
1766
1767 const url = "%gh&%ij"
1768 _, err := ParseQuery(url)
1769 errStr := fmt.Sprint(err)
1770 if !strings.Contains(errStr, "%gh") {
1771 t.Errorf(`ParseQuery(%q) returned error %q, want something containing %q"`, url, errStr, "%gh")
1772 }
1773 }
1774
1775 func TestParseErrors(t *testing.T) {
1776 tests := []struct {
1777 in string
1778 wantErr bool
1779 }{
1780 {"http://[::1]", false},
1781 {"http://[::1]:80", false},
1782 {"http://[::1]:namedport", true},
1783 {"http://x:namedport", true},
1784 {"http://[::1]/", false},
1785 {"http://[::1]a", true},
1786 {"http://[::1]%23", true},
1787 {"http://[::1%25en0]", false},
1788 {"http://[::1]:", false},
1789 {"http://x:", false},
1790 {"http://[::1]:%38%30", true},
1791 {"http://[::1%25%41]", false},
1792 {"http://[%10::1]", true},
1793 {"http://[::1]/%48", false},
1794 {"http://%41:8080/", true},
1795 {"mysql://x@y(z:123)/foo", true},
1796 {"mysql://x@y(1.2.3.4:123)/foo", true},
1797
1798 {" http://foo.com", true},
1799 {"ht tp://foo.com", true},
1800 {"ahttp://foo.com", false},
1801 {"1http://foo.com", true},
1802
1803 {"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},
1804 {"http://a b.com/", true},
1805 {"cache_object://foo", true},
1806 {"cache_object:foo", true},
1807 {"cache_object:foo/bar", true},
1808 {"cache_object/:foo/bar", false},
1809
1810 {"http://[192.168.0.1]/", true},
1811 {"http://[192.168.0.1]:8080/", true},
1812 {"http://[::ffff:192.168.0.1]/", false},
1813 {"http://[::ffff:192.168.0.1000]/", true},
1814 {"http://[::ffff:192.168.0.1]:8080/", false},
1815 {"http://[::ffff:c0a8:1]/", false},
1816 {"http://[not-an-ip]/", true},
1817 {"http://[fe80::1%foo]/", true},
1818 {"http://[fe80::1", true},
1819 {"http://fe80::1]/", true},
1820 {"http://[test.com]/", true},
1821 {"http://example.com[::1]", true},
1822 {"http://example.com[::1", true},
1823 {"http://[::1", true},
1824 {"http://.[::1]", true},
1825 {"http:// [::1]", true},
1826 {"hxxp://mathepqo[.]serveftp(.)com:9059", true},
1827 }
1828 for _, tt := range tests {
1829 u, err := Parse(tt.in)
1830 if tt.wantErr {
1831 if err == nil {
1832 t.Errorf("Parse(%q) = %#v; want an error", tt.in, u)
1833 }
1834 continue
1835 }
1836 if err != nil {
1837 t.Errorf("Parse(%q) = %v; want no error", tt.in, err)
1838 }
1839 }
1840 }
1841
1842
1843 func TestStarRequest(t *testing.T) {
1844 u, err := Parse("*")
1845 if err != nil {
1846 t.Fatal(err)
1847 }
1848 if got, want := u.RequestURI(), "*"; got != want {
1849 t.Errorf("RequestURI = %q; want %q", got, want)
1850 }
1851 }
1852
1853 type shouldEscapeTest struct {
1854 in byte
1855 mode encoding
1856 escape bool
1857 }
1858
1859 var shouldEscapeTests = []shouldEscapeTest{
1860
1861 {'a', encodePath, false},
1862 {'a', encodeUserPassword, false},
1863 {'a', encodeQueryComponent, false},
1864 {'a', encodeFragment, false},
1865 {'a', encodeHost, false},
1866 {'z', encodePath, false},
1867 {'A', encodePath, false},
1868 {'Z', encodePath, false},
1869 {'0', encodePath, false},
1870 {'9', encodePath, false},
1871 {'-', encodePath, false},
1872 {'-', encodeUserPassword, false},
1873 {'-', encodeQueryComponent, false},
1874 {'-', encodeFragment, false},
1875 {'.', encodePath, false},
1876 {'_', encodePath, false},
1877 {'~', encodePath, false},
1878
1879
1880 {':', encodeUserPassword, true},
1881 {'/', encodeUserPassword, true},
1882 {'?', encodeUserPassword, true},
1883 {'@', encodeUserPassword, true},
1884 {'$', encodeUserPassword, false},
1885 {'&', encodeUserPassword, false},
1886 {'+', encodeUserPassword, false},
1887 {',', encodeUserPassword, false},
1888 {';', encodeUserPassword, false},
1889 {'=', encodeUserPassword, false},
1890
1891
1892 {'!', encodeHost, false},
1893 {'$', encodeHost, false},
1894 {'&', encodeHost, false},
1895 {'\'', encodeHost, false},
1896 {'(', encodeHost, false},
1897 {')', encodeHost, false},
1898 {'*', encodeHost, false},
1899 {'+', encodeHost, false},
1900 {',', encodeHost, false},
1901 {';', encodeHost, false},
1902 {'=', encodeHost, false},
1903 {':', encodeHost, false},
1904 {'[', encodeHost, false},
1905 {']', encodeHost, false},
1906 {'0', encodeHost, false},
1907 {'9', encodeHost, false},
1908 {'A', encodeHost, false},
1909 {'z', encodeHost, false},
1910 {'_', encodeHost, false},
1911 {'-', encodeHost, false},
1912 {'.', encodeHost, false},
1913 }
1914
1915 func TestShouldEscape(t *testing.T) {
1916 for _, tt := range shouldEscapeTests {
1917 if shouldEscape(tt.in, tt.mode) != tt.escape {
1918 t.Errorf("shouldEscape(%q, %v) returned %v; expected %v", tt.in, tt.mode, !tt.escape, tt.escape)
1919 }
1920 }
1921 }
1922
1923 type timeoutError struct {
1924 timeout bool
1925 }
1926
1927 func (e *timeoutError) Error() string { return "timeout error" }
1928 func (e *timeoutError) Timeout() bool { return e.timeout }
1929
1930 type temporaryError struct {
1931 temporary bool
1932 }
1933
1934 func (e *temporaryError) Error() string { return "temporary error" }
1935 func (e *temporaryError) Temporary() bool { return e.temporary }
1936
1937 type timeoutTemporaryError struct {
1938 timeoutError
1939 temporaryError
1940 }
1941
1942 func (e *timeoutTemporaryError) Error() string { return "timeout/temporary error" }
1943
1944 var netErrorTests = []struct {
1945 err error
1946 timeout bool
1947 temporary bool
1948 }{{
1949 err: &Error{"Get", "http://google.com/", &timeoutError{timeout: true}},
1950 timeout: true,
1951 temporary: false,
1952 }, {
1953 err: &Error{"Get", "http://google.com/", &timeoutError{timeout: false}},
1954 timeout: false,
1955 temporary: false,
1956 }, {
1957 err: &Error{"Get", "http://google.com/", &temporaryError{temporary: true}},
1958 timeout: false,
1959 temporary: true,
1960 }, {
1961 err: &Error{"Get", "http://google.com/", &temporaryError{temporary: false}},
1962 timeout: false,
1963 temporary: false,
1964 }, {
1965 err: &Error{"Get", "http://google.com/", &timeoutTemporaryError{timeoutError{timeout: true}, temporaryError{temporary: true}}},
1966 timeout: true,
1967 temporary: true,
1968 }, {
1969 err: &Error{"Get", "http://google.com/", &timeoutTemporaryError{timeoutError{timeout: false}, temporaryError{temporary: true}}},
1970 timeout: false,
1971 temporary: true,
1972 }, {
1973 err: &Error{"Get", "http://google.com/", &timeoutTemporaryError{timeoutError{timeout: true}, temporaryError{temporary: false}}},
1974 timeout: true,
1975 temporary: false,
1976 }, {
1977 err: &Error{"Get", "http://google.com/", &timeoutTemporaryError{timeoutError{timeout: false}, temporaryError{temporary: false}}},
1978 timeout: false,
1979 temporary: false,
1980 }, {
1981 err: &Error{"Get", "http://google.com/", io.EOF},
1982 timeout: false,
1983 temporary: false,
1984 }}
1985
1986
1987 func TestURLErrorImplementsNetError(t *testing.T) {
1988 for i, tt := range netErrorTests {
1989 err, ok := tt.err.(net.Error)
1990 if !ok {
1991 t.Errorf("%d: %T does not implement net.Error", i+1, tt.err)
1992 continue
1993 }
1994 if err.Timeout() != tt.timeout {
1995 t.Errorf("%d: err.Timeout(): got %v, want %v", i+1, err.Timeout(), tt.timeout)
1996 continue
1997 }
1998 if err.Temporary() != tt.temporary {
1999 t.Errorf("%d: err.Temporary(): got %v, want %v", i+1, err.Temporary(), tt.temporary)
2000 }
2001 }
2002 }
2003
2004 func TestURLHostnameAndPort(t *testing.T) {
2005 tests := []struct {
2006 in string
2007 host string
2008 port string
2009 }{
2010 {"foo.com:80", "foo.com", "80"},
2011 {"foo.com", "foo.com", ""},
2012 {"foo.com:", "foo.com", ""},
2013 {"FOO.COM", "FOO.COM", ""},
2014 {"1.2.3.4", "1.2.3.4", ""},
2015 {"1.2.3.4:80", "1.2.3.4", "80"},
2016 {"[1:2:3:4]", "1:2:3:4", ""},
2017 {"[1:2:3:4]:80", "1:2:3:4", "80"},
2018 {"[::1]:80", "::1", "80"},
2019 {"[::1]", "::1", ""},
2020 {"[::1]:", "::1", ""},
2021 {"localhost", "localhost", ""},
2022 {"localhost:443", "localhost", "443"},
2023 {"some.super.long.domain.example.org:8080", "some.super.long.domain.example.org", "8080"},
2024 {"[2001:0db8:85a3:0000:0000:8a2e:0370:7334]:17000", "2001:0db8:85a3:0000:0000:8a2e:0370:7334", "17000"},
2025 {"[2001:0db8:85a3:0000:0000:8a2e:0370:7334]", "2001:0db8:85a3:0000:0000:8a2e:0370:7334", ""},
2026
2027
2028
2029
2030 {"[google.com]:80", "google.com", "80"},
2031 {"google.com]:80", "google.com]", "80"},
2032 {"google.com:80_invalid_port", "google.com:80_invalid_port", ""},
2033 {"[::1]extra]:80", "::1]extra", "80"},
2034 {"google.com]extra:extra", "google.com]extra:extra", ""},
2035 }
2036 for _, tt := range tests {
2037 u := &URL{Host: tt.in}
2038 host, port := u.Hostname(), u.Port()
2039 if host != tt.host {
2040 t.Errorf("Hostname for Host %q = %q; want %q", tt.in, host, tt.host)
2041 }
2042 if port != tt.port {
2043 t.Errorf("Port for Host %q = %q; want %q", tt.in, port, tt.port)
2044 }
2045 }
2046 }
2047
2048 var _ encodingPkg.BinaryMarshaler = (*URL)(nil)
2049 var _ encodingPkg.BinaryUnmarshaler = (*URL)(nil)
2050 var _ encodingPkg.BinaryAppender = (*URL)(nil)
2051
2052 func TestJSON(t *testing.T) {
2053 u, err := Parse("https://www.google.com/x?y=z")
2054 if err != nil {
2055 t.Fatal(err)
2056 }
2057 js, err := json.Marshal(u)
2058 if err != nil {
2059 t.Fatal(err)
2060 }
2061
2062
2063
2064
2065
2066
2067
2068
2069 u1 := new(URL)
2070 err = json.Unmarshal(js, u1)
2071 if err != nil {
2072 t.Fatal(err)
2073 }
2074 if u1.String() != u.String() {
2075 t.Errorf("json decoded to: %s\nwant: %s\n", u1, u)
2076 }
2077 }
2078
2079 func TestGob(t *testing.T) {
2080 u, err := Parse("https://www.google.com/x?y=z")
2081 if err != nil {
2082 t.Fatal(err)
2083 }
2084 var w bytes.Buffer
2085 err = gob.NewEncoder(&w).Encode(u)
2086 if err != nil {
2087 t.Fatal(err)
2088 }
2089
2090 u1 := new(URL)
2091 err = gob.NewDecoder(&w).Decode(u1)
2092 if err != nil {
2093 t.Fatal(err)
2094 }
2095 if u1.String() != u.String() {
2096 t.Errorf("json decoded to: %s\nwant: %s\n", u1, u)
2097 }
2098 }
2099
2100 func TestNilUser(t *testing.T) {
2101 defer func() {
2102 if v := recover(); v != nil {
2103 t.Fatalf("unexpected panic: %v", v)
2104 }
2105 }()
2106
2107 u, err := Parse("http://foo.com/")
2108
2109 if err != nil {
2110 t.Fatalf("parse err: %v", err)
2111 }
2112
2113 if v := u.User.Username(); v != "" {
2114 t.Fatalf("expected empty username, got %s", v)
2115 }
2116
2117 if v, ok := u.User.Password(); v != "" || ok {
2118 t.Fatalf("expected empty password, got %s (%v)", v, ok)
2119 }
2120
2121 if v := u.User.String(); v != "" {
2122 t.Fatalf("expected empty string, got %s", v)
2123 }
2124 }
2125
2126 func TestInvalidUserPassword(t *testing.T) {
2127 _, err := Parse("http://user^:passwo^rd@foo.com/")
2128 if got, wantsub := fmt.Sprint(err), "net/url: invalid userinfo"; !strings.Contains(got, wantsub) {
2129 t.Errorf("error = %q; want substring %q", got, wantsub)
2130 }
2131 }
2132
2133 func TestRejectControlCharacters(t *testing.T) {
2134 tests := []string{
2135 "http://foo.com/?foo\nbar",
2136 "http\r://foo.com/",
2137 "http://foo\x7f.com/",
2138 }
2139 for _, s := range tests {
2140 _, err := Parse(s)
2141 const wantSub = "net/url: invalid control character in URL"
2142 if got := fmt.Sprint(err); !strings.Contains(got, wantSub) {
2143 t.Errorf("Parse(%q) error = %q; want substring %q", s, got, wantSub)
2144 }
2145 }
2146
2147
2148 if _, err := Parse("http://foo.com/ctl\x80"); err != nil {
2149 t.Errorf("error parsing URL with non-ASCII control byte: %v", err)
2150 }
2151
2152 }
2153
2154 var escapeBenchmarks = []struct {
2155 unescaped string
2156 query string
2157 path string
2158 }{
2159 {
2160 unescaped: "one two",
2161 query: "one+two",
2162 path: "one%20two",
2163 },
2164 {
2165 unescaped: "Фотки собак",
2166 query: "%D0%A4%D0%BE%D1%82%D0%BA%D0%B8+%D1%81%D0%BE%D0%B1%D0%B0%D0%BA",
2167 path: "%D0%A4%D0%BE%D1%82%D0%BA%D0%B8%20%D1%81%D0%BE%D0%B1%D0%B0%D0%BA",
2168 },
2169
2170 {
2171 unescaped: "shortrun(break)shortrun",
2172 query: "shortrun%28break%29shortrun",
2173 path: "shortrun%28break%29shortrun",
2174 },
2175
2176 {
2177 unescaped: "longerrunofcharacters(break)anotherlongerrunofcharacters",
2178 query: "longerrunofcharacters%28break%29anotherlongerrunofcharacters",
2179 path: "longerrunofcharacters%28break%29anotherlongerrunofcharacters",
2180 },
2181
2182 {
2183 unescaped: strings.Repeat("padded/with+various%characters?that=need$some@escaping+paddedsowebreak/256bytes", 4),
2184 query: strings.Repeat("padded%2Fwith%2Bvarious%25characters%3Fthat%3Dneed%24some%40escaping%2Bpaddedsowebreak%2F256bytes", 4),
2185 path: strings.Repeat("padded%2Fwith+various%25characters%3Fthat=need$some@escaping+paddedsowebreak%2F256bytes", 4),
2186 },
2187 }
2188
2189 func BenchmarkQueryEscape(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 = QueryEscape(tc.unescaped)
2196 }
2197 b.StopTimer()
2198 if g != tc.query {
2199 b.Errorf("QueryEscape(%q) == %q, want %q", tc.unescaped, g, tc.query)
2200 }
2201
2202 })
2203 }
2204 }
2205
2206 func BenchmarkPathEscape(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 = PathEscape(tc.unescaped)
2213 }
2214 b.StopTimer()
2215 if g != tc.path {
2216 b.Errorf("PathEscape(%q) == %q, want %q", tc.unescaped, g, tc.path)
2217 }
2218
2219 })
2220 }
2221 }
2222
2223 func BenchmarkQueryUnescape(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, _ = QueryUnescape(tc.query)
2230 }
2231 b.StopTimer()
2232 if g != tc.unescaped {
2233 b.Errorf("QueryUnescape(%q) == %q, want %q", tc.query, g, tc.unescaped)
2234 }
2235
2236 })
2237 }
2238 }
2239
2240 func BenchmarkPathUnescape(b *testing.B) {
2241 for _, tc := range escapeBenchmarks {
2242 b.Run("", func(b *testing.B) {
2243 b.ReportAllocs()
2244 var g string
2245 for i := 0; i < b.N; i++ {
2246 g, _ = PathUnescape(tc.path)
2247 }
2248 b.StopTimer()
2249 if g != tc.unescaped {
2250 b.Errorf("PathUnescape(%q) == %q, want %q", tc.path, g, tc.unescaped)
2251 }
2252
2253 })
2254 }
2255 }
2256
2257 func TestJoinPath(t *testing.T) {
2258 tests := []struct {
2259 base string
2260 elem []string
2261 out string
2262 }{
2263 {
2264 base: "https://go.googlesource.com",
2265 elem: []string{"go"},
2266 out: "https://go.googlesource.com/go",
2267 },
2268 {
2269 base: "https://go.googlesource.com/a/b/c",
2270 elem: []string{"../../../go"},
2271 out: "https://go.googlesource.com/go",
2272 },
2273 {
2274 base: "https://go.googlesource.com/",
2275 elem: []string{"../go"},
2276 out: "https://go.googlesource.com/go",
2277 },
2278 {
2279 base: "https://go.googlesource.com",
2280 elem: []string{"../go", "../../go", "../../../go"},
2281 out: "https://go.googlesource.com/go",
2282 },
2283 {
2284 base: "https://go.googlesource.com/../go",
2285 elem: nil,
2286 out: "https://go.googlesource.com/go",
2287 },
2288 {
2289 base: "https://go.googlesource.com/",
2290 elem: []string{"./go"},
2291 out: "https://go.googlesource.com/go",
2292 },
2293 {
2294 base: "https://go.googlesource.com//",
2295 elem: []string{"/go"},
2296 out: "https://go.googlesource.com/go",
2297 },
2298 {
2299 base: "https://go.googlesource.com//",
2300 elem: []string{"/go", "a", "b", "c"},
2301 out: "https://go.googlesource.com/go/a/b/c",
2302 },
2303 {
2304 base: "http://[fe80::1%en0]:8080/",
2305 elem: []string{"/go"},
2306 },
2307 {
2308 base: "https://go.googlesource.com",
2309 elem: []string{"go/"},
2310 out: "https://go.googlesource.com/go/",
2311 },
2312 {
2313 base: "https://go.googlesource.com",
2314 elem: []string{"go//"},
2315 out: "https://go.googlesource.com/go/",
2316 },
2317 {
2318 base: "https://go.googlesource.com",
2319 elem: nil,
2320 out: "https://go.googlesource.com/",
2321 },
2322 {
2323 base: "https://go.googlesource.com/",
2324 elem: nil,
2325 out: "https://go.googlesource.com/",
2326 },
2327 {
2328 base: "https://go.googlesource.com/a%2fb",
2329 elem: []string{"c"},
2330 out: "https://go.googlesource.com/a%2fb/c",
2331 },
2332 {
2333 base: "https://go.googlesource.com/a%2fb",
2334 elem: []string{"c%2fd"},
2335 out: "https://go.googlesource.com/a%2fb/c%2fd",
2336 },
2337 {
2338 base: "https://go.googlesource.com/a/b",
2339 elem: []string{"/go"},
2340 out: "https://go.googlesource.com/a/b/go",
2341 },
2342 {
2343 base: "https://go.googlesource.com/",
2344 elem: []string{"100%"},
2345 },
2346 {
2347 base: "/",
2348 elem: nil,
2349 out: "/",
2350 },
2351 {
2352 base: "a",
2353 elem: nil,
2354 out: "a",
2355 },
2356 {
2357 base: "a",
2358 elem: []string{"b"},
2359 out: "a/b",
2360 },
2361 {
2362 base: "a",
2363 elem: []string{"../b"},
2364 out: "b",
2365 },
2366 {
2367 base: "a",
2368 elem: []string{"../../b"},
2369 out: "b",
2370 },
2371 {
2372 base: "",
2373 elem: []string{"a"},
2374 out: "a",
2375 },
2376 {
2377 base: "",
2378 elem: []string{"../a"},
2379 out: "a",
2380 },
2381 }
2382 for _, tt := range tests {
2383 wantErr := "nil"
2384 if tt.out == "" {
2385 wantErr = "non-nil error"
2386 }
2387 out, err := JoinPath(tt.base, tt.elem...)
2388 if out != tt.out || (err == nil) != (tt.out != "") {
2389 t.Errorf("JoinPath(%q, %q) = %q, %v, want %q, %v", tt.base, tt.elem, out, err, tt.out, wantErr)
2390 }
2391
2392 u, err := Parse(tt.base)
2393 if err != nil {
2394 if tt.out != "" {
2395 t.Errorf("Parse(%q) = %v", tt.base, err)
2396 }
2397 continue
2398 }
2399 if tt.out == "" {
2400
2401 tt.out = tt.base
2402 }
2403 out = u.JoinPath(tt.elem...).String()
2404 if out != tt.out {
2405 t.Errorf("Parse(%q).JoinPath(%q) = %q, want %q", tt.base, tt.elem, out, tt.out)
2406 }
2407 }
2408 }
2409
2410 func TestParseStrictIpv6(t *testing.T) {
2411 t.Setenv("GODEBUG", "urlstrictcolons=0")
2412
2413 tests := []struct {
2414 url string
2415 }{
2416
2417 {"https://1:2:3:4:5:6:7:8"},
2418 {"https://1:2:3:4:5:6:7:8:80"},
2419 {"https://example.com:80:"},
2420 }
2421 for i, tc := range tests {
2422 t.Run(strconv.Itoa(i), func(t *testing.T) {
2423 _, err := Parse(tc.url)
2424 if err != nil {
2425 t.Errorf("Parse(%q) error = %v, want nil", tc.url, err)
2426 }
2427 })
2428 }
2429
2430 }
2431
2432 func TestURLClone(t *testing.T) {
2433 tests := []struct {
2434 name string
2435 in *URL
2436 }{
2437 {"nil", nil},
2438 {"zero value", &URL{}},
2439 {
2440 "Populated but nil .User",
2441 &URL{
2442 User: nil,
2443 Host: "foo",
2444 Path: "/path",
2445 RawQuery: "a=b",
2446 },
2447 },
2448 {
2449 "non-nil .User",
2450 &URL{
2451 User: User("user"),
2452 Host: "foo",
2453 Path: "/path",
2454 RawQuery: "a=b",
2455 },
2456 },
2457 {
2458 "non-nil .User: user and password set",
2459 &URL{
2460 User: UserPassword("user", "password"),
2461 Host: "foo",
2462 Path: "/path",
2463 RawQuery: "a=b",
2464 },
2465 },
2466 }
2467
2468 for _, tt := range tests {
2469 t.Run(tt.name, func(t *testing.T) {
2470
2471 cloned := tt.in.Clone()
2472 if !reflect.DeepEqual(tt.in, cloned) {
2473 t.Fatalf("Differing values\n%s",
2474 diff.Diff("original", []byte(tt.in.String()), "cloned", []byte(cloned.String())))
2475 }
2476 if tt.in == nil {
2477 return
2478 }
2479
2480
2481 if tt.in == cloned {
2482 t.Fatalf("URL: same pointer returned: %p", cloned)
2483 }
2484
2485
2486 cloned.Scheme = "https"
2487 if cloned.Scheme == tt.in.Scheme {
2488 t.Error("Inconsistent state: cloned.scheme changed and reflected in the input's scheme")
2489 }
2490 if reflect.DeepEqual(tt.in, cloned) {
2491 t.Fatal("Inconsistent state: cloned and input are somehow the same")
2492 }
2493
2494
2495 if !reflect.DeepEqual(tt.in.User, cloned.User) {
2496 t.Fatalf("Differing .User\n%s",
2497 diff.Diff("original", []byte(tt.in.String()), "cloned", []byte(cloned.String())))
2498 }
2499 bothNil := tt.in.User == nil && cloned.User == nil
2500 if !bothNil && tt.in.User == cloned.User {
2501 t.Fatalf(".User: same pointer returned: %p", cloned.User)
2502 }
2503 })
2504 }
2505 }
2506
2507 func TestValuesClone(t *testing.T) {
2508 tests := []struct {
2509 name string
2510 in Values
2511 }{
2512 {"nil", nil},
2513 {"empty", Values{}},
2514 {"1 key, nil values", Values{"1": nil}},
2515 {"1 key, no values", Values{"1": {}}},
2516 {"1 key, some values", Values{"1": {"a", "b"}}},
2517 {"multiple keys, diverse values", Values{"1": {"a", "b"}, "X": nil, "B": {"abcdefghi"}}},
2518 }
2519
2520 for _, tt := range tests {
2521 t.Run(tt.name, func(t *testing.T) {
2522
2523 cloned1 := tt.in.Clone()
2524 if !reflect.DeepEqual(tt.in, cloned1) {
2525 t.Fatal("reflect.DeepEqual failed")
2526 }
2527
2528 if cloned1 == nil && tt.in == nil {
2529 return
2530 }
2531 if len(cloned1) == 0 && len(tt.in) == 0 && (cloned1 == nil || tt.in == nil) {
2532 t.Fatalf("Inconsistency: both have len=0, yet not both nil\nCloned: %#v\nOriginal: %#v\n", cloned1, tt.in)
2533 }
2534
2535 cloned1["XXXXXXXXXXX"] = []string{"a", "b"}
2536 if reflect.DeepEqual(tt.in, cloned1) {
2537 t.Fatal("Inconsistent state: cloned and input are somehow the same")
2538 }
2539
2540
2541 cloned2 := tt.in.Clone()
2542 if !reflect.DeepEqual(tt.in, cloned2) {
2543 t.Fatal("reflect.DeepEqual failed")
2544 }
2545 cloned2.Add("a", "A")
2546 if !cloned2.Has("a") {
2547 t.Error("Cloned doesn't have the desired key: a")
2548 }
2549 if !cloned2.Has("a") {
2550 t.Error("Cloned doesn't have the desired key: a")
2551 }
2552
2553 if reflect.DeepEqual(tt.in, cloned2) {
2554 t.Fatal("reflect.DeepEqual unexpectedly passed after modify cloned")
2555 }
2556 cloned2.Del("a")
2557
2558 if !reflect.DeepEqual(tt.in, cloned2) {
2559 t.Fatal("reflect.DeepEqual failed")
2560 }
2561
2562 cloned3 := tt.in.Clone()
2563 clonedKeys := slices.Collect(maps.Keys(cloned3))
2564 if len(clonedKeys) == 0 {
2565 return
2566 }
2567 key0 := clonedKeys[0]
2568
2569 if len(cloned3[key0]) == 0 {
2570 cloned3[key0] = append(cloned3[key0], "golang")
2571 } else {
2572 cloned3[key0][0] = "directly modified"
2573 if got, want := cloned3.Get(key0), "directly modified"; got != want {
2574 t.Errorf("Get failed:\n\tGot: %q\n\tWant: %q", got, want)
2575 }
2576 }
2577 if reflect.DeepEqual(tt.in, cloned3) {
2578 t.Fatal("reflect.DeepEqual unexpectedly passed after modify cloned")
2579 }
2580
2581
2582 cloned4 := tt.in.Clone()
2583 if !reflect.DeepEqual(tt.in, cloned4) {
2584 t.Fatal("reflect.DeepEqual failed")
2585 }
2586 cloned4.Set(key0, "good night")
2587 if reflect.DeepEqual(tt.in, cloned4) {
2588 t.Fatal("reflect.DeepEqual unexpectedly passed after modify cloned")
2589 }
2590 if got, want := cloned4.Get(key0), "good night"; got != want {
2591 t.Errorf("Get failed:\n\tGot: %q\n\tWant: %q", got, want)
2592 }
2593 })
2594 }
2595 }
2596
View as plain text