Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS
Mirrored from https://github.com/Slazee/caddy.git
Synced Nov 14, 05:15 UTC
caddyhttp: 'not' matcher now accepts multiple matcher sets and OR's them (#3208)
See https://caddy.community/t/v2-matcher-or-in-not/7355/
Matt Holt committed
Apr 1, 2020 at 10:58 UTC
73643ea736ca7c6a9ef32dbf78b38fbcdf5e92b2
3 files changed
+172
-42
modules/caddyhttp/matchers.go
+55
-40
@@ -99,13 +99,15 @@ type (
99
cidrs []*net.IPNet
100
}
101
102
- // MatchNot matches requests by negating its matchers' results.
103
- // To use, simply specify a set of matchers like you normally would;
104
- // the only difference is that their result will be negated.
102
+ // MatchNot matches requests by negating the results of its matcher
103
+ // sets. A single "not" matcher takes one or more matcher sets. Each
104
+ // matcher set is OR'ed; in other words, if any matcher set returns
105
+ // true, the final result of the "not" matcher is false. Individual
106
+ // matchers within a set work the same (i.e. different matchers in
107
+ // the same set are AND'ed).
108
MatchNot struct {
106
- MatchersRaw caddy.ModuleMap `json:"-" caddy:"namespace=http.matchers"`
107
-
108
- Matchers MatcherSet `json:"-"`
109
+ MatcherSetsRaw []caddy.ModuleMap `json:"-" caddy:"namespace=http.matchers"`
110
+ MatcherSets []MatcherSet `json:"-"`
111
}
112
)
113
@@ -538,25 +540,16 @@ func (MatchNot) CaddyModule() caddy.ModuleInfo {
540
}
541
}
542
541
-// UnmarshalJSON unmarshals data into m's unexported map field.
542
-// This is done because we cannot embed the map directly into
543
-// the struct, but we need a struct because we need another
544
-// field just for the provisioned modules.
545
-func (m *MatchNot) UnmarshalJSON(data []byte) error {
546
- return json.Unmarshal(data, &m.MatchersRaw)
547
-}
548
-
549
-// MarshalJSON marshals m's matchers.
550
-func (m MatchNot) MarshalJSON() ([]byte, error) {
551
- return json.Marshal(m.MatchersRaw)
552
-}
553
-
543
// UnmarshalCaddyfile implements caddyfile.Unmarshaler.
544
func (m *MatchNot) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
545
// first, unmarshal each matcher in the set from its tokens
557
-
558
- matcherMap := make(map[string]RequestMatcher)
546
+ type matcherPair struct {
547
+ raw caddy.ModuleMap
548
+ decoded MatcherSet
549
+ }
550
for d.Next() {
551
+ var mp matcherPair
552
+ matcherMap := make(map[string]RequestMatcher)
553
for d.NextBlock(0) {
554
matcherName := d.Val()
555
mod, err := caddy.GetModule("http.matchers." + matcherName)
@@ -572,42 +565,64 @@ func (m *MatchNot) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
565
return err
566
}
567
rm := unm.(RequestMatcher)
575
- m.Matchers = append(m.Matchers, rm)
568
matcherMap[matcherName] = rm
569
+ mp.decoded = append(mp.decoded, rm)
570
}
578
- }
571
580
- // we should now be functional, but we also need
581
- // to be able to marshal as JSON, otherwise config
582
- // adaptation won't work properly
583
- m.MatchersRaw = make(caddy.ModuleMap)
584
- for name, matchers := range matcherMap {
585
- jsonBytes, err := json.Marshal(matchers)
586
- if err != nil {
587
- return fmt.Errorf("marshaling matcher %s: %v", name, err)
572
+ // we should now have a functional 'not' matcher, but we also
573
+ // need to be able to marshal as JSON, otherwise config
574
+ // adaptation will be missing the matchers!
575
+ mp.raw = make(caddy.ModuleMap)
576
+ for name, matcher := range matcherMap {
577
+ jsonBytes, err := json.Marshal(matcher)
578
+ if err != nil {
579
+ return fmt.Errorf("marshaling %T matcher: %v", matcher, err)
580
+ }
581
+ mp.raw[name] = jsonBytes
582
}
589
- m.MatchersRaw[name] = jsonBytes
583
+ m.MatcherSetsRaw = append(m.MatcherSetsRaw, mp.raw)
584
}
591
-
585
return nil
586
}
587
588
+// UnmarshalJSON satisfies json.Unmarshaler. It puts the JSON
589
+// bytes directly into m's MatcherSetsRaw field.
590
+func (m *MatchNot) UnmarshalJSON(data []byte) error {
591
+ return json.Unmarshal(data, &m.MatcherSetsRaw)
592
+}
593
+
594
+// MarshalJSON satisfies json.Marshaler by marshaling
595
+// m's raw matcher sets.
596
+func (m MatchNot) MarshalJSON() ([]byte, error) {
597
+ return json.Marshal(m.MatcherSetsRaw)
598
+}
599
+
600
// Provision loads the matcher modules to be negated.
601
func (m *MatchNot) Provision(ctx caddy.Context) error {
597
- mods, err := ctx.LoadModule(m, "MatchersRaw")
602
+ matcherSets, err := ctx.LoadModule(m, "MatcherSetsRaw")
603
if err != nil {
599
- return fmt.Errorf("loading matchers: %v", err)
604
+ return fmt.Errorf("loading matcher sets: %v", err)
605
}
601
- for _, modIface := range mods.(map[string]interface{}) {
602
- m.Matchers = append(m.Matchers, modIface.(RequestMatcher))
606
+ for _, modMap := range matcherSets.([]map[string]interface{}) {
607
+ var ms MatcherSet
608
+ for _, modIface := range modMap {
609
+ ms = append(ms, modIface.(RequestMatcher))
610
+ }
611
+ m.MatcherSets = append(m.MatcherSets, ms)
612
}
613
return nil
614
}
615
607
-// Match returns true if r matches m. Since this matcher negates the
608
-// embedded matchers, false is returned if any of its matchers match.
616
+// Match returns true if r matches m. Since this matcher negates
617
+// the embedded matchers, false is returned if any of its matcher
618
+// sets return true.
619
func (m MatchNot) Match(r *http.Request) bool {
610
- return !m.Matchers.Match(r)
620
+ for _, ms := range m.MatcherSets {
621
+ if ms.Match(r) {
622
+ return false
623
+ }
624
+ }
625
+ return true
626
}
627
628
// CaddyModule returns the Caddy module information.
modules/caddyhttp/matchers_test.go
+113
@@ -823,6 +823,119 @@ func TestResponseMatcher(t *testing.T) {
823
}
824
}
825
826
+func TestNotMatcher(t *testing.T) {
827
+ for i, tc := range []struct {
828
+ host, path string
829
+ match MatchNot
830
+ expect bool
831
+ }{
832
+ {
833
+ host: "example.com", path: "/",
834
+ match: MatchNot{},
835
+ expect: true,
836
+ },
837
+ {
838
+ host: "example.com", path: "/foo",
839
+ match: MatchNot{
840
+ MatcherSets: []MatcherSet{
841
+ {
842
+ MatchPath{"/foo"},
843
+ },
844
+ },
845
+ },
846
+ expect: false,
847
+ },
848
+ {
849
+ host: "example.com", path: "/bar",
850
+ match: MatchNot{
851
+ MatcherSets: []MatcherSet{
852
+ {
853
+ MatchPath{"/foo"},
854
+ },
855
+ },
856
+ },
857
+ expect: true,
858
+ },
859
+ {
860
+ host: "example.com", path: "/bar",
861
+ match: MatchNot{
862
+ MatcherSets: []MatcherSet{
863
+ {
864
+ MatchPath{"/foo"},
865
+ },
866
+ {
867
+ MatchHost{"example.com"},
868
+ },
869
+ },
870
+ },
871
+ expect: false,
872
+ },
873
+ {
874
+ host: "example.com", path: "/bar",
875
+ match: MatchNot{
876
+ MatcherSets: []MatcherSet{
877
+ {
878
+ MatchPath{"/bar"},
879
+ },
880
+ {
881
+ MatchHost{"example.com"},
882
+ },
883
+ },
884
+ },
885
+ expect: false,
886
+ },
887
+ {
888
+ host: "example.com", path: "/foo",
889
+ match: MatchNot{
890
+ MatcherSets: []MatcherSet{
891
+ {
892
+ MatchPath{"/bar"},
893
+ },
894
+ {
895
+ MatchHost{"sub.example.com"},
896
+ },
897
+ },
898
+ },
899
+ expect: true,
900
+ },
901
+ {
902
+ host: "example.com", path: "/foo",
903
+ match: MatchNot{
904
+ MatcherSets: []MatcherSet{
905
+ {
906
+ MatchPath{"/foo"},
907
+ MatchHost{"example.com"},
908
+ },
909
+ },
910
+ },
911
+ expect: false,
912
+ },
913
+ {
914
+ host: "example.com", path: "/foo",
915
+ match: MatchNot{
916
+ MatcherSets: []MatcherSet{
917
+ {
918
+ MatchPath{"/bar"},
919
+ MatchHost{"example.com"},
920
+ },
921
+ },
922
+ },
923
+ expect: true,
924
+ },
925
+ } {
926
+ req := &http.Request{Host: tc.host, URL: &url.URL{Path: tc.path}}
927
+ repl := caddy.NewReplacer()
928
+ ctx := context.WithValue(req.Context(), caddy.ReplacerCtxKey, repl)
929
+ req = req.WithContext(ctx)
930
+
931
+ actual := tc.match.Match(req)
932
+ if actual != tc.expect {
933
+ t.Errorf("Test %d %+v: Expected %t, got %t for: host=%s path=%s'", i, tc.match, tc.expect, actual, tc.host, tc.path)
934
+ continue
935
+ }
936
+ }
937
+}
938
+
939
func BenchmarkHostMatcherWithoutPlaceholder(b *testing.B) {
940
req := &http.Request{Host: "localhost"}
941
repl := caddy.NewReplacer()
modules/caddyhttp/reverseproxy/fastcgi/caddyfile.go
+4
-2
@@ -128,8 +128,10 @@ func parsePHPFastCGI(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error
128
TryFiles: []string{"{http.request.uri.path}/index.php"},
129
}),
130
"not": h.JSON(caddyhttp.MatchNot{
131
- MatchersRaw: caddy.ModuleMap{
132
- "path": h.JSON(caddyhttp.MatchPath{"*/"}),
131
+ MatcherSetsRaw: []caddy.ModuleMap{
132
+ {
133
+ "path": h.JSON(caddyhttp.MatchPath{"*/"}),
134
+ },
135
},
136
}),
137
}