From 73643ea736ca7c6a9ef32dbf78b38fbcdf5e92b2 Mon Sep 17 00:00:00 2001 From: Matt Holt Date: Wed, 1 Apr 2020 10:58:29 -0600 Subject: caddyhttp: 'not' matcher now accepts multiple matcher sets and OR's them (#3208) See https://caddy.community/t/v2-matcher-or-in-not/7355/ --- modules/caddyhttp/matchers.go | 95 +++++++++++++++++++++++++------------------ 1 file changed, 55 insertions(+), 40 deletions(-) (limited to 'modules/caddyhttp/matchers.go') 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. -- cgit v1.2.3