Commit 20fbc730 authored by Matt Holt's avatar Matt Holt Committed by GitHub

Merge pull request #1796 from mholt/bugfix_rewrite_1794

Fix for #1794: Fixes issues with IfMatcher and regular expressions.
parents 6b546389 d48e51cb
...@@ -53,59 +53,8 @@ const ( ...@@ -53,59 +53,8 @@ const (
matchOp = "match" matchOp = "match"
) )
func operatorError(operator string) error {
return fmt.Errorf("Invalid operator %v", operator)
}
// ifCondition is a 'if' condition. // ifCondition is a 'if' condition.
type ifCondition func(string, string) bool type ifFunc func(a, b string) bool
var ifConditions = map[string]ifCondition{
isOp: isFunc,
notOp: notFunc,
hasOp: hasFunc,
startsWithOp: startsWithFunc,
endsWithOp: endsWithFunc,
matchOp: matchFunc,
}
// isFunc is condition for Is operator.
// It checks for equality.
func isFunc(a, b string) bool {
return a == b
}
// notFunc is condition for Not operator.
// It checks for inequality.
func notFunc(a, b string) bool {
return !isFunc(a, b)
}
// hasFunc is condition for Has operator.
// It checks if b is a substring of a.
func hasFunc(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 {
return strings.HasPrefix(a, b)
}
// endsWithFunc is condition for EndsWith operator.
// It checks if b is a suffix of a.
func endsWithFunc(a, b string) bool {
return strings.HasSuffix(a, b)
}
// 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
}
// ifCond is statement for a IfMatcher condition. // ifCond is statement for a IfMatcher condition.
type ifCond struct { type ifCond struct {
...@@ -113,40 +62,79 @@ type ifCond struct { ...@@ -113,40 +62,79 @@ type ifCond struct {
op string op string
b string b string
neg bool neg bool
rex *regexp.Regexp
f ifFunc
} }
// newIfCond creates a new If condition. // newIfCond creates a new If condition.
func newIfCond(a, operator, b string) (ifCond, error) { func newIfCond(a, op, b string) (ifCond, error) {
neg := false i := ifCond{a: a, op: op, b: b}
if strings.HasPrefix(operator, "not_") { if strings.HasPrefix(op, "not_") {
neg = true i.neg = true
operator = operator[4:] i.op = op[4:]
} }
if _, ok := ifConditions[operator]; !ok {
return ifCond{}, operatorError(operator) switch i.op {
case isOp:
// It checks for equality.
i.f = i.isFunc
case notOp:
// It checks for inequality.
i.f = i.notFunc
case hasOp:
// It checks if b is a substring of a.
i.f = strings.Contains
case startsWithOp:
// It checks if b is a prefix of a.
i.f = strings.HasPrefix
case endsWithOp:
// It checks if b is a suffix of a.
i.f = strings.HasSuffix
case matchOp:
// It does regexp matching of a against pattern in b and returns if they match.
var err error
if i.rex, err = regexp.Compile(i.b); err != nil {
return ifCond{}, fmt.Errorf("Invalid regular expression: '%s', %v", i.b, err)
}
i.f = i.matchFunc
default:
return ifCond{}, fmt.Errorf("Invalid operator %v", i.op)
} }
return ifCond{
a: a, return i, nil
op: operator, }
b: b,
neg: neg, // isFunc is condition for Is operator.
}, nil func (i ifCond) isFunc(a, b string) bool {
return a == b
}
// notFunc is condition for Not operator.
func (i ifCond) notFunc(a, b string) bool {
return a != b
}
// matchFunc is condition for Match operator.
func (i ifCond) matchFunc(a, b string) bool {
return i.rex.MatchString(a)
} }
// True returns true if the condition is true and false otherwise. // True returns true if the condition is true and false otherwise.
// If r is not nil, it replaces placeholders before comparison. // If r is not nil, it replaces placeholders before comparison.
func (i ifCond) True(r *http.Request) bool { func (i ifCond) True(r *http.Request) bool {
if c, ok := ifConditions[i.op]; ok { if i.f != nil {
a, b := i.a, i.b a, b := i.a, i.b
if r != nil { if r != nil {
replacer := NewReplacer(r, nil, "") replacer := NewReplacer(r, nil, "")
a = replacer.Replace(i.a) a = replacer.Replace(i.a)
b = replacer.Replace(i.b) if i.op != matchOp {
b = replacer.Replace(i.b)
}
} }
if i.neg { if i.neg {
return !c(a, b) return !i.f(a, b)
} }
return c(a, b) return i.f(a, b)
} }
return i.neg // false if not negated, true otherwise return i.neg // false if not negated, true otherwise
} }
......
...@@ -2,8 +2,8 @@ package httpserver ...@@ -2,8 +2,8 @@ package httpserver
import ( import (
"context" "context"
"fmt"
"net/http" "net/http"
"regexp"
"strings" "strings"
"testing" "testing"
...@@ -14,66 +14,72 @@ func TestConditions(t *testing.T) { ...@@ -14,66 +14,72 @@ func TestConditions(t *testing.T) {
tests := []struct { tests := []struct {
condition string condition string
isTrue bool isTrue bool
shouldErr bool
}{ }{
{"a is b", false}, {"a is b", false, false},
{"a is a", true}, {"a is a", true, false},
{"a not b", true}, {"a not b", true, false},
{"a not a", false}, {"a not a", false, false},
{"a has a", true}, {"a has a", true, false},
{"a has b", false}, {"a has b", false, false},
{"ba has b", true}, {"ba has b", true, false},
{"bab has b", true}, {"bab has b", true, false},
{"bab has bb", false}, {"bab has bb", false, false},
{"a not_has a", false}, {"a not_has a", false, false},
{"a not_has b", true}, {"a not_has b", true, false},
{"ba not_has b", false}, {"ba not_has b", false, false},
{"bab not_has b", false}, {"bab not_has b", false, false},
{"bab not_has bb", true}, {"bab not_has bb", true, false},
{"bab starts_with bb", false}, {"bab starts_with bb", false, false},
{"bab starts_with ba", true}, {"bab starts_with ba", true, false},
{"bab starts_with bab", true}, {"bab starts_with bab", true, false},
{"bab not_starts_with bb", true}, {"bab not_starts_with bb", true, false},
{"bab not_starts_with ba", false}, {"bab not_starts_with ba", false, false},
{"bab not_starts_with bab", false}, {"bab not_starts_with bab", false, false},
{"bab ends_with bb", false}, {"bab ends_with bb", false, false},
{"bab ends_with bab", true}, {"bab ends_with bab", true, false},
{"bab ends_with ab", true}, {"bab ends_with ab", true, false},
{"bab not_ends_with bb", true}, {"bab not_ends_with bb", true, false},
{"bab not_ends_with ab", false}, {"bab not_ends_with ab", false, false},
{"bab not_ends_with bab", false}, {"bab not_ends_with bab", false, false},
{"a match *", false}, {"a match *", false, true},
{"a match a", true}, {"a match a", true, false},
{"a match .*", true}, {"a match .*", true, false},
{"a match a.*", true}, {"a match a.*", true, false},
{"a match b.*", false}, {"a match b.*", false, false},
{"ba match b.*", true}, {"ba match b.*", true, false},
{"ba match b[a-z]", true}, {"ba match b[a-z]", true, false},
{"b0 match b[a-z]", false}, {"b0 match b[a-z]", false, false},
{"b0a match b[a-z]", false}, {"b0a match b[a-z]", false, false},
{"b0a match b[a-z]+", false}, {"b0a match b[a-z]+", false, false},
{"b0a match b[a-z0-9]+", true}, {"b0a match b[a-z0-9]+", true, false},
{"a not_match *", true}, {"bac match b[a-z]{2}", true, false},
{"a not_match a", false}, {"a not_match *", false, true},
{"a not_match .*", false}, {"a not_match a", false, false},
{"a not_match a.*", false}, {"a not_match .*", false, false},
{"a not_match b.*", true}, {"a not_match a.*", false, false},
{"ba not_match b.*", false}, {"a not_match b.*", true, false},
{"ba not_match b[a-z]", false}, {"ba not_match b.*", false, false},
{"b0 not_match b[a-z]", true}, {"ba not_match b[a-z]", false, false},
{"b0a not_match b[a-z]", true}, {"b0 not_match b[a-z]", true, false},
{"b0a not_match b[a-z]+", true}, {"b0a not_match b[a-z]", true, false},
{"b0a not_match b[a-z0-9]+", false}, {"b0a not_match b[a-z]+", true, false},
{"b0a not_match b[a-z0-9]+", false, false},
{"bac not_match b[a-z]{2}", false, false},
} }
for i, test := range tests { for i, test := range tests {
str := strings.Fields(test.condition) str := strings.Fields(test.condition)
ifCond, err := newIfCond(str[0], str[1], str[2]) ifCond, err := newIfCond(str[0], str[1], str[2])
if err != nil { if err != nil {
t.Error(err) if !test.shouldErr {
t.Error(err)
}
continue
} }
isTrue := ifCond.True(nil) isTrue := ifCond.True(nil)
if isTrue != test.isTrue { if isTrue != test.isTrue {
t.Errorf("Test %d: expected %v found %v", i, test.isTrue, isTrue) t.Errorf("Test %d: '%s' expected %v found %v", i, test.condition, test.isTrue, isTrue)
} }
} }
...@@ -192,6 +198,7 @@ func TestIfMatcher(t *testing.T) { ...@@ -192,6 +198,7 @@ func TestIfMatcher(t *testing.T) {
} }
func TestSetupIfMatcher(t *testing.T) { func TestSetupIfMatcher(t *testing.T) {
rex_b, _ := regexp.Compile("b")
tests := []struct { tests := []struct {
input string input string
shouldErr bool shouldErr bool
...@@ -201,7 +208,7 @@ func TestSetupIfMatcher(t *testing.T) { ...@@ -201,7 +208,7 @@ func TestSetupIfMatcher(t *testing.T) {
if a match b if a match b
}`, false, IfMatcher{ }`, false, IfMatcher{
ifs: []ifCond{ ifs: []ifCond{
{a: "a", op: "match", b: "b", neg: false}, {a: "a", op: "match", b: "b", neg: false, rex: rex_b},
}, },
}}, }},
{`test { {`test {
...@@ -209,7 +216,7 @@ func TestSetupIfMatcher(t *testing.T) { ...@@ -209,7 +216,7 @@ func TestSetupIfMatcher(t *testing.T) {
if_op or if_op or
}`, false, IfMatcher{ }`, false, IfMatcher{
ifs: []ifCond{ ifs: []ifCond{
{a: "a", op: "match", b: "b", neg: false}, {a: "a", op: "match", b: "b", neg: false, rex: rex_b},
}, },
isOr: true, isOr: true,
}}, }},
...@@ -255,6 +262,7 @@ func TestSetupIfMatcher(t *testing.T) { ...@@ -255,6 +262,7 @@ func TestSetupIfMatcher(t *testing.T) {
for i, test := range tests { for i, test := range tests {
c := caddy.NewTestController("http", test.input) c := caddy.NewTestController("http", test.input)
c.Next() c.Next()
matcher, err := SetupIfMatcher(c) matcher, err := SetupIfMatcher(c)
if err == nil && test.shouldErr { if err == nil && test.shouldErr {
t.Errorf("Test %d didn't error, but it should have", i) t.Errorf("Test %d didn't error, but it should have", i)
...@@ -263,15 +271,60 @@ func TestSetupIfMatcher(t *testing.T) { ...@@ -263,15 +271,60 @@ func TestSetupIfMatcher(t *testing.T) {
} else if err != nil && test.shouldErr { } else if err != nil && test.shouldErr {
continue continue
} }
if _, ok := matcher.(IfMatcher); !ok {
test_if, ok := matcher.(IfMatcher)
if !ok {
t.Error("RequestMatcher should be of type IfMatcher") t.Error("RequestMatcher should be of type IfMatcher")
} }
if err != nil { if err != nil {
t.Errorf("Expected no error, but got: %v", err) t.Errorf("Expected no error, but got: %v", err)
} }
if fmt.Sprint(matcher) != fmt.Sprint(test.expected) {
t.Errorf("Test %v: Expected %v, found %v", i, if len(test_if.ifs) != len(test.expected.ifs) {
fmt.Sprint(test.expected), fmt.Sprint(matcher)) t.Errorf("Test %d: Expected %d ifConditions, found %v", i,
len(test.expected.ifs), len(test_if.ifs))
}
for j, if_c := range test_if.ifs {
expected_c := test.expected.ifs[j]
if if_c.a != expected_c.a {
t.Errorf("Test %d, ifCond %d: Expected A=%s, got %s",
i, j, if_c.a, expected_c.a)
}
if if_c.op != expected_c.op {
t.Errorf("Test %d, ifCond %d: Expected Op=%s, got %s",
i, j, if_c.op, expected_c.op)
}
if if_c.b != expected_c.b {
t.Errorf("Test %d, ifCond %d: Expected B=%s, got %s",
i, j, if_c.b, expected_c.b)
}
if if_c.neg != expected_c.neg {
t.Errorf("Test %d, ifCond %d: Expected Neg=%v, got %v",
i, j, if_c.neg, expected_c.neg)
}
if expected_c.rex != nil && if_c.rex == nil {
t.Errorf("Test %d, ifCond %d: Expected Rex=%v, got <nil>",
i, j, expected_c.rex)
}
if expected_c.rex == nil && if_c.rex != nil {
t.Errorf("Test %d, ifCond %d: Expected Rex=<nil>, got %v",
i, j, if_c.rex)
}
if expected_c.rex != nil && if_c.rex != nil {
if if_c.rex.String() != expected_c.rex.String() {
t.Errorf("Test %d, ifCond %d: Expected Rex=%v, got %v",
i, j, if_c.rex, expected_c.rex)
}
}
} }
} }
} }
......
...@@ -84,19 +84,19 @@ type ComplexRule struct { ...@@ -84,19 +84,19 @@ type ComplexRule struct {
// Request matcher // Request matcher
httpserver.RequestMatcher httpserver.RequestMatcher
*regexp.Regexp Regexp *regexp.Regexp
} }
// NewComplexRule creates a new RegexpRule. It returns an error if regexp // NewComplexRule creates a new RegexpRule. It returns an error if regexp
// pattern (pattern) or extensions (ext) are invalid. // pattern (pattern) or extensions (ext) are invalid.
func NewComplexRule(base, pattern, to string, ext []string, matcher httpserver.RequestMatcher) (*ComplexRule, error) { func NewComplexRule(base, pattern, to string, ext []string, matcher httpserver.RequestMatcher) (ComplexRule, error) {
// validate regexp if present // validate regexp if present
var r *regexp.Regexp var r *regexp.Regexp
if pattern != "" { if pattern != "" {
var err error var err error
r, err = regexp.Compile(pattern) r, err = regexp.Compile(pattern)
if err != nil { if err != nil {
return nil, err return ComplexRule{}, err
} }
} }
...@@ -105,7 +105,7 @@ func NewComplexRule(base, pattern, to string, ext []string, matcher httpserver.R ...@@ -105,7 +105,7 @@ func NewComplexRule(base, pattern, to string, ext []string, matcher httpserver.R
if len(v) < 2 || (len(v) < 3 && v[0] == '!') { if len(v) < 2 || (len(v) < 3 && v[0] == '!') {
// check if no extension is specified // check if no extension is specified
if v != "/" && v != "!/" { if v != "/" && v != "!/" {
return nil, fmt.Errorf("invalid extension %v", v) return ComplexRule{}, fmt.Errorf("invalid extension %v", v)
} }
} }
} }
...@@ -118,7 +118,7 @@ func NewComplexRule(base, pattern, to string, ext []string, matcher httpserver.R ...@@ -118,7 +118,7 @@ func NewComplexRule(base, pattern, to string, ext []string, matcher httpserver.R
httpserver.PathMatcher(base), httpserver.PathMatcher(base),
) )
return &ComplexRule{ return ComplexRule{
Base: base, Base: base,
To: to, To: to,
Exts: ext, Exts: ext,
...@@ -128,13 +128,13 @@ func NewComplexRule(base, pattern, to string, ext []string, matcher httpserver.R ...@@ -128,13 +128,13 @@ func NewComplexRule(base, pattern, to string, ext []string, matcher httpserver.R
} }
// BasePath satisfies httpserver.Config // BasePath satisfies httpserver.Config
func (r *ComplexRule) BasePath() string { return r.Base } func (r ComplexRule) BasePath() string { return r.Base }
// Match satisfies httpserver.Config. // Match satisfies httpserver.Config.
// //
// Though ComplexRule embeds a RequestMatcher, additional // Though ComplexRule embeds a RequestMatcher, additional
// checks are needed which requires a custom implementation. // checks are needed which requires a custom implementation.
func (r *ComplexRule) Match(req *http.Request) bool { func (r ComplexRule) Match(req *http.Request) bool {
// validate RequestMatcher // validate RequestMatcher
// includes if and path // includes if and path
if !r.RequestMatcher.Match(req) { if !r.RequestMatcher.Match(req) {
...@@ -155,7 +155,7 @@ func (r *ComplexRule) Match(req *http.Request) bool { ...@@ -155,7 +155,7 @@ func (r *ComplexRule) Match(req *http.Request) bool {
} }
// Rewrite rewrites the internal location of the current request. // Rewrite rewrites the internal location of the current request.
func (r *ComplexRule) Rewrite(fs http.FileSystem, req *http.Request) (re Result) { func (r ComplexRule) Rewrite(fs http.FileSystem, req *http.Request) (re Result) {
replacer := newReplacer(req) replacer := newReplacer(req)
// validate regexp if present // validate regexp if present
...@@ -189,7 +189,7 @@ func (r *ComplexRule) Rewrite(fs http.FileSystem, req *http.Request) (re Result) ...@@ -189,7 +189,7 @@ func (r *ComplexRule) Rewrite(fs http.FileSystem, req *http.Request) (re Result)
// matchExt matches rPath against registered file extensions. // matchExt matches rPath against registered file extensions.
// Returns true if a match is found and false otherwise. // Returns true if a match is found and false otherwise.
func (r *ComplexRule) matchExt(rPath string) bool { func (r ComplexRule) matchExt(rPath string) bool {
f := filepath.Base(rPath) f := filepath.Base(rPath)
ext := path.Ext(f) ext := path.Ext(f)
if ext == "" { if ext == "" {
...@@ -216,14 +216,14 @@ func (r *ComplexRule) matchExt(rPath string) bool { ...@@ -216,14 +216,14 @@ func (r *ComplexRule) matchExt(rPath string) bool {
return !mustUse return !mustUse
} }
func (r *ComplexRule) regexpMatches(rPath string) []string { func (r ComplexRule) regexpMatches(rPath string) []string {
if r.Regexp != nil { if r.Regexp != nil {
// include trailing slash in regexp if present // include trailing slash in regexp if present
start := len(r.Base) start := len(r.Base)
if strings.HasSuffix(r.Base, "/") { if strings.HasSuffix(r.Base, "/") {
start-- start--
} }
return r.FindStringSubmatch(rPath[start:]) return r.Regexp.FindStringSubmatch(rPath[start:])
} }
return nil return nil
} }
......
...@@ -3,6 +3,7 @@ package rewrite ...@@ -3,6 +3,7 @@ package rewrite
import ( import (
"fmt" "fmt"
"regexp" "regexp"
"strings"
"testing" "testing"
"github.com/mholt/caddy" "github.com/mholt/caddy"
...@@ -97,14 +98,14 @@ func TestRewriteParse(t *testing.T) { ...@@ -97,14 +98,14 @@ func TestRewriteParse(t *testing.T) {
r .* r .*
to /to /index.php? to /to /index.php?
}`, false, []Rule{ }`, false, []Rule{
&ComplexRule{Base: "/", To: "/to /index.php?", Regexp: regexp.MustCompile(".*")}, ComplexRule{Base: "/", To: "/to /index.php?", Regexp: regexp.MustCompile(".*")},
}}, }},
{`rewrite { {`rewrite {
regexp .* regexp .*
to /to to /to
ext / html txt ext / html txt
}`, false, []Rule{ }`, false, []Rule{
&ComplexRule{Base: "/", To: "/to", Exts: []string{"/", "html", "txt"}, Regexp: regexp.MustCompile(".*")}, ComplexRule{Base: "/", To: "/to", Exts: []string{"/", "html", "txt"}, Regexp: regexp.MustCompile(".*")},
}}, }},
{`rewrite /path { {`rewrite /path {
r rr r rr
...@@ -115,27 +116,27 @@ func TestRewriteParse(t *testing.T) { ...@@ -115,27 +116,27 @@ func TestRewriteParse(t *testing.T) {
to /to /to2 to /to /to2
} }
`, false, []Rule{ `, false, []Rule{
&ComplexRule{Base: "/path", To: "/dest", Regexp: regexp.MustCompile("rr")}, ComplexRule{Base: "/path", To: "/dest", Regexp: regexp.MustCompile("rr")},
&ComplexRule{Base: "/", To: "/to /to2", Regexp: regexp.MustCompile("[a-z]+")}, ComplexRule{Base: "/", To: "/to /to2", Regexp: regexp.MustCompile("[a-z]+")},
}}, }},
{`rewrite { {`rewrite {
r .* r .*
}`, true, []Rule{ }`, true, []Rule{
&ComplexRule{}, ComplexRule{},
}}, }},
{`rewrite { {`rewrite {
}`, true, []Rule{ }`, true, []Rule{
&ComplexRule{}, ComplexRule{},
}}, }},
{`rewrite /`, true, []Rule{ {`rewrite /`, true, []Rule{
&ComplexRule{}, ComplexRule{},
}}, }},
{`rewrite { {`rewrite {
if {path} match / if {path} match /
to /to to /to
}`, false, []Rule{ }`, false, []Rule{
&ComplexRule{Base: "/", To: "/to"}, ComplexRule{Base: "/", To: "/to"},
}}, }},
} }
...@@ -156,8 +157,8 @@ func TestRewriteParse(t *testing.T) { ...@@ -156,8 +157,8 @@ func TestRewriteParse(t *testing.T) {
} }
for j, e := range test.expected { for j, e := range test.expected {
actualRule := actual[j].(*ComplexRule) actualRule := actual[j].(ComplexRule)
expectedRule := e.(*ComplexRule) expectedRule := e.(ComplexRule)
if actualRule.Base != expectedRule.Base { if actualRule.Base != expectedRule.Base {
t.Errorf("Test %d, rule %d: Expected Base=%s, got %s", t.Errorf("Test %d, rule %d: Expected Base=%s, got %s",
...@@ -175,13 +176,15 @@ func TestRewriteParse(t *testing.T) { ...@@ -175,13 +176,15 @@ func TestRewriteParse(t *testing.T) {
} }
if actualRule.Regexp != nil { if actualRule.Regexp != nil {
if actualRule.String() != expectedRule.String() { if actualRule.Regexp.String() != expectedRule.Regexp.String() {
t.Errorf("Test %d, rule %d: Expected Pattern=%s, got %s", t.Errorf("Test %d, rule %d: Expected Pattern=%s, got %s",
i, j, expectedRule.String(), actualRule.String()) i, j, actualRule.Regexp.String(), expectedRule.Regexp.String())
} }
} }
}
if rules_fmt := fmt.Sprintf("%v", actual); strings.HasPrefix(rules_fmt, "%!") {
t.Errorf("Test %d: Failed to string encode: %#v", i, rules_fmt)
} }
} }
} }
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment