summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--modules/caddyhttp/matchers.go95
-rw-r--r--modules/caddyhttp/matchers_test.go113
-rw-r--r--modules/caddyhttp/reverseproxy/fastcgi/caddyfile.go6
3 files changed, 172 insertions, 42 deletions
diff --git a/modules/caddyhttp/matchers.go b/modules/caddyhttp/matchers.go
index 2bcdc85..19f6bf2 100644
--- a/modules/caddyhttp/matchers.go
+++ b/modules/caddyhttp/matchers.go
@@ -99,13 +99,15 @@ type (
cidrs []*net.IPNet
}
- // MatchNot matches requests by negating its matchers' results.
- // To use, simply specify a set of matchers like you normally would;
- // the only difference is that their result will be negated.
+ // MatchNot matches requests by negating the results of its matcher
+ // sets. A single "not" matcher takes one or more matcher sets. Each
+ // matcher set is OR'ed; in other words, if any matcher set returns
+ // true, the final result of the "not" matcher is false. Individual
+ // matchers within a set work the same (i.e. different matchers in
+ // the same set are AND'ed).
MatchNot struct {
- MatchersRaw caddy.ModuleMap `json:"-" caddy:"namespace=http.matchers"`
-
- Matchers MatcherSet `json:"-"`
+ MatcherSetsRaw []caddy.ModuleMap `json:"-" caddy:"namespace=http.matchers"`
+ MatcherSets []MatcherSet `json:"-"`
}
)
@@ -538,25 +540,16 @@ func (MatchNot) CaddyModule() caddy.ModuleInfo {
}
}
-// UnmarshalJSON unmarshals data into m's unexported map field.
-// This is done because we cannot embed the map directly into
-// the struct, but we need a struct because we need another
-// field just for the provisioned modules.
-func (m *MatchNot) UnmarshalJSON(data []byte) error {
- return json.Unmarshal(data, &m.MatchersRaw)
-}
-
-// MarshalJSON marshals m's matchers.
-func (m MatchNot) MarshalJSON() ([]byte, error) {
- return json.Marshal(m.MatchersRaw)
-}
-
// UnmarshalCaddyfile implements caddyfile.Unmarshaler.
func (m *MatchNot) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
// first, unmarshal each matcher in the set from its tokens
-
- matcherMap := make(map[string]RequestMatcher)
+ type matcherPair struct {
+ raw caddy.ModuleMap
+ decoded MatcherSet
+ }
for d.Next() {
+ var mp matcherPair
+ matcherMap := make(map[string]RequestMatcher)
for d.NextBlock(0) {
matcherName := d.Val()
mod, err := caddy.GetModule("http.matchers." + matcherName)
@@ -572,42 +565,64 @@ func (m *MatchNot) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
return err
}
rm := unm.(RequestMatcher)
- m.Matchers = append(m.Matchers, rm)
matcherMap[matcherName] = rm
+ mp.decoded = append(mp.decoded, rm)
}
- }
- // we should now be functional, but we also need
- // to be able to marshal as JSON, otherwise config
- // adaptation won't work properly
- m.MatchersRaw = make(caddy.ModuleMap)
- for name, matchers := range matcherMap {
- jsonBytes, err := json.Marshal(matchers)
- if err != nil {
- return fmt.Errorf("marshaling matcher %s: %v", name, err)
+ // we should now have a functional 'not' matcher, but we also
+ // need to be able to marshal as JSON, otherwise config
+ // adaptation will be missing the matchers!
+ mp.raw = make(caddy.ModuleMap)
+ for name, matcher := range matcherMap {
+ jsonBytes, err := json.Marshal(matcher)
+ if err != nil {
+ return fmt.Errorf("marshaling %T matcher: %v", matcher, err)
+ }
+ mp.raw[name] = jsonBytes
}
- m.MatchersRaw[name] = jsonBytes
+ m.MatcherSetsRaw = append(m.MatcherSetsRaw, mp.raw)
}
-
return nil
}
+// UnmarshalJSON satisfies json.Unmarshaler. It puts the JSON
+// bytes directly into m's MatcherSetsRaw field.
+func (m *MatchNot) UnmarshalJSON(data []byte) error {
+ return json.Unmarshal(data, &m.MatcherSetsRaw)
+}
+
+// MarshalJSON satisfies json.Marshaler by marshaling
+// m's raw matcher sets.
+func (m MatchNot) MarshalJSON() ([]byte, error) {
+ return json.Marshal(m.MatcherSetsRaw)
+}
+
// Provision loads the matcher modules to be negated.
func (m *MatchNot) Provision(ctx caddy.Context) error {
- mods, err := ctx.LoadModule(m, "MatchersRaw")
+ matcherSets, err := ctx.LoadModule(m, "MatcherSetsRaw")
if err != nil {
- return fmt.Errorf("loading matchers: %v", err)
+ return fmt.Errorf("loading matcher sets: %v", err)
}
- for _, modIface := range mods.(map[string]interface{}) {
- m.Matchers = append(m.Matchers, modIface.(RequestMatcher))
+ for _, modMap := range matcherSets.([]map[string]interface{}) {
+ var ms MatcherSet
+ for _, modIface := range modMap {
+ ms = append(ms, modIface.(RequestMatcher))
+ }
+ m.MatcherSets = append(m.MatcherSets, ms)
}
return nil
}
-// Match returns true if r matches m. Since this matcher negates the
-// embedded matchers, false is returned if any of its matchers match.
+// Match returns true if r matches m. Since this matcher negates
+// the embedded matchers, false is returned if any of its matcher
+// sets return true.
func (m MatchNot) Match(r *http.Request) bool {
- return !m.Matchers.Match(r)
+ for _, ms := range m.MatcherSets {
+ if ms.Match(r) {
+ return false
+ }
+ }
+ return true
}
// CaddyModule returns the Caddy module information.
diff --git a/modules/caddyhttp/matchers_test.go b/modules/caddyhttp/matchers_test.go
index 1e50776..021bb98 100644
--- a/modules/caddyhttp/matchers_test.go
+++ b/modules/caddyhttp/matchers_test.go
@@ -823,6 +823,119 @@ func TestResponseMatcher(t *testing.T) {
}
}
+func TestNotMatcher(t *testing.T) {
+ for i, tc := range []struct {
+ host, path string
+ match MatchNot
+ expect bool
+ }{
+ {
+ host: "example.com", path: "/",
+ match: MatchNot{},
+ expect: true,
+ },
+ {
+ host: "example.com", path: "/foo",
+ match: MatchNot{
+ MatcherSets: []MatcherSet{
+ {
+ MatchPath{"/foo"},
+ },
+ },
+ },
+ expect: false,
+ },
+ {
+ host: "example.com", path: "/bar",
+ match: MatchNot{
+ MatcherSets: []MatcherSet{
+ {
+ MatchPath{"/foo"},
+ },
+ },
+ },
+ expect: true,
+ },
+ {
+ host: "example.com", path: "/bar",
+ match: MatchNot{
+ MatcherSets: []MatcherSet{
+ {
+ MatchPath{"/foo"},
+ },
+ {
+ MatchHost{"example.com"},
+ },
+ },
+ },
+ expect: false,
+ },
+ {
+ host: "example.com", path: "/bar",
+ match: MatchNot{
+ MatcherSets: []MatcherSet{
+ {
+ MatchPath{"/bar"},
+ },
+ {
+ MatchHost{"example.com"},
+ },
+ },
+ },
+ expect: false,
+ },
+ {
+ host: "example.com", path: "/foo",
+ match: MatchNot{
+ MatcherSets: []MatcherSet{
+ {
+ MatchPath{"/bar"},
+ },
+ {
+ MatchHost{"sub.example.com"},
+ },
+ },
+ },
+ expect: true,
+ },
+ {
+ host: "example.com", path: "/foo",
+ match: MatchNot{
+ MatcherSets: []MatcherSet{
+ {
+ MatchPath{"/foo"},
+ MatchHost{"example.com"},
+ },
+ },
+ },
+ expect: false,
+ },
+ {
+ host: "example.com", path: "/foo",
+ match: MatchNot{
+ MatcherSets: []MatcherSet{
+ {
+ MatchPath{"/bar"},
+ MatchHost{"example.com"},
+ },
+ },
+ },
+ expect: true,
+ },
+ } {
+ req := &http.Request{Host: tc.host, URL: &url.URL{Path: tc.path}}
+ repl := caddy.NewReplacer()
+ ctx := context.WithValue(req.Context(), caddy.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: host=%s path=%s'", i, tc.match, tc.expect, actual, tc.host, tc.path)
+ continue
+ }
+ }
+}
+
func BenchmarkHostMatcherWithoutPlaceholder(b *testing.B) {
req := &http.Request{Host: "localhost"}
repl := caddy.NewReplacer()
diff --git a/modules/caddyhttp/reverseproxy/fastcgi/caddyfile.go b/modules/caddyhttp/reverseproxy/fastcgi/caddyfile.go
index 2eb1f3d..fdf9818 100644
--- a/modules/caddyhttp/reverseproxy/fastcgi/caddyfile.go
+++ b/modules/caddyhttp/reverseproxy/fastcgi/caddyfile.go
@@ -128,8 +128,10 @@ func parsePHPFastCGI(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error
TryFiles: []string{"{http.request.uri.path}/index.php"},
}),
"not": h.JSON(caddyhttp.MatchNot{
- MatchersRaw: caddy.ModuleMap{
- "path": h.JSON(caddyhttp.MatchPath{"*/"}),
+ MatcherSetsRaw: []caddy.ModuleMap{
+ {
+ "path": h.JSON(caddyhttp.MatchPath{"*/"}),
+ },
},
}),
}