github.com/riscv/riscv-go@v0.0.0-20200123204226-124ebd6fcc8e/src/net/url/url_test.go (about) 1 // Copyright 2009 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package url 6 7 import ( 8 "bytes" 9 encodingPkg "encoding" 10 "encoding/gob" 11 "encoding/json" 12 "fmt" 13 "io" 14 "net" 15 "reflect" 16 "strings" 17 "testing" 18 ) 19 20 type URLTest struct { 21 in string 22 out *URL // expected parse; RawPath="" means same as Path 23 roundtrip string // expected result of reserializing the URL; empty means same as "in". 24 } 25 26 var urltests = []URLTest{ 27 // no path 28 { 29 "http://www.google.com", 30 &URL{ 31 Scheme: "http", 32 Host: "www.google.com", 33 }, 34 "", 35 }, 36 // path 37 { 38 "http://www.google.com/", 39 &URL{ 40 Scheme: "http", 41 Host: "www.google.com", 42 Path: "/", 43 }, 44 "", 45 }, 46 // path with hex escaping 47 { 48 "http://www.google.com/file%20one%26two", 49 &URL{ 50 Scheme: "http", 51 Host: "www.google.com", 52 Path: "/file one&two", 53 RawPath: "/file%20one%26two", 54 }, 55 "", 56 }, 57 // user 58 { 59 "ftp://webmaster@www.google.com/", 60 &URL{ 61 Scheme: "ftp", 62 User: User("webmaster"), 63 Host: "www.google.com", 64 Path: "/", 65 }, 66 "", 67 }, 68 // escape sequence in username 69 { 70 "ftp://john%20doe@www.google.com/", 71 &URL{ 72 Scheme: "ftp", 73 User: User("john doe"), 74 Host: "www.google.com", 75 Path: "/", 76 }, 77 "ftp://john%20doe@www.google.com/", 78 }, 79 // empty query 80 { 81 "http://www.google.com/?", 82 &URL{ 83 Scheme: "http", 84 Host: "www.google.com", 85 Path: "/", 86 ForceQuery: true, 87 }, 88 "", 89 }, 90 // query ending in question mark (Issue 14573) 91 { 92 "http://www.google.com/?foo=bar?", 93 &URL{ 94 Scheme: "http", 95 Host: "www.google.com", 96 Path: "/", 97 RawQuery: "foo=bar?", 98 }, 99 "", 100 }, 101 // query 102 { 103 "http://www.google.com/?q=go+language", 104 &URL{ 105 Scheme: "http", 106 Host: "www.google.com", 107 Path: "/", 108 RawQuery: "q=go+language", 109 }, 110 "", 111 }, 112 // query with hex escaping: NOT parsed 113 { 114 "http://www.google.com/?q=go%20language", 115 &URL{ 116 Scheme: "http", 117 Host: "www.google.com", 118 Path: "/", 119 RawQuery: "q=go%20language", 120 }, 121 "", 122 }, 123 // %20 outside query 124 { 125 "http://www.google.com/a%20b?q=c+d", 126 &URL{ 127 Scheme: "http", 128 Host: "www.google.com", 129 Path: "/a b", 130 RawQuery: "q=c+d", 131 }, 132 "", 133 }, 134 // path without leading /, so no parsing 135 { 136 "http:www.google.com/?q=go+language", 137 &URL{ 138 Scheme: "http", 139 Opaque: "www.google.com/", 140 RawQuery: "q=go+language", 141 }, 142 "http:www.google.com/?q=go+language", 143 }, 144 // path without leading /, so no parsing 145 { 146 "http:%2f%2fwww.google.com/?q=go+language", 147 &URL{ 148 Scheme: "http", 149 Opaque: "%2f%2fwww.google.com/", 150 RawQuery: "q=go+language", 151 }, 152 "http:%2f%2fwww.google.com/?q=go+language", 153 }, 154 // non-authority with path 155 { 156 "mailto:/webmaster@golang.org", 157 &URL{ 158 Scheme: "mailto", 159 Path: "/webmaster@golang.org", 160 }, 161 "mailto:///webmaster@golang.org", // unfortunate compromise 162 }, 163 // non-authority 164 { 165 "mailto:webmaster@golang.org", 166 &URL{ 167 Scheme: "mailto", 168 Opaque: "webmaster@golang.org", 169 }, 170 "", 171 }, 172 // unescaped :// in query should not create a scheme 173 { 174 "/foo?query=http://bad", 175 &URL{ 176 Path: "/foo", 177 RawQuery: "query=http://bad", 178 }, 179 "", 180 }, 181 // leading // without scheme should create an authority 182 { 183 "//foo", 184 &URL{ 185 Host: "foo", 186 }, 187 "", 188 }, 189 // leading // without scheme, with userinfo, path, and query 190 { 191 "//user@foo/path?a=b", 192 &URL{ 193 User: User("user"), 194 Host: "foo", 195 Path: "/path", 196 RawQuery: "a=b", 197 }, 198 "", 199 }, 200 // Three leading slashes isn't an authority, but doesn't return an error. 201 // (We can't return an error, as this code is also used via 202 // ServeHTTP -> ReadRequest -> Parse, which is arguably a 203 // different URL parsing context, but currently shares the 204 // same codepath) 205 { 206 "///threeslashes", 207 &URL{ 208 Path: "///threeslashes", 209 }, 210 "", 211 }, 212 { 213 "http://user:password@google.com", 214 &URL{ 215 Scheme: "http", 216 User: UserPassword("user", "password"), 217 Host: "google.com", 218 }, 219 "http://user:password@google.com", 220 }, 221 // unescaped @ in username should not confuse host 222 { 223 "http://j@ne:password@google.com", 224 &URL{ 225 Scheme: "http", 226 User: UserPassword("j@ne", "password"), 227 Host: "google.com", 228 }, 229 "http://j%40ne:password@google.com", 230 }, 231 // unescaped @ in password should not confuse host 232 { 233 "http://jane:p@ssword@google.com", 234 &URL{ 235 Scheme: "http", 236 User: UserPassword("jane", "p@ssword"), 237 Host: "google.com", 238 }, 239 "http://jane:p%40ssword@google.com", 240 }, 241 { 242 "http://j@ne:password@google.com/p@th?q=@go", 243 &URL{ 244 Scheme: "http", 245 User: UserPassword("j@ne", "password"), 246 Host: "google.com", 247 Path: "/p@th", 248 RawQuery: "q=@go", 249 }, 250 "http://j%40ne:password@google.com/p@th?q=@go", 251 }, 252 { 253 "http://www.google.com/?q=go+language#foo", 254 &URL{ 255 Scheme: "http", 256 Host: "www.google.com", 257 Path: "/", 258 RawQuery: "q=go+language", 259 Fragment: "foo", 260 }, 261 "", 262 }, 263 { 264 "http://www.google.com/?q=go+language#foo%26bar", 265 &URL{ 266 Scheme: "http", 267 Host: "www.google.com", 268 Path: "/", 269 RawQuery: "q=go+language", 270 Fragment: "foo&bar", 271 }, 272 "http://www.google.com/?q=go+language#foo&bar", 273 }, 274 { 275 "file:///home/adg/rabbits", 276 &URL{ 277 Scheme: "file", 278 Host: "", 279 Path: "/home/adg/rabbits", 280 }, 281 "file:///home/adg/rabbits", 282 }, 283 // "Windows" paths are no exception to the rule. 284 // See golang.org/issue/6027, especially comment #9. 285 { 286 "file:///C:/FooBar/Baz.txt", 287 &URL{ 288 Scheme: "file", 289 Host: "", 290 Path: "/C:/FooBar/Baz.txt", 291 }, 292 "file:///C:/FooBar/Baz.txt", 293 }, 294 // case-insensitive scheme 295 { 296 "MaIlTo:webmaster@golang.org", 297 &URL{ 298 Scheme: "mailto", 299 Opaque: "webmaster@golang.org", 300 }, 301 "mailto:webmaster@golang.org", 302 }, 303 // Relative path 304 { 305 "a/b/c", 306 &URL{ 307 Path: "a/b/c", 308 }, 309 "a/b/c", 310 }, 311 // escaped '?' in username and password 312 { 313 "http://%3Fam:pa%3Fsword@google.com", 314 &URL{ 315 Scheme: "http", 316 User: UserPassword("?am", "pa?sword"), 317 Host: "google.com", 318 }, 319 "", 320 }, 321 // host subcomponent; IPv4 address in RFC 3986 322 { 323 "http://192.168.0.1/", 324 &URL{ 325 Scheme: "http", 326 Host: "192.168.0.1", 327 Path: "/", 328 }, 329 "", 330 }, 331 // host and port subcomponents; IPv4 address in RFC 3986 332 { 333 "http://192.168.0.1:8080/", 334 &URL{ 335 Scheme: "http", 336 Host: "192.168.0.1:8080", 337 Path: "/", 338 }, 339 "", 340 }, 341 // host subcomponent; IPv6 address in RFC 3986 342 { 343 "http://[fe80::1]/", 344 &URL{ 345 Scheme: "http", 346 Host: "[fe80::1]", 347 Path: "/", 348 }, 349 "", 350 }, 351 // host and port subcomponents; IPv6 address in RFC 3986 352 { 353 "http://[fe80::1]:8080/", 354 &URL{ 355 Scheme: "http", 356 Host: "[fe80::1]:8080", 357 Path: "/", 358 }, 359 "", 360 }, 361 // host subcomponent; IPv6 address with zone identifier in RFC 6874 362 { 363 "http://[fe80::1%25en0]/", // alphanum zone identifier 364 &URL{ 365 Scheme: "http", 366 Host: "[fe80::1%en0]", 367 Path: "/", 368 }, 369 "", 370 }, 371 // host and port subcomponents; IPv6 address with zone identifier in RFC 6874 372 { 373 "http://[fe80::1%25en0]:8080/", // alphanum zone identifier 374 &URL{ 375 Scheme: "http", 376 Host: "[fe80::1%en0]:8080", 377 Path: "/", 378 }, 379 "", 380 }, 381 // host subcomponent; IPv6 address with zone identifier in RFC 6874 382 { 383 "http://[fe80::1%25%65%6e%301-._~]/", // percent-encoded+unreserved zone identifier 384 &URL{ 385 Scheme: "http", 386 Host: "[fe80::1%en01-._~]", 387 Path: "/", 388 }, 389 "http://[fe80::1%25en01-._~]/", 390 }, 391 // host and port subcomponents; IPv6 address with zone identifier in RFC 6874 392 { 393 "http://[fe80::1%25%65%6e%301-._~]:8080/", // percent-encoded+unreserved zone identifier 394 &URL{ 395 Scheme: "http", 396 Host: "[fe80::1%en01-._~]:8080", 397 Path: "/", 398 }, 399 "http://[fe80::1%25en01-._~]:8080/", 400 }, 401 // alternate escapings of path survive round trip 402 { 403 "http://rest.rsc.io/foo%2fbar/baz%2Fquux?alt=media", 404 &URL{ 405 Scheme: "http", 406 Host: "rest.rsc.io", 407 Path: "/foo/bar/baz/quux", 408 RawPath: "/foo%2fbar/baz%2Fquux", 409 RawQuery: "alt=media", 410 }, 411 "", 412 }, 413 // issue 12036 414 { 415 "mysql://a,b,c/bar", 416 &URL{ 417 Scheme: "mysql", 418 Host: "a,b,c", 419 Path: "/bar", 420 }, 421 "", 422 }, 423 // worst case host, still round trips 424 { 425 "scheme://!$&'()*+,;=hello!:port/path", 426 &URL{ 427 Scheme: "scheme", 428 Host: "!$&'()*+,;=hello!:port", 429 Path: "/path", 430 }, 431 "", 432 }, 433 // worst case path, still round trips 434 { 435 "http://host/!$&'()*+,;=:@[hello]", 436 &URL{ 437 Scheme: "http", 438 Host: "host", 439 Path: "/!$&'()*+,;=:@[hello]", 440 RawPath: "/!$&'()*+,;=:@[hello]", 441 }, 442 "", 443 }, 444 // golang.org/issue/5684 445 { 446 "http://example.com/oid/[order_id]", 447 &URL{ 448 Scheme: "http", 449 Host: "example.com", 450 Path: "/oid/[order_id]", 451 RawPath: "/oid/[order_id]", 452 }, 453 "", 454 }, 455 // golang.org/issue/12200 (colon with empty port) 456 { 457 "http://192.168.0.2:8080/foo", 458 &URL{ 459 Scheme: "http", 460 Host: "192.168.0.2:8080", 461 Path: "/foo", 462 }, 463 "", 464 }, 465 { 466 "http://192.168.0.2:/foo", 467 &URL{ 468 Scheme: "http", 469 Host: "192.168.0.2:", 470 Path: "/foo", 471 }, 472 "", 473 }, 474 { 475 // Malformed IPv6 but still accepted. 476 "http://2b01:e34:ef40:7730:8e70:5aff:fefe:edac:8080/foo", 477 &URL{ 478 Scheme: "http", 479 Host: "2b01:e34:ef40:7730:8e70:5aff:fefe:edac:8080", 480 Path: "/foo", 481 }, 482 "", 483 }, 484 { 485 // Malformed IPv6 but still accepted. 486 "http://2b01:e34:ef40:7730:8e70:5aff:fefe:edac:/foo", 487 &URL{ 488 Scheme: "http", 489 Host: "2b01:e34:ef40:7730:8e70:5aff:fefe:edac:", 490 Path: "/foo", 491 }, 492 "", 493 }, 494 { 495 "http://[2b01:e34:ef40:7730:8e70:5aff:fefe:edac]:8080/foo", 496 &URL{ 497 Scheme: "http", 498 Host: "[2b01:e34:ef40:7730:8e70:5aff:fefe:edac]:8080", 499 Path: "/foo", 500 }, 501 "", 502 }, 503 { 504 "http://[2b01:e34:ef40:7730:8e70:5aff:fefe:edac]:/foo", 505 &URL{ 506 Scheme: "http", 507 Host: "[2b01:e34:ef40:7730:8e70:5aff:fefe:edac]:", 508 Path: "/foo", 509 }, 510 "", 511 }, 512 // golang.org/issue/7991 and golang.org/issue/12719 (non-ascii %-encoded in host) 513 { 514 "http://hello.世界.com/foo", 515 &URL{ 516 Scheme: "http", 517 Host: "hello.世界.com", 518 Path: "/foo", 519 }, 520 "http://hello.%E4%B8%96%E7%95%8C.com/foo", 521 }, 522 { 523 "http://hello.%e4%b8%96%e7%95%8c.com/foo", 524 &URL{ 525 Scheme: "http", 526 Host: "hello.世界.com", 527 Path: "/foo", 528 }, 529 "http://hello.%E4%B8%96%E7%95%8C.com/foo", 530 }, 531 { 532 "http://hello.%E4%B8%96%E7%95%8C.com/foo", 533 &URL{ 534 Scheme: "http", 535 Host: "hello.世界.com", 536 Path: "/foo", 537 }, 538 "", 539 }, 540 // golang.org/issue/10433 (path beginning with //) 541 { 542 "http://example.com//foo", 543 &URL{ 544 Scheme: "http", 545 Host: "example.com", 546 Path: "//foo", 547 }, 548 "", 549 }, 550 // test that we can reparse the host names we accept. 551 { 552 "myscheme://authority<\"hi\">/foo", 553 &URL{ 554 Scheme: "myscheme", 555 Host: "authority<\"hi\">", 556 Path: "/foo", 557 }, 558 "", 559 }, 560 // spaces in hosts are disallowed but escaped spaces in IPv6 scope IDs are grudgingly OK. 561 // This happens on Windows. 562 // golang.org/issue/14002 563 { 564 "tcp://[2020::2020:20:2020:2020%25Windows%20Loves%20Spaces]:2020", 565 &URL{ 566 Scheme: "tcp", 567 Host: "[2020::2020:20:2020:2020%Windows Loves Spaces]:2020", 568 }, 569 "", 570 }, 571 } 572 573 // more useful string for debugging than fmt's struct printer 574 func ufmt(u *URL) string { 575 var user, pass interface{} 576 if u.User != nil { 577 user = u.User.Username() 578 if p, ok := u.User.Password(); ok { 579 pass = p 580 } 581 } 582 return fmt.Sprintf("opaque=%q, scheme=%q, user=%#v, pass=%#v, host=%q, path=%q, rawpath=%q, rawq=%q, frag=%q, forcequery=%v", 583 u.Opaque, u.Scheme, user, pass, u.Host, u.Path, u.RawPath, u.RawQuery, u.Fragment, u.ForceQuery) 584 } 585 586 func BenchmarkString(b *testing.B) { 587 b.StopTimer() 588 b.ReportAllocs() 589 for _, tt := range urltests { 590 u, err := Parse(tt.in) 591 if err != nil { 592 b.Errorf("Parse(%q) returned error %s", tt.in, err) 593 continue 594 } 595 if tt.roundtrip == "" { 596 continue 597 } 598 b.StartTimer() 599 var g string 600 for i := 0; i < b.N; i++ { 601 g = u.String() 602 } 603 b.StopTimer() 604 if w := tt.roundtrip; b.N > 0 && g != w { 605 b.Errorf("Parse(%q).String() == %q, want %q", tt.in, g, w) 606 } 607 } 608 } 609 610 func TestParse(t *testing.T) { 611 for _, tt := range urltests { 612 u, err := Parse(tt.in) 613 if err != nil { 614 t.Errorf("Parse(%q) returned error %v", tt.in, err) 615 continue 616 } 617 if !reflect.DeepEqual(u, tt.out) { 618 t.Errorf("Parse(%q):\n\tgot %v\n\twant %v\n", tt.in, ufmt(u), ufmt(tt.out)) 619 } 620 } 621 } 622 623 const pathThatLooksSchemeRelative = "//not.a.user@not.a.host/just/a/path" 624 625 var parseRequestURLTests = []struct { 626 url string 627 expectedValid bool 628 }{ 629 {"http://foo.com", true}, 630 {"http://foo.com/", true}, 631 {"http://foo.com/path", true}, 632 {"/", true}, 633 {pathThatLooksSchemeRelative, true}, 634 {"//not.a.user@%66%6f%6f.com/just/a/path/also", true}, 635 {"*", true}, 636 {"http://192.168.0.1/", true}, 637 {"http://192.168.0.1:8080/", true}, 638 {"http://[fe80::1]/", true}, 639 {"http://[fe80::1]:8080/", true}, 640 641 // Tests exercising RFC 6874 compliance: 642 {"http://[fe80::1%25en0]/", true}, // with alphanum zone identifier 643 {"http://[fe80::1%25en0]:8080/", true}, // with alphanum zone identifier 644 {"http://[fe80::1%25%65%6e%301-._~]/", true}, // with percent-encoded+unreserved zone identifier 645 {"http://[fe80::1%25%65%6e%301-._~]:8080/", true}, // with percent-encoded+unreserved zone identifier 646 647 {"foo.html", false}, 648 {"../dir/", false}, 649 {"http://192.168.0.%31/", false}, 650 {"http://192.168.0.%31:8080/", false}, 651 {"http://[fe80::%31]/", false}, 652 {"http://[fe80::%31]:8080/", false}, 653 {"http://[fe80::%31%25en0]/", false}, 654 {"http://[fe80::%31%25en0]:8080/", false}, 655 656 // These two cases are valid as textual representations as 657 // described in RFC 4007, but are not valid as address 658 // literals with IPv6 zone identifiers in URIs as described in 659 // RFC 6874. 660 {"http://[fe80::1%en0]/", false}, 661 {"http://[fe80::1%en0]:8080/", false}, 662 } 663 664 func TestParseRequestURI(t *testing.T) { 665 for _, test := range parseRequestURLTests { 666 _, err := ParseRequestURI(test.url) 667 if test.expectedValid && err != nil { 668 t.Errorf("ParseRequestURI(%q) gave err %v; want no error", test.url, err) 669 } else if !test.expectedValid && err == nil { 670 t.Errorf("ParseRequestURI(%q) gave nil error; want some error", test.url) 671 } 672 } 673 674 url, err := ParseRequestURI(pathThatLooksSchemeRelative) 675 if err != nil { 676 t.Fatalf("Unexpected error %v", err) 677 } 678 if url.Path != pathThatLooksSchemeRelative { 679 t.Errorf("ParseRequestURI path:\ngot %q\nwant %q", url.Path, pathThatLooksSchemeRelative) 680 } 681 } 682 683 var stringURLTests = []struct { 684 url URL 685 want string 686 }{ 687 // No leading slash on path should prepend slash on String() call 688 { 689 url: URL{ 690 Scheme: "http", 691 Host: "www.google.com", 692 Path: "search", 693 }, 694 want: "http://www.google.com/search", 695 }, 696 // Relative path with first element containing ":" should be prepended with "./", golang.org/issue/17184 697 { 698 url: URL{ 699 Path: "this:that", 700 }, 701 want: "./this:that", 702 }, 703 // Relative path with second element containing ":" should not be prepended with "./" 704 { 705 url: URL{ 706 Path: "here/this:that", 707 }, 708 want: "here/this:that", 709 }, 710 // Non-relative path with first element containing ":" should not be prepended with "./" 711 { 712 url: URL{ 713 Scheme: "http", 714 Host: "www.google.com", 715 Path: "this:that", 716 }, 717 want: "http://www.google.com/this:that", 718 }, 719 } 720 721 func TestURLString(t *testing.T) { 722 for _, tt := range urltests { 723 u, err := Parse(tt.in) 724 if err != nil { 725 t.Errorf("Parse(%q) returned error %s", tt.in, err) 726 continue 727 } 728 expected := tt.in 729 if tt.roundtrip != "" { 730 expected = tt.roundtrip 731 } 732 s := u.String() 733 if s != expected { 734 t.Errorf("Parse(%q).String() == %q (expected %q)", tt.in, s, expected) 735 } 736 } 737 738 for _, tt := range stringURLTests { 739 if got := tt.url.String(); got != tt.want { 740 t.Errorf("%+v.String() = %q; want %q", tt.url, got, tt.want) 741 } 742 } 743 } 744 745 type EscapeTest struct { 746 in string 747 out string 748 err error 749 } 750 751 var unescapeTests = []EscapeTest{ 752 { 753 "", 754 "", 755 nil, 756 }, 757 { 758 "abc", 759 "abc", 760 nil, 761 }, 762 { 763 "1%41", 764 "1A", 765 nil, 766 }, 767 { 768 "1%41%42%43", 769 "1ABC", 770 nil, 771 }, 772 { 773 "%4a", 774 "J", 775 nil, 776 }, 777 { 778 "%6F", 779 "o", 780 nil, 781 }, 782 { 783 "%", // not enough characters after % 784 "", 785 EscapeError("%"), 786 }, 787 { 788 "%a", // not enough characters after % 789 "", 790 EscapeError("%a"), 791 }, 792 { 793 "%1", // not enough characters after % 794 "", 795 EscapeError("%1"), 796 }, 797 { 798 "123%45%6", // not enough characters after % 799 "", 800 EscapeError("%6"), 801 }, 802 { 803 "%zzzzz", // invalid hex digits 804 "", 805 EscapeError("%zz"), 806 }, 807 { 808 "a+b", 809 "a b", 810 nil, 811 }, 812 { 813 "a%20b", 814 "a b", 815 nil, 816 }, 817 } 818 819 func TestUnescape(t *testing.T) { 820 for _, tt := range unescapeTests { 821 actual, err := QueryUnescape(tt.in) 822 if actual != tt.out || (err != nil) != (tt.err != nil) { 823 t.Errorf("QueryUnescape(%q) = %q, %s; want %q, %s", tt.in, actual, err, tt.out, tt.err) 824 } 825 826 in := tt.in 827 out := tt.out 828 if strings.Contains(tt.in, "+") { 829 in = strings.Replace(tt.in, "+", "%20", -1) 830 actual, err := PathUnescape(in) 831 if actual != tt.out || (err != nil) != (tt.err != nil) { 832 t.Errorf("PathUnescape(%q) = %q, %s; want %q, %s", in, actual, err, tt.out, tt.err) 833 } 834 if tt.err == nil { 835 s, err := QueryUnescape(strings.Replace(tt.in, "+", "XXX", -1)) 836 if err != nil { 837 continue 838 } 839 in = tt.in 840 out = strings.Replace(s, "XXX", "+", -1) 841 } 842 } 843 844 actual, err = PathUnescape(in) 845 if actual != out || (err != nil) != (tt.err != nil) { 846 t.Errorf("PathUnescape(%q) = %q, %s; want %q, %s", in, actual, err, out, tt.err) 847 } 848 } 849 } 850 851 var queryEscapeTests = []EscapeTest{ 852 { 853 "", 854 "", 855 nil, 856 }, 857 { 858 "abc", 859 "abc", 860 nil, 861 }, 862 { 863 "one two", 864 "one+two", 865 nil, 866 }, 867 { 868 "10%", 869 "10%25", 870 nil, 871 }, 872 { 873 " ?&=#+%!<>#\"{}|\\^[]`☺\t:/@$'()*,;", 874 "+%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", 875 nil, 876 }, 877 } 878 879 func TestQueryEscape(t *testing.T) { 880 for _, tt := range queryEscapeTests { 881 actual := QueryEscape(tt.in) 882 if tt.out != actual { 883 t.Errorf("QueryEscape(%q) = %q, want %q", tt.in, actual, tt.out) 884 } 885 886 // for bonus points, verify that escape:unescape is an identity. 887 roundtrip, err := QueryUnescape(actual) 888 if roundtrip != tt.in || err != nil { 889 t.Errorf("QueryUnescape(%q) = %q, %s; want %q, %s", actual, roundtrip, err, tt.in, "[no error]") 890 } 891 } 892 } 893 894 var pathEscapeTests = []EscapeTest{ 895 { 896 "", 897 "", 898 nil, 899 }, 900 { 901 "abc", 902 "abc", 903 nil, 904 }, 905 { 906 "abc+def", 907 "abc+def", 908 nil, 909 }, 910 { 911 "one two", 912 "one%20two", 913 nil, 914 }, 915 { 916 "10%", 917 "10%25", 918 nil, 919 }, 920 { 921 " ?&=#+%!<>#\"{}|\\^[]`☺\t:/@$'()*,;", 922 "%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", 923 nil, 924 }, 925 } 926 927 func TestPathEscape(t *testing.T) { 928 for _, tt := range pathEscapeTests { 929 actual := PathEscape(tt.in) 930 if tt.out != actual { 931 t.Errorf("PathEscape(%q) = %q, want %q", tt.in, actual, tt.out) 932 } 933 934 // for bonus points, verify that escape:unescape is an identity. 935 roundtrip, err := PathUnescape(actual) 936 if roundtrip != tt.in || err != nil { 937 t.Errorf("PathUnescape(%q) = %q, %s; want %q, %s", actual, roundtrip, err, tt.in, "[no error]") 938 } 939 } 940 } 941 942 //var userinfoTests = []UserinfoTest{ 943 // {"user", "password", "user:password"}, 944 // {"foo:bar", "~!@#$%^&*()_+{}|[]\\-=`:;'\"<>?,./", 945 // "foo%3Abar:~!%40%23$%25%5E&*()_+%7B%7D%7C%5B%5D%5C-=%60%3A;'%22%3C%3E?,.%2F"}, 946 //} 947 948 type EncodeQueryTest struct { 949 m Values 950 expected string 951 } 952 953 var encodeQueryTests = []EncodeQueryTest{ 954 {nil, ""}, 955 {Values{"q": {"puppies"}, "oe": {"utf8"}}, "oe=utf8&q=puppies"}, 956 {Values{"q": {"dogs", "&", "7"}}, "q=dogs&q=%26&q=7"}, 957 {Values{ 958 "a": {"a1", "a2", "a3"}, 959 "b": {"b1", "b2", "b3"}, 960 "c": {"c1", "c2", "c3"}, 961 }, "a=a1&a=a2&a=a3&b=b1&b=b2&b=b3&c=c1&c=c2&c=c3"}, 962 } 963 964 func TestEncodeQuery(t *testing.T) { 965 for _, tt := range encodeQueryTests { 966 if q := tt.m.Encode(); q != tt.expected { 967 t.Errorf(`EncodeQuery(%+v) = %q, want %q`, tt.m, q, tt.expected) 968 } 969 } 970 } 971 972 var resolvePathTests = []struct { 973 base, ref, expected string 974 }{ 975 {"a/b", ".", "/a/"}, 976 {"a/b", "c", "/a/c"}, 977 {"a/b", "..", "/"}, 978 {"a/", "..", "/"}, 979 {"a/", "../..", "/"}, 980 {"a/b/c", "..", "/a/"}, 981 {"a/b/c", "../d", "/a/d"}, 982 {"a/b/c", ".././d", "/a/d"}, 983 {"a/b", "./..", "/"}, 984 {"a/./b", ".", "/a/"}, 985 {"a/../", ".", "/"}, 986 {"a/.././b", "c", "/c"}, 987 } 988 989 func TestResolvePath(t *testing.T) { 990 for _, test := range resolvePathTests { 991 got := resolvePath(test.base, test.ref) 992 if got != test.expected { 993 t.Errorf("For %q + %q got %q; expected %q", test.base, test.ref, got, test.expected) 994 } 995 } 996 } 997 998 var resolveReferenceTests = []struct { 999 base, rel, expected string 1000 }{ 1001 // Absolute URL references 1002 {"http://foo.com?a=b", "https://bar.com/", "https://bar.com/"}, 1003 {"http://foo.com/", "https://bar.com/?a=b", "https://bar.com/?a=b"}, 1004 {"http://foo.com/", "https://bar.com/?", "https://bar.com/?"}, 1005 {"http://foo.com/bar", "mailto:foo@example.com", "mailto:foo@example.com"}, 1006 1007 // Path-absolute references 1008 {"http://foo.com/bar", "/baz", "http://foo.com/baz"}, 1009 {"http://foo.com/bar?a=b#f", "/baz", "http://foo.com/baz"}, 1010 {"http://foo.com/bar?a=b", "/baz?", "http://foo.com/baz?"}, 1011 {"http://foo.com/bar?a=b", "/baz?c=d", "http://foo.com/baz?c=d"}, 1012 1013 // Scheme-relative 1014 {"https://foo.com/bar?a=b", "//bar.com/quux", "https://bar.com/quux"}, 1015 1016 // Path-relative references: 1017 1018 // ... current directory 1019 {"http://foo.com", ".", "http://foo.com/"}, 1020 {"http://foo.com/bar", ".", "http://foo.com/"}, 1021 {"http://foo.com/bar/", ".", "http://foo.com/bar/"}, 1022 1023 // ... going down 1024 {"http://foo.com", "bar", "http://foo.com/bar"}, 1025 {"http://foo.com/", "bar", "http://foo.com/bar"}, 1026 {"http://foo.com/bar/baz", "quux", "http://foo.com/bar/quux"}, 1027 1028 // ... going up 1029 {"http://foo.com/bar/baz", "../quux", "http://foo.com/quux"}, 1030 {"http://foo.com/bar/baz", "../../../../../quux", "http://foo.com/quux"}, 1031 {"http://foo.com/bar", "..", "http://foo.com/"}, 1032 {"http://foo.com/bar/baz", "./..", "http://foo.com/"}, 1033 // ".." in the middle (issue 3560) 1034 {"http://foo.com/bar/baz", "quux/dotdot/../tail", "http://foo.com/bar/quux/tail"}, 1035 {"http://foo.com/bar/baz", "quux/./dotdot/../tail", "http://foo.com/bar/quux/tail"}, 1036 {"http://foo.com/bar/baz", "quux/./dotdot/.././tail", "http://foo.com/bar/quux/tail"}, 1037 {"http://foo.com/bar/baz", "quux/./dotdot/./../tail", "http://foo.com/bar/quux/tail"}, 1038 {"http://foo.com/bar/baz", "quux/./dotdot/dotdot/././../../tail", "http://foo.com/bar/quux/tail"}, 1039 {"http://foo.com/bar/baz", "quux/./dotdot/dotdot/./.././../tail", "http://foo.com/bar/quux/tail"}, 1040 {"http://foo.com/bar/baz", "quux/./dotdot/dotdot/dotdot/./../../.././././tail", "http://foo.com/bar/quux/tail"}, 1041 {"http://foo.com/bar/baz", "quux/./dotdot/../dotdot/../dot/./tail/..", "http://foo.com/bar/quux/dot/"}, 1042 1043 // Remove any dot-segments prior to forming the target URI. 1044 // http://tools.ietf.org/html/rfc3986#section-5.2.4 1045 {"http://foo.com/dot/./dotdot/../foo/bar", "../baz", "http://foo.com/dot/baz"}, 1046 1047 // Triple dot isn't special 1048 {"http://foo.com/bar", "...", "http://foo.com/..."}, 1049 1050 // Fragment 1051 {"http://foo.com/bar", ".#frag", "http://foo.com/#frag"}, 1052 1053 // Paths with escaping (issue 16947). 1054 {"http://foo.com/foo%2fbar/", "../baz", "http://foo.com/baz"}, 1055 {"http://foo.com/1/2%2f/3%2f4/5", "../../a/b/c", "http://foo.com/1/a/b/c"}, 1056 {"http://foo.com/1/2/3", "./a%2f../../b/..%2fc", "http://foo.com/1/2/b/..%2fc"}, 1057 {"http://foo.com/1/2%2f/3%2f4/5", "./a%2f../b/../c", "http://foo.com/1/2%2f/3%2f4/a%2f../c"}, 1058 {"http://foo.com/foo%20bar/", "../baz", "http://foo.com/baz"}, 1059 {"http://foo.com/foo", "../bar%2fbaz", "http://foo.com/bar%2fbaz"}, 1060 {"http://foo.com/foo%2dbar/", "./baz-quux", "http://foo.com/foo%2dbar/baz-quux"}, 1061 1062 // RFC 3986: Normal Examples 1063 // http://tools.ietf.org/html/rfc3986#section-5.4.1 1064 {"http://a/b/c/d;p?q", "g:h", "g:h"}, 1065 {"http://a/b/c/d;p?q", "g", "http://a/b/c/g"}, 1066 {"http://a/b/c/d;p?q", "./g", "http://a/b/c/g"}, 1067 {"http://a/b/c/d;p?q", "g/", "http://a/b/c/g/"}, 1068 {"http://a/b/c/d;p?q", "/g", "http://a/g"}, 1069 {"http://a/b/c/d;p?q", "//g", "http://g"}, 1070 {"http://a/b/c/d;p?q", "?y", "http://a/b/c/d;p?y"}, 1071 {"http://a/b/c/d;p?q", "g?y", "http://a/b/c/g?y"}, 1072 {"http://a/b/c/d;p?q", "#s", "http://a/b/c/d;p?q#s"}, 1073 {"http://a/b/c/d;p?q", "g#s", "http://a/b/c/g#s"}, 1074 {"http://a/b/c/d;p?q", "g?y#s", "http://a/b/c/g?y#s"}, 1075 {"http://a/b/c/d;p?q", ";x", "http://a/b/c/;x"}, 1076 {"http://a/b/c/d;p?q", "g;x", "http://a/b/c/g;x"}, 1077 {"http://a/b/c/d;p?q", "g;x?y#s", "http://a/b/c/g;x?y#s"}, 1078 {"http://a/b/c/d;p?q", "", "http://a/b/c/d;p?q"}, 1079 {"http://a/b/c/d;p?q", ".", "http://a/b/c/"}, 1080 {"http://a/b/c/d;p?q", "./", "http://a/b/c/"}, 1081 {"http://a/b/c/d;p?q", "..", "http://a/b/"}, 1082 {"http://a/b/c/d;p?q", "../", "http://a/b/"}, 1083 {"http://a/b/c/d;p?q", "../g", "http://a/b/g"}, 1084 {"http://a/b/c/d;p?q", "../..", "http://a/"}, 1085 {"http://a/b/c/d;p?q", "../../", "http://a/"}, 1086 {"http://a/b/c/d;p?q", "../../g", "http://a/g"}, 1087 1088 // RFC 3986: Abnormal Examples 1089 // http://tools.ietf.org/html/rfc3986#section-5.4.2 1090 {"http://a/b/c/d;p?q", "../../../g", "http://a/g"}, 1091 {"http://a/b/c/d;p?q", "../../../../g", "http://a/g"}, 1092 {"http://a/b/c/d;p?q", "/./g", "http://a/g"}, 1093 {"http://a/b/c/d;p?q", "/../g", "http://a/g"}, 1094 {"http://a/b/c/d;p?q", "g.", "http://a/b/c/g."}, 1095 {"http://a/b/c/d;p?q", ".g", "http://a/b/c/.g"}, 1096 {"http://a/b/c/d;p?q", "g..", "http://a/b/c/g.."}, 1097 {"http://a/b/c/d;p?q", "..g", "http://a/b/c/..g"}, 1098 {"http://a/b/c/d;p?q", "./../g", "http://a/b/g"}, 1099 {"http://a/b/c/d;p?q", "./g/.", "http://a/b/c/g/"}, 1100 {"http://a/b/c/d;p?q", "g/./h", "http://a/b/c/g/h"}, 1101 {"http://a/b/c/d;p?q", "g/../h", "http://a/b/c/h"}, 1102 {"http://a/b/c/d;p?q", "g;x=1/./y", "http://a/b/c/g;x=1/y"}, 1103 {"http://a/b/c/d;p?q", "g;x=1/../y", "http://a/b/c/y"}, 1104 {"http://a/b/c/d;p?q", "g?y/./x", "http://a/b/c/g?y/./x"}, 1105 {"http://a/b/c/d;p?q", "g?y/../x", "http://a/b/c/g?y/../x"}, 1106 {"http://a/b/c/d;p?q", "g#s/./x", "http://a/b/c/g#s/./x"}, 1107 {"http://a/b/c/d;p?q", "g#s/../x", "http://a/b/c/g#s/../x"}, 1108 1109 // Extras. 1110 {"https://a/b/c/d;p?q", "//g?q", "https://g?q"}, 1111 {"https://a/b/c/d;p?q", "//g#s", "https://g#s"}, 1112 {"https://a/b/c/d;p?q", "//g/d/e/f?y#s", "https://g/d/e/f?y#s"}, 1113 {"https://a/b/c/d;p#s", "?y", "https://a/b/c/d;p?y"}, 1114 {"https://a/b/c/d;p?q#s", "?y", "https://a/b/c/d;p?y"}, 1115 } 1116 1117 func TestResolveReference(t *testing.T) { 1118 mustParse := func(url string) *URL { 1119 u, err := Parse(url) 1120 if err != nil { 1121 t.Fatalf("Parse(%q) got err %v", url, err) 1122 } 1123 return u 1124 } 1125 opaque := &URL{Scheme: "scheme", Opaque: "opaque"} 1126 for _, test := range resolveReferenceTests { 1127 base := mustParse(test.base) 1128 rel := mustParse(test.rel) 1129 url := base.ResolveReference(rel) 1130 if got := url.String(); got != test.expected { 1131 t.Errorf("URL(%q).ResolveReference(%q)\ngot %q\nwant %q", test.base, test.rel, got, test.expected) 1132 } 1133 // Ensure that new instances are returned. 1134 if base == url { 1135 t.Errorf("Expected URL.ResolveReference to return new URL instance.") 1136 } 1137 // Test the convenience wrapper too. 1138 url, err := base.Parse(test.rel) 1139 if err != nil { 1140 t.Errorf("URL(%q).Parse(%q) failed: %v", test.base, test.rel, err) 1141 } else if got := url.String(); got != test.expected { 1142 t.Errorf("URL(%q).Parse(%q)\ngot %q\nwant %q", test.base, test.rel, got, test.expected) 1143 } else if base == url { 1144 // Ensure that new instances are returned for the wrapper too. 1145 t.Errorf("Expected URL.Parse to return new URL instance.") 1146 } 1147 // Ensure Opaque resets the URL. 1148 url = base.ResolveReference(opaque) 1149 if *url != *opaque { 1150 t.Errorf("ResolveReference failed to resolve opaque URL:\ngot %#v\nwant %#v", url, opaque) 1151 } 1152 // Test the convenience wrapper with an opaque URL too. 1153 url, err = base.Parse("scheme:opaque") 1154 if err != nil { 1155 t.Errorf(`URL(%q).Parse("scheme:opaque") failed: %v`, test.base, err) 1156 } else if *url != *opaque { 1157 t.Errorf("Parse failed to resolve opaque URL:\ngot %#v\nwant %#v", opaque, url) 1158 } else if base == url { 1159 // Ensure that new instances are returned, again. 1160 t.Errorf("Expected URL.Parse to return new URL instance.") 1161 } 1162 } 1163 } 1164 1165 func TestQueryValues(t *testing.T) { 1166 u, _ := Parse("http://x.com?foo=bar&bar=1&bar=2") 1167 v := u.Query() 1168 if len(v) != 2 { 1169 t.Errorf("got %d keys in Query values, want 2", len(v)) 1170 } 1171 if g, e := v.Get("foo"), "bar"; g != e { 1172 t.Errorf("Get(foo) = %q, want %q", g, e) 1173 } 1174 // Case sensitive: 1175 if g, e := v.Get("Foo"), ""; g != e { 1176 t.Errorf("Get(Foo) = %q, want %q", g, e) 1177 } 1178 if g, e := v.Get("bar"), "1"; g != e { 1179 t.Errorf("Get(bar) = %q, want %q", g, e) 1180 } 1181 if g, e := v.Get("baz"), ""; g != e { 1182 t.Errorf("Get(baz) = %q, want %q", g, e) 1183 } 1184 v.Del("bar") 1185 if g, e := v.Get("bar"), ""; g != e { 1186 t.Errorf("second Get(bar) = %q, want %q", g, e) 1187 } 1188 } 1189 1190 type parseTest struct { 1191 query string 1192 out Values 1193 } 1194 1195 var parseTests = []parseTest{ 1196 { 1197 query: "a=1&b=2", 1198 out: Values{"a": []string{"1"}, "b": []string{"2"}}, 1199 }, 1200 { 1201 query: "a=1&a=2&a=banana", 1202 out: Values{"a": []string{"1", "2", "banana"}}, 1203 }, 1204 { 1205 query: "ascii=%3Ckey%3A+0x90%3E", 1206 out: Values{"ascii": []string{"<key: 0x90>"}}, 1207 }, 1208 { 1209 query: "a=1;b=2", 1210 out: Values{"a": []string{"1"}, "b": []string{"2"}}, 1211 }, 1212 { 1213 query: "a=1&a=2;a=banana", 1214 out: Values{"a": []string{"1", "2", "banana"}}, 1215 }, 1216 } 1217 1218 func TestParseQuery(t *testing.T) { 1219 for i, test := range parseTests { 1220 form, err := ParseQuery(test.query) 1221 if err != nil { 1222 t.Errorf("test %d: Unexpected error: %v", i, err) 1223 continue 1224 } 1225 if len(form) != len(test.out) { 1226 t.Errorf("test %d: len(form) = %d, want %d", i, len(form), len(test.out)) 1227 } 1228 for k, evs := range test.out { 1229 vs, ok := form[k] 1230 if !ok { 1231 t.Errorf("test %d: Missing key %q", i, k) 1232 continue 1233 } 1234 if len(vs) != len(evs) { 1235 t.Errorf("test %d: len(form[%q]) = %d, want %d", i, k, len(vs), len(evs)) 1236 continue 1237 } 1238 for j, ev := range evs { 1239 if v := vs[j]; v != ev { 1240 t.Errorf("test %d: form[%q][%d] = %q, want %q", i, k, j, v, ev) 1241 } 1242 } 1243 } 1244 } 1245 } 1246 1247 type RequestURITest struct { 1248 url *URL 1249 out string 1250 } 1251 1252 var requritests = []RequestURITest{ 1253 { 1254 &URL{ 1255 Scheme: "http", 1256 Host: "example.com", 1257 Path: "", 1258 }, 1259 "/", 1260 }, 1261 { 1262 &URL{ 1263 Scheme: "http", 1264 Host: "example.com", 1265 Path: "/a b", 1266 }, 1267 "/a%20b", 1268 }, 1269 // golang.org/issue/4860 variant 1 1270 { 1271 &URL{ 1272 Scheme: "http", 1273 Host: "example.com", 1274 Opaque: "/%2F/%2F/", 1275 }, 1276 "/%2F/%2F/", 1277 }, 1278 // golang.org/issue/4860 variant 2 1279 { 1280 &URL{ 1281 Scheme: "http", 1282 Host: "example.com", 1283 Opaque: "//other.example.com/%2F/%2F/", 1284 }, 1285 "http://other.example.com/%2F/%2F/", 1286 }, 1287 // better fix for issue 4860 1288 { 1289 &URL{ 1290 Scheme: "http", 1291 Host: "example.com", 1292 Path: "/////", 1293 RawPath: "/%2F/%2F/", 1294 }, 1295 "/%2F/%2F/", 1296 }, 1297 { 1298 &URL{ 1299 Scheme: "http", 1300 Host: "example.com", 1301 Path: "/////", 1302 RawPath: "/WRONG/", // ignored because doesn't match Path 1303 }, 1304 "/////", 1305 }, 1306 { 1307 &URL{ 1308 Scheme: "http", 1309 Host: "example.com", 1310 Path: "/a b", 1311 RawQuery: "q=go+language", 1312 }, 1313 "/a%20b?q=go+language", 1314 }, 1315 { 1316 &URL{ 1317 Scheme: "http", 1318 Host: "example.com", 1319 Path: "/a b", 1320 RawPath: "/a b", // ignored because invalid 1321 RawQuery: "q=go+language", 1322 }, 1323 "/a%20b?q=go+language", 1324 }, 1325 { 1326 &URL{ 1327 Scheme: "http", 1328 Host: "example.com", 1329 Path: "/a?b", 1330 RawPath: "/a?b", // ignored because invalid 1331 RawQuery: "q=go+language", 1332 }, 1333 "/a%3Fb?q=go+language", 1334 }, 1335 { 1336 &URL{ 1337 Scheme: "myschema", 1338 Opaque: "opaque", 1339 }, 1340 "opaque", 1341 }, 1342 { 1343 &URL{ 1344 Scheme: "myschema", 1345 Opaque: "opaque", 1346 RawQuery: "q=go+language", 1347 }, 1348 "opaque?q=go+language", 1349 }, 1350 { 1351 &URL{ 1352 Scheme: "http", 1353 Host: "example.com", 1354 Path: "//foo", 1355 }, 1356 "//foo", 1357 }, 1358 { 1359 &URL{ 1360 Scheme: "http", 1361 Host: "example.com", 1362 Path: "/foo", 1363 ForceQuery: true, 1364 }, 1365 "/foo?", 1366 }, 1367 } 1368 1369 func TestRequestURI(t *testing.T) { 1370 for _, tt := range requritests { 1371 s := tt.url.RequestURI() 1372 if s != tt.out { 1373 t.Errorf("%#v.RequestURI() == %q (expected %q)", tt.url, s, tt.out) 1374 } 1375 } 1376 } 1377 1378 func TestParseFailure(t *testing.T) { 1379 // Test that the first parse error is returned. 1380 const url = "%gh&%ij" 1381 _, err := ParseQuery(url) 1382 errStr := fmt.Sprint(err) 1383 if !strings.Contains(errStr, "%gh") { 1384 t.Errorf(`ParseQuery(%q) returned error %q, want something containing %q"`, url, errStr, "%gh") 1385 } 1386 } 1387 1388 func TestParseErrors(t *testing.T) { 1389 tests := []struct { 1390 in string 1391 wantErr bool 1392 }{ 1393 {"http://[::1]", false}, 1394 {"http://[::1]:80", false}, 1395 {"http://[::1]:namedport", true}, // rfc3986 3.2.3 1396 {"http://[::1]/", false}, 1397 {"http://[::1]a", true}, 1398 {"http://[::1]%23", true}, 1399 {"http://[::1%25en0]", false}, // valid zone id 1400 {"http://[::1]:", false}, // colon, but no port OK 1401 {"http://[::1]:%38%30", true}, // not allowed: % encoding only for non-ASCII 1402 {"http://[::1%25%41]", false}, // RFC 6874 allows over-escaping in zone 1403 {"http://[%10::1]", true}, // no %xx escapes in IP address 1404 {"http://[::1]/%48", false}, // %xx in path is fine 1405 {"http://%41:8080/", true}, // not allowed: % encoding only for non-ASCII 1406 {"mysql://x@y(z:123)/foo", false}, // golang.org/issue/12023 1407 {"mysql://x@y(1.2.3.4:123)/foo", false}, 1408 1409 {"http://[]%20%48%54%54%50%2f%31%2e%31%0a%4d%79%48%65%61%64%65%72%3a%20%31%32%33%0a%0a/", true}, // golang.org/issue/11208 1410 {"http://a b.com/", true}, // no space in host name please 1411 {"cache_object://foo", true}, // scheme cannot have _, relative path cannot have : in first segment 1412 {"cache_object:foo", true}, 1413 {"cache_object:foo/bar", true}, 1414 {"cache_object/:foo/bar", false}, 1415 } 1416 for _, tt := range tests { 1417 u, err := Parse(tt.in) 1418 if tt.wantErr { 1419 if err == nil { 1420 t.Errorf("Parse(%q) = %#v; want an error", tt.in, u) 1421 } 1422 continue 1423 } 1424 if err != nil { 1425 t.Logf("Parse(%q) = %v; want no error", tt.in, err) 1426 } 1427 } 1428 } 1429 1430 // Issue 11202 1431 func TestStarRequest(t *testing.T) { 1432 u, err := Parse("*") 1433 if err != nil { 1434 t.Fatal(err) 1435 } 1436 if got, want := u.RequestURI(), "*"; got != want { 1437 t.Errorf("RequestURI = %q; want %q", got, want) 1438 } 1439 } 1440 1441 type shouldEscapeTest struct { 1442 in byte 1443 mode encoding 1444 escape bool 1445 } 1446 1447 var shouldEscapeTests = []shouldEscapeTest{ 1448 // Unreserved characters (§2.3) 1449 {'a', encodePath, false}, 1450 {'a', encodeUserPassword, false}, 1451 {'a', encodeQueryComponent, false}, 1452 {'a', encodeFragment, false}, 1453 {'a', encodeHost, false}, 1454 {'z', encodePath, false}, 1455 {'A', encodePath, false}, 1456 {'Z', encodePath, false}, 1457 {'0', encodePath, false}, 1458 {'9', encodePath, false}, 1459 {'-', encodePath, false}, 1460 {'-', encodeUserPassword, false}, 1461 {'-', encodeQueryComponent, false}, 1462 {'-', encodeFragment, false}, 1463 {'.', encodePath, false}, 1464 {'_', encodePath, false}, 1465 {'~', encodePath, false}, 1466 1467 // User information (§3.2.1) 1468 {':', encodeUserPassword, true}, 1469 {'/', encodeUserPassword, true}, 1470 {'?', encodeUserPassword, true}, 1471 {'@', encodeUserPassword, true}, 1472 {'$', encodeUserPassword, false}, 1473 {'&', encodeUserPassword, false}, 1474 {'+', encodeUserPassword, false}, 1475 {',', encodeUserPassword, false}, 1476 {';', encodeUserPassword, false}, 1477 {'=', encodeUserPassword, false}, 1478 1479 // Host (IP address, IPv6 address, registered name, port suffix; §3.2.2) 1480 {'!', encodeHost, false}, 1481 {'$', encodeHost, false}, 1482 {'&', encodeHost, false}, 1483 {'\'', encodeHost, false}, 1484 {'(', encodeHost, false}, 1485 {')', encodeHost, false}, 1486 {'*', encodeHost, false}, 1487 {'+', encodeHost, false}, 1488 {',', encodeHost, false}, 1489 {';', encodeHost, false}, 1490 {'=', encodeHost, false}, 1491 {':', encodeHost, false}, 1492 {'[', encodeHost, false}, 1493 {']', encodeHost, false}, 1494 {'0', encodeHost, false}, 1495 {'9', encodeHost, false}, 1496 {'A', encodeHost, false}, 1497 {'z', encodeHost, false}, 1498 {'_', encodeHost, false}, 1499 {'-', encodeHost, false}, 1500 {'.', encodeHost, false}, 1501 } 1502 1503 func TestShouldEscape(t *testing.T) { 1504 for _, tt := range shouldEscapeTests { 1505 if shouldEscape(tt.in, tt.mode) != tt.escape { 1506 t.Errorf("shouldEscape(%q, %v) returned %v; expected %v", tt.in, tt.mode, !tt.escape, tt.escape) 1507 } 1508 } 1509 } 1510 1511 type timeoutError struct { 1512 timeout bool 1513 } 1514 1515 func (e *timeoutError) Error() string { return "timeout error" } 1516 func (e *timeoutError) Timeout() bool { return e.timeout } 1517 1518 type temporaryError struct { 1519 temporary bool 1520 } 1521 1522 func (e *temporaryError) Error() string { return "temporary error" } 1523 func (e *temporaryError) Temporary() bool { return e.temporary } 1524 1525 type timeoutTemporaryError struct { 1526 timeoutError 1527 temporaryError 1528 } 1529 1530 func (e *timeoutTemporaryError) Error() string { return "timeout/temporary error" } 1531 1532 var netErrorTests = []struct { 1533 err error 1534 timeout bool 1535 temporary bool 1536 }{{ 1537 err: &Error{"Get", "http://google.com/", &timeoutError{timeout: true}}, 1538 timeout: true, 1539 temporary: false, 1540 }, { 1541 err: &Error{"Get", "http://google.com/", &timeoutError{timeout: false}}, 1542 timeout: false, 1543 temporary: false, 1544 }, { 1545 err: &Error{"Get", "http://google.com/", &temporaryError{temporary: true}}, 1546 timeout: false, 1547 temporary: true, 1548 }, { 1549 err: &Error{"Get", "http://google.com/", &temporaryError{temporary: false}}, 1550 timeout: false, 1551 temporary: false, 1552 }, { 1553 err: &Error{"Get", "http://google.com/", &timeoutTemporaryError{timeoutError{timeout: true}, temporaryError{temporary: true}}}, 1554 timeout: true, 1555 temporary: true, 1556 }, { 1557 err: &Error{"Get", "http://google.com/", &timeoutTemporaryError{timeoutError{timeout: false}, temporaryError{temporary: true}}}, 1558 timeout: false, 1559 temporary: true, 1560 }, { 1561 err: &Error{"Get", "http://google.com/", &timeoutTemporaryError{timeoutError{timeout: true}, temporaryError{temporary: false}}}, 1562 timeout: true, 1563 temporary: false, 1564 }, { 1565 err: &Error{"Get", "http://google.com/", &timeoutTemporaryError{timeoutError{timeout: false}, temporaryError{temporary: false}}}, 1566 timeout: false, 1567 temporary: false, 1568 }, { 1569 err: &Error{"Get", "http://google.com/", io.EOF}, 1570 timeout: false, 1571 temporary: false, 1572 }} 1573 1574 // Test that url.Error implements net.Error and that it forwards 1575 func TestURLErrorImplementsNetError(t *testing.T) { 1576 for i, tt := range netErrorTests { 1577 err, ok := tt.err.(net.Error) 1578 if !ok { 1579 t.Errorf("%d: %T does not implement net.Error", i+1, tt.err) 1580 continue 1581 } 1582 if err.Timeout() != tt.timeout { 1583 t.Errorf("%d: err.Timeout(): got %v, want %v", i+1, err.Timeout(), tt.timeout) 1584 continue 1585 } 1586 if err.Temporary() != tt.temporary { 1587 t.Errorf("%d: err.Temporary(): got %v, want %v", i+1, err.Temporary(), tt.temporary) 1588 } 1589 } 1590 } 1591 1592 func TestURLHostname(t *testing.T) { 1593 tests := []struct { 1594 host string // URL.Host field 1595 want string 1596 }{ 1597 {"foo.com:80", "foo.com"}, 1598 {"foo.com", "foo.com"}, 1599 {"FOO.COM", "FOO.COM"}, // no canonicalization (yet?) 1600 {"1.2.3.4", "1.2.3.4"}, 1601 {"1.2.3.4:80", "1.2.3.4"}, 1602 {"[1:2:3:4]", "1:2:3:4"}, 1603 {"[1:2:3:4]:80", "1:2:3:4"}, 1604 {"[::1]:80", "::1"}, 1605 } 1606 for _, tt := range tests { 1607 u := &URL{Host: tt.host} 1608 got := u.Hostname() 1609 if got != tt.want { 1610 t.Errorf("Hostname for Host %q = %q; want %q", tt.host, got, tt.want) 1611 } 1612 } 1613 } 1614 1615 func TestURLPort(t *testing.T) { 1616 tests := []struct { 1617 host string // URL.Host field 1618 want string 1619 }{ 1620 {"foo.com", ""}, 1621 {"foo.com:80", "80"}, 1622 {"1.2.3.4", ""}, 1623 {"1.2.3.4:80", "80"}, 1624 {"[1:2:3:4]", ""}, 1625 {"[1:2:3:4]:80", "80"}, 1626 } 1627 for _, tt := range tests { 1628 u := &URL{Host: tt.host} 1629 got := u.Port() 1630 if got != tt.want { 1631 t.Errorf("Port for Host %q = %q; want %q", tt.host, got, tt.want) 1632 } 1633 } 1634 } 1635 1636 var _ encodingPkg.BinaryMarshaler = (*URL)(nil) 1637 var _ encodingPkg.BinaryUnmarshaler = (*URL)(nil) 1638 1639 func TestJSON(t *testing.T) { 1640 u, err := Parse("https://www.google.com/x?y=z") 1641 if err != nil { 1642 t.Fatal(err) 1643 } 1644 js, err := json.Marshal(u) 1645 if err != nil { 1646 t.Fatal(err) 1647 } 1648 1649 // If only we could implement TextMarshaler/TextUnmarshaler, 1650 // this would work: 1651 // 1652 // if string(js) != strconv.Quote(u.String()) { 1653 // t.Errorf("json encoding: %s\nwant: %s\n", js, strconv.Quote(u.String())) 1654 // } 1655 1656 u1 := new(URL) 1657 err = json.Unmarshal(js, u1) 1658 if err != nil { 1659 t.Fatal(err) 1660 } 1661 if u1.String() != u.String() { 1662 t.Errorf("json decoded to: %s\nwant: %s\n", u1, u) 1663 } 1664 } 1665 1666 func TestGob(t *testing.T) { 1667 u, err := Parse("https://www.google.com/x?y=z") 1668 if err != nil { 1669 t.Fatal(err) 1670 } 1671 var w bytes.Buffer 1672 err = gob.NewEncoder(&w).Encode(u) 1673 if err != nil { 1674 t.Fatal(err) 1675 } 1676 1677 u1 := new(URL) 1678 err = gob.NewDecoder(&w).Decode(u1) 1679 if err != nil { 1680 t.Fatal(err) 1681 } 1682 if u1.String() != u.String() { 1683 t.Errorf("json decoded to: %s\nwant: %s\n", u1, u) 1684 } 1685 }