Source file src/net/http/pattern_test.go

     1  // Copyright 2023 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 http
     6  
     7  import (
     8  	"slices"
     9  	"strings"
    10  	"testing"
    11  )
    12  
    13  func TestParsePattern(t *testing.T) {
    14  	lit := func(name string) segment {
    15  		return segment{s: name}
    16  	}
    17  
    18  	wild := func(name string) segment {
    19  		return segment{s: name, wild: true}
    20  	}
    21  
    22  	multi := func(name string) segment {
    23  		s := wild(name)
    24  		s.multi = true
    25  		return s
    26  	}
    27  
    28  	for _, test := range []struct {
    29  		in   string
    30  		want pattern
    31  	}{
    32  		{"/", pattern{segments: []segment{multi("")}}},
    33  		{"/a", pattern{segments: []segment{lit("a")}}},
    34  		{
    35  			"/a/",
    36  			pattern{segments: []segment{lit("a"), multi("")}},
    37  		},
    38  		{"/path/to/something", pattern{segments: []segment{
    39  			lit("path"), lit("to"), lit("something"),
    40  		}}},
    41  		{
    42  			"/{w1}/lit/{w2}",
    43  			pattern{
    44  				segments: []segment{wild("w1"), lit("lit"), wild("w2")},
    45  			},
    46  		},
    47  		{
    48  			"/{w1}/lit/{w2}/",
    49  			pattern{
    50  				segments: []segment{wild("w1"), lit("lit"), wild("w2"), multi("")},
    51  			},
    52  		},
    53  		{
    54  			"example.com/",
    55  			pattern{host: "example.com", segments: []segment{multi("")}},
    56  		},
    57  		{
    58  			"GET /",
    59  			pattern{method: "GET", segments: []segment{multi("")}},
    60  		},
    61  		{
    62  			"POST example.com/foo/{w}",
    63  			pattern{
    64  				method:   "POST",
    65  				host:     "example.com",
    66  				segments: []segment{lit("foo"), wild("w")},
    67  			},
    68  		},
    69  		{
    70  			"/{$}",
    71  			pattern{segments: []segment{lit("/")}},
    72  		},
    73  		{
    74  			"DELETE example.com/a/{foo12}/{$}",
    75  			pattern{method: "DELETE", host: "example.com", segments: []segment{lit("a"), wild("foo12"), lit("/")}},
    76  		},
    77  		{
    78  			"/foo/{$}",
    79  			pattern{segments: []segment{lit("foo"), lit("/")}},
    80  		},
    81  		{
    82  			"/{a}/foo/{rest...}",
    83  			pattern{segments: []segment{wild("a"), lit("foo"), multi("rest")}},
    84  		},
    85  		{
    86  			"//",
    87  			pattern{segments: []segment{lit(""), multi("")}},
    88  		},
    89  		{
    90  			"/foo///./../bar",
    91  			pattern{segments: []segment{lit("foo"), lit(""), lit(""), lit("."), lit(".."), lit("bar")}},
    92  		},
    93  		{
    94  			"a.com/foo//",
    95  			pattern{host: "a.com", segments: []segment{lit("foo"), lit(""), multi("")}},
    96  		},
    97  		{
    98  			"/%61%62/%7b/%",
    99  			pattern{segments: []segment{lit("ab"), lit("{"), lit("%")}},
   100  		},
   101  	} {
   102  		got := mustParsePattern(t, test.in)
   103  		if !got.equal(&test.want) {
   104  			t.Errorf("%q:\ngot  %#v\nwant %#v", test.in, got, &test.want)
   105  		}
   106  	}
   107  }
   108  
   109  func TestParsePatternError(t *testing.T) {
   110  	for _, test := range []struct {
   111  		in       string
   112  		contains string
   113  	}{
   114  		{"", "empty pattern"},
   115  		{"A=B /", "at offset 0: invalid method"},
   116  		{" ", "at offset 1: host/path missing /"},
   117  		{"/{w}x", "at offset 1: bad wildcard segment"},
   118  		{"/x{w}", "at offset 1: bad wildcard segment"},
   119  		{"/{wx", "at offset 1: bad wildcard segment"},
   120  		{"/a/{/}/c", "at offset 3: bad wildcard segment"},
   121  		{"/a/{%61}/c", "at offset 3: bad wildcard name"}, // wildcard names aren't unescaped
   122  		{"/{a$}", "at offset 1: bad wildcard name"},
   123  		{"/{}", "at offset 1: empty wildcard"},
   124  		{"POST a.com/x/{}/y", "at offset 13: empty wildcard"},
   125  		{"/{...}", "at offset 1: empty wildcard"},
   126  		{"/{$...}", "at offset 1: bad wildcard"},
   127  		{"/{$}/", "at offset 1: {$} not at end"},
   128  		{"/{$}/x", "at offset 1: {$} not at end"},
   129  		{"/abc/{$}/x", "at offset 5: {$} not at end"},
   130  		{"/{a...}/", "at offset 1: {...} wildcard not at end"},
   131  		{"/{a...}/x", "at offset 1: {...} wildcard not at end"},
   132  		{"{a}/b", "at offset 0: host contains '{' (missing initial '/'?)"},
   133  		{"/a/{x}/b/{x...}", "at offset 9: duplicate wildcard name"},
   134  		{"GET //", "at offset 4: non-CONNECT pattern with unclean path"},
   135  	} {
   136  		_, err := parsePattern(test.in)
   137  		if err == nil || !strings.Contains(err.Error(), test.contains) {
   138  			t.Errorf("%q:\ngot %v, want error containing %q", test.in, err, test.contains)
   139  		}
   140  	}
   141  }
   142  
   143  func (p1 *pattern) equal(p2 *pattern) bool {
   144  	return p1.method == p2.method && p1.host == p2.host &&
   145  		slices.Equal(p1.segments, p2.segments)
   146  }
   147  
   148  func mustParsePattern(tb testing.TB, s string) *pattern {
   149  	tb.Helper()
   150  	p, err := parsePattern(s)
   151  	if err != nil {
   152  		tb.Fatal(err)
   153  	}
   154  	return p
   155  }
   156  
   157  func TestCompareMethods(t *testing.T) {
   158  	for _, test := range []struct {
   159  		p1, p2 string
   160  		want   relationship
   161  	}{
   162  		{"/", "/", equivalent},
   163  		{"GET /", "GET /", equivalent},
   164  		{"HEAD /", "HEAD /", equivalent},
   165  		{"POST /", "POST /", equivalent},
   166  		{"GET /", "POST /", disjoint},
   167  		{"GET /", "/", moreSpecific},
   168  		{"HEAD /", "/", moreSpecific},
   169  		{"GET /", "HEAD /", moreGeneral},
   170  	} {
   171  		pat1 := mustParsePattern(t, test.p1)
   172  		pat2 := mustParsePattern(t, test.p2)
   173  		got := pat1.compareMethods(pat2)
   174  		if got != test.want {
   175  			t.Errorf("%s vs %s: got %s, want %s", test.p1, test.p2, got, test.want)
   176  		}
   177  		got2 := pat2.compareMethods(pat1)
   178  		want2 := inverseRelationship(test.want)
   179  		if got2 != want2 {
   180  			t.Errorf("%s vs %s: got %s, want %s", test.p2, test.p1, got2, want2)
   181  		}
   182  	}
   183  }
   184  
   185  func TestComparePaths(t *testing.T) {
   186  	for _, test := range []struct {
   187  		p1, p2 string
   188  		want   relationship
   189  	}{
   190  		// A non-final pattern segment can have one of two values: literal or
   191  		// single wildcard. A final pattern segment can have one of 5: empty
   192  		// (trailing slash), literal, dollar, single wildcard, or multi
   193  		// wildcard. Trailing slash and multi wildcard are the same.
   194  
   195  		// A literal should be more specific than anything it overlaps, except itself.
   196  		{"/a", "/a", equivalent},
   197  		{"/a", "/b", disjoint},
   198  		{"/a", "/", moreSpecific},
   199  		{"/a", "/{$}", disjoint},
   200  		{"/a", "/{x}", moreSpecific},
   201  		{"/a", "/{x...}", moreSpecific},
   202  
   203  		// Adding a segment doesn't change that.
   204  		{"/b/a", "/b/a", equivalent},
   205  		{"/b/a", "/b/b", disjoint},
   206  		{"/b/a", "/b/", moreSpecific},
   207  		{"/b/a", "/b/{$}", disjoint},
   208  		{"/b/a", "/b/{x}", moreSpecific},
   209  		{"/b/a", "/b/{x...}", moreSpecific},
   210  		{"/{z}/a", "/{z}/a", equivalent},
   211  		{"/{z}/a", "/{z}/b", disjoint},
   212  		{"/{z}/a", "/{z}/", moreSpecific},
   213  		{"/{z}/a", "/{z}/{$}", disjoint},
   214  		{"/{z}/a", "/{z}/{x}", moreSpecific},
   215  		{"/{z}/a", "/{z}/{x...}", moreSpecific},
   216  
   217  		// Single wildcard on left.
   218  		{"/{z}", "/a", moreGeneral},
   219  		{"/{z}", "/a/b", disjoint},
   220  		{"/{z}", "/{$}", disjoint},
   221  		{"/{z}", "/{x}", equivalent},
   222  		{"/{z}", "/", moreSpecific},
   223  		{"/{z}", "/{x...}", moreSpecific},
   224  		{"/b/{z}", "/b/a", moreGeneral},
   225  		{"/b/{z}", "/b/a/b", disjoint},
   226  		{"/b/{z}", "/b/{$}", disjoint},
   227  		{"/b/{z}", "/b/{x}", equivalent},
   228  		{"/b/{z}", "/b/", moreSpecific},
   229  		{"/b/{z}", "/b/{x...}", moreSpecific},
   230  
   231  		// Trailing slash on left.
   232  		{"/", "/a", moreGeneral},
   233  		{"/", "/a/b", moreGeneral},
   234  		{"/", "/{$}", moreGeneral},
   235  		{"/", "/{x}", moreGeneral},
   236  		{"/", "/", equivalent},
   237  		{"/", "/{x...}", equivalent},
   238  
   239  		{"/b/", "/b/a", moreGeneral},
   240  		{"/b/", "/b/a/b", moreGeneral},
   241  		{"/b/", "/b/{$}", moreGeneral},
   242  		{"/b/", "/b/{x}", moreGeneral},
   243  		{"/b/", "/b/", equivalent},
   244  		{"/b/", "/b/{x...}", equivalent},
   245  
   246  		{"/{z}/", "/{z}/a", moreGeneral},
   247  		{"/{z}/", "/{z}/a/b", moreGeneral},
   248  		{"/{z}/", "/{z}/{$}", moreGeneral},
   249  		{"/{z}/", "/{z}/{x}", moreGeneral},
   250  		{"/{z}/", "/{z}/", equivalent},
   251  		{"/{z}/", "/a/", moreGeneral},
   252  		{"/{z}/", "/{z}/{x...}", equivalent},
   253  		{"/{z}/", "/a/{x...}", moreGeneral},
   254  		{"/a/{z}/", "/{z}/a/", overlaps},
   255  		{"/a/{z}/b/", "/{x}/c/{y...}", overlaps},
   256  
   257  		// Multi wildcard on left.
   258  		{"/{m...}", "/a", moreGeneral},
   259  		{"/{m...}", "/a/b", moreGeneral},
   260  		{"/{m...}", "/{$}", moreGeneral},
   261  		{"/{m...}", "/{x}", moreGeneral},
   262  		{"/{m...}", "/", equivalent},
   263  		{"/{m...}", "/{x...}", equivalent},
   264  
   265  		{"/b/{m...}", "/b/a", moreGeneral},
   266  		{"/b/{m...}", "/b/a/b", moreGeneral},
   267  		{"/b/{m...}", "/b/{$}", moreGeneral},
   268  		{"/b/{m...}", "/b/{x}", moreGeneral},
   269  		{"/b/{m...}", "/b/", equivalent},
   270  		{"/b/{m...}", "/b/{x...}", equivalent},
   271  		{"/b/{m...}", "/a/{x...}", disjoint},
   272  
   273  		{"/{z}/{m...}", "/{z}/a", moreGeneral},
   274  		{"/{z}/{m...}", "/{z}/a/b", moreGeneral},
   275  		{"/{z}/{m...}", "/{z}/{$}", moreGeneral},
   276  		{"/{z}/{m...}", "/{z}/{x}", moreGeneral},
   277  		{"/{z}/{m...}", "/{w}/", equivalent},
   278  		{"/{z}/{m...}", "/a/", moreGeneral},
   279  		{"/{z}/{m...}", "/{z}/{x...}", equivalent},
   280  		{"/{z}/{m...}", "/a/{x...}", moreGeneral},
   281  		{"/a/{m...}", "/a/b/{y...}", moreGeneral},
   282  		{"/a/{m...}", "/a/{x}/{y...}", moreGeneral},
   283  		{"/a/{z}/{m...}", "/a/b/{y...}", moreGeneral},
   284  		{"/a/{z}/{m...}", "/{z}/a/", overlaps},
   285  		{"/a/{z}/{m...}", "/{z}/b/{y...}", overlaps},
   286  		{"/a/{z}/b/{m...}", "/{x}/c/{y...}", overlaps},
   287  		{"/a/{z}/a/{m...}", "/{x}/b", disjoint},
   288  
   289  		// Dollar on left.
   290  		{"/{$}", "/a", disjoint},
   291  		{"/{$}", "/a/b", disjoint},
   292  		{"/{$}", "/{$}", equivalent},
   293  		{"/{$}", "/{x}", disjoint},
   294  		{"/{$}", "/", moreSpecific},
   295  		{"/{$}", "/{x...}", moreSpecific},
   296  
   297  		{"/b/{$}", "/b", disjoint},
   298  		{"/b/{$}", "/b/a", disjoint},
   299  		{"/b/{$}", "/b/a/b", disjoint},
   300  		{"/b/{$}", "/b/{$}", equivalent},
   301  		{"/b/{$}", "/b/{x}", disjoint},
   302  		{"/b/{$}", "/b/", moreSpecific},
   303  		{"/b/{$}", "/b/{x...}", moreSpecific},
   304  		{"/b/{$}", "/b/c/{x...}", disjoint},
   305  		{"/b/{x}/a/{$}", "/{x}/c/{y...}", overlaps},
   306  		{"/{x}/b/{$}", "/a/{x}/{y}", disjoint},
   307  		{"/{x}/b/{$}", "/a/{x}/c", disjoint},
   308  
   309  		{"/{z}/{$}", "/{z}/a", disjoint},
   310  		{"/{z}/{$}", "/{z}/a/b", disjoint},
   311  		{"/{z}/{$}", "/{z}/{$}", equivalent},
   312  		{"/{z}/{$}", "/{z}/{x}", disjoint},
   313  		{"/{z}/{$}", "/{z}/", moreSpecific},
   314  		{"/{z}/{$}", "/a/", overlaps},
   315  		{"/{z}/{$}", "/a/{x...}", overlaps},
   316  		{"/{z}/{$}", "/{z}/{x...}", moreSpecific},
   317  		{"/a/{z}/{$}", "/{z}/a/", overlaps},
   318  	} {
   319  		pat1 := mustParsePattern(t, test.p1)
   320  		pat2 := mustParsePattern(t, test.p2)
   321  		if g := pat1.comparePaths(pat1); g != equivalent {
   322  			t.Errorf("%s does not match itself; got %s", pat1, g)
   323  		}
   324  		if g := pat2.comparePaths(pat2); g != equivalent {
   325  			t.Errorf("%s does not match itself; got %s", pat2, g)
   326  		}
   327  		got := pat1.comparePaths(pat2)
   328  		if got != test.want {
   329  			t.Errorf("%s vs %s: got %s, want %s", test.p1, test.p2, got, test.want)
   330  			t.Logf("pat1: %+v\n", pat1.segments)
   331  			t.Logf("pat2: %+v\n", pat2.segments)
   332  		}
   333  		want2 := inverseRelationship(test.want)
   334  		got2 := pat2.comparePaths(pat1)
   335  		if got2 != want2 {
   336  			t.Errorf("%s vs %s: got %s, want %s", test.p2, test.p1, got2, want2)
   337  		}
   338  	}
   339  }
   340  
   341  func TestConflictsWith(t *testing.T) {
   342  	for _, test := range []struct {
   343  		p1, p2 string
   344  		want   bool
   345  	}{
   346  		{"/a", "/a", true},
   347  		{"/a", "/ab", false},
   348  		{"/a/b/cd", "/a/b/cd", true},
   349  		{"/a/b/cd", "/a/b/c", false},
   350  		{"/a/b/c", "/a/c/c", false},
   351  		{"/{x}", "/{y}", true},
   352  		{"/{x}", "/a", false}, // more specific
   353  		{"/{x}/{y}", "/{x}/a", false},
   354  		{"/{x}/{y}", "/{x}/a/b", false},
   355  		{"/{x}", "/a/{y}", false},
   356  		{"/{x}/{y}", "/{x}/a/", false},
   357  		{"/{x}", "/a/{y...}", false},           // more specific
   358  		{"/{x}/a/{y}", "/{x}/a/{y...}", false}, // more specific
   359  		{"/{x}/{y}", "/{x}/a/{$}", false},      // more specific
   360  		{"/{x}/{y}/{$}", "/{x}/a/{$}", false},
   361  		{"/a/{x}", "/{x}/b", true},
   362  		{"/", "GET /", false},
   363  		{"/", "GET /foo", false},
   364  		{"GET /", "GET /foo", false},
   365  		{"GET /", "/foo", true},
   366  		{"GET /foo", "HEAD /", true},
   367  	} {
   368  		pat1 := mustParsePattern(t, test.p1)
   369  		pat2 := mustParsePattern(t, test.p2)
   370  		got := pat1.conflictsWith(pat2)
   371  		if got != test.want {
   372  			t.Errorf("%q.ConflictsWith(%q) = %t, want %t",
   373  				test.p1, test.p2, got, test.want)
   374  		}
   375  		// conflictsWith should be commutative.
   376  		got = pat2.conflictsWith(pat1)
   377  		if got != test.want {
   378  			t.Errorf("%q.ConflictsWith(%q) = %t, want %t",
   379  				test.p2, test.p1, got, test.want)
   380  		}
   381  	}
   382  }
   383  
   384  func TestRegisterConflict(t *testing.T) {
   385  	mux := NewServeMux()
   386  	pat1 := "/a/{x}/"
   387  	if err := mux.registerErr(pat1, NotFoundHandler()); err != nil {
   388  		t.Fatal(err)
   389  	}
   390  	pat2 := "/a/{y}/{z...}"
   391  	err := mux.registerErr(pat2, NotFoundHandler())
   392  	var got string
   393  	if err == nil {
   394  		got = "<nil>"
   395  	} else {
   396  		got = err.Error()
   397  	}
   398  	want := "matches the same requests as"
   399  	if !strings.Contains(got, want) {
   400  		t.Errorf("got\n%s\nwant\n%s", got, want)
   401  	}
   402  }
   403  
   404  func TestDescribeConflict(t *testing.T) {
   405  	for _, test := range []struct {
   406  		p1, p2 string
   407  		want   string
   408  	}{
   409  		{"/a/{x}", "/a/{y}", "the same requests"},
   410  		{"/", "/{m...}", "the same requests"},
   411  		{"/a/{x}", "/{y}/b", "both match some paths"},
   412  		{"/a", "GET /{x}", "matches more methods than GET /{x}, but has a more specific path pattern"},
   413  		{"GET /a", "HEAD /", "matches more methods than HEAD /, but has a more specific path pattern"},
   414  		{"POST /", "/a", "matches fewer methods than /a, but has a more general path pattern"},
   415  	} {
   416  		got := describeConflict(mustParsePattern(t, test.p1), mustParsePattern(t, test.p2))
   417  		if !strings.Contains(got, test.want) {
   418  			t.Errorf("%s vs. %s:\ngot:\n%s\nwhich does not contain %q",
   419  				test.p1, test.p2, got, test.want)
   420  		}
   421  	}
   422  }
   423  
   424  func TestCommonPath(t *testing.T) {
   425  	for _, test := range []struct {
   426  		p1, p2 string
   427  		want   string
   428  	}{
   429  		{"/a/{x}", "/{x}/a", "/a/a"},
   430  		{"/a/{z}/", "/{z}/a/", "/a/a/"},
   431  		{"/a/{z}/{m...}", "/{z}/a/", "/a/a/"},
   432  		{"/{z}/{$}", "/a/", "/a/"},
   433  		{"/{z}/{$}", "/a/{x...}", "/a/"},
   434  		{"/a/{z}/{$}", "/{z}/a/", "/a/a/"},
   435  		{"/a/{x}/b/{y...}", "/{x}/c/{y...}", "/a/c/b/"},
   436  		{"/a/{x}/b/", "/{x}/c/{y...}", "/a/c/b/"},
   437  		{"/a/{x}/b/{$}", "/{x}/c/{y...}", "/a/c/b/"},
   438  		{"/a/{z}/{x...}", "/{z}/b/{y...}", "/a/b/"},
   439  	} {
   440  		pat1 := mustParsePattern(t, test.p1)
   441  		pat2 := mustParsePattern(t, test.p2)
   442  		if pat1.comparePaths(pat2) != overlaps {
   443  			t.Fatalf("%s does not overlap %s", test.p1, test.p2)
   444  		}
   445  		got := commonPath(pat1, pat2)
   446  		if got != test.want {
   447  			t.Errorf("%s vs. %s: got %q, want %q", test.p1, test.p2, got, test.want)
   448  		}
   449  	}
   450  }
   451  
   452  func TestDifferencePath(t *testing.T) {
   453  	for _, test := range []struct {
   454  		p1, p2 string
   455  		want   string
   456  	}{
   457  		{"/a/{x}", "/{x}/a", "/a/x"},
   458  		{"/{x}/a", "/a/{x}", "/x/a"},
   459  		{"/a/{z}/", "/{z}/a/", "/a/z/"},
   460  		{"/{z}/a/", "/a/{z}/", "/z/a/"},
   461  		{"/{a}/a/", "/a/{z}/", "/ax/a/"},
   462  		{"/a/{z}/{x...}", "/{z}/b/{y...}", "/a/z/"},
   463  		{"/{z}/b/{y...}", "/a/{z}/{x...}", "/z/b/"},
   464  		{"/a/b/", "/a/b/c", "/a/b/"},
   465  		{"/a/b/{x...}", "/a/b/c", "/a/b/"},
   466  		{"/a/b/{x...}", "/a/b/c/d", "/a/b/"},
   467  		{"/a/b/{x...}", "/a/b/c/d/", "/a/b/"},
   468  		{"/a/{z}/{m...}", "/{z}/a/", "/a/z/"},
   469  		{"/{z}/a/", "/a/{z}/{m...}", "/z/a/"},
   470  		{"/{z}/{$}", "/a/", "/z/"},
   471  		{"/a/", "/{z}/{$}", "/a/x"},
   472  		{"/{z}/{$}", "/a/{x...}", "/z/"},
   473  		{"/a/{foo...}", "/{z}/{$}", "/a/foo"},
   474  		{"/a/{z}/{$}", "/{z}/a/", "/a/z/"},
   475  		{"/{z}/a/", "/a/{z}/{$}", "/z/a/x"},
   476  		{"/a/{x}/b/{y...}", "/{x}/c/{y...}", "/a/x/b/"},
   477  		{"/{x}/c/{y...}", "/a/{x}/b/{y...}", "/x/c/"},
   478  		{"/a/{c}/b/", "/{x}/c/{y...}", "/a/cx/b/"},
   479  		{"/{x}/c/{y...}", "/a/{c}/b/", "/x/c/"},
   480  		{"/a/{x}/b/{$}", "/{x}/c/{y...}", "/a/x/b/"},
   481  		{"/{x}/c/{y...}", "/a/{x}/b/{$}", "/x/c/"},
   482  	} {
   483  		pat1 := mustParsePattern(t, test.p1)
   484  		pat2 := mustParsePattern(t, test.p2)
   485  		rel := pat1.comparePaths(pat2)
   486  		if rel != overlaps && rel != moreGeneral {
   487  			t.Fatalf("%s vs. %s are %s, need overlaps or moreGeneral", pat1, pat2, rel)
   488  		}
   489  		got := differencePath(pat1, pat2)
   490  		if got != test.want {
   491  			t.Errorf("%s vs. %s: got %q, want %q", test.p1, test.p2, got, test.want)
   492  		}
   493  	}
   494  }
   495  

View as plain text