mirror of
https://github.com/caddyserver/caddy.git
synced 2025-02-08 17:16:36 +01:00
caddyhttp: 'not' matcher now accepts multiple matcher sets and OR's them (#3208)
See https://caddy.community/t/v2-matcher-or-in-not/7355/
This commit is contained in:
parent
809e72792c
commit
73643ea736
3 changed files with 172 additions and 42 deletions
|
@ -99,13 +99,15 @@ type (
|
||||||
cidrs []*net.IPNet
|
cidrs []*net.IPNet
|
||||||
}
|
}
|
||||||
|
|
||||||
// MatchNot matches requests by negating its matchers' results.
|
// MatchNot matches requests by negating the results of its matcher
|
||||||
// To use, simply specify a set of matchers like you normally would;
|
// sets. A single "not" matcher takes one or more matcher sets. Each
|
||||||
// the only difference is that their result will be negated.
|
// 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 {
|
MatchNot struct {
|
||||||
MatchersRaw caddy.ModuleMap `json:"-" caddy:"namespace=http.matchers"`
|
MatcherSetsRaw []caddy.ModuleMap `json:"-" caddy:"namespace=http.matchers"`
|
||||||
|
MatcherSets []MatcherSet `json:"-"`
|
||||||
Matchers 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.
|
// UnmarshalCaddyfile implements caddyfile.Unmarshaler.
|
||||||
func (m *MatchNot) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
func (m *MatchNot) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||||
// first, unmarshal each matcher in the set from its tokens
|
// first, unmarshal each matcher in the set from its tokens
|
||||||
|
type matcherPair struct {
|
||||||
matcherMap := make(map[string]RequestMatcher)
|
raw caddy.ModuleMap
|
||||||
|
decoded MatcherSet
|
||||||
|
}
|
||||||
for d.Next() {
|
for d.Next() {
|
||||||
|
var mp matcherPair
|
||||||
|
matcherMap := make(map[string]RequestMatcher)
|
||||||
for d.NextBlock(0) {
|
for d.NextBlock(0) {
|
||||||
matcherName := d.Val()
|
matcherName := d.Val()
|
||||||
mod, err := caddy.GetModule("http.matchers." + matcherName)
|
mod, err := caddy.GetModule("http.matchers." + matcherName)
|
||||||
|
@ -572,42 +565,64 @@ func (m *MatchNot) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
rm := unm.(RequestMatcher)
|
rm := unm.(RequestMatcher)
|
||||||
m.Matchers = append(m.Matchers, rm)
|
|
||||||
matcherMap[matcherName] = rm
|
matcherMap[matcherName] = rm
|
||||||
}
|
mp.decoded = append(mp.decoded, rm)
|
||||||
}
|
}
|
||||||
|
|
||||||
// we should now be functional, but we also need
|
// we should now have a functional 'not' matcher, but we also
|
||||||
// to be able to marshal as JSON, otherwise config
|
// need to be able to marshal as JSON, otherwise config
|
||||||
// adaptation won't work properly
|
// adaptation will be missing the matchers!
|
||||||
m.MatchersRaw = make(caddy.ModuleMap)
|
mp.raw = make(caddy.ModuleMap)
|
||||||
for name, matchers := range matcherMap {
|
for name, matcher := range matcherMap {
|
||||||
jsonBytes, err := json.Marshal(matchers)
|
jsonBytes, err := json.Marshal(matcher)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("marshaling matcher %s: %v", name, err)
|
return fmt.Errorf("marshaling %T matcher: %v", matcher, err)
|
||||||
}
|
}
|
||||||
m.MatchersRaw[name] = jsonBytes
|
mp.raw[name] = jsonBytes
|
||||||
|
}
|
||||||
|
m.MatcherSetsRaw = append(m.MatcherSetsRaw, mp.raw)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
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.
|
// Provision loads the matcher modules to be negated.
|
||||||
func (m *MatchNot) Provision(ctx caddy.Context) error {
|
func (m *MatchNot) Provision(ctx caddy.Context) error {
|
||||||
mods, err := ctx.LoadModule(m, "MatchersRaw")
|
matcherSets, err := ctx.LoadModule(m, "MatcherSetsRaw")
|
||||||
if err != nil {
|
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{}) {
|
for _, modMap := range matcherSets.([]map[string]interface{}) {
|
||||||
m.Matchers = append(m.Matchers, modIface.(RequestMatcher))
|
var ms MatcherSet
|
||||||
|
for _, modIface := range modMap {
|
||||||
|
ms = append(ms, modIface.(RequestMatcher))
|
||||||
|
}
|
||||||
|
m.MatcherSets = append(m.MatcherSets, ms)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Match returns true if r matches m. Since this matcher negates the
|
// Match returns true if r matches m. Since this matcher negates
|
||||||
// embedded matchers, false is returned if any of its matchers match.
|
// the embedded matchers, false is returned if any of its matcher
|
||||||
|
// sets return true.
|
||||||
func (m MatchNot) Match(r *http.Request) bool {
|
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.
|
// CaddyModule returns the Caddy module information.
|
||||||
|
|
|
@ -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) {
|
func BenchmarkHostMatcherWithoutPlaceholder(b *testing.B) {
|
||||||
req := &http.Request{Host: "localhost"}
|
req := &http.Request{Host: "localhost"}
|
||||||
repl := caddy.NewReplacer()
|
repl := caddy.NewReplacer()
|
||||||
|
|
|
@ -128,9 +128,11 @@ func parsePHPFastCGI(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error
|
||||||
TryFiles: []string{"{http.request.uri.path}/index.php"},
|
TryFiles: []string{"{http.request.uri.path}/index.php"},
|
||||||
}),
|
}),
|
||||||
"not": h.JSON(caddyhttp.MatchNot{
|
"not": h.JSON(caddyhttp.MatchNot{
|
||||||
MatchersRaw: caddy.ModuleMap{
|
MatcherSetsRaw: []caddy.ModuleMap{
|
||||||
|
{
|
||||||
"path": h.JSON(caddyhttp.MatchPath{"*/"}),
|
"path": h.JSON(caddyhttp.MatchPath{"*/"}),
|
||||||
},
|
},
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
redirHandler := caddyhttp.StaticResponse{
|
redirHandler := caddyhttp.StaticResponse{
|
||||||
|
|
Loading…
Reference in a new issue