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 "fmt" 9 "reflect" 10 "strings" 11 "testing" 12 ) 13 14 type URLTest struct { 15 in string 16 out *URL // expected parse; RawPath="" means same as Path 17 roundtrip string // expected result of reserializing the URL; empty means same as "in". 18 } 19 20 var urltests = []URLTest{ 21 // no path 22 { 23 "http://www.google.com", 24 &URL{ 25 Scheme: "http", 26 Host: "www.google.com", 27 }, 28 "", 29 }, 30 // path 31 { 32 "http://www.google.com/", 33 &URL{ 34 Scheme: "http", 35 Host: "www.google.com", 36 Path: "/", 37 }, 38 "", 39 }, 40 // path with hex escaping 41 { 42 "http://www.google.com/file%20one%26two", 43 &URL{ 44 Scheme: "http", 45 Host: "www.google.com", 46 Path: "/file one&two", 47 RawPath: "/file%20one%26two", 48 }, 49 "", 50 }, 51 // user 52 { 53 "ftp://webmaster@www.google.com/", 54 &URL{ 55 Scheme: "ftp", 56 User: User("webmaster"), 57 Host: "www.google.com", 58 Path: "/", 59 }, 60 "", 61 }, 62 // escape sequence in username 63 { 64 "ftp://john%20doe@www.google.com/", 65 &URL{ 66 Scheme: "ftp", 67 User: User("john doe"), 68 Host: "www.google.com", 69 Path: "/", 70 }, 71 "ftp://john%20doe@www.google.com/", 72 }, 73 // query 74 { 75 "http://www.google.com/?q=go+language", 76 &URL{ 77 Scheme: "http", 78 Host: "www.google.com", 79 Path: "/", 80 RawQuery: "q=go+language", 81 }, 82 "", 83 }, 84 // query with hex escaping: NOT parsed 85 { 86 "http://www.google.com/?q=go%20language", 87 &URL{ 88 Scheme: "http", 89 Host: "www.google.com", 90 Path: "/", 91 RawQuery: "q=go%20language", 92 }, 93 "", 94 }, 95 // %20 outside query 96 { 97 "http://www.google.com/a%20b?q=c+d", 98 &URL{ 99 Scheme: "http", 100 Host: "www.google.com", 101 Path: "/a b", 102 RawQuery: "q=c+d", 103 }, 104 "", 105 }, 106 // path without leading /, so no parsing 107 { 108 "http:www.google.com/?q=go+language", 109 &URL{ 110 Scheme: "http", 111 Opaque: "www.google.com/", 112 RawQuery: "q=go+language", 113 }, 114 "http:www.google.com/?q=go+language", 115 }, 116 // path without leading /, so no parsing 117 { 118 "http:%2f%2fwww.google.com/?q=go+language", 119 &URL{ 120 Scheme: "http", 121 Opaque: "%2f%2fwww.google.com/", 122 RawQuery: "q=go+language", 123 }, 124 "http:%2f%2fwww.google.com/?q=go+language", 125 }, 126 // non-authority with path 127 { 128 "mailto:/webmaster (a] golang.org", 129 &URL{ 130 Scheme: "mailto", 131 Path: "/webmaster (a] golang.org", 132 }, 133 "mailto:///webmaster (a] golang.org", // unfortunate compromise 134 }, 135 // non-authority 136 { 137 "mailto:webmaster (a] golang.org", 138 &URL{ 139 Scheme: "mailto", 140 Opaque: "webmaster (a] golang.org", 141 }, 142 "", 143 }, 144 // unescaped :// in query should not create a scheme 145 { 146 "/foo?query=http://bad", 147 &URL{ 148 Path: "/foo", 149 RawQuery: "query=http://bad", 150 }, 151 "", 152 }, 153 // leading // without scheme should create an authority 154 { 155 "//foo", 156 &URL{ 157 Host: "foo", 158 }, 159 "", 160 }, 161 // leading // without scheme, with userinfo, path, and query 162 { 163 "//user@foo/path?a=b", 164 &URL{ 165 User: User("user"), 166 Host: "foo", 167 Path: "/path", 168 RawQuery: "a=b", 169 }, 170 "", 171 }, 172 // Three leading slashes isn't an authority, but doesn't return an error. 173 // (We can't return an error, as this code is also used via 174 // ServeHTTP -> ReadRequest -> Parse, which is arguably a 175 // different URL parsing context, but currently shares the 176 // same codepath) 177 { 178 "///threeslashes", 179 &URL{ 180 Path: "///threeslashes", 181 }, 182 "", 183 }, 184 { 185 "http://user:password@google.com", 186 &URL{ 187 Scheme: "http", 188 User: UserPassword("user", "password"), 189 Host: "google.com", 190 }, 191 "http://user:password@google.com", 192 }, 193 // unescaped @ in username should not confuse host 194 { 195 "http://j@ne:password@google.com", 196 &URL{ 197 Scheme: "http", 198 User: UserPassword("j@ne", "password"), 199 Host: "google.com", 200 }, 201 "http://j%40ne:password@google.com", 202 }, 203 // unescaped @ in password should not confuse host 204 { 205 "http://jane:p@ssword@google.com", 206 &URL{ 207 Scheme: "http", 208 User: UserPassword("jane", "p@ssword"), 209 Host: "google.com", 210 }, 211 "http://jane:p%40ssword@google.com", 212 }, 213 { 214 "http://j@ne:password@google.com/p@th?q=@go", 215 &URL{ 216 Scheme: "http", 217 User: UserPassword("j@ne", "password"), 218 Host: "google.com", 219 Path: "/p@th", 220 RawQuery: "q=@go", 221 }, 222 "http://j%40ne:password@google.com/p@th?q=@go", 223 }, 224 { 225 "http://www.google.com/?q=go+language#foo", 226 &URL{ 227 Scheme: "http", 228 Host: "www.google.com", 229 Path: "/", 230 RawQuery: "q=go+language", 231 Fragment: "foo", 232 }, 233 "", 234 }, 235 { 236 "http://www.google.com/?q=go+language#foo%26bar", 237 &URL{ 238 Scheme: "http", 239 Host: "www.google.com", 240 Path: "/", 241 RawQuery: "q=go+language", 242 Fragment: "foo&bar", 243 }, 244 "http://www.google.com/?q=go+language#foo&bar", 245 }, 246 { 247 "file:///home/adg/rabbits", 248 &URL{ 249 Scheme: "file", 250 Host: "", 251 Path: "/home/adg/rabbits", 252 }, 253 "file:///home/adg/rabbits", 254 }, 255 // "Windows" paths are no exception to the rule. 256 // See golang.org/issue/6027, especially comment #9. 257 { 258 "file:///C:/FooBar/Baz.txt", 259 &URL{ 260 Scheme: "file", 261 Host: "", 262 Path: "/C:/FooBar/Baz.txt", 263 }, 264 "file:///C:/FooBar/Baz.txt", 265 }, 266 // case-insensitive scheme 267 { 268 "MaIlTo:webmaster (a] golang.org", 269 &URL{ 270 Scheme: "mailto", 271 Opaque: "webmaster (a] golang.org", 272 }, 273 "mailto:webmaster (a] golang.org", 274 }, 275 // Relative path 276 { 277 "a/b/c", 278 &URL{ 279 Path: "a/b/c", 280 }, 281 "a/b/c", 282 }, 283 // escaped '?' in username and password 284 { 285 "http://%3Fam:pa%3Fsword@google.com", 286 &URL{ 287 Scheme: "http", 288 User: UserPassword("?am", "pa?sword"), 289 Host: "google.com", 290 }, 291 "", 292 }, 293 // host subcomponent; IPv4 address in RFC 3986 294 { 295 "http://192.168.0.1/", 296 &URL{ 297 Scheme: "http", 298 Host: "192.168.0.1", 299 Path: "/", 300 }, 301 "", 302 }, 303 // host and port subcomponents; IPv4 address in RFC 3986 304 { 305 "http://192.168.0.1:8080/", 306 &URL{ 307 Scheme: "http", 308 Host: "192.168.0.1:8080", 309 Path: "/", 310 }, 311 "", 312 }, 313 // host subcomponent; IPv6 address in RFC 3986 314 { 315 "http://[fe80::1]/", 316 &URL{ 317 Scheme: "http", 318 Host: "[fe80::1]", 319 Path: "/", 320 }, 321 "", 322 }, 323 // host and port subcomponents; IPv6 address in RFC 3986 324 { 325 "http://[fe80::1]:8080/", 326 &URL{ 327 Scheme: "http", 328 Host: "[fe80::1]:8080", 329 Path: "/", 330 }, 331 "", 332 }, 333 // host subcomponent; IPv6 address with zone identifier in RFC 6847 334 { 335 "http://[fe80::1%25en0]/", // alphanum zone identifier 336 &URL{ 337 Scheme: "http", 338 Host: "[fe80::1%en0]", 339 Path: "/", 340 }, 341 "", 342 }, 343 // host and port subcomponents; IPv6 address with zone identifier in RFC 6847 344 { 345 "http://[fe80::1%25en0]:8080/", // alphanum zone identifier 346 &URL{ 347 Scheme: "http", 348 Host: "[fe80::1%en0]:8080", 349 Path: "/", 350 }, 351 "", 352 }, 353 // host subcomponent; IPv6 address with zone identifier in RFC 6847 354 { 355 "http://[fe80::1%25%65%6e%301-._~]/", // percent-encoded+unreserved zone identifier 356 &URL{ 357 Scheme: "http", 358 Host: "[fe80::1%en01-._~]", 359 Path: "/", 360 }, 361 "http://[fe80::1%25en01-._~]/", 362 }, 363 // host and port subcomponents; IPv6 address with zone identifier in RFC 6847 364 { 365 "http://[fe80::1%25%65%6e%301-._~]:8080/", // percent-encoded+unreserved zone identifier 366 &URL{ 367 Scheme: "http", 368 Host: "[fe80::1%en01-._~]:8080", 369 Path: "/", 370 }, 371 "http://[fe80::1%25en01-._~]:8080/", 372 }, 373 // alternate escapings of path survive round trip 374 { 375 "http://rest.rsc.io/foo%2fbar/baz%2Fquux?alt=media", 376 &URL{ 377 Scheme: "http", 378 Host: "rest.rsc.io", 379 Path: "/foo/bar/baz/quux", 380 RawPath: "/foo%2fbar/baz%2Fquux", 381 RawQuery: "alt=media", 382 }, 383 "", 384 }, 385 // issue 12036 386 { 387 "mysql://a,b,c/bar", 388 &URL{ 389 Scheme: "mysql", 390 Host: "a,b,c", 391 Path: "/bar", 392 }, 393 "", 394 }, 395 // worst case host, still round trips 396 { 397 "scheme://!$&'()*+,;=hello!:port/path", 398 &URL{ 399 Scheme: "scheme", 400 Host: "!$&'()*+,;=hello!:port", 401 Path: "/path", 402 }, 403 "", 404 }, 405 // worst case path, still round trips 406 { 407 "http://host/!$&'()*+,;=:@[hello]", 408 &URL{ 409 Scheme: "http", 410 Host: "host", 411 Path: "/!$&'()*+,;=:@[hello]", 412 RawPath: "/!$&'()*+,;=:@[hello]", 413 }, 414 "", 415 }, 416 // golang.org/issue/5684 417 { 418 "http://example.com/oid/[order_id]", 419 &URL{ 420 Scheme: "http", 421 Host: "example.com", 422 Path: "/oid/[order_id]", 423 RawPath: "/oid/[order_id]", 424 }, 425 "", 426 }, 427 } 428 429 // more useful string for debugging than fmt's struct printer 430 func ufmt(u *URL) string { 431 var user, pass interface{} 432 if u.User != nil { 433 user = u.User.Username() 434 if p, ok := u.User.Password(); ok { 435 pass = p 436 } 437 } 438 return fmt.Sprintf("opaque=%q, scheme=%q, user=%#v, pass=%#v, host=%q, path=%q, rawpath=%q, rawq=%q, frag=%q", 439 u.Opaque, u.Scheme, user, pass, u.Host, u.Path, u.RawPath, u.RawQuery, u.Fragment) 440 } 441 442 func DoTest(t *testing.T, parse func(string) (*URL, error), name string, tests []URLTest) { 443 for _, tt := range tests { 444 u, err := parse(tt.in) 445 if err != nil { 446 t.Errorf("%s(%q) returned error %s", name, tt.in, err) 447 continue 448 } 449 if !reflect.DeepEqual(u, tt.out) { 450 t.Errorf("%s(%q):\n\thave %v\n\twant %v\n", 451 name, tt.in, ufmt(u), ufmt(tt.out)) 452 } 453 } 454 } 455 456 func BenchmarkString(b *testing.B) { 457 b.StopTimer() 458 b.ReportAllocs() 459 for _, tt := range urltests { 460 u, err := Parse(tt.in) 461 if err != nil { 462 b.Errorf("Parse(%q) returned error %s", tt.in, err) 463 continue 464 } 465 if tt.roundtrip == "" { 466 continue 467 } 468 b.StartTimer() 469 var g string 470 for i := 0; i < b.N; i++ { 471 g = u.String() 472 } 473 b.StopTimer() 474 if w := tt.roundtrip; g != w { 475 b.Errorf("Parse(%q).String() == %q, want %q", tt.in, g, w) 476 } 477 } 478 } 479 480 func TestParse(t *testing.T) { 481 DoTest(t, Parse, "Parse", urltests) 482 } 483 484 const pathThatLooksSchemeRelative = "//not.a.user (a] not.a.host/just/a/path" 485 486 var parseRequestURLTests = []struct { 487 url string 488 expectedValid bool 489 }{ 490 {"http://foo.com", true}, 491 {"http://foo.com/", true}, 492 {"http://foo.com/path", true}, 493 {"/", true}, 494 {pathThatLooksSchemeRelative, true}, 495 {"//not.a.user@%66%6f%6f.com/just/a/path/also", true}, 496 {"*", true}, 497 {"http://192.168.0.1/", true}, 498 {"http://192.168.0.1:8080/", true}, 499 {"http://[fe80::1]/", true}, 500 {"http://[fe80::1]:8080/", true}, 501 502 // Tests exercising RFC 6874 compliance: 503 {"http://[fe80::1%25en0]/", true}, // with alphanum zone identifier 504 {"http://[fe80::1%25en0]:8080/", true}, // with alphanum zone identifier 505 {"http://[fe80::1%25%65%6e%301-._~]/", true}, // with percent-encoded+unreserved zone identifier 506 {"http://[fe80::1%25%65%6e%301-._~]:8080/", true}, // with percent-encoded+unreserved zone identifier 507 508 {"foo.html", false}, 509 {"../dir/", false}, 510 {"http://192.168.0.%31/", false}, 511 {"http://192.168.0.%31:8080/", false}, 512 {"http://[fe80::%31]/", false}, 513 {"http://[fe80::%31]:8080/", false}, 514 {"http://[fe80::%31%25en0]/", false}, 515 {"http://[fe80::%31%25en0]:8080/", false}, 516 517 // These two cases are valid as textual representations as 518 // described in RFC 4007, but are not valid as address 519 // literals with IPv6 zone identifiers in URIs as described in 520 // RFC 6874. 521 {"http://[fe80::1%en0]/", false}, 522 {"http://[fe80::1%en0]:8080/", false}, 523 } 524 525 func TestParseRequestURI(t *testing.T) { 526 for _, test := range parseRequestURLTests { 527 _, err := ParseRequestURI(test.url) 528 valid := err == nil 529 if valid != test.expectedValid { 530 t.Errorf("Expected valid=%v for %q; got %v", test.expectedValid, test.url, valid) 531 } 532 } 533 534 url, err := ParseRequestURI(pathThatLooksSchemeRelative) 535 if err != nil { 536 t.Fatalf("Unexpected error %v", err) 537 } 538 if url.Path != pathThatLooksSchemeRelative { 539 t.Errorf("Expected path %q; got %q", pathThatLooksSchemeRelative, url.Path) 540 } 541 } 542 543 func DoTestString(t *testing.T, parse func(string) (*URL, error), name string, tests []URLTest) { 544 for _, tt := range tests { 545 u, err := parse(tt.in) 546 if err != nil { 547 t.Errorf("%s(%q) returned error %s", name, tt.in, err) 548 continue 549 } 550 expected := tt.in 551 if len(tt.roundtrip) > 0 { 552 expected = tt.roundtrip 553 } 554 s := u.String() 555 if s != expected { 556 t.Errorf("%s(%q).String() == %q (expected %q)", name, tt.in, s, expected) 557 } 558 } 559 } 560 561 func TestURLString(t *testing.T) { 562 DoTestString(t, Parse, "Parse", urltests) 563 564 // no leading slash on path should prepend 565 // slash on String() call 566 noslash := URLTest{ 567 "http://www.google.com/search", 568 &URL{ 569 Scheme: "http", 570 Host: "www.google.com", 571 Path: "search", 572 }, 573 "", 574 } 575 s := noslash.out.String() 576 if s != noslash.in { 577 t.Errorf("Expected %s; go %s", noslash.in, s) 578 } 579 } 580 581 type EscapeTest struct { 582 in string 583 out string 584 err error 585 } 586 587 var unescapeTests = []EscapeTest{ 588 { 589 "", 590 "", 591 nil, 592 }, 593 { 594 "abc", 595 "abc", 596 nil, 597 }, 598 { 599 "1%41", 600 "1A", 601 nil, 602 }, 603 { 604 "1%41%42%43", 605 "1ABC", 606 nil, 607 }, 608 { 609 "%4a", 610 "J", 611 nil, 612 }, 613 { 614 "%6F", 615 "o", 616 nil, 617 }, 618 { 619 "%", // not enough characters after % 620 "", 621 EscapeError("%"), 622 }, 623 { 624 "%a", // not enough characters after % 625 "", 626 EscapeError("%a"), 627 }, 628 { 629 "%1", // not enough characters after % 630 "", 631 EscapeError("%1"), 632 }, 633 { 634 "123%45%6", // not enough characters after % 635 "", 636 EscapeError("%6"), 637 }, 638 { 639 "%zzzzz", // invalid hex digits 640 "", 641 EscapeError("%zz"), 642 }, 643 } 644 645 func TestUnescape(t *testing.T) { 646 for _, tt := range unescapeTests { 647 actual, err := QueryUnescape(tt.in) 648 if actual != tt.out || (err != nil) != (tt.err != nil) { 649 t.Errorf("QueryUnescape(%q) = %q, %s; want %q, %s", tt.in, actual, err, tt.out, tt.err) 650 } 651 } 652 } 653 654 var escapeTests = []EscapeTest{ 655 { 656 "", 657 "", 658 nil, 659 }, 660 { 661 "abc", 662 "abc", 663 nil, 664 }, 665 { 666 "one two", 667 "one+two", 668 nil, 669 }, 670 { 671 "10%", 672 "10%25", 673 nil, 674 }, 675 { 676 " ?&=#+%!<>#\"{}|\\^[]`\t:/@$'()*,;", 677 "+%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", 678 nil, 679 }, 680 } 681 682 func TestEscape(t *testing.T) { 683 for _, tt := range escapeTests { 684 actual := QueryEscape(tt.in) 685 if tt.out != actual { 686 t.Errorf("QueryEscape(%q) = %q, want %q", tt.in, actual, tt.out) 687 } 688 689 // for bonus points, verify that escape:unescape is an identity. 690 roundtrip, err := QueryUnescape(actual) 691 if roundtrip != tt.in || err != nil { 692 t.Errorf("QueryUnescape(%q) = %q, %s; want %q, %s", actual, roundtrip, err, tt.in, "[no error]") 693 } 694 } 695 } 696 697 //var userinfoTests = []UserinfoTest{ 698 // {"user", "password", "user:password"}, 699 // {"foo:bar", "~!@#$%^&*()_+{}|[]\\-=`:;'\"<>?,./", 700 // "foo%3Abar:~!%40%23$%25%5E&*()_+%7B%7D%7C%5B%5D%5C-=%60%3A;'%22%3C%3E?,.%2F"}, 701 //} 702 703 type EncodeQueryTest struct { 704 m Values 705 expected string 706 } 707 708 var encodeQueryTests = []EncodeQueryTest{ 709 {nil, ""}, 710 {Values{"q": {"puppies"}, "oe": {"utf8"}}, "oe=utf8&q=puppies"}, 711 {Values{"q": {"dogs", "&", "7"}}, "q=dogs&q=%26&q=7"}, 712 {Values{ 713 "a": {"a1", "a2", "a3"}, 714 "b": {"b1", "b2", "b3"}, 715 "c": {"c1", "c2", "c3"}, 716 }, "a=a1&a=a2&a=a3&b=b1&b=b2&b=b3&c=c1&c=c2&c=c3"}, 717 } 718 719 func TestEncodeQuery(t *testing.T) { 720 for _, tt := range encodeQueryTests { 721 if q := tt.m.Encode(); q != tt.expected { 722 t.Errorf(`EncodeQuery(%+v) = %q, want %q`, tt.m, q, tt.expected) 723 } 724 } 725 } 726 727 var resolvePathTests = []struct { 728 base, ref, expected string 729 }{ 730 {"a/b", ".", "/a/"}, 731 {"a/b", "c", "/a/c"}, 732 {"a/b", "..", "/"}, 733 {"a/", "..", "/"}, 734 {"a/", "../..", "/"}, 735 {"a/b/c", "..", "/a/"}, 736 {"a/b/c", "../d", "/a/d"}, 737 {"a/b/c", ".././d", "/a/d"}, 738 {"a/b", "./..", "/"}, 739 {"a/./b", ".", "/a/"}, 740 {"a/../", ".", "/"}, 741 {"a/.././b", "c", "/c"}, 742 } 743 744 func TestResolvePath(t *testing.T) { 745 for _, test := range resolvePathTests { 746 got := resolvePath(test.base, test.ref) 747 if got != test.expected { 748 t.Errorf("For %q + %q got %q; expected %q", test.base, test.ref, got, test.expected) 749 } 750 } 751 } 752 753 var resolveReferenceTests = []struct { 754 base, rel, expected string 755 }{ 756 // Absolute URL references 757 {"http://foo.com?a=b", "https://bar.com/", "https://bar.com/"}, 758 {"http://foo.com/", "https://bar.com/?a=b", "https://bar.com/?a=b"}, 759 {"http://foo.com/bar", "mailto:foo (a] example.com", "mailto:foo (a] example.com"}, 760 761 // Path-absolute references 762 {"http://foo.com/bar", "/baz", "http://foo.com/baz"}, 763 {"http://foo.com/bar?a=b#f", "/baz", "http://foo.com/baz"}, 764 {"http://foo.com/bar?a=b", "/baz?c=d", "http://foo.com/baz?c=d"}, 765 766 // Scheme-relative 767 {"https://foo.com/bar?a=b", "//bar.com/quux", "https://bar.com/quux"}, 768 769 // Path-relative references: 770 771 // ... current directory 772 {"http://foo.com", ".", "http://foo.com/"}, 773 {"http://foo.com/bar", ".", "http://foo.com/"}, 774 {"http://foo.com/bar/", ".", "http://foo.com/bar/"}, 775 776 // ... going down 777 {"http://foo.com", "bar", "http://foo.com/bar"}, 778 {"http://foo.com/", "bar", "http://foo.com/bar"}, 779 {"http://foo.com/bar/baz", "quux", "http://foo.com/bar/quux"}, 780 781 // ... going up 782 {"http://foo.com/bar/baz", "../quux", "http://foo.com/quux"}, 783 {"http://foo.com/bar/baz", "../../../../../quux", "http://foo.com/quux"}, 784 {"http://foo.com/bar", "..", "http://foo.com/"}, 785 {"http://foo.com/bar/baz", "./..", "http://foo.com/"}, 786 // ".." in the middle (issue 3560) 787 {"http://foo.com/bar/baz", "quux/dotdot/../tail", "http://foo.com/bar/quux/tail"}, 788 {"http://foo.com/bar/baz", "quux/./dotdot/../tail", "http://foo.com/bar/quux/tail"}, 789 {"http://foo.com/bar/baz", "quux/./dotdot/.././tail", "http://foo.com/bar/quux/tail"}, 790 {"http://foo.com/bar/baz", "quux/./dotdot/./../tail", "http://foo.com/bar/quux/tail"}, 791 {"http://foo.com/bar/baz", "quux/./dotdot/dotdot/././../../tail", "http://foo.com/bar/quux/tail"}, 792 {"http://foo.com/bar/baz", "quux/./dotdot/dotdot/./.././../tail", "http://foo.com/bar/quux/tail"}, 793 {"http://foo.com/bar/baz", "quux/./dotdot/dotdot/dotdot/./../../.././././tail", "http://foo.com/bar/quux/tail"}, 794 {"http://foo.com/bar/baz", "quux/./dotdot/../dotdot/../dot/./tail/..", "http://foo.com/bar/quux/dot/"}, 795 796 // Remove any dot-segments prior to forming the target URI. 797 // http://tools.ietf.org/html/rfc3986#section-5.2.4 798 {"http://foo.com/dot/./dotdot/../foo/bar", "../baz", "http://foo.com/dot/baz"}, 799 800 // Triple dot isn't special 801 {"http://foo.com/bar", "...", "http://foo.com/..."}, 802 803 // Fragment 804 {"http://foo.com/bar", ".#frag", "http://foo.com/#frag"}, 805 806 // RFC 3986: Normal Examples 807 // http://tools.ietf.org/html/rfc3986#section-5.4.1 808 {"http://a/b/c/d;p?q", "g:h", "g:h"}, 809 {"http://a/b/c/d;p?q", "g", "http://a/b/c/g"}, 810 {"http://a/b/c/d;p?q", "./g", "http://a/b/c/g"}, 811 {"http://a/b/c/d;p?q", "g/", "http://a/b/c/g/"}, 812 {"http://a/b/c/d;p?q", "/g", "http://a/g"}, 813 {"http://a/b/c/d;p?q", "//g", "http://g"}, 814 {"http://a/b/c/d;p?q", "?y", "http://a/b/c/d;p?y"}, 815 {"http://a/b/c/d;p?q", "g?y", "http://a/b/c/g?y"}, 816 {"http://a/b/c/d;p?q", "#s", "http://a/b/c/d;p?q#s"}, 817 {"http://a/b/c/d;p?q", "g#s", "http://a/b/c/g#s"}, 818 {"http://a/b/c/d;p?q", "g?y#s", "http://a/b/c/g?y#s"}, 819 {"http://a/b/c/d;p?q", ";x", "http://a/b/c/;x"}, 820 {"http://a/b/c/d;p?q", "g;x", "http://a/b/c/g;x"}, 821 {"http://a/b/c/d;p?q", "g;x?y#s", "http://a/b/c/g;x?y#s"}, 822 {"http://a/b/c/d;p?q", "", "http://a/b/c/d;p?q"}, 823 {"http://a/b/c/d;p?q", ".", "http://a/b/c/"}, 824 {"http://a/b/c/d;p?q", "./", "http://a/b/c/"}, 825 {"http://a/b/c/d;p?q", "..", "http://a/b/"}, 826 {"http://a/b/c/d;p?q", "../", "http://a/b/"}, 827 {"http://a/b/c/d;p?q", "../g", "http://a/b/g"}, 828 {"http://a/b/c/d;p?q", "../..", "http://a/"}, 829 {"http://a/b/c/d;p?q", "../../", "http://a/"}, 830 {"http://a/b/c/d;p?q", "../../g", "http://a/g"}, 831 832 // RFC 3986: Abnormal Examples 833 // http://tools.ietf.org/html/rfc3986#section-5.4.2 834 {"http://a/b/c/d;p?q", "../../../g", "http://a/g"}, 835 {"http://a/b/c/d;p?q", "../../../../g", "http://a/g"}, 836 {"http://a/b/c/d;p?q", "/./g", "http://a/g"}, 837 {"http://a/b/c/d;p?q", "/../g", "http://a/g"}, 838 {"http://a/b/c/d;p?q", "g.", "http://a/b/c/g."}, 839 {"http://a/b/c/d;p?q", ".g", "http://a/b/c/.g"}, 840 {"http://a/b/c/d;p?q", "g..", "http://a/b/c/g.."}, 841 {"http://a/b/c/d;p?q", "..g", "http://a/b/c/..g"}, 842 {"http://a/b/c/d;p?q", "./../g", "http://a/b/g"}, 843 {"http://a/b/c/d;p?q", "./g/.", "http://a/b/c/g/"}, 844 {"http://a/b/c/d;p?q", "g/./h", "http://a/b/c/g/h"}, 845 {"http://a/b/c/d;p?q", "g/../h", "http://a/b/c/h"}, 846 {"http://a/b/c/d;p?q", "g;x=1/./y", "http://a/b/c/g;x=1/y"}, 847 {"http://a/b/c/d;p?q", "g;x=1/../y", "http://a/b/c/y"}, 848 {"http://a/b/c/d;p?q", "g?y/./x", "http://a/b/c/g?y/./x"}, 849 {"http://a/b/c/d;p?q", "g?y/../x", "http://a/b/c/g?y/../x"}, 850 {"http://a/b/c/d;p?q", "g#s/./x", "http://a/b/c/g#s/./x"}, 851 {"http://a/b/c/d;p?q", "g#s/../x", "http://a/b/c/g#s/../x"}, 852 853 // Extras. 854 {"https://a/b/c/d;p?q", "//g?q", "https://g?q"}, 855 {"https://a/b/c/d;p?q", "//g#s", "https://g#s"}, 856 {"https://a/b/c/d;p?q", "//g/d/e/f?y#s", "https://g/d/e/f?y#s"}, 857 {"https://a/b/c/d;p#s", "?y", "https://a/b/c/d;p?y"}, 858 {"https://a/b/c/d;p?q#s", "?y", "https://a/b/c/d;p?y"}, 859 } 860 861 func TestResolveReference(t *testing.T) { 862 mustParse := func(url string) *URL { 863 u, err := Parse(url) 864 if err != nil { 865 t.Fatalf("Expected URL to parse: %q, got error: %v", url, err) 866 } 867 return u 868 } 869 opaque := &URL{Scheme: "scheme", Opaque: "opaque"} 870 for _, test := range resolveReferenceTests { 871 base := mustParse(test.base) 872 rel := mustParse(test.rel) 873 url := base.ResolveReference(rel) 874 if url.String() != test.expected { 875 t.Errorf("URL(%q).ResolveReference(%q) == %q, got %q", test.base, test.rel, test.expected, url.String()) 876 } 877 // Ensure that new instances are returned. 878 if base == url { 879 t.Errorf("Expected URL.ResolveReference to return new URL instance.") 880 } 881 // Test the convenience wrapper too. 882 url, err := base.Parse(test.rel) 883 if err != nil { 884 t.Errorf("URL(%q).Parse(%q) failed: %v", test.base, test.rel, err) 885 } else if url.String() != test.expected { 886 t.Errorf("URL(%q).Parse(%q) == %q, got %q", test.base, test.rel, test.expected, url.String()) 887 } else if base == url { 888 // Ensure that new instances are returned for the wrapper too. 889 t.Errorf("Expected URL.Parse to return new URL instance.") 890 } 891 // Ensure Opaque resets the URL. 892 url = base.ResolveReference(opaque) 893 if *url != *opaque { 894 t.Errorf("ResolveReference failed to resolve opaque URL: want %#v, got %#v", url, opaque) 895 } 896 // Test the convenience wrapper with an opaque URL too. 897 url, err = base.Parse("scheme:opaque") 898 if err != nil { 899 t.Errorf(`URL(%q).Parse("scheme:opaque") failed: %v`, test.base, err) 900 } else if *url != *opaque { 901 t.Errorf("Parse failed to resolve opaque URL: want %#v, got %#v", url, opaque) 902 } else if base == url { 903 // Ensure that new instances are returned, again. 904 t.Errorf("Expected URL.Parse to return new URL instance.") 905 } 906 } 907 } 908 909 func TestQueryValues(t *testing.T) { 910 u, _ := Parse("http://x.com?foo=bar&bar=1&bar=2") 911 v := u.Query() 912 if len(v) != 2 { 913 t.Errorf("got %d keys in Query values, want 2", len(v)) 914 } 915 if g, e := v.Get("foo"), "bar"; g != e { 916 t.Errorf("Get(foo) = %q, want %q", g, e) 917 } 918 // Case sensitive: 919 if g, e := v.Get("Foo"), ""; g != e { 920 t.Errorf("Get(Foo) = %q, want %q", g, e) 921 } 922 if g, e := v.Get("bar"), "1"; g != e { 923 t.Errorf("Get(bar) = %q, want %q", g, e) 924 } 925 if g, e := v.Get("baz"), ""; g != e { 926 t.Errorf("Get(baz) = %q, want %q", g, e) 927 } 928 v.Del("bar") 929 if g, e := v.Get("bar"), ""; g != e { 930 t.Errorf("second Get(bar) = %q, want %q", g, e) 931 } 932 } 933 934 type parseTest struct { 935 query string 936 out Values 937 } 938 939 var parseTests = []parseTest{ 940 { 941 query: "a=1&b=2", 942 out: Values{"a": []string{"1"}, "b": []string{"2"}}, 943 }, 944 { 945 query: "a=1&a=2&a=banana", 946 out: Values{"a": []string{"1", "2", "banana"}}, 947 }, 948 { 949 query: "ascii=%3Ckey%3A+0x90%3E", 950 out: Values{"ascii": []string{"<key: 0x90>"}}, 951 }, 952 { 953 query: "a=1;b=2", 954 out: Values{"a": []string{"1"}, "b": []string{"2"}}, 955 }, 956 { 957 query: "a=1&a=2;a=banana", 958 out: Values{"a": []string{"1", "2", "banana"}}, 959 }, 960 } 961 962 func TestParseQuery(t *testing.T) { 963 for i, test := range parseTests { 964 form, err := ParseQuery(test.query) 965 if err != nil { 966 t.Errorf("test %d: Unexpected error: %v", i, err) 967 continue 968 } 969 if len(form) != len(test.out) { 970 t.Errorf("test %d: len(form) = %d, want %d", i, len(form), len(test.out)) 971 } 972 for k, evs := range test.out { 973 vs, ok := form[k] 974 if !ok { 975 t.Errorf("test %d: Missing key %q", i, k) 976 continue 977 } 978 if len(vs) != len(evs) { 979 t.Errorf("test %d: len(form[%q]) = %d, want %d", i, k, len(vs), len(evs)) 980 continue 981 } 982 for j, ev := range evs { 983 if v := vs[j]; v != ev { 984 t.Errorf("test %d: form[%q][%d] = %q, want %q", i, k, j, v, ev) 985 } 986 } 987 } 988 } 989 } 990 991 type RequestURITest struct { 992 url *URL 993 out string 994 } 995 996 var requritests = []RequestURITest{ 997 { 998 &URL{ 999 Scheme: "http", 1000 Host: "example.com", 1001 Path: "", 1002 }, 1003 "/", 1004 }, 1005 { 1006 &URL{ 1007 Scheme: "http", 1008 Host: "example.com", 1009 Path: "/a b", 1010 }, 1011 "/a%20b", 1012 }, 1013 // golang.org/issue/4860 variant 1 1014 { 1015 &URL{ 1016 Scheme: "http", 1017 Host: "example.com", 1018 Opaque: "/%2F/%2F/", 1019 }, 1020 "/%2F/%2F/", 1021 }, 1022 // golang.org/issue/4860 variant 2 1023 { 1024 &URL{ 1025 Scheme: "http", 1026 Host: "example.com", 1027 Opaque: "//other.example.com/%2F/%2F/", 1028 }, 1029 "http://other.example.com/%2F/%2F/", 1030 }, 1031 // better fix for issue 4860 1032 { 1033 &URL{ 1034 Scheme: "http", 1035 Host: "example.com", 1036 Path: "/////", 1037 RawPath: "/%2F/%2F/", 1038 }, 1039 "/%2F/%2F/", 1040 }, 1041 { 1042 &URL{ 1043 Scheme: "http", 1044 Host: "example.com", 1045 Path: "/////", 1046 RawPath: "/WRONG/", // ignored because doesn't match Path 1047 }, 1048 "/////", 1049 }, 1050 { 1051 &URL{ 1052 Scheme: "http", 1053 Host: "example.com", 1054 Path: "/a b", 1055 RawQuery: "q=go+language", 1056 }, 1057 "/a%20b?q=go+language", 1058 }, 1059 { 1060 &URL{ 1061 Scheme: "http", 1062 Host: "example.com", 1063 Path: "/a b", 1064 RawPath: "/a b", // ignored because invalid 1065 RawQuery: "q=go+language", 1066 }, 1067 "/a%20b?q=go+language", 1068 }, 1069 { 1070 &URL{ 1071 Scheme: "http", 1072 Host: "example.com", 1073 Path: "/a?b", 1074 RawPath: "/a?b", // ignored because invalid 1075 RawQuery: "q=go+language", 1076 }, 1077 "/a%3Fb?q=go+language", 1078 }, 1079 { 1080 &URL{ 1081 Scheme: "myschema", 1082 Opaque: "opaque", 1083 }, 1084 "opaque", 1085 }, 1086 { 1087 &URL{ 1088 Scheme: "myschema", 1089 Opaque: "opaque", 1090 RawQuery: "q=go+language", 1091 }, 1092 "opaque?q=go+language", 1093 }, 1094 } 1095 1096 func TestRequestURI(t *testing.T) { 1097 for _, tt := range requritests { 1098 s := tt.url.RequestURI() 1099 if s != tt.out { 1100 t.Errorf("%#v.RequestURI() == %q (expected %q)", tt.url, s, tt.out) 1101 } 1102 } 1103 } 1104 1105 func TestParseFailure(t *testing.T) { 1106 // Test that the first parse error is returned. 1107 const url = "%gh&%ij" 1108 _, err := ParseQuery(url) 1109 errStr := fmt.Sprint(err) 1110 if !strings.Contains(errStr, "%gh") { 1111 t.Errorf(`ParseQuery(%q) returned error %q, want something containing %q"`, url, errStr, "%gh") 1112 } 1113 } 1114 1115 func TestParseAuthority(t *testing.T) { 1116 tests := []struct { 1117 in string 1118 wantErr bool 1119 }{ 1120 {"http://[::1]", false}, 1121 {"http://[::1]:80", false}, 1122 {"http://[::1]:namedport", true}, // rfc3986 3.2.3 1123 {"http://[::1]/", false}, 1124 {"http://[::1]a", true}, 1125 {"http://[::1]%23", true}, 1126 {"http://[::1%25en0]", false}, // valid zone id 1127 {"http://[::1]:", true}, // colon, but no port 1128 {"http://[::1]:%38%30", true}, // no hex in port 1129 {"http://[::1%25%10]", false}, // TODO: reject the %10 after the valid zone %25 separator? 1130 {"http://[%10::1]", true}, // no %xx escapes in IP address 1131 {"http://[::1]/%48", false}, // %xx in path is fine 1132 {"http://%41:8080/", true}, // TODO: arguably we should accept reg-name with %xx 1133 {"mysql://x@y(z:123)/foo", false}, // golang.org/issue/12023 1134 {"mysql://x@y(1.2.3.4:123)/foo", false}, 1135 {"mysql://x@y([2001:db8::1]:123)/foo", false}, 1136 {"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 1137 } 1138 for _, tt := range tests { 1139 u, err := Parse(tt.in) 1140 if tt.wantErr { 1141 if err == nil { 1142 t.Errorf("Parse(%q) = %#v; want an error", tt.in, u) 1143 } 1144 continue 1145 } 1146 if err != nil { 1147 t.Logf("Parse(%q) = %v; want no error", tt.in, err) 1148 } 1149 } 1150 } 1151 1152 // Issue 11202 1153 func TestStarRequest(t *testing.T) { 1154 u, err := Parse("*") 1155 if err != nil { 1156 t.Fatal(err) 1157 } 1158 if got, want := u.RequestURI(), "*"; got != want { 1159 t.Errorf("RequestURI = %q; want %q", got, want) 1160 } 1161 } 1162 1163 type shouldEscapeTest struct { 1164 in byte 1165 mode encoding 1166 escape bool 1167 } 1168 1169 var shouldEscapeTests = []shouldEscapeTest{ 1170 // Unreserved characters (2.3) 1171 {'a', encodePath, false}, 1172 {'a', encodeUserPassword, false}, 1173 {'a', encodeQueryComponent, false}, 1174 {'a', encodeFragment, false}, 1175 {'a', encodeHost, false}, 1176 {'z', encodePath, false}, 1177 {'A', encodePath, false}, 1178 {'Z', encodePath, false}, 1179 {'0', encodePath, false}, 1180 {'9', encodePath, false}, 1181 {'-', encodePath, false}, 1182 {'-', encodeUserPassword, false}, 1183 {'-', encodeQueryComponent, false}, 1184 {'-', encodeFragment, false}, 1185 {'.', encodePath, false}, 1186 {'_', encodePath, false}, 1187 {'~', encodePath, false}, 1188 1189 // User information (3.2.1) 1190 {':', encodeUserPassword, true}, 1191 {'/', encodeUserPassword, true}, 1192 {'?', encodeUserPassword, true}, 1193 {'@', encodeUserPassword, true}, 1194 {'$', encodeUserPassword, false}, 1195 {'&', encodeUserPassword, false}, 1196 {'+', encodeUserPassword, false}, 1197 {',', encodeUserPassword, false}, 1198 {';', encodeUserPassword, false}, 1199 {'=', encodeUserPassword, false}, 1200 1201 // Host (IP address, IPv6 address, registered name, port suffix; 3.2.2) 1202 {'!', encodeHost, false}, 1203 {'$', encodeHost, false}, 1204 {'&', encodeHost, false}, 1205 {'\'', encodeHost, false}, 1206 {'(', encodeHost, false}, 1207 {')', encodeHost, false}, 1208 {'*', encodeHost, false}, 1209 {'+', encodeHost, false}, 1210 {',', encodeHost, false}, 1211 {';', encodeHost, false}, 1212 {'=', encodeHost, false}, 1213 {':', encodeHost, false}, 1214 {'[', encodeHost, false}, 1215 {']', encodeHost, false}, 1216 {'0', encodeHost, false}, 1217 {'9', encodeHost, false}, 1218 {'A', encodeHost, false}, 1219 {'z', encodeHost, false}, 1220 {'_', encodeHost, false}, 1221 {'-', encodeHost, false}, 1222 {'.', encodeHost, false}, 1223 } 1224 1225 func TestShouldEscape(t *testing.T) { 1226 for _, tt := range shouldEscapeTests { 1227 if shouldEscape(tt.in, tt.mode) != tt.escape { 1228 t.Errorf("shouldEscape(%q, %v) returned %v; expected %v", tt.in, tt.mode, !tt.escape, tt.escape) 1229 } 1230 } 1231 } 1232