From 55f69fd7429fa24ad647a5d0dedae360ee3f520e Mon Sep 17 00:00:00 2001 From: Abiola Ibrahim Date: Thu, 31 Dec 2015 20:10:42 +0100 Subject: [PATCH 1/5] Add not_has and not_match conditions. --- middleware/rewrite/condition.go | 19 +++++++++++++++++++ middleware/rewrite/condition_test.go | 16 ++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/middleware/rewrite/condition.go b/middleware/rewrite/condition.go index c863af4f..220b489f 100644 --- a/middleware/rewrite/condition.go +++ b/middleware/rewrite/condition.go @@ -14,9 +14,11 @@ const ( Is = "is" Not = "not" Has = "has" + NotHas = "not_has" StartsWith = "starts_with" EndsWith = "ends_with" Match = "match" + NotMatch = "not_match" ) func operatorError(operator string) error { @@ -34,9 +36,11 @@ var conditions = map[string]condition{ Is: isFunc, Not: notFunc, Has: hasFunc, + NotHas: notHasFunc, StartsWith: startsWithFunc, EndsWith: endsWithFunc, Match: matchFunc, + NotMatch: notMatchFunc, } // isFunc is condition for Is operator. @@ -57,6 +61,12 @@ func hasFunc(a, b string) bool { return strings.Contains(a, b) } +// notHasFunc is condition for NotHas operator. +// It checks if b is not a substring of a. +func notHasFunc(a, b string) bool { + return !strings.Contains(a, b) +} + // startsWithFunc is condition for StartsWith operator. // It checks if b is a prefix of a. func startsWithFunc(a, b string) bool { @@ -71,11 +81,20 @@ func endsWithFunc(a, b string) bool { // matchFunc is condition for Match operator. // It does regexp matching of a against pattern in b +// and returns if they match. func matchFunc(a, b string) bool { matched, _ := regexp.MatchString(b, a) return matched } +// notMatchFunc is condition for NotMatch operator. +// It does regexp matching of a against pattern in b +// and returns if they do not match. +func notMatchFunc(a, b string) bool { + matched, _ := regexp.MatchString(b, a) + return !matched +} + // If is statement for a rewrite condition. type If struct { A string diff --git a/middleware/rewrite/condition_test.go b/middleware/rewrite/condition_test.go index c056f896..3c3b6053 100644 --- a/middleware/rewrite/condition_test.go +++ b/middleware/rewrite/condition_test.go @@ -20,6 +20,11 @@ func TestConditions(t *testing.T) { {"ba has b", true}, {"bab has b", true}, {"bab has bb", false}, + {"a not_has a", false}, + {"a not_has b", true}, + {"ba not_has b", false}, + {"bab not_has b", false}, + {"bab not_has bb", true}, {"bab starts_with bb", false}, {"bab starts_with ba", true}, {"bab starts_with bab", true}, @@ -37,6 +42,17 @@ func TestConditions(t *testing.T) { {"b0a match b[a-z]", false}, {"b0a match b[a-z]+", false}, {"b0a match b[a-z0-9]+", true}, + {"a not_match *", true}, + {"a not_match a", false}, + {"a not_match .*", false}, + {"a not_match a.*", false}, + {"a not_match b.*", true}, + {"ba not_match b.*", false}, + {"ba not_match b[a-z]", false}, + {"b0 not_match b[a-z]", true}, + {"b0a not_match b[a-z]", true}, + {"b0a not_match b[a-z]+", true}, + {"b0a not_match b[a-z0-9]+", false}, } for i, test := range tests { From 3468986260871b874f2b1654685602ea8122472f Mon Sep 17 00:00:00 2001 From: Abiola Ibrahim Date: Thu, 31 Dec 2015 20:11:31 +0100 Subject: [PATCH 2/5] Support multiple values for `to` in simple rule. --- caddy/setup/rewrite.go | 8 ++++---- caddy/setup/rewrite_test.go | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/caddy/setup/rewrite.go b/caddy/setup/rewrite.go index 4c84cb5f..badbaaee 100644 --- a/caddy/setup/rewrite.go +++ b/caddy/setup/rewrite.go @@ -40,9 +40,6 @@ func rewriteParse(c *Controller) ([]rewrite.Rule, error) { var ifs []rewrite.If switch len(args) { - case 2: - rule = rewrite.NewSimpleRule(args[0], args[1]) - simpleRules = append(simpleRules, rule) case 1: base = args[0] fallthrough @@ -88,8 +85,11 @@ func rewriteParse(c *Controller) ([]rewrite.Rule, error) { return nil, err } regexpRules = append(regexpRules, rule) + + // the only unhandled case is 2 and above default: - return nil, c.ArgErr() + rule = rewrite.NewSimpleRule(args[0], strings.Join(args[1:], " ")) + simpleRules = append(simpleRules, rule) } } diff --git a/caddy/setup/rewrite_test.go b/caddy/setup/rewrite_test.go index c43818b2..c0dd2fb9 100644 --- a/caddy/setup/rewrite_test.go +++ b/caddy/setup/rewrite_test.go @@ -50,8 +50,8 @@ func TestRewriteParse(t *testing.T) { }}, {`rewrite a`, true, []rewrite.Rule{}}, {`rewrite`, true, []rewrite.Rule{}}, - {`rewrite a b c`, true, []rewrite.Rule{ - rewrite.SimpleRule{From: "a", To: "b"}, + {`rewrite a b c`, false, []rewrite.Rule{ + rewrite.SimpleRule{From: "a", To: "b c"}, }}, } From be2f5c4b38305a7406bc4b71920f10c5b89fa9da Mon Sep 17 00:00:00 2001 From: Abiola Ibrahim Date: Thu, 31 Dec 2015 23:19:11 +0100 Subject: [PATCH 3/5] Support for 4xx status codes. --- caddy/setup/rewrite.go | 16 ++++++++++++--- caddy/setup/rewrite_test.go | 27 ++++++++++++++++++++++++++ middleware/rewrite/rewrite.go | 14 +++++++++++++- middleware/rewrite/rewrite_test.go | 31 +++++++++++++++++++++++++++++- 4 files changed, 83 insertions(+), 5 deletions(-) diff --git a/caddy/setup/rewrite.go b/caddy/setup/rewrite.go index badbaaee..ab997d27 100644 --- a/caddy/setup/rewrite.go +++ b/caddy/setup/rewrite.go @@ -2,6 +2,7 @@ package setup import ( "net/http" + "strconv" "strings" "github.com/mholt/caddy/middleware" @@ -33,6 +34,7 @@ func rewriteParse(c *Controller) ([]rewrite.Rule, error) { var err error var base = "/" var pattern, to string + var status int var ext []string args := c.RemainingArgs() @@ -73,15 +75,23 @@ func rewriteParse(c *Controller) ([]rewrite.Rule, error) { return nil, err } ifs = append(ifs, ifCond) + case "status": + if !c.NextArg() { + return nil, c.ArgErr() + } + status, _ = strconv.Atoi(c.Val()) + if status < 400 || status > 499 { + return nil, c.Err("status must be 4xx") + } default: return nil, c.ArgErr() } } - // ensure to is specified - if to == "" { + // ensure to or status is specified + if to == "" && status == 0 { return nil, c.ArgErr() } - if rule, err = rewrite.NewComplexRule(base, pattern, to, ext, ifs); err != nil { + if rule, err = rewrite.NewComplexRule(base, pattern, to, status, ext, ifs); err != nil { return nil, err } regexpRules = append(regexpRules, rule) diff --git a/caddy/setup/rewrite_test.go b/caddy/setup/rewrite_test.go index c0dd2fb9..224ab643 100644 --- a/caddy/setup/rewrite_test.go +++ b/caddy/setup/rewrite_test.go @@ -137,6 +137,33 @@ func TestRewriteParse(t *testing.T) { }`, false, []rewrite.Rule{ &rewrite.ComplexRule{Base: "/", To: "/to", Ifs: []rewrite.If{rewrite.If{A: "{path}", Operator: "is", B: "a"}}}, }}, + {`rewrite { + status 400 + }`, false, []rewrite.Rule{ + &rewrite.ComplexRule{Base: "/", Regexp: regexp.MustCompile(".*"), Status: 400}, + }}, + {`rewrite { + to /to + status 400 + }`, false, []rewrite.Rule{ + &rewrite.ComplexRule{Base: "/", To: "/to", Regexp: regexp.MustCompile(".*"), Status: 400}, + }}, + {`rewrite { + status 399 + }`, true, []rewrite.Rule{ + &rewrite.ComplexRule{}, + }}, + {`rewrite { + status 0 + }`, true, []rewrite.Rule{ + &rewrite.ComplexRule{}, + }}, + {`rewrite { + to /to + status 0 + }`, true, []rewrite.Rule{ + &rewrite.ComplexRule{}, + }}, } for i, test := range regexpTests { diff --git a/middleware/rewrite/rewrite.go b/middleware/rewrite/rewrite.go index 60cc0b9d..30f5fc49 100644 --- a/middleware/rewrite/rewrite.go +++ b/middleware/rewrite/rewrite.go @@ -24,6 +24,13 @@ type Rewrite struct { func (rw Rewrite) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) { for _, rule := range rw.Rules { if ok := rule.Rewrite(rw.FileSys, r); ok { + + // if rule is complex rule and status code is set + if cRule, ok := rule.(*ComplexRule); ok && cRule.Status != 0 { + return cRule.Status, nil + } + + // rewrite done break } } @@ -67,6 +74,10 @@ type ComplexRule struct { // Path to rewrite to To string + // If set, neither performs rewrite nor proceeds + // with request. Only returns code. + Status int + // Extensions to filter by Exts []string @@ -78,7 +89,7 @@ type ComplexRule struct { // NewRegexpRule creates a new RegexpRule. It returns an error if regexp // pattern (pattern) or extensions (ext) are invalid. -func NewComplexRule(base, pattern, to string, ext []string, ifs []If) (*ComplexRule, error) { +func NewComplexRule(base, pattern, to string, status int, ext []string, ifs []If) (*ComplexRule, error) { // validate regexp if present var r *regexp.Regexp if pattern != "" { @@ -102,6 +113,7 @@ func NewComplexRule(base, pattern, to string, ext []string, ifs []If) (*ComplexR return &ComplexRule{ Base: base, To: to, + Status: status, Exts: ext, Ifs: ifs, Regexp: r, diff --git a/middleware/rewrite/rewrite_test.go b/middleware/rewrite/rewrite_test.go index 5b891606..7f39a56b 100644 --- a/middleware/rewrite/rewrite_test.go +++ b/middleware/rewrite/rewrite_test.go @@ -41,7 +41,7 @@ func TestRewrite(t *testing.T) { if s := strings.Split(regexpRule[3], "|"); len(s) > 1 { ext = s[:len(s)-1] } - rule, err := NewComplexRule(regexpRule[0], regexpRule[1], regexpRule[2], ext, nil) + rule, err := NewComplexRule(regexpRule[0], regexpRule[1], regexpRule[2], 0, ext, nil) if err != nil { t.Fatal(err) } @@ -106,6 +106,35 @@ func TestRewrite(t *testing.T) { i, test.expectedTo, rec.Body.String()) } } + + statusTests := []int{ + 401, 405, 403, 400, + } + + for i, s := range statusTests { + urlPath := fmt.Sprintf("/status%d", i) + rule, err := NewComplexRule(urlPath, "", "", s, nil, nil) + if err != nil { + t.Fatalf("Test %d: No error expected for rule but found %v", i, err) + } + rw.Rules = append(rw.Rules, rule) + req, err := http.NewRequest("GET", urlPath, nil) + if err != nil { + t.Fatalf("Test %d: Could not create HTTP request: %v", i, err) + } + + rec := httptest.NewRecorder() + code, err := rw.ServeHTTP(rec, req) + if err != nil { + t.Fatalf("Test %d: No error expected for handler but found %v", i, err) + } + if rec.Body.String() != "" { + t.Errorf("Test %d: Expected empty body but found %s", i, rec.Body.String()) + } + if code != s { + t.Errorf("Text %d: Expected status code %d found %d", i, s, code) + } + } } func urlPrinter(w http.ResponseWriter, r *http.Request) (int, error) { From 48d7f1ead27284f41bcaf46111990ba685764b84 Mon Sep 17 00:00:00 2001 From: Abiola Ibrahim Date: Fri, 1 Jan 2016 07:05:30 +0100 Subject: [PATCH 4/5] Refactor. Stop useless rewrite if status code is set. --- middleware/rewrite/rewrite.go | 60 +++++++++++++++++++++++------------ middleware/rewrite/to.go | 6 ++-- 2 files changed, 43 insertions(+), 23 deletions(-) diff --git a/middleware/rewrite/rewrite.go b/middleware/rewrite/rewrite.go index 30f5fc49..d5465e48 100644 --- a/middleware/rewrite/rewrite.go +++ b/middleware/rewrite/rewrite.go @@ -13,6 +13,19 @@ import ( "github.com/mholt/caddy/middleware" ) +// RewriteResult is the result of a rewrite +type RewriteResult int + +const ( + // RewriteIgnored is returned when rewrite is not done on request. + RewriteIgnored RewriteResult = iota + // RewriteDone is returned when rewrite is done on request. + RewriteDone + // RewriteStatus is returned when rewrite is not needed and status code should be set + // for the request. + RewriteStatus +) + // Rewrite is middleware to rewrite request locations internally before being handled. type Rewrite struct { Next middleware.Handler @@ -22,16 +35,18 @@ type Rewrite struct { // ServeHTTP implements the middleware.Handler interface. func (rw Rewrite) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) { +outer: for _, rule := range rw.Rules { - if ok := rule.Rewrite(rw.FileSys, r); ok { - - // if rule is complex rule and status code is set + switch result := rule.Rewrite(rw.FileSys, r); result { + case RewriteDone: + break outer + case RewriteIgnored: + break + case RewriteStatus: + // only valid for complex rules. if cRule, ok := rule.(*ComplexRule); ok && cRule.Status != 0 { return cRule.Status, nil } - - // rewrite done - break } } return rw.Next.ServeHTTP(w, r) @@ -40,7 +55,7 @@ func (rw Rewrite) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) // Rule describes an internal location rewrite rule. type Rule interface { // Rewrite rewrites the internal location of the current request. - Rewrite(http.FileSystem, *http.Request) bool + Rewrite(http.FileSystem, *http.Request) RewriteResult } // SimpleRule is a simple rewrite rule. @@ -54,7 +69,7 @@ func NewSimpleRule(from, to string) SimpleRule { } // Rewrite rewrites the internal location of the current request. -func (s SimpleRule) Rewrite(fs http.FileSystem, r *http.Request) bool { +func (s SimpleRule) Rewrite(fs http.FileSystem, r *http.Request) RewriteResult { if s.From == r.URL.Path { // take note of this rewrite for internal use by fastcgi // all we need is the URI, not full URL @@ -63,7 +78,7 @@ func (s SimpleRule) Rewrite(fs http.FileSystem, r *http.Request) bool { // attempt rewrite return To(fs, r, s.To, newReplacer(r)) } - return false + return RewriteIgnored } // ComplexRule is a rewrite rule based on a regular expression @@ -121,33 +136,38 @@ func NewComplexRule(base, pattern, to string, status int, ext []string, ifs []If } // Rewrite rewrites the internal location of the current request. -func (r *ComplexRule) Rewrite(fs http.FileSystem, req *http.Request) bool { +func (r *ComplexRule) Rewrite(fs http.FileSystem, req *http.Request) (re RewriteResult) { rPath := req.URL.Path replacer := newReplacer(req) // validate base if !middleware.Path(rPath).Matches(r.Base) { - return false + return + } + + // if status is present, stop rewrite and return it. + if r.Status != 0 { + return RewriteStatus } // validate extensions if !r.matchExt(rPath) { - return false - } - - // include trailing slash in regexp if present - start := len(r.Base) - if strings.HasSuffix(r.Base, "/") { - start-- + return } // validate regexp if present if r.Regexp != nil { + // include trailing slash in regexp if present + start := len(r.Base) + if strings.HasSuffix(r.Base, "/") { + start-- + } + matches := r.FindStringSubmatch(rPath[start:]) switch len(matches) { case 0: // no match - return false + return default: // set regexp match variables {1}, {2} ... for i := 1; i < len(matches); i++ { @@ -159,7 +179,7 @@ func (r *ComplexRule) Rewrite(fs http.FileSystem, req *http.Request) bool { // validate rewrite conditions for _, i := range r.Ifs { if !i.True(req) { - return false + return } } diff --git a/middleware/rewrite/to.go b/middleware/rewrite/to.go index 8a577c5e..de07b7fb 100644 --- a/middleware/rewrite/to.go +++ b/middleware/rewrite/to.go @@ -13,7 +13,7 @@ import ( // To attempts rewrite. It attempts to rewrite to first valid path // or the last path if none of the paths are valid. // Returns true if rewrite is successful and false otherwise. -func To(fs http.FileSystem, r *http.Request, to string, replacer middleware.Replacer) bool { +func To(fs http.FileSystem, r *http.Request, to string, replacer middleware.Replacer) RewriteResult { tos := strings.Fields(to) // try each rewrite paths @@ -38,7 +38,7 @@ func To(fs http.FileSystem, r *http.Request, to string, replacer middleware.Repl // Let the user know we got here. Rewrite is expected but // the resulting url is invalid. log.Printf("[ERROR] rewrite: resulting path '%v' is invalid. error: %v", t, err) - return false + return RewriteIgnored } // take note of this rewrite for internal use by fastcgi @@ -56,7 +56,7 @@ func To(fs http.FileSystem, r *http.Request, to string, replacer middleware.Repl r.URL.Fragment = u.Fragment } - return true + return RewriteDone } // isValidFile checks if file exists on the filesystem. From 0a04fa40f407f25066d440515f906c4873fa7d3a Mon Sep 17 00:00:00 2001 From: Abiola Ibrahim Date: Sat, 2 Jan 2016 08:08:55 +0100 Subject: [PATCH 5/5] Oops. status code check should be after all validations. --- middleware/rewrite/rewrite.go | 10 ++++----- middleware/rewrite/rewrite_test.go | 34 ++++++++++++++++++++++-------- 2 files changed, 30 insertions(+), 14 deletions(-) diff --git a/middleware/rewrite/rewrite.go b/middleware/rewrite/rewrite.go index d5465e48..ad3b4adf 100644 --- a/middleware/rewrite/rewrite.go +++ b/middleware/rewrite/rewrite.go @@ -145,11 +145,6 @@ func (r *ComplexRule) Rewrite(fs http.FileSystem, req *http.Request) (re Rewrite return } - // if status is present, stop rewrite and return it. - if r.Status != 0 { - return RewriteStatus - } - // validate extensions if !r.matchExt(rPath) { return @@ -183,6 +178,11 @@ func (r *ComplexRule) Rewrite(fs http.FileSystem, req *http.Request) (re Rewrite } } + // if status is present, stop rewrite and return it. + if r.Status != 0 { + return RewriteStatus + } + // attempt rewrite return To(fs, req, r.To, replacer) } diff --git a/middleware/rewrite/rewrite_test.go b/middleware/rewrite/rewrite_test.go index 7f39a56b..6f03116d 100644 --- a/middleware/rewrite/rewrite_test.go +++ b/middleware/rewrite/rewrite_test.go @@ -107,17 +107,27 @@ func TestRewrite(t *testing.T) { } } - statusTests := []int{ - 401, 405, 403, 400, + statusTests := []struct { + status int + base string + to string + regexp string + statusExpected bool + }{ + {400, "/status", "", "", true}, + {400, "/ignore", "", "", false}, + {400, "/", "", "^/ignore", false}, + {400, "/", "", "(.*)", true}, + {400, "/status", "", "", true}, } for i, s := range statusTests { urlPath := fmt.Sprintf("/status%d", i) - rule, err := NewComplexRule(urlPath, "", "", s, nil, nil) + rule, err := NewComplexRule(s.base, s.regexp, s.to, s.status, nil, nil) if err != nil { t.Fatalf("Test %d: No error expected for rule but found %v", i, err) } - rw.Rules = append(rw.Rules, rule) + rw.Rules = []Rule{rule} req, err := http.NewRequest("GET", urlPath, nil) if err != nil { t.Fatalf("Test %d: Could not create HTTP request: %v", i, err) @@ -128,11 +138,17 @@ func TestRewrite(t *testing.T) { if err != nil { t.Fatalf("Test %d: No error expected for handler but found %v", i, err) } - if rec.Body.String() != "" { - t.Errorf("Test %d: Expected empty body but found %s", i, rec.Body.String()) - } - if code != s { - t.Errorf("Text %d: Expected status code %d found %d", i, s, code) + if s.statusExpected { + if rec.Body.String() != "" { + t.Errorf("Test %d: Expected empty body but found %s", i, rec.Body.String()) + } + if code != s.status { + t.Errorf("Test %d: Expected status code %d found %d", i, s.status, code) + } + } else { + if code != 0 { + t.Errorf("Test %d: Expected no status code found %d", i, code) + } } } }