Skip to content

Commit

Permalink
Merge pull request #284 from richardmarshall/regex_literal_patterns
Browse files Browse the repository at this point in the history
Regex patterns must be literals
  • Loading branch information
ysugimoto authored Apr 1, 2024
2 parents f619e8a + beecb05 commit e8871e5
Show file tree
Hide file tree
Showing 9 changed files with 176 additions and 72 deletions.
83 changes: 28 additions & 55 deletions interpreter/function/builtin/regsub.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

22 changes: 17 additions & 5 deletions interpreter/function/builtin/regsub_test.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion interpreter/function/builtin/regsuball.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 15 additions & 4 deletions interpreter/function/builtin/regsuball_test.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions interpreter/operator/operator.go
Original file line number Diff line number Diff line change
Expand Up @@ -635,6 +635,11 @@ func Regex(ctx *context.Context, left, right value.Value) (value.Value, error) {
switch right.Type() {
case value.StringType:
rv := value.Unwrap[*value.String](right)
if !rv.IsLiteral() {
return value.Null, errors.WithStack(
fmt.Errorf("Right String type must be a literal"),
)
}
re, err := regexp.Compile(rv.Value)
if err != nil {
return value.Null, errors.WithStack(
Expand Down
45 changes: 43 additions & 2 deletions interpreter/operator/operator_not_regex_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"testing"
"time"

"github.com/google/go-cmp/cmp"
"github.com/ysugimoto/falco/ast"
"github.com/ysugimoto/falco/interpreter/context"
"github.com/ysugimoto/falco/interpreter/value"
Expand Down Expand Up @@ -125,8 +126,8 @@ func TestNotRegexOperator(t *testing.T) {
{left: &value.String{Value: "example"}, right: &value.Integer{Value: 10, Literal: true}, isError: true},
{left: &value.String{Value: "example"}, right: &value.Float{Value: 10.0}, isError: true},
{left: &value.String{Value: "example"}, right: &value.Float{Value: 10.0, Literal: true}, isError: true},
{left: &value.String{Value: "example"}, right: &value.String{Value: "amp"}, expect: false},
{left: &value.String{Value: "example"}, right: &value.String{Value: "^++a"}, isError: true}, // invalid regex syntax
{left: &value.String{Value: "example"}, right: &value.String{Value: "amp"}, isError: true}, // pattern must be literal
{left: &value.String{Value: "example"}, right: &value.String{Value: "^++a"}, isError: true},
{left: &value.String{Value: "example"}, right: &value.String{Value: "amp", Literal: true}, expect: false},
{left: &value.String{Value: "example"}, right: &value.String{Value: "^++a", Literal: true}, isError: true}, // invalid regex syntax
{left: &value.String{Value: "example"}, right: &value.RTime{Value: 100 * time.Second}, isError: true},
Expand Down Expand Up @@ -429,4 +430,44 @@ func TestNotRegexOperator(t *testing.T) {
}
}
})

t.Run("re.match.{N}", func(t *testing.T) {
tests := []struct {
left value.Value
right value.Value
expect map[string]*value.String
}{
{
left: &value.String{Value: "example"},
right: &value.String{Value: "amp", Literal: true},
expect: map[string]*value.String{
"0": {Value: "amp"},
},
},
{
left: &value.String{Value: "www.example.com"},
right: &value.String{Value: `^([^.]+)\.([^.]+)\.([^.]+)$`, Literal: true},
expect: map[string]*value.String{
"0": {Value: "www.example.com"},
"1": {Value: "www"},
"2": {Value: "example"},
"3": {Value: "com"},
},
},
}

for i, tt := range tests {
ctx := &context.Context{
RegexMatchedValues: make(map[string]*value.String),
}
_, err := NotRegex(ctx, tt.left, tt.right)
if err != nil {
t.Errorf("Index %d: Unexpected error %s", i, err)
continue
}
if diff := cmp.Diff(ctx.RegexMatchedValues, tt.expect); diff != "" {
t.Errorf("Index %d: unexpected re.group.{N} values, diff=%s", i, diff)
}
}
})
}
45 changes: 43 additions & 2 deletions interpreter/operator/operator_regex_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"testing"
"time"

"github.com/google/go-cmp/cmp"
"github.com/ysugimoto/falco/ast"
"github.com/ysugimoto/falco/interpreter/context"
"github.com/ysugimoto/falco/interpreter/value"
Expand Down Expand Up @@ -135,8 +136,8 @@ func TestRegexOperator(t *testing.T) {
{left: &value.String{Value: "example"}, right: &value.Integer{Value: 10, Literal: true}, isError: true},
{left: &value.String{Value: "example"}, right: &value.Float{Value: 10.0}, isError: true},
{left: &value.String{Value: "example"}, right: &value.Float{Value: 10.0, Literal: true}, isError: true},
{left: &value.String{Value: "example"}, right: &value.String{Value: "amp"}, expect: true},
{left: &value.String{Value: "example"}, right: &value.String{Value: "^++a"}, isError: true}, // invalid regex syntax
{left: &value.String{Value: "example"}, right: &value.String{Value: "amp"}, isError: true}, // pattern must be literal
{left: &value.String{Value: "example"}, right: &value.String{Value: "^++a"}, isError: true},
{left: &value.String{Value: "example"}, right: &value.String{Value: "amp", Literal: true}, expect: true},
{left: &value.String{Value: "example"}, right: &value.String{Value: "^++a", Literal: true}, isError: true}, // invalid regex syntax
{left: &value.String{Value: "example"}, right: &value.RTime{Value: 100 * time.Second}, isError: true},
Expand Down Expand Up @@ -442,4 +443,44 @@ func TestRegexOperator(t *testing.T) {
}
}
})

t.Run("re.match.{N}", func(t *testing.T) {
tests := []struct {
left value.Value
right value.Value
expect map[string]*value.String
}{
{
left: &value.String{Value: "example"},
right: &value.String{Value: "amp", Literal: true},
expect: map[string]*value.String{
"0": {Value: "amp"},
},
},
{
left: &value.String{Value: "www.example.com"},
right: &value.String{Value: `^([^.]+)\.([^.]+)\.([^.]+)$`, Literal: true},
expect: map[string]*value.String{
"0": {Value: "www.example.com"},
"1": {Value: "www"},
"2": {Value: "example"},
"3": {Value: "com"},
},
},
}

for i, tt := range tests {
ctx := &context.Context{
RegexMatchedValues: make(map[string]*value.String),
}
_, err := Regex(ctx, tt.left, tt.right)
if err != nil {
t.Errorf("Index %d: Unexpected error %s", i, err)
continue
}
if diff := cmp.Diff(ctx.RegexMatchedValues, tt.expect); diff != "" {
t.Errorf("Index %d: unexpected re.group.{N} values, diff=%s", i, diff)
}
}
})
}
11 changes: 11 additions & 0 deletions linter/function.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,5 +101,16 @@ func (l *Linter) lintFunctionArguments(fn *context.BuiltinFunction, calledFn fun
}
}

// Special cases
if calledFn.name == "regsub" || calledFn.name == "regsuball" {
if !isTypeLiteral(calledFn.arguments[1]) {
l.Error(&LintError{
Severity: ERROR,
Token: calledFn.arguments[1].GetMeta().Token,
Message: "Regex patterns must be string literals.",
})
}
}

return fn.Return
}
13 changes: 10 additions & 3 deletions linter/linter.go
Original file line number Diff line number Diff line change
Expand Up @@ -1659,10 +1659,17 @@ func (l *Linter) lintInfixExpression(exp *ast.InfixExpression, ctx *context.Cont
return types.BoolType
case "~", "!~":
// Regex operator could compare only STRING, IP or ACL type
if !expectType(left, types.StringType, types.IPType, types.AclType) {
if !expectType(left, types.StringType, types.IPType) {
l.Error(InvalidTypeExpression(exp.GetMeta(), left, types.StringType, types.IPType, types.AclType).Match(OPERATOR_CONDITIONAL))
} else if !expectType(right, types.StringType, types.IPType, types.AclType) {
l.Error(InvalidTypeExpression(exp.GetMeta(), right, types.StringType, types.IPType, types.AclType).Match(OPERATOR_CONDITIONAL))
} else if !expectType(right, types.StringType, types.AclType) {
l.Error(InvalidTypeExpression(exp.GetMeta(), right, types.StringType).Match(OPERATOR_CONDITIONAL))
}
if expectType(right, types.StringType) && !isLiteralExpression(exp.Right) {
l.Error(&LintError{
Severity: ERROR,
Token: exp.Right.GetMeta().Token,
Message: "Regex patterns must be string literals.",
})
}
// And, if right expression is STRING, regex must be valid
if v, ok := exp.Right.(*ast.String); ok {
Expand Down

0 comments on commit e8871e5

Please sign in to comment.