mirror of
https://github.com/caddyserver/caddy.git
synced 2025-01-06 22:40:31 -05:00
rewrite: Regular expression support for simple rule (#2082)
* Regexp support for simple rewrite rule * Add negate option for simplicity * ascertain explicit regexp char
This commit is contained in:
parent
88edca65d3
commit
9fe2ef417c
4 changed files with 110 additions and 26 deletions
|
@ -63,22 +63,38 @@ type Rule interface {
|
|||
|
||||
// SimpleRule is a simple rewrite rule.
|
||||
type SimpleRule struct {
|
||||
From, To string
|
||||
Regexp *regexp.Regexp
|
||||
To string
|
||||
Negate bool
|
||||
}
|
||||
|
||||
// NewSimpleRule creates a new Simple Rule
|
||||
func NewSimpleRule(from, to string) SimpleRule {
|
||||
return SimpleRule{from, to}
|
||||
func NewSimpleRule(from, to string, negate bool) (*SimpleRule, error) {
|
||||
r, err := regexp.Compile(from)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &SimpleRule{
|
||||
Regexp: r,
|
||||
To: to,
|
||||
Negate: negate,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// BasePath satisfies httpserver.Config
|
||||
func (s SimpleRule) BasePath() string { return s.From }
|
||||
func (s SimpleRule) BasePath() string { return "/" }
|
||||
|
||||
// Match satisfies httpserver.Config
|
||||
func (s SimpleRule) Match(r *http.Request) bool { return s.From == r.URL.Path }
|
||||
func (s *SimpleRule) Match(r *http.Request) bool {
|
||||
matches := regexpMatches(s.Regexp, "/", r.URL.Path)
|
||||
if s.Negate {
|
||||
return len(matches) == 0
|
||||
}
|
||||
return len(matches) > 0
|
||||
}
|
||||
|
||||
// Rewrite rewrites the internal location of the current request.
|
||||
func (s SimpleRule) Rewrite(fs http.FileSystem, r *http.Request) Result {
|
||||
func (s *SimpleRule) Rewrite(fs http.FileSystem, r *http.Request) Result {
|
||||
|
||||
// attempt rewrite
|
||||
return To(fs, r, s.To, newReplacer(r))
|
||||
|
@ -165,7 +181,7 @@ func (r ComplexRule) Match(req *http.Request) bool {
|
|||
return true
|
||||
}
|
||||
// otherwise validate regex
|
||||
return r.regexpMatches(req.URL.Path) != nil
|
||||
return regexpMatches(r.Regexp, r.Base, req.URL.Path) != nil
|
||||
}
|
||||
|
||||
// Rewrite rewrites the internal location of the current request.
|
||||
|
@ -174,7 +190,7 @@ func (r ComplexRule) Rewrite(fs http.FileSystem, req *http.Request) (re Result)
|
|||
|
||||
// validate regexp if present
|
||||
if r.Regexp != nil {
|
||||
matches := r.regexpMatches(req.URL.Path)
|
||||
matches := regexpMatches(r.Regexp, r.Base, req.URL.Path)
|
||||
switch len(matches) {
|
||||
case 0:
|
||||
// no match
|
||||
|
@ -230,14 +246,14 @@ func (r ComplexRule) matchExt(rPath string) bool {
|
|||
return !mustUse
|
||||
}
|
||||
|
||||
func (r ComplexRule) regexpMatches(rPath string) []string {
|
||||
if r.Regexp != nil {
|
||||
func regexpMatches(regexp *regexp.Regexp, base, rPath string) []string {
|
||||
if regexp != nil {
|
||||
// include trailing slash in regexp if present
|
||||
start := len(r.Base)
|
||||
if strings.HasSuffix(r.Base, "/") {
|
||||
start := len(base)
|
||||
if strings.HasSuffix(base, "/") {
|
||||
start--
|
||||
}
|
||||
return r.Regexp.FindStringSubmatch(rPath[start:])
|
||||
return regexp.FindStringSubmatch(rPath[start:])
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -29,9 +29,9 @@ func TestRewrite(t *testing.T) {
|
|||
rw := Rewrite{
|
||||
Next: httpserver.HandlerFunc(urlPrinter),
|
||||
Rules: []httpserver.HandlerConfig{
|
||||
NewSimpleRule("/from", "/to"),
|
||||
NewSimpleRule("/a", "/b"),
|
||||
NewSimpleRule("/b", "/b{uri}"),
|
||||
newSimpleRule(t, "^/from$", "/to"),
|
||||
newSimpleRule(t, "^/a$", "/b"),
|
||||
newSimpleRule(t, "^/b$", "/b{uri}"),
|
||||
},
|
||||
FileSys: http.Dir("."),
|
||||
}
|
||||
|
@ -131,6 +131,45 @@ func TestRewrite(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
// TestWordpress is a test for wordpress usecase.
|
||||
func TestWordpress(t *testing.T) {
|
||||
rw := Rewrite{
|
||||
Next: httpserver.HandlerFunc(urlPrinter),
|
||||
Rules: []httpserver.HandlerConfig{
|
||||
// both rules are same, thanks to Go regexp (confusion).
|
||||
newSimpleRule(t, "^/wp-admin", "{path} {path}/ /index.php?{query}", true),
|
||||
newSimpleRule(t, "^\\/wp-admin", "{path} {path}/ /index.php?{query}", true),
|
||||
},
|
||||
FileSys: http.Dir("."),
|
||||
}
|
||||
tests := []struct {
|
||||
from string
|
||||
expectedTo string
|
||||
}{
|
||||
{"/wp-admin", "/wp-admin"},
|
||||
{"/wp-admin/login.php", "/wp-admin/login.php"},
|
||||
{"/not-wp-admin/login.php?not=admin", "/index.php?not=admin"},
|
||||
{"/loophole", "/index.php"},
|
||||
{"/user?name=john", "/index.php?name=john"},
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
req, err := http.NewRequest("GET", test.from, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Test %d: Could not create HTTP request: %v", i, err)
|
||||
}
|
||||
ctx := context.WithValue(req.Context(), httpserver.OriginalURLCtxKey, *req.URL)
|
||||
req = req.WithContext(ctx)
|
||||
|
||||
rec := httptest.NewRecorder()
|
||||
rw.ServeHTTP(rec, req)
|
||||
|
||||
if got, want := rec.Body.String(), test.expectedTo; got != want {
|
||||
t.Errorf("Test %d: Expected URL to be '%s' but was '%s'", i, want, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func urlPrinter(w http.ResponseWriter, r *http.Request) (int, error) {
|
||||
fmt.Fprint(w, r.URL.String())
|
||||
return 0, nil
|
||||
|
|
|
@ -58,6 +58,7 @@ func rewriteParse(c *caddy.Controller) ([]httpserver.HandlerConfig, error) {
|
|||
var base = "/"
|
||||
var pattern, to string
|
||||
var ext []string
|
||||
var negate bool
|
||||
|
||||
args := c.RemainingArgs()
|
||||
|
||||
|
@ -111,7 +112,14 @@ func rewriteParse(c *caddy.Controller) ([]httpserver.HandlerConfig, error) {
|
|||
|
||||
// the only unhandled case is 2 and above
|
||||
default:
|
||||
rule = NewSimpleRule(args[0], strings.Join(args[1:], " "))
|
||||
if args[0] == "not" {
|
||||
negate = true
|
||||
args = args[1:]
|
||||
}
|
||||
rule, err = NewSimpleRule(args[0], strings.Join(args[1:], " "), negate)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rules = append(rules, rule)
|
||||
}
|
||||
|
||||
|
|
|
@ -50,6 +50,19 @@ func TestSetup(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
// newSimpleRule is convenience test function for SimpleRule.
|
||||
func newSimpleRule(t *testing.T, from, to string, negate ...bool) Rule {
|
||||
var n bool
|
||||
if len(negate) > 0 {
|
||||
n = negate[0]
|
||||
}
|
||||
rule, err := NewSimpleRule(from, to, n)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return rule
|
||||
}
|
||||
|
||||
func TestRewriteParse(t *testing.T) {
|
||||
simpleTests := []struct {
|
||||
input string
|
||||
|
@ -57,17 +70,20 @@ func TestRewriteParse(t *testing.T) {
|
|||
expected []Rule
|
||||
}{
|
||||
{`rewrite /from /to`, false, []Rule{
|
||||
SimpleRule{From: "/from", To: "/to"},
|
||||
newSimpleRule(t, "/from", "/to"),
|
||||
}},
|
||||
{`rewrite /from /to
|
||||
rewrite a b`, false, []Rule{
|
||||
SimpleRule{From: "/from", To: "/to"},
|
||||
SimpleRule{From: "a", To: "b"},
|
||||
newSimpleRule(t, "/from", "/to"),
|
||||
newSimpleRule(t, "a", "b"),
|
||||
}},
|
||||
{`rewrite a`, true, []Rule{}},
|
||||
{`rewrite`, true, []Rule{}},
|
||||
{`rewrite a b c`, false, []Rule{
|
||||
SimpleRule{From: "a", To: "b c"},
|
||||
newSimpleRule(t, "a", "b c"),
|
||||
}},
|
||||
{`rewrite not a b c`, false, []Rule{
|
||||
newSimpleRule(t, "a", "b c", true),
|
||||
}},
|
||||
}
|
||||
|
||||
|
@ -88,17 +104,22 @@ func TestRewriteParse(t *testing.T) {
|
|||
}
|
||||
|
||||
for j, e := range test.expected {
|
||||
actualRule := actual[j].(SimpleRule)
|
||||
expectedRule := e.(SimpleRule)
|
||||
actualRule := actual[j].(*SimpleRule)
|
||||
expectedRule := e.(*SimpleRule)
|
||||
|
||||
if actualRule.From != expectedRule.From {
|
||||
if actualRule.Regexp.String() != expectedRule.Regexp.String() {
|
||||
t.Errorf("Test %d, rule %d: Expected From=%s, got %s",
|
||||
i, j, expectedRule.From, actualRule.From)
|
||||
i, j, expectedRule.Regexp.String(), actualRule.Regexp.String())
|
||||
}
|
||||
|
||||
if actualRule.To != expectedRule.To {
|
||||
t.Errorf("Test %d, rule %d: Expected To=%s, got %s",
|
||||
i, j, expectedRule.To, actualRule.To)
|
||||
i, j, expectedRule.Regexp.String(), actualRule.Regexp.String())
|
||||
}
|
||||
|
||||
if actualRule.Negate != expectedRule.Negate {
|
||||
t.Errorf("Test %d, rule %d: Expected Negate=%v, got %v",
|
||||
i, j, expectedRule.Negate, actualRule.Negate)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue