Merge pull request #1796 from mholt/bugfix_rewrite_1794

Fix for #1794: Fixes issues with IfMatcher and regular expressions.
This commit is contained in:
Matt Holt
2017-08-12 15:17:28 -06:00
committed by GitHub
4 changed files with 194 additions and 150 deletions
+57 -69
View File
@@ -53,59 +53,8 @@ const (
matchOp = "match"
)
func operatorError(operator string) error {
return fmt.Errorf("Invalid operator %v", operator)
}
// ifCondition is a 'if' condition.
type ifCondition func(string, 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
}
type ifFunc func(a, b string) bool
// ifCond is statement for a IfMatcher condition.
type ifCond struct {
@@ -113,40 +62,79 @@ type ifCond struct {
op string
b string
neg bool
rex *regexp.Regexp
f ifFunc
}
// newIfCond creates a new If condition.
func newIfCond(a, operator, b string) (ifCond, error) {
neg := false
if strings.HasPrefix(operator, "not_") {
neg = true
operator = operator[4:]
func newIfCond(a, op, b string) (ifCond, error) {
i := ifCond{a: a, op: op, b: b}
if strings.HasPrefix(op, "not_") {
i.neg = true
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,
op: operator,
b: b,
neg: neg,
}, nil
return i, nil
}
// isFunc is condition for Is operator.
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.
// If r is not nil, it replaces placeholders before comparison.
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
if r != nil {
replacer := NewReplacer(r, nil, "")
a = replacer.Replace(i.a)
b = replacer.Replace(i.b)
if i.op != matchOp {
b = replacer.Replace(i.b)
}
}
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
}
+110 -57
View File
@@ -2,8 +2,8 @@ package httpserver
import (
"context"
"fmt"
"net/http"
"regexp"
"strings"
"testing"
@@ -14,66 +14,72 @@ func TestConditions(t *testing.T) {
tests := []struct {
condition string
isTrue bool
shouldErr bool
}{
{"a is b", false},
{"a is a", true},
{"a not b", true},
{"a not a", false},
{"a has a", true},
{"a has b", false},
{"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},
{"bab not_starts_with bb", true},
{"bab not_starts_with ba", false},
{"bab not_starts_with bab", false},
{"bab ends_with bb", false},
{"bab ends_with bab", true},
{"bab ends_with ab", true},
{"bab not_ends_with bb", true},
{"bab not_ends_with ab", false},
{"bab not_ends_with bab", false},
{"a match *", false},
{"a match a", true},
{"a match .*", true},
{"a match a.*", true},
{"a match b.*", false},
{"ba match b.*", true},
{"ba match b[a-z]", true},
{"b0 match b[a-z]", false},
{"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},
{"a is b", false, false},
{"a is a", true, false},
{"a not b", true, false},
{"a not a", false, false},
{"a has a", true, false},
{"a has b", false, false},
{"ba has b", true, false},
{"bab has b", true, false},
{"bab has bb", false, false},
{"a not_has a", false, false},
{"a not_has b", true, false},
{"ba not_has b", false, false},
{"bab not_has b", false, false},
{"bab not_has bb", true, false},
{"bab starts_with bb", false, false},
{"bab starts_with ba", true, false},
{"bab starts_with bab", true, false},
{"bab not_starts_with bb", true, false},
{"bab not_starts_with ba", false, false},
{"bab not_starts_with bab", false, false},
{"bab ends_with bb", false, false},
{"bab ends_with bab", true, false},
{"bab ends_with ab", true, false},
{"bab not_ends_with bb", true, false},
{"bab not_ends_with ab", false, false},
{"bab not_ends_with bab", false, false},
{"a match *", false, true},
{"a match a", true, false},
{"a match .*", true, false},
{"a match a.*", true, false},
{"a match b.*", false, false},
{"ba match b.*", true, false},
{"ba match b[a-z]", true, false},
{"b0 match b[a-z]", false, false},
{"b0a match b[a-z]", false, false},
{"b0a match b[a-z]+", false, false},
{"b0a match b[a-z0-9]+", true, false},
{"bac match b[a-z]{2}", true, false},
{"a not_match *", false, true},
{"a not_match a", false, false},
{"a not_match .*", false, false},
{"a not_match a.*", false, false},
{"a not_match b.*", true, false},
{"ba not_match b.*", false, false},
{"ba not_match b[a-z]", false, false},
{"b0 not_match b[a-z]", true, false},
{"b0a not_match b[a-z]", true, 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 {
str := strings.Fields(test.condition)
ifCond, err := newIfCond(str[0], str[1], str[2])
if err != nil {
t.Error(err)
if !test.shouldErr {
t.Error(err)
}
continue
}
isTrue := ifCond.True(nil)
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) {
}
func TestSetupIfMatcher(t *testing.T) {
rex_b, _ := regexp.Compile("b")
tests := []struct {
input string
shouldErr bool
@@ -201,7 +208,7 @@ func TestSetupIfMatcher(t *testing.T) {
if a match b
}`, false, IfMatcher{
ifs: []ifCond{
{a: "a", op: "match", b: "b", neg: false},
{a: "a", op: "match", b: "b", neg: false, rex: rex_b},
},
}},
{`test {
@@ -209,7 +216,7 @@ func TestSetupIfMatcher(t *testing.T) {
if_op or
}`, false, IfMatcher{
ifs: []ifCond{
{a: "a", op: "match", b: "b", neg: false},
{a: "a", op: "match", b: "b", neg: false, rex: rex_b},
},
isOr: true,
}},
@@ -255,6 +262,7 @@ func TestSetupIfMatcher(t *testing.T) {
for i, test := range tests {
c := caddy.NewTestController("http", test.input)
c.Next()
matcher, err := SetupIfMatcher(c)
if err == nil && test.shouldErr {
t.Errorf("Test %d didn't error, but it should have", i)
@@ -263,15 +271,60 @@ func TestSetupIfMatcher(t *testing.T) {
} else if err != nil && test.shouldErr {
continue
}
if _, ok := matcher.(IfMatcher); !ok {
test_if, ok := matcher.(IfMatcher)
if !ok {
t.Error("RequestMatcher should be of type IfMatcher")
}
if err != nil {
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,
fmt.Sprint(test.expected), fmt.Sprint(matcher))
if len(test_if.ifs) != len(test.expected.ifs) {
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)
}
}
}
}
}