Home | History | Annotate | Download | only in url
      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