mirror of
https://github.com/caddyserver/caddy.git
synced 2025-03-13 00:58:53 +01:00
Condition upgrades (if, if_op) for rewrite, redir (#889)
* checkpoint * Added RequestMatcher interface. Extract 'if' condition into a RequestMatcher. * Added tests for IfMatcher * Minor refactors * Refactors * Use if_op * conform with new 0.9 beta function changes.
This commit is contained in:
parent
0a3f68f0d7
commit
d9b6563d88
12 changed files with 546 additions and 287 deletions
199
caddyhttp/httpserver/condition.go
Normal file
199
caddyhttp/httpserver/condition.go
Normal file
|
@ -0,0 +1,199 @@
|
||||||
|
package httpserver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/mholt/caddy/caddyfile"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SetupIfMatcher parses `if` or `if_type` in the current dispenser block.
|
||||||
|
// It returns a RequestMatcher and an error if any.
|
||||||
|
func SetupIfMatcher(c caddyfile.Dispenser) (RequestMatcher, error) {
|
||||||
|
var matcher IfMatcher
|
||||||
|
for c.NextBlock() {
|
||||||
|
switch c.Val() {
|
||||||
|
case "if":
|
||||||
|
args1 := c.RemainingArgs()
|
||||||
|
if len(args1) != 3 {
|
||||||
|
return matcher, c.ArgErr()
|
||||||
|
}
|
||||||
|
ifc, err := newIfCond(args1[0], args1[1], args1[2])
|
||||||
|
if err != nil {
|
||||||
|
return matcher, err
|
||||||
|
}
|
||||||
|
matcher.ifs = append(matcher.ifs, ifc)
|
||||||
|
case "if_op":
|
||||||
|
if !c.NextArg() {
|
||||||
|
return matcher, c.ArgErr()
|
||||||
|
}
|
||||||
|
switch c.Val() {
|
||||||
|
case "and":
|
||||||
|
matcher.isOr = false
|
||||||
|
case "or":
|
||||||
|
matcher.isOr = true
|
||||||
|
default:
|
||||||
|
return matcher, c.ArgErr()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return matcher, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// operators
|
||||||
|
const (
|
||||||
|
isOp = "is"
|
||||||
|
notOp = "not"
|
||||||
|
hasOp = "has"
|
||||||
|
notHasOp = "not_has"
|
||||||
|
startsWithOp = "starts_with"
|
||||||
|
endsWithOp = "ends_with"
|
||||||
|
matchOp = "match"
|
||||||
|
notMatchOp = "not_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,
|
||||||
|
notHasOp: notHasFunc,
|
||||||
|
startsWithOp: startsWithFunc,
|
||||||
|
endsWithOp: endsWithFunc,
|
||||||
|
matchOp: matchFunc,
|
||||||
|
notMatchOp: notMatchFunc,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// ifCond is statement for a IfMatcher condition.
|
||||||
|
type ifCond struct {
|
||||||
|
a string
|
||||||
|
op string
|
||||||
|
b string
|
||||||
|
}
|
||||||
|
|
||||||
|
// newIfCond creates a new If condition.
|
||||||
|
func newIfCond(a, operator, b string) (ifCond, error) {
|
||||||
|
if _, ok := ifConditions[operator]; !ok {
|
||||||
|
return ifCond{}, operatorError(operator)
|
||||||
|
}
|
||||||
|
return ifCond{
|
||||||
|
a: a,
|
||||||
|
op: operator,
|
||||||
|
b: b,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
a, b := i.a, i.b
|
||||||
|
if r != nil {
|
||||||
|
replacer := NewReplacer(r, nil, "")
|
||||||
|
a = replacer.Replace(i.a)
|
||||||
|
b = replacer.Replace(i.b)
|
||||||
|
}
|
||||||
|
return c(a, b)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// IfMatcher is a RequestMatcher for 'if' conditions.
|
||||||
|
type IfMatcher struct {
|
||||||
|
ifs []ifCond // list of If
|
||||||
|
isOr bool // if true, conditions are 'or' instead of 'and'
|
||||||
|
}
|
||||||
|
|
||||||
|
// Match satisfies RequestMatcher interface.
|
||||||
|
// It returns true if the conditions in m are true.
|
||||||
|
func (m IfMatcher) Match(r *http.Request) bool {
|
||||||
|
if m.isOr {
|
||||||
|
return m.Or(r)
|
||||||
|
}
|
||||||
|
return m.And(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// And returns true if all conditions in m are true.
|
||||||
|
func (m IfMatcher) And(r *http.Request) bool {
|
||||||
|
for _, i := range m.ifs {
|
||||||
|
if !i.True(r) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Or returns true if any of the conditions in m is true.
|
||||||
|
func (m IfMatcher) Or(r *http.Request) bool {
|
||||||
|
for _, i := range m.ifs {
|
||||||
|
if i.True(r) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// IfMatcherKeyword returns if k is a keyword for 'if' config block.
|
||||||
|
func IfMatcherKeyword(k string) bool {
|
||||||
|
return k == "if" || k == "if_cond"
|
||||||
|
}
|
265
caddyhttp/httpserver/condition_test.go
Normal file
265
caddyhttp/httpserver/condition_test.go
Normal file
|
@ -0,0 +1,265 @@
|
||||||
|
package httpserver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/mholt/caddy"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestConditions(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
condition string
|
||||||
|
isTrue 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 ends_with bb", false},
|
||||||
|
{"bab ends_with bab", true},
|
||||||
|
{"bab ends_with ab", true},
|
||||||
|
{"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},
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
isTrue := ifCond.True(nil)
|
||||||
|
if isTrue != test.isTrue {
|
||||||
|
t.Errorf("Test %d: expected %v found %v", i, test.isTrue, isTrue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
invalidOperators := []string{"ss", "and", "if"}
|
||||||
|
for _, op := range invalidOperators {
|
||||||
|
_, err := newIfCond("a", op, "b")
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("Invalid operator %v used, expected error.", op)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
replaceTests := []struct {
|
||||||
|
url string
|
||||||
|
condition string
|
||||||
|
isTrue bool
|
||||||
|
}{
|
||||||
|
{"/home", "{uri} match /home", true},
|
||||||
|
{"/hom", "{uri} match /home", false},
|
||||||
|
{"/hom", "{uri} starts_with /home", false},
|
||||||
|
{"/hom", "{uri} starts_with /h", true},
|
||||||
|
{"/home/.hiddenfile", `{uri} match \/\.(.*)`, true},
|
||||||
|
{"/home/.hiddendir/afile", `{uri} match \/\.(.*)`, true},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, test := range replaceTests {
|
||||||
|
r, err := http.NewRequest("GET", test.url, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
str := strings.Fields(test.condition)
|
||||||
|
ifCond, err := newIfCond(str[0], str[1], str[2])
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
isTrue := ifCond.True(r)
|
||||||
|
if isTrue != test.isTrue {
|
||||||
|
t.Errorf("Test %v: expected %v found %v", i, test.isTrue, isTrue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIfMatcher(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
conditions []string
|
||||||
|
isOr bool
|
||||||
|
isTrue bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
[]string{
|
||||||
|
"a is a",
|
||||||
|
"b is b",
|
||||||
|
"c is c",
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[]string{
|
||||||
|
"a is b",
|
||||||
|
"b is c",
|
||||||
|
"c is c",
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[]string{
|
||||||
|
"a is a",
|
||||||
|
"b is a",
|
||||||
|
"c is c",
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[]string{
|
||||||
|
"a is b",
|
||||||
|
"b is c",
|
||||||
|
"c is a",
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[]string{},
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[]string{},
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, test := range tests {
|
||||||
|
matcher := IfMatcher{isOr: test.isOr}
|
||||||
|
for _, condition := range test.conditions {
|
||||||
|
str := strings.Fields(condition)
|
||||||
|
ifCond, err := newIfCond(str[0], str[1], str[2])
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
matcher.ifs = append(matcher.ifs, ifCond)
|
||||||
|
}
|
||||||
|
isTrue := matcher.Match(nil)
|
||||||
|
if isTrue != test.isTrue {
|
||||||
|
t.Errorf("Test %d: expected %v found %v", i, test.isTrue, isTrue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetupIfMatcher(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
input string
|
||||||
|
shouldErr bool
|
||||||
|
expected IfMatcher
|
||||||
|
}{
|
||||||
|
{`test {
|
||||||
|
if a match b
|
||||||
|
}`, false, IfMatcher{
|
||||||
|
ifs: []ifCond{
|
||||||
|
{a: "a", op: "match", b: "b"},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
{`test {
|
||||||
|
if a match b
|
||||||
|
if_op or
|
||||||
|
}`, false, IfMatcher{
|
||||||
|
ifs: []ifCond{
|
||||||
|
{a: "a", op: "match", b: "b"},
|
||||||
|
},
|
||||||
|
isOr: true,
|
||||||
|
}},
|
||||||
|
{`test {
|
||||||
|
if a match
|
||||||
|
}`, true, IfMatcher{},
|
||||||
|
},
|
||||||
|
{`test {
|
||||||
|
if a isnt b
|
||||||
|
}`, true, IfMatcher{},
|
||||||
|
},
|
||||||
|
{`test {
|
||||||
|
if a match b c
|
||||||
|
}`, true, IfMatcher{},
|
||||||
|
},
|
||||||
|
{`test {
|
||||||
|
if goal has go
|
||||||
|
if cook not_has go
|
||||||
|
}`, false, IfMatcher{
|
||||||
|
ifs: []ifCond{
|
||||||
|
{a: "goal", op: "has", b: "go"},
|
||||||
|
{a: "cook", op: "not_has", b: "go"},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
{`test {
|
||||||
|
if goal has go
|
||||||
|
if cook not_has go
|
||||||
|
if_op and
|
||||||
|
}`, false, IfMatcher{
|
||||||
|
ifs: []ifCond{
|
||||||
|
{a: "goal", op: "has", b: "go"},
|
||||||
|
{a: "cook", op: "not_has", b: "go"},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
{`test {
|
||||||
|
if goal has go
|
||||||
|
if cook not_has go
|
||||||
|
if_op not
|
||||||
|
}`, true, IfMatcher{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, test := range tests {
|
||||||
|
c := caddy.NewTestController("http", test.input)
|
||||||
|
c.Next()
|
||||||
|
matcher, err := SetupIfMatcher(c.Dispenser)
|
||||||
|
if err == nil && test.shouldErr {
|
||||||
|
t.Errorf("Test %d didn't error, but it should have", i)
|
||||||
|
} else if err != nil && !test.shouldErr {
|
||||||
|
t.Errorf("Test %d errored, but it shouldn't have; got '%v'", i, err)
|
||||||
|
} else if err != nil && test.shouldErr {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, ok := matcher.(IfMatcher); !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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -45,6 +45,16 @@ type (
|
||||||
// ServeHTTP returns a status code and an error. See Handler
|
// ServeHTTP returns a status code and an error. See Handler
|
||||||
// documentation for more information.
|
// documentation for more information.
|
||||||
HandlerFunc func(http.ResponseWriter, *http.Request) (int, error)
|
HandlerFunc func(http.ResponseWriter, *http.Request) (int, error)
|
||||||
|
|
||||||
|
// RequestMatcher checks to see if current request should be handled
|
||||||
|
// by underlying handler.
|
||||||
|
//
|
||||||
|
// TODO The long term plan is to get all middleware implement this
|
||||||
|
// interface and have validation done before requests are dispatched
|
||||||
|
// to each middleware.
|
||||||
|
RequestMatcher interface {
|
||||||
|
Match(r *http.Request) bool
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
// ServeHTTP implements the Handler interface.
|
// ServeHTTP implements the Handler interface.
|
||||||
|
@ -135,6 +145,24 @@ func (p Path) Matches(other string) bool {
|
||||||
return strings.HasPrefix(strings.ToLower(string(p)), strings.ToLower(other))
|
return strings.HasPrefix(strings.ToLower(string(p)), strings.ToLower(other))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MergeRequestMatchers merges multiple RequestMatchers into one.
|
||||||
|
// This allows a middleware to use multiple RequestMatchers.
|
||||||
|
func MergeRequestMatchers(matchers ...RequestMatcher) RequestMatcher {
|
||||||
|
return requestMatchers(matchers)
|
||||||
|
}
|
||||||
|
|
||||||
|
type requestMatchers []RequestMatcher
|
||||||
|
|
||||||
|
// Match satisfies RequestMatcher interface.
|
||||||
|
func (m requestMatchers) Match(r *http.Request) bool {
|
||||||
|
for _, matcher := range m {
|
||||||
|
if !matcher.Match(r) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
// currentTime, as it is defined here, returns time.Now().
|
// currentTime, as it is defined here, returns time.Now().
|
||||||
// It's defined as a variable for mocking time in tests.
|
// It's defined as a variable for mocking time in tests.
|
||||||
var currentTime = func() time.Time { return time.Now() }
|
var currentTime = func() time.Time { return time.Now() }
|
||||||
|
|
|
@ -19,7 +19,7 @@ type Redirect struct {
|
||||||
// ServeHTTP implements the httpserver.Handler interface.
|
// ServeHTTP implements the httpserver.Handler interface.
|
||||||
func (rd Redirect) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
|
func (rd Redirect) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
|
||||||
for _, rule := range rd.Rules {
|
for _, rule := range rd.Rules {
|
||||||
if (rule.FromPath == "/" || r.URL.Path == rule.FromPath) && schemeMatches(rule, r) {
|
if (rule.FromPath == "/" || r.URL.Path == rule.FromPath) && schemeMatches(rule, r) && rule.Match(r) {
|
||||||
to := httpserver.NewReplacer(r, nil, "").Replace(rule.To)
|
to := httpserver.NewReplacer(r, nil, "").Replace(rule.To)
|
||||||
if rule.Meta {
|
if rule.Meta {
|
||||||
safeTo := html.EscapeString(to)
|
safeTo := html.EscapeString(to)
|
||||||
|
@ -43,6 +43,7 @@ type Rule struct {
|
||||||
FromScheme, FromPath, To string
|
FromScheme, FromPath, To string
|
||||||
Code int
|
Code int
|
||||||
Meta bool
|
Meta bool
|
||||||
|
httpserver.RequestMatcher
|
||||||
}
|
}
|
||||||
|
|
||||||
// Script tag comes first since that will better imitate a redirect in the browser's
|
// Script tag comes first since that will better imitate a redirect in the browser's
|
||||||
|
|
|
@ -47,16 +47,16 @@ func TestRedirect(t *testing.T) {
|
||||||
return 0, nil
|
return 0, nil
|
||||||
}),
|
}),
|
||||||
Rules: []Rule{
|
Rules: []Rule{
|
||||||
{FromPath: "/from", To: "/to", Code: http.StatusMovedPermanently},
|
{FromPath: "/from", To: "/to", Code: http.StatusMovedPermanently, RequestMatcher: httpserver.IfMatcher{}},
|
||||||
{FromPath: "/a", To: "/b", Code: http.StatusTemporaryRedirect},
|
{FromPath: "/a", To: "/b", Code: http.StatusTemporaryRedirect, RequestMatcher: httpserver.IfMatcher{}},
|
||||||
|
|
||||||
// These http and https schemes would never actually be mixed in the same
|
// These http and https schemes would never actually be mixed in the same
|
||||||
// redirect rule with Caddy because http and https schemes have different listeners,
|
// redirect rule with Caddy because http and https schemes have different listeners,
|
||||||
// so they don't share a redirect rule. So although these tests prove something
|
// so they don't share a redirect rule. So although these tests prove something
|
||||||
// impossible with Caddy, it's extra bulletproofing at very little cost.
|
// impossible with Caddy, it's extra bulletproofing at very little cost.
|
||||||
{FromScheme: "http", FromPath: "/scheme", To: "https://localhost/scheme", Code: http.StatusMovedPermanently},
|
{FromScheme: "http", FromPath: "/scheme", To: "https://localhost/scheme", Code: http.StatusMovedPermanently, RequestMatcher: httpserver.IfMatcher{}},
|
||||||
{FromScheme: "https", FromPath: "/scheme2", To: "http://localhost/scheme2", Code: http.StatusMovedPermanently},
|
{FromScheme: "https", FromPath: "/scheme2", To: "http://localhost/scheme2", Code: http.StatusMovedPermanently, RequestMatcher: httpserver.IfMatcher{}},
|
||||||
{FromScheme: "", FromPath: "/scheme3", To: "https://localhost/scheme3", Code: http.StatusMovedPermanently},
|
{FromScheme: "", FromPath: "/scheme3", To: "https://localhost/scheme3", Code: http.StatusMovedPermanently, RequestMatcher: httpserver.IfMatcher{}},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -90,7 +90,7 @@ func TestRedirect(t *testing.T) {
|
||||||
func TestParametersRedirect(t *testing.T) {
|
func TestParametersRedirect(t *testing.T) {
|
||||||
re := Redirect{
|
re := Redirect{
|
||||||
Rules: []Rule{
|
Rules: []Rule{
|
||||||
{FromPath: "/", Meta: false, To: "http://example.com{uri}"},
|
{FromPath: "/", Meta: false, To: "http://example.com{uri}", RequestMatcher: httpserver.IfMatcher{}},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -108,7 +108,7 @@ func TestParametersRedirect(t *testing.T) {
|
||||||
|
|
||||||
re = Redirect{
|
re = Redirect{
|
||||||
Rules: []Rule{
|
Rules: []Rule{
|
||||||
{FromPath: "/", Meta: false, To: "http://example.com/a{path}?b=c&{query}"},
|
{FromPath: "/", Meta: false, To: "http://example.com/a{path}?b=c&{query}", RequestMatcher: httpserver.IfMatcher{}},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -127,8 +127,8 @@ func TestParametersRedirect(t *testing.T) {
|
||||||
func TestMetaRedirect(t *testing.T) {
|
func TestMetaRedirect(t *testing.T) {
|
||||||
re := Redirect{
|
re := Redirect{
|
||||||
Rules: []Rule{
|
Rules: []Rule{
|
||||||
{FromPath: "/whatever", Meta: true, To: "/something"},
|
{FromPath: "/whatever", Meta: true, To: "/something", RequestMatcher: httpserver.IfMatcher{}},
|
||||||
{FromPath: "/", Meta: true, To: "https://example.com/"},
|
{FromPath: "/", Meta: true, To: "https://example.com/", RequestMatcher: httpserver.IfMatcher{}},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -63,13 +63,23 @@ func redirParse(c *caddy.Controller) ([]Rule, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for c.Next() {
|
for c.Next() {
|
||||||
|
matcher, err := httpserver.SetupIfMatcher(c.Dispenser)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
args := c.RemainingArgs()
|
args := c.RemainingArgs()
|
||||||
|
|
||||||
var hadOptionalBlock bool
|
var hadOptionalBlock bool
|
||||||
for c.NextBlock() {
|
for c.NextBlock() {
|
||||||
|
if httpserver.IfMatcherKeyword(c.Val()) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
hadOptionalBlock = true
|
hadOptionalBlock = true
|
||||||
|
|
||||||
var rule Rule
|
var rule = Rule{
|
||||||
|
RequestMatcher: matcher,
|
||||||
|
}
|
||||||
|
|
||||||
if cfg.TLS.Enabled {
|
if cfg.TLS.Enabled {
|
||||||
rule.FromScheme = "https"
|
rule.FromScheme = "https"
|
||||||
|
@ -126,7 +136,9 @@ func redirParse(c *caddy.Controller) ([]Rule, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if !hadOptionalBlock {
|
if !hadOptionalBlock {
|
||||||
var rule Rule
|
var rule = Rule{
|
||||||
|
RequestMatcher: matcher,
|
||||||
|
}
|
||||||
|
|
||||||
if cfg.TLS.Enabled {
|
if cfg.TLS.Enabled {
|
||||||
rule.FromScheme = "https"
|
rule.FromScheme = "https"
|
||||||
|
|
|
@ -1,130 +0,0 @@
|
||||||
package rewrite
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/mholt/caddy/caddyhttp/httpserver"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Operators
|
|
||||||
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 {
|
|
||||||
return fmt.Errorf("Invalid operator %v", operator)
|
|
||||||
}
|
|
||||||
|
|
||||||
func newReplacer(r *http.Request) httpserver.Replacer {
|
|
||||||
return httpserver.NewReplacer(r, nil, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
// condition is a rewrite condition.
|
|
||||||
type condition func(string, string) bool
|
|
||||||
|
|
||||||
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.
|
|
||||||
// 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 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)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 {
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
Operator string
|
|
||||||
B string
|
|
||||||
}
|
|
||||||
|
|
||||||
// True returns true if the condition is true and false otherwise.
|
|
||||||
// If r is not nil, it replaces placeholders before comparison.
|
|
||||||
func (i If) True(r *http.Request) bool {
|
|
||||||
if c, ok := conditions[i.Operator]; ok {
|
|
||||||
a, b := i.A, i.B
|
|
||||||
if r != nil {
|
|
||||||
replacer := newReplacer(r)
|
|
||||||
a = replacer.Replace(i.A)
|
|
||||||
b = replacer.Replace(i.B)
|
|
||||||
}
|
|
||||||
return c(a, b)
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewIf creates a new If condition.
|
|
||||||
func NewIf(a, operator, b string) (If, error) {
|
|
||||||
if _, ok := conditions[operator]; !ok {
|
|
||||||
return If{}, operatorError(operator)
|
|
||||||
}
|
|
||||||
return If{
|
|
||||||
A: a,
|
|
||||||
Operator: operator,
|
|
||||||
B: b,
|
|
||||||
}, nil
|
|
||||||
}
|
|
|
@ -1,106 +0,0 @@
|
||||||
package rewrite
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestConditions(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
condition string
|
|
||||||
isTrue 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 ends_with bb", false},
|
|
||||||
{"bab ends_with bab", true},
|
|
||||||
{"bab ends_with ab", true},
|
|
||||||
{"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},
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, test := range tests {
|
|
||||||
str := strings.Fields(test.condition)
|
|
||||||
ifCond, err := NewIf(str[0], str[1], str[2])
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
isTrue := ifCond.True(nil)
|
|
||||||
if isTrue != test.isTrue {
|
|
||||||
t.Errorf("Test %v: expected %v found %v", i, test.isTrue, isTrue)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
invalidOperators := []string{"ss", "and", "if"}
|
|
||||||
for _, op := range invalidOperators {
|
|
||||||
_, err := NewIf("a", op, "b")
|
|
||||||
if err == nil {
|
|
||||||
t.Errorf("Invalid operator %v used, expected error.", op)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
replaceTests := []struct {
|
|
||||||
url string
|
|
||||||
condition string
|
|
||||||
isTrue bool
|
|
||||||
}{
|
|
||||||
{"/home", "{uri} match /home", true},
|
|
||||||
{"/hom", "{uri} match /home", false},
|
|
||||||
{"/hom", "{uri} starts_with /home", false},
|
|
||||||
{"/hom", "{uri} starts_with /h", true},
|
|
||||||
{"/home/.hiddenfile", `{uri} match \/\.(.*)`, true},
|
|
||||||
{"/home/.hiddendir/afile", `{uri} match \/\.(.*)`, true},
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, test := range replaceTests {
|
|
||||||
r, err := http.NewRequest("GET", test.url, nil)
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
str := strings.Fields(test.condition)
|
|
||||||
ifCond, err := NewIf(str[0], str[1], str[2])
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
isTrue := ifCond.True(r)
|
|
||||||
if isTrue != test.isTrue {
|
|
||||||
t.Errorf("Test %v: expected %v found %v", i, test.isTrue, isTrue)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -97,15 +97,15 @@ type ComplexRule struct {
|
||||||
// Extensions to filter by
|
// Extensions to filter by
|
||||||
Exts []string
|
Exts []string
|
||||||
|
|
||||||
// Rewrite conditions
|
// Request matcher
|
||||||
Ifs []If
|
httpserver.RequestMatcher
|
||||||
|
|
||||||
*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, status int, ext []string, ifs []If) (*ComplexRule, error) {
|
func NewComplexRule(base, pattern, to string, status int, ext []string, m httpserver.RequestMatcher) (*ComplexRule, error) {
|
||||||
// validate regexp if present
|
// validate regexp if present
|
||||||
var r *regexp.Regexp
|
var r *regexp.Regexp
|
||||||
if pattern != "" {
|
if pattern != "" {
|
||||||
|
@ -131,7 +131,7 @@ func NewComplexRule(base, pattern, to string, status int, ext []string, ifs []If
|
||||||
To: to,
|
To: to,
|
||||||
Status: status,
|
Status: status,
|
||||||
Exts: ext,
|
Exts: ext,
|
||||||
Ifs: ifs,
|
RequestMatcher: m,
|
||||||
Regexp: r,
|
Regexp: r,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
@ -182,12 +182,10 @@ func (r *ComplexRule) Rewrite(fs http.FileSystem, req *http.Request) (re Result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// validate rewrite conditions
|
// validate if conditions
|
||||||
for _, i := range r.Ifs {
|
if !r.RequestMatcher.Match(req) {
|
||||||
if !i.True(req) {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// if status is present, stop rewrite and return it.
|
// if status is present, stop rewrite and return it.
|
||||||
if r.Status != 0 {
|
if r.Status != 0 {
|
||||||
|
@ -230,6 +228,10 @@ func (r *ComplexRule) matchExt(rPath string) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func newReplacer(r *http.Request) httpserver.Replacer {
|
||||||
|
return httpserver.NewReplacer(r, nil, "")
|
||||||
|
}
|
||||||
|
|
||||||
// When a rewrite is performed, this header is added to the request
|
// When a rewrite is performed, this header is added to the request
|
||||||
// and is for internal use only, specifically the fastcgi middleware.
|
// and is for internal use only, specifically the fastcgi middleware.
|
||||||
// It contains the original request URI before the rewrite.
|
// It contains the original request URI before the rewrite.
|
||||||
|
|
|
@ -42,7 +42,7 @@ func TestRewrite(t *testing.T) {
|
||||||
if s := strings.Split(regexpRule[3], "|"); len(s) > 1 {
|
if s := strings.Split(regexpRule[3], "|"); len(s) > 1 {
|
||||||
ext = s[:len(s)-1]
|
ext = s[:len(s)-1]
|
||||||
}
|
}
|
||||||
rule, err := NewComplexRule(regexpRule[0], regexpRule[1], regexpRule[2], 0, ext, nil)
|
rule, err := NewComplexRule(regexpRule[0], regexpRule[1], regexpRule[2], 0, ext, httpserver.IfMatcher{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -127,7 +127,7 @@ func TestRewrite(t *testing.T) {
|
||||||
|
|
||||||
for i, s := range statusTests {
|
for i, s := range statusTests {
|
||||||
urlPath := fmt.Sprintf("/status%d", i)
|
urlPath := fmt.Sprintf("/status%d", i)
|
||||||
rule, err := NewComplexRule(s.base, s.regexp, s.to, s.status, nil, nil)
|
rule, err := NewComplexRule(s.base, s.regexp, s.to, s.status, nil, httpserver.IfMatcher{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Test %d: No error expected for rule but found %v", i, err)
|
t.Fatalf("Test %d: No error expected for rule but found %v", i, err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,13 +50,19 @@ func rewriteParse(c *caddy.Controller) ([]Rule, error) {
|
||||||
|
|
||||||
args := c.RemainingArgs()
|
args := c.RemainingArgs()
|
||||||
|
|
||||||
var ifs []If
|
var matcher httpserver.RequestMatcher
|
||||||
|
|
||||||
switch len(args) {
|
switch len(args) {
|
||||||
case 1:
|
case 1:
|
||||||
base = args[0]
|
base = args[0]
|
||||||
fallthrough
|
fallthrough
|
||||||
case 0:
|
case 0:
|
||||||
|
// Integrate request matcher for 'if' conditions.
|
||||||
|
matcher, err = httpserver.SetupIfMatcher(c.Dispenser)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
block:
|
||||||
for c.NextBlock() {
|
for c.NextBlock() {
|
||||||
switch c.Val() {
|
switch c.Val() {
|
||||||
case "r", "regexp":
|
case "r", "regexp":
|
||||||
|
@ -76,16 +82,6 @@ func rewriteParse(c *caddy.Controller) ([]Rule, error) {
|
||||||
return nil, c.ArgErr()
|
return nil, c.ArgErr()
|
||||||
}
|
}
|
||||||
ext = args1
|
ext = args1
|
||||||
case "if":
|
|
||||||
args1 := c.RemainingArgs()
|
|
||||||
if len(args1) != 3 {
|
|
||||||
return nil, c.ArgErr()
|
|
||||||
}
|
|
||||||
ifCond, err := NewIf(args1[0], args1[1], args1[2])
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
ifs = append(ifs, ifCond)
|
|
||||||
case "status":
|
case "status":
|
||||||
if !c.NextArg() {
|
if !c.NextArg() {
|
||||||
return nil, c.ArgErr()
|
return nil, c.ArgErr()
|
||||||
|
@ -95,6 +91,9 @@ func rewriteParse(c *caddy.Controller) ([]Rule, error) {
|
||||||
return nil, c.Err("status must be 2xx or 4xx")
|
return nil, c.Err("status must be 2xx or 4xx")
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
|
if httpserver.IfMatcherKeyword(c.Val()) {
|
||||||
|
continue block
|
||||||
|
}
|
||||||
return nil, c.ArgErr()
|
return nil, c.ArgErr()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -102,7 +101,7 @@ func rewriteParse(c *caddy.Controller) ([]Rule, error) {
|
||||||
if to == "" && status == 0 {
|
if to == "" && status == 0 {
|
||||||
return nil, c.ArgErr()
|
return nil, c.ArgErr()
|
||||||
}
|
}
|
||||||
if rule, err = NewComplexRule(base, pattern, to, status, ext, ifs); err != nil {
|
if rule, err = NewComplexRule(base, pattern, to, status, ext, matcher); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
regexpRules = append(regexpRules, rule)
|
regexpRules = append(regexpRules, rule)
|
||||||
|
|
|
@ -131,12 +131,6 @@ func TestRewriteParse(t *testing.T) {
|
||||||
{`rewrite /`, true, []Rule{
|
{`rewrite /`, true, []Rule{
|
||||||
&ComplexRule{},
|
&ComplexRule{},
|
||||||
}},
|
}},
|
||||||
{`rewrite {
|
|
||||||
to /to
|
|
||||||
if {path} is a
|
|
||||||
}`, false, []Rule{
|
|
||||||
&ComplexRule{Base: "/", To: "/to", Ifs: []If{{A: "{path}", Operator: "is", B: "a"}}},
|
|
||||||
}},
|
|
||||||
{`rewrite {
|
{`rewrite {
|
||||||
status 500
|
status 500
|
||||||
}`, true, []Rule{
|
}`, true, []Rule{
|
||||||
|
@ -229,11 +223,6 @@ func TestRewriteParse(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if fmt.Sprint(actualRule.Ifs) != fmt.Sprint(expectedRule.Ifs) {
|
|
||||||
t.Errorf("Test %d, rule %d: Expected Pattern=%s, got %s",
|
|
||||||
i, j, fmt.Sprint(expectedRule.Ifs), fmt.Sprint(actualRule.Ifs))
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue