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