Black Lives Matter. Support the Equal Justice Initiative.

Source file src/net/url/url_test.go

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

View as plain text