diff --git a/engine/filters.go b/engine/filters.go index 116a34c3e..8630883f3 100644 --- a/engine/filters.go +++ b/engine/filters.go @@ -19,6 +19,7 @@ import ( "fmt" "net" "reflect" + "regexp" "strings" "time" @@ -224,19 +225,20 @@ var supportedFiltersType utils.StringSet = utils.NewStringSet([]string{ utils.MetaTimings, utils.MetaRSR, utils.MetaDestinations, 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.MetaTimings, utils.MetaRSR, utils.MetaDestinations, 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.MetaTimings, utils.MetaRSR, utils.MetaDestinations, 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) { @@ -270,55 +272,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, *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 - 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 } @@ -356,6 +350,8 @@ func (fltr *FilterRule) Pass(dDP utils.DataProvider) (result bool, err error) { result, err = fltr.passAPIBan(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) } @@ -740,3 +736,19 @@ func CheckFilter(fltr *Filter) (err error) { } return nil } + +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 0f3c82b5c..ed863d7bb 100644 --- a/engine/filters_test.go +++ b/engine/filters_test.go @@ -83,6 +83,61 @@ func TestFilterPassString(t *testing.T) { } } +func TestFilterPassRegex(t *testing.T) { + cd := &CallDescriptor{ + Category: "call", + Tenant: "cgrates.org", + Subject: "dan", + Destination: "+4986517174963", + TimeStart: time.Date(2013, time.October, 7, 14, 50, 0, 0, time.UTC), + TimeEnd: time.Date(2013, time.October, 7, 14, 52, 12, 0, time.UTC), + DurationIndex: 132 * time.Second, + ExtraFields: map[string]string{"navigation": "off"}, + } + rf := &FilterRule{Type: utils.MetaRegex, + Element: "~Category", Values: []string{"^call$"}} + if err := rf.CompileValues(); err != nil { + t.Fatal(err) + } + if passes, err := rf.passRegex(cd); err != nil { + t.Error(err) + } else if !passes { + t.Error("Not passes filter") + } + + rf = &FilterRule{Type: utils.MetaRegex, + Element: "~Category", Values: []string{"cal$"}} + if err := rf.CompileValues(); err != nil { + t.Fatal(err) + } + if passes, err := rf.passRegex(cd); err != nil { + t.Error(err) + } else if passes { + t.Error("Filter passes") + } + //not + rf = &FilterRule{Type: utils.MetaNotRegex, + Element: "~Category", Values: []string{"^call$"}} + if err := rf.CompileValues(); err != nil { + t.Fatal(err) + } + if passes, err := rf.Pass(cd); err != nil { + t.Error(err) + } else if passes { + t.Error("Filter passes") + } + rf = &FilterRule{Type: utils.MetaNotRegex, + Element: "~Category", Values: []string{"cal$"}} + if err := rf.CompileValues(); err != nil { + t.Fatal(err) + } + if passes, err := rf.Pass(cd); err != nil { + t.Error(err) + } else if !passes { + t.Error("Not passes filter") + } +} + func TestFilterPassEmpty(t *testing.T) { cd := &CallDescriptor{ Category: "", @@ -678,6 +733,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) { @@ -865,6 +932,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("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("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("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("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 ab084f278..c5a023691 100644 --- a/packages/debian/changelog +++ b/packages/debian/changelog @@ -164,7 +164,7 @@ cgrates (0.11.0~dev) 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 Wed, 19 Feb 2020 13:25:52 +0200 cgrates (0.10.0) UNRELEASED; urgency=medium diff --git a/utils/consts.go b/utils/consts.go index 7bf2f9fb6..5644096b8 100644 --- a/utils/consts.go +++ b/utils/consts.go @@ -1164,6 +1164,7 @@ const ( MetaIPNet = "*ipnet" MetaAPIBan = "*apiban" MetaActivationInterval = "*ai" + MetaRegex = "*regex" MetaNotString = "*notstring" MetaNotPrefix = "*notprefix" @@ -1179,6 +1180,7 @@ const ( MetaNotIPNet = "*notipnet" MetaNotAPIBan = "*notapiban" MetaNotActivationInterval = "*notai" + MetaNotRegex = "*notregex" MetaEC = "*ec" )