diff --git a/engine/filters.go b/engine/filters.go index 310ecc86a..8ae23eefe 100644 --- a/engine/filters.go +++ b/engine/filters.go @@ -19,6 +19,7 @@ import ( "fmt" "net" "reflect" + "regexp" "strings" "time" @@ -216,19 +217,20 @@ var supportedFiltersType utils.StringSet = utils.NewStringSet([]string{ utils.MetaCronExp, utils.MetaRSR, utils.MetaEmpty, utils.MetaExists, utils.MetaLessThan, utils.MetaLessOrEqual, utils.MetaGreaterThan, utils.MetaGreaterOrEqual, utils.MetaEqual, - utils.MetaNotEqual, utils.MetaIPNet, utils.MetaAPIBan, - utils.MetaActivationInterval}) + utils.MetaIPNet, utils.MetaAPIBan, utils.MetaActivationInterval, + utils.MetaRegex}) var needsFieldName utils.StringSet = utils.NewStringSet([]string{ utils.MetaString, utils.MetaPrefix, utils.MetaSuffix, utils.MetaCronExp, utils.MetaRSR, utils.MetaLessThan, utils.MetaEmpty, utils.MetaExists, utils.MetaLessOrEqual, utils.MetaGreaterThan, - utils.MetaGreaterOrEqual, utils.MetaEqual, utils.MetaNotEqual, utils.MetaIPNet, utils.MetaAPIBan, - utils.MetaActivationInterval}) + utils.MetaGreaterOrEqual, utils.MetaEqual, utils.MetaIPNet, utils.MetaAPIBan, + utils.MetaActivationInterval, + utils.MetaRegex}) var needsValues utils.StringSet = utils.NewStringSet([]string{utils.MetaString, utils.MetaPrefix, utils.MetaSuffix, utils.MetaCronExp, utils.MetaRSR, utils.MetaLessThan, utils.MetaLessOrEqual, utils.MetaGreaterThan, utils.MetaGreaterOrEqual, - utils.MetaEqual, utils.MetaNotEqual, utils.MetaIPNet, utils.MetaAPIBan, - utils.MetaActivationInterval}) + utils.MetaEqual, utils.MetaIPNet, utils.MetaAPIBan, utils.MetaActivationInterval, + utils.MetaRegex}) // NewFilterRule returns a new filter func NewFilterRule(rfType, fieldName string, vals []string) (*FilterRule, error) { @@ -262,55 +264,47 @@ func NewFilterRule(rfType, fieldName string, vals []string) (*FilterRule, error) // FilterRule filters requests coming into various places // Pass rule: default negative, one matching rule should pass the filter type FilterRule struct { - Type string // Filter type (*string, *rsr_filters, *stats, *lt, *lte, *gt, *gte) - Element string // Name of the field providing us the Values to check (used in case of some ) - Values []string // Filter definition - rsrValues config.RSRParsers // Cache here the - rsrElement *config.RSRParser // Cache here the - rsrFilters utils.RSRFilters // Cache here the RSRFilter Values - negative *bool + Type string // Filter type (*string, *timing, *rsr_filters, *stats, *lt, *lte, *gt, *gte) + Element string // Name of the field providing us the Values to check (used in case of some ) + Values []string // Filter definition + rsrValues config.RSRParsers // Cache here the + rsrElement *config.RSRParser // Cache here the + rsrFilters utils.RSRFilters // Cache here the RSRFilter Values + regexValues []*regexp.Regexp + negative *bool } // CompileValues compiles RSR fields func (fltr *FilterRule) CompileValues() (err error) { switch fltr.Type { + case utils.MetaRegex, utils.MetaNotRegex: + fltr.regexValues = make([]*regexp.Regexp, len(fltr.Values)) + for i, val := range fltr.Values { + if fltr.regexValues[i], err = regexp.Compile(val); err != nil { + return + } + } case utils.MetaRSR, utils.MetaNotRSR: if fltr.rsrFilters, err = utils.ParseRSRFiltersFromSlice(fltr.Values); err != nil { return } - if fltr.rsrElement, err = config.NewRSRParser(fltr.Element); err != nil { - return - } else if fltr.rsrElement == nil { - return fmt.Errorf("emtpy RSRParser in rule: <%s>", fltr.Element) - } case utils.MetaExists, utils.MetaNotExists, utils.MetaEmpty, utils.MetaNotEmpty: // only the element is builded - if fltr.rsrElement, err = config.NewRSRParser(fltr.Element); err != nil { - return - } else if fltr.rsrElement == nil { - return fmt.Errorf("emtpy RSRParser in rule: <%s>", fltr.Element) - } case utils.MetaActivationInterval, utils.MetaNotActivationInterval: - if fltr.rsrElement, err = config.NewRSRParser(fltr.Element); err != nil { - return - } else if fltr.rsrElement == nil { - return fmt.Errorf("emtpy RSRParser in rule: <%s>", fltr.Element) - } - for _, strVal := range fltr.Values { - rsrPrsr, err := config.NewRSRParser(strVal) - if err != nil { - return err + fltr.rsrValues = make(config.RSRParsers, len(fltr.Values)) + for i, strVal := range fltr.Values { + if fltr.rsrValues[i], err = config.NewRSRParser(strVal); err != nil { + return } - fltr.rsrValues = append(fltr.rsrValues, rsrPrsr) } default: if fltr.rsrValues, err = config.NewRSRParsersFromSlice(fltr.Values); err != nil { return } - if fltr.rsrElement, err = config.NewRSRParser(fltr.Element); err != nil { - return - } else if fltr.rsrElement == nil { - return fmt.Errorf("emtpy RSRParser in rule: <%s>", fltr.Element) - } + } + if fltr.rsrElement, err = config.NewRSRParser(fltr.Element); err != nil { + return + } else if fltr.rsrElement == nil { + return fmt.Errorf("emtpy RSRParser in rule: <%s>", fltr.Element) } return } @@ -346,6 +340,8 @@ func (fltr *FilterRule) Pass(ctx *context.Context, dDP utils.DataProvider) (resu result, err = fltr.passAPIBan(ctx, dDP) case utils.MetaActivationInterval, utils.MetaNotActivationInterval: result, err = fltr.passActivationInterval(dDP) + case utils.MetaRegex, utils.MetaNotRegex: + result, err = fltr.passRegex(dDP) default: err = utils.ErrPrefixNotErrNotImplemented(fltr.Type) } @@ -677,3 +673,19 @@ func verifyInlineFilterS(fltrs []string) (err error) { } return } + +func (fltr *FilterRule) passRegex(dDP utils.DataProvider) (bool, error) { + strVal, err := fltr.rsrElement.ParseDataProvider(dDP) + if err != nil { + if err == utils.ErrNotFound { + return false, nil + } + return false, err + } + for _, val := range fltr.regexValues { + if val.MatchString(strVal) { + return true, nil + } + } + return false, nil +} diff --git a/engine/filters_test.go b/engine/filters_test.go index 1eb60967f..a2914d139 100644 --- a/engine/filters_test.go +++ b/engine/filters_test.go @@ -291,6 +291,18 @@ func TestFilterNewRequestFilter(t *testing.T) { if !reflect.DeepEqual(erf, rf) { t.Errorf("Expecting: %+v, received: %+v", erf, rf) } + + rf, err = NewFilterRule(utils.MetaRegex, "~MetaRegex", []string{"Regex"}) + if err != nil { + t.Errorf("Error: %+v", err) + } + erf = &FilterRule{Type: utils.MetaRegex, Element: "~MetaRegex", Values: []string{"Regex"}, negative: utils.BoolPointer(false)} + if err = erf.CompileValues(); err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(erf, rf) { + t.Errorf("Expecting: %+v, received: %+v", erf, rf) + } } func TestInlineFilterPassFiltersForEvent(t *testing.T) { @@ -477,6 +489,42 @@ func TestInlineFilterPassFiltersForEvent(t *testing.T) { } else if !pass { t.Errorf("For NewKey expecting: %+v, received: %+v", true, pass) } + + failEvent = map[string]interface{}{ + "Account": "1001", + } + passEvent = map[string]interface{}{ + "Account": "1007", + } + fEv = utils.MapStorage{} + fEv.Set([]string{utils.MetaReq}, failEvent) + if pass, err := filterS.Pass(context.TODO(), "cgrates.org", + []string{"*regex:~*req.Account:^1007:error"}, fEv); err != nil { + t.Error(err) + } else if pass { + t.Errorf("Expecting: %+v, received: %+v", false, pass) + } + if pass, err := filterS.Pass(context.TODO(), "cgrates.org", + []string{"*regex:~*req.Account:\\d{3}7"}, fEv); err != nil { + t.Errorf(err.Error()) + } else if pass { + t.Errorf("Expecting: %+v, received: %+v", false, pass) + } + pEv = utils.MapStorage{} + pEv.Set([]string{utils.MetaReq}, passEvent) + if pass, err := filterS.Pass(context.TODO(), "cgrates.org", + []string{"*regex:~*req.Account:\\d{3}7"}, pEv); err != nil { + t.Errorf(err.Error()) + } else if !pass { + t.Errorf("Expecting: %+v, received: %+v", true, pass) + } + //not + if pass, err := filterS.Pass(context.TODO(), "cgrates.org", + []string{"*notregex:~*req.Account:\\d{3}7"}, pEv); err != nil { + t.Errorf(err.Error()) + } else if pass { + t.Errorf("Expecting: %+v, received: %+v", false, pass) + } } func TestPassFiltersForEventWithEmptyFilter(t *testing.T) { diff --git a/packages/debian/changelog b/packages/debian/changelog index 4b7fe670c..9c99a2b79 100644 --- a/packages/debian/changelog +++ b/packages/debian/changelog @@ -8,7 +8,8 @@ cgrates (1.0) UNRELEASED; urgency=medium * [DispatcherS] Added any_subsyste config to control the matching dispatchers * [StatS] AverageCallCost and TotalCallCost now returns error for negative Cost field * [SessionS] The sessions are no longer terminated on shutdown if the replication_conns are set - + * [FilterS] Added *regex filter + -- DanB Thu, 4 May 2021 12:05:00 +0200 cgrates (0.11.0) UNRELEASED; urgency=medium diff --git a/utils/consts.go b/utils/consts.go index 6bb0db674..cb399388c 100644 --- a/utils/consts.go +++ b/utils/consts.go @@ -1052,6 +1052,7 @@ const ( MetaIPNet = "*ipnet" MetaAPIBan = "*apiban" MetaActivationInterval = "*ai" + MetaRegex = "*regex" MetaNotString = "*notstring" MetaNotPrefix = "*notprefix" @@ -1066,6 +1067,7 @@ const ( MetaNotIPNet = "*notipnet" MetaNotAPIBan = "*notapiban" MetaNotActivationInterval = "*notai" + MetaNotRegex = "*notregex" MetaEC = "*ec" )