@marcel91200 / caddy Mirror · read-only / commits / 73643ea7
Fork 0

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 }