diff --git a/internal/actions/ctl.go b/internal/actions/ctl.go index 9bb0da00a..aef900428 100644 --- a/internal/actions/ctl.go +++ b/internal/actions/ctl.go @@ -6,6 +6,7 @@ package actions import ( "errors" "fmt" + "regexp" "strconv" "strings" @@ -105,11 +106,12 @@ type ctlFn struct { value string collection variables.RuleVariable colKey string + colKeyRx *regexp.Regexp } func (a *ctlFn) Init(_ plugintypes.RuleMetadata, data string) error { var err error - a.action, a.value, a.collection, a.colKey, err = parseCtl(data) + a.action, a.value, a.collection, a.colKey, a.colKeyRx, err = parseCtl(data) return err } @@ -375,10 +377,10 @@ func (a *ctlFn) Type() plugintypes.ActionType { return plugintypes.ActionTypeNondisruptive } -func parseCtl(data string) (ctlFunctionType, string, variables.RuleVariable, string, error) { +func parseCtl(data string) (ctlFunctionType, string, variables.RuleVariable, string, *regexp.Regexp, error) { action, ctlVal, ok := strings.Cut(data, "=") if !ok { - return ctlUnknown, "", 0, "", errors.New("invalid syntax") + return ctlUnknown, "", 0, "", nil, errors.New("invalid syntax") } value, col, ok := strings.Cut(ctlVal, ";") var colkey, colname string @@ -386,7 +388,14 @@ func parseCtl(data string) (ctlFunctionType, string, variables.RuleVariable, str colname, colkey, _ = strings.Cut(col, ":") } collection, _ := variables.Parse(strings.TrimSpace(colname)) - colkey = strings.ToLower(colkey) + var re *regexp.Regexp + if len(colkey) > 2 && colkey[0] == '/' && colkey[len(colkey)-1] == '/' { + var err error + re, err = regexp.Compile(colkey[1 : len(colkey)-1]) + if err != nil { + return ctlUnknown, "", 0x00, "", nil, err + } + } var act ctlFunctionType switch action { case "auditEngine": @@ -430,9 +439,12 @@ func parseCtl(data string) (ctlFunctionType, string, variables.RuleVariable, str case "debugLogLevel": act = ctlDebugLogLevel default: - return ctlUnknown, "", 0x00, "", fmt.Errorf("unknown ctl action %q", action) + return ctlUnknown, "", 0x00, "", nil, fmt.Errorf("unknown ctl action %q", action) + } + if re != nil { + return act, value, collection, strings.TrimSpace(colkey), re, nil } - return act, value, collection, strings.TrimSpace(colkey), nil + return act, value, collection, strings.TrimSpace(strings.ToLower(colkey)), nil, nil } func rangeToInts(rules []corazawaf.Rule, input string) ([]int, error) { diff --git a/internal/actions/ctl_test.go b/internal/actions/ctl_test.go index 252d3879b..25c778c8d 100644 --- a/internal/actions/ctl_test.go +++ b/internal/actions/ctl_test.go @@ -368,7 +368,7 @@ func TestCtl(t *testing.T) { func TestParseCtl(t *testing.T) { t.Run("invalid ctl", func(t *testing.T) { - ctl, _, _, _, err := parseCtl("invalid") + ctl, _, _, _, _, err := parseCtl("invalid") if err == nil { t.Errorf("expected error, got nil") } @@ -379,7 +379,7 @@ func TestParseCtl(t *testing.T) { }) t.Run("malformed ctl", func(t *testing.T) { - ctl, _, _, _, err := parseCtl("unknown=") + ctl, _, _, _, _, err := parseCtl("unknown=") if err == nil { t.Errorf("expected error, got nil") } @@ -417,10 +417,13 @@ func TestParseCtl(t *testing.T) { for _, tCase := range tCases { testName, _, _ := strings.Cut(tCase.input, "=") t.Run(testName, func(t *testing.T) { - action, value, collection, colKey, err := parseCtl(tCase.input) + action, value, collection, colKey, rx, err := parseCtl(tCase.input) if err != nil { t.Fatalf("unexpected error: %s", err.Error()) } + if rx != nil { + t.Error("unexpected regex, want nil regex") + } if action != tCase.expectAction { t.Errorf("unexpected action, want: %d, have: %d", tCase.expectAction, action) } @@ -435,8 +438,24 @@ func TestParseCtl(t *testing.T) { } }) } +} +func TestCtlRegexColname(t *testing.T) { + _, _, _, _, rx, err := parseCtl("ruleRemoveTargetById=2;ARGS:/user/") + if err != nil { + t.Fatalf("unexpected error: %s", err.Error()) + } + if rx == nil { + t.Error("unexpected nil regex") + } + if rx.String() != "user" { + t.Errorf("unexpected regex, want: user, have: %s", rx.String()) + } + if !rx.MatchString("user") { + t.Error("unexpected match") + } } + func TestCtlParseRange(t *testing.T) { rules := []corazawaf.Rule{ {