summaryrefslogtreecommitdiff
path: root/modules/caddyhttp/matchers_test.go
diff options
context:
space:
mode:
authorMatthew Holt <mholt@users.noreply.github.com>2019-05-10 21:07:02 -0600
committerMatthew Holt <mholt@users.noreply.github.com>2019-05-10 21:07:02 -0600
commit8ae0d6a509fd1b871457cf742369af04346933a8 (patch)
tree9a0b977e5f36acc6f06737ff34b1f21d608ada6f /modules/caddyhttp/matchers_test.go
parent48b5a803208548d143b7ead9b6fc9b524cd0e031 (diff)
caddyhttp: Implement better HTTP matchers including regexp; add tests
Diffstat (limited to 'modules/caddyhttp/matchers_test.go')
-rw-r--r--modules/caddyhttp/matchers_test.go346
1 files changed, 346 insertions, 0 deletions
diff --git a/modules/caddyhttp/matchers_test.go b/modules/caddyhttp/matchers_test.go
new file mode 100644
index 0000000..07c98e3
--- /dev/null
+++ b/modules/caddyhttp/matchers_test.go
@@ -0,0 +1,346 @@
+package caddyhttp
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "net/http/httptest"
+ "net/url"
+ "testing"
+)
+
+func TestHostMatcher(t *testing.T) {
+ for i, tc := range []struct {
+ match matchHost
+ input string
+ expect bool
+ }{
+ {
+ match: matchHost{},
+ input: "example.com",
+ expect: false,
+ },
+ {
+ match: matchHost{"example.com"},
+ input: "example.com",
+ expect: true,
+ },
+ {
+ match: matchHost{"example.com"},
+ input: "foo.example.com",
+ expect: false,
+ },
+ {
+ match: matchHost{"example.com"},
+ input: "EXAMPLE.COM",
+ expect: true,
+ },
+ {
+ match: matchHost{"foo.example.com"},
+ input: "foo.example.com",
+ expect: true,
+ },
+ {
+ match: matchHost{"foo.example.com"},
+ input: "bar.example.com",
+ expect: false,
+ },
+ {
+ match: matchHost{"*.example.com"},
+ input: "example.com",
+ expect: false,
+ },
+ {
+ match: matchHost{"*.example.com"},
+ input: "foo.example.com",
+ expect: true,
+ },
+ {
+ match: matchHost{"*.example.com"},
+ input: "foo.bar.example.com",
+ expect: false,
+ },
+ {
+ match: matchHost{"*.example.com", "example.net"},
+ input: "example.net",
+ expect: true,
+ },
+ {
+ match: matchHost{"example.net", "*.example.com"},
+ input: "foo.example.com",
+ expect: true,
+ },
+ {
+ match: matchHost{"*.example.net", "*.*.example.com"},
+ input: "foo.bar.example.com",
+ expect: true,
+ },
+ {
+ match: matchHost{"*.example.net", "sub.*.example.com"},
+ input: "sub.foo.example.com",
+ expect: true,
+ },
+ {
+ match: matchHost{"*.example.net", "sub.*.example.com"},
+ input: "sub.foo.example.net",
+ expect: false,
+ },
+ } {
+ req := &http.Request{Host: tc.input}
+ actual := tc.match.Match(req)
+ if actual != tc.expect {
+ t.Errorf("Test %d %v: Expected %t, got %t for '%s'", i, tc.match, tc.expect, actual, tc.input)
+ continue
+ }
+ }
+}
+
+func TestPathMatcher(t *testing.T) {
+ for i, tc := range []struct {
+ match matchPath
+ input string
+ expect bool
+ }{
+ {
+ match: matchPath{},
+ input: "/",
+ expect: false,
+ },
+ {
+ match: matchPath{"/"},
+ input: "/",
+ expect: true,
+ },
+ {
+ match: matchPath{"/foo/bar"},
+ input: "/",
+ expect: false,
+ },
+ {
+ match: matchPath{"/foo/bar"},
+ input: "/foo/bar",
+ expect: true,
+ },
+ {
+ match: matchPath{"/foo/bar/"},
+ input: "/foo/bar",
+ expect: false,
+ },
+ {
+ match: matchPath{"/foo/bar/", "/other"},
+ input: "/other/",
+ expect: true,
+ },
+ } {
+ req := &http.Request{URL: &url.URL{Path: tc.input}}
+ actual := tc.match.Match(req)
+ if actual != tc.expect {
+ t.Errorf("Test %d %v: Expected %t, got %t for '%s'", i, tc.match, tc.expect, actual, tc.input)
+ continue
+ }
+ }
+}
+
+func TestPathREMatcher(t *testing.T) {
+ for i, tc := range []struct {
+ match matchPathRE
+ input string
+ expect bool
+ expectRepl map[string]string
+ }{
+ {
+ match: matchPathRE{},
+ input: "/",
+ expect: true,
+ },
+ {
+ match: matchPathRE{matchRegexp{Pattern: "/"}},
+ input: "/",
+ expect: true,
+ },
+ {
+ match: matchPathRE{matchRegexp{Pattern: "/foo"}},
+ input: "/foo",
+ expect: true,
+ },
+ {
+ match: matchPathRE{matchRegexp{Pattern: "/foo"}},
+ input: "/foo/",
+ expect: true,
+ },
+ {
+ match: matchPathRE{matchRegexp{Pattern: "/bar"}},
+ input: "/foo/",
+ expect: false,
+ },
+ {
+ match: matchPathRE{matchRegexp{Pattern: "^/bar"}},
+ input: "/foo/bar",
+ expect: false,
+ },
+ {
+ match: matchPathRE{matchRegexp{Pattern: "^/foo/(.*)/baz$", Name: "name"}},
+ input: "/foo/bar/baz",
+ expect: true,
+ expectRepl: map[string]string{"name.1": "bar"},
+ },
+ {
+ match: matchPathRE{matchRegexp{Pattern: "^/foo/(?P<myparam>.*)/baz$", Name: "name"}},
+ input: "/foo/bar/baz",
+ expect: true,
+ expectRepl: map[string]string{"name.myparam": "bar"},
+ },
+ } {
+ // compile the regexp and validate its name
+ err := tc.match.Provision()
+ if err != nil {
+ t.Errorf("Test %d %v: Provisioning: %v", i, tc.match, err)
+ continue
+ }
+ err = tc.match.Validate()
+ if err != nil {
+ t.Errorf("Test %d %v: Validating: %v", i, tc.match, err)
+ continue
+ }
+
+ // set up the fake request and its Replacer
+ req := &http.Request{URL: &url.URL{Path: tc.input}}
+ repl := &Replacer{req: req, resp: httptest.NewRecorder(), custom: make(map[string]string)}
+ ctx := context.WithValue(req.Context(), ReplacerCtxKey, repl)
+ req = req.WithContext(ctx)
+
+ actual := tc.match.Match(req)
+ if actual != tc.expect {
+ t.Errorf("Test %d [%v]: Expected %t, got %t for input '%s'",
+ i, tc.match.Pattern, tc.expect, actual, tc.input)
+ continue
+ }
+
+ for key, expectVal := range tc.expectRepl {
+ placeholder := fmt.Sprintf("{http.matchers.path_regexp.%s}", key)
+ actualVal := repl.Replace(placeholder, "<empty>")
+ if actualVal != expectVal {
+ t.Errorf("Test %d [%v]: Expected placeholder {http.matchers.path_regexp.%s} to be '%s' but got '%s'",
+ i, tc.match.Pattern, key, expectVal, actualVal)
+ continue
+ }
+ }
+ }
+}
+
+func TestHeaderMatcher(t *testing.T) {
+ for i, tc := range []struct {
+ match matchHeader
+ input http.Header // make sure these are canonical cased (std lib will do that in a real request)
+ expect bool
+ }{
+ {
+ match: matchHeader{"Field": []string{"foo"}},
+ input: http.Header{"Field": []string{"foo"}},
+ expect: true,
+ },
+ {
+ match: matchHeader{"Field": []string{"foo", "bar"}},
+ input: http.Header{"Field": []string{"bar"}},
+ expect: true,
+ },
+ {
+ match: matchHeader{"Field": []string{"foo", "bar"}},
+ input: http.Header{"Alakazam": []string{"kapow"}},
+ expect: false,
+ },
+ {
+ match: matchHeader{"Field": []string{"foo", "bar"}},
+ input: http.Header{"Field": []string{"kapow"}},
+ expect: false,
+ },
+ {
+ match: matchHeader{"Field": []string{"foo", "bar"}},
+ input: http.Header{"Field": []string{"kapow", "foo"}},
+ expect: true,
+ },
+ {
+ match: matchHeader{"Field1": []string{"foo"}, "Field2": []string{"bar"}},
+ input: http.Header{"Field1": []string{"foo"}, "Field2": []string{"bar"}},
+ expect: true,
+ },
+ {
+ match: matchHeader{"field1": []string{"foo"}, "field2": []string{"bar"}},
+ input: http.Header{"Field1": []string{"foo"}, "Field2": []string{"bar"}},
+ expect: true,
+ },
+ {
+ match: matchHeader{"field1": []string{"foo"}, "field2": []string{"bar"}},
+ input: http.Header{"Field1": []string{"foo"}, "Field2": []string{"kapow"}},
+ expect: false,
+ },
+ } {
+ req := &http.Request{Header: tc.input}
+ actual := tc.match.Match(req)
+ if actual != tc.expect {
+ t.Errorf("Test %d %v: Expected %t, got %t for '%s'", i, tc.match, tc.expect, actual, tc.input)
+ continue
+ }
+ }
+}
+
+func TestHeaderREMatcher(t *testing.T) {
+ for i, tc := range []struct {
+ match matchHeaderRE
+ input http.Header // make sure these are canonical cased (std lib will do that in a real request)
+ expect bool
+ expectRepl map[string]string
+ }{
+ {
+ match: matchHeaderRE{"Field": &matchRegexp{Pattern: "foo"}},
+ input: http.Header{"Field": []string{"foo"}},
+ expect: true,
+ },
+ {
+ match: matchHeaderRE{"Field": &matchRegexp{Pattern: "$foo^"}},
+ input: http.Header{"Field": []string{"foobar"}},
+ expect: false,
+ },
+ {
+ match: matchHeaderRE{"Field": &matchRegexp{Pattern: "^foo(.*)$", Name: "name"}},
+ input: http.Header{"Field": []string{"foobar"}},
+ expect: true,
+ expectRepl: map[string]string{"name.1": "bar"},
+ },
+ } {
+ // compile the regexp and validate its name
+ err := tc.match.Provision()
+ if err != nil {
+ t.Errorf("Test %d %v: Provisioning: %v", i, tc.match, err)
+ continue
+ }
+ err = tc.match.Validate()
+ if err != nil {
+ t.Errorf("Test %d %v: Validating: %v", i, tc.match, err)
+ continue
+ }
+
+ // set up the fake request and its Replacer
+ req := &http.Request{Header: tc.input, URL: new(url.URL)}
+ repl := &Replacer{req: req, resp: httptest.NewRecorder(), custom: make(map[string]string)}
+ ctx := context.WithValue(req.Context(), ReplacerCtxKey, repl)
+ req = req.WithContext(ctx)
+
+ actual := tc.match.Match(req)
+ if actual != tc.expect {
+ t.Errorf("Test %d [%v]: Expected %t, got %t for input '%s'",
+ i, tc.match, tc.expect, actual, tc.input)
+ continue
+ }
+
+ for key, expectVal := range tc.expectRepl {
+ placeholder := fmt.Sprintf("{http.matchers.header_regexp.%s}", key)
+ actualVal := repl.Replace(placeholder, "<empty>")
+ if actualVal != expectVal {
+ t.Errorf("Test %d [%v]: Expected placeholder {http.matchers.header_regexp.%s} to be '%s' but got '%s'",
+ i, tc.match, key, expectVal, actualVal)
+ continue
+ }
+ }
+ }
+}