From 540fd925ee02f6c0fe58d5ebac9ee27f8407bc25 Mon Sep 17 00:00:00 2001 From: Kirill Solomko Date: Fri, 26 Jun 2026 18:44:23 +0200 Subject: [PATCH] MT#65092 address query param string pattern match in certain endpoints * new function Generic::Utils::parse_query_string($str) that performs the following: - escapes unescaped '%' and '_' occurrences when needed (as they are reserved internally for the "like" operation - converts all unescaped '*' and '?' chars to their "sql like op" '%' and '_' counterparts. * change endpoints that used escape_search_string_pattern() to use parse_query_string() instead, to avoid: a) overescaping b) automatic string wrapping with '*' that prevents strict match all sql builder queries that use $q after parse_query_string() use returned $op ('like', '=') as the operator, to easily set strict or like match. Change-Id: I961b5d032036f13a7460d91f8c197538ffba1997 (cherry picked from commit 8089861097f4b07b4161db16378ebabeef3f5654) --- lib/NGCP/Panel/Controller/API/Domains.pm | 4 +-- .../Panel/Controller/API/FaxserverSettings.pm | 6 ++-- lib/NGCP/Panel/Controller/API/PbxDevices.pm | 8 ++--- lib/NGCP/Panel/Controller/API/PbxUsers.pm | 12 ++++---- .../Panel/Controller/API/ProfilePackages.pm | 8 ++--- lib/NGCP/Panel/Controller/API/Subscribers.pm | 29 +++++++++---------- .../Controller/API/VoicemailGreetings.pm | 5 ++-- lib/NGCP/Panel/Controller/API/Voicemails.pm | 5 ++-- lib/NGCP/Panel/Utils/Generic.pm | 22 ++++++++++++-- 9 files changed, 56 insertions(+), 43 deletions(-) diff --git a/lib/NGCP/Panel/Controller/API/Domains.pm b/lib/NGCP/Panel/Controller/API/Domains.pm index c1db0d1291..b8f94cbd83 100644 --- a/lib/NGCP/Panel/Controller/API/Domains.pm +++ b/lib/NGCP/Panel/Controller/API/Domains.pm @@ -31,8 +31,8 @@ sub query_params { description => 'Filter for domains matching the given pattern', query => { first => sub { - my ($q,$is_pattern) = escape_search_string_pattern(shift); - { domain => { like => $q } }; + my ($q, $op) = parse_search_string(shift); + { domain => { $op => $q } }; }, second => sub { }, }, diff --git a/lib/NGCP/Panel/Controller/API/FaxserverSettings.pm b/lib/NGCP/Panel/Controller/API/FaxserverSettings.pm index 19a0e265ad..00dd1bd325 100644 --- a/lib/NGCP/Panel/Controller/API/FaxserverSettings.pm +++ b/lib/NGCP/Panel/Controller/API/FaxserverSettings.pm @@ -25,10 +25,10 @@ sub query_params { description => 'Filter for items (subscribers) where name or password field match given pattern', query => { first => sub { - my ($q,$is_pattern) = escape_search_string_pattern(shift); + my ($q, $op) = parse_search_string(shift); return { '-or' => [ - { 'voip_fax_preference.name' => { like => $q } }, - { 'voip_fax_preference.password' => { like => $q } }, + { 'voip_fax_preference.name' => { $op => $q } }, + { 'voip_fax_preference.password' => { $op => $q } }, ] }; }, second => sub { diff --git a/lib/NGCP/Panel/Controller/API/PbxDevices.pm b/lib/NGCP/Panel/Controller/API/PbxDevices.pm index 7506fb44d2..415ca2e8cd 100644 --- a/lib/NGCP/Panel/Controller/API/PbxDevices.pm +++ b/lib/NGCP/Panel/Controller/API/PbxDevices.pm @@ -57,8 +57,8 @@ sub query_params { description => 'Search for PBX devices matching a subscriber\'s extension pattern', query => { first => sub { - my ($q,$is_pattern) = escape_search_string_pattern(shift,0,1,1); - { 'provisioning_voip_subscriber.pbx_extension' => { like => $q } }; + my ($q, $op) = parse_search_string(shift); + { 'provisioning_voip_subscriber.pbx_extension' => { $op => $q } }; }, second => sub { @@ -71,10 +71,10 @@ sub query_params { description => 'Search for PBX devices matching a subscriber\'s display name pattern', query => { first => sub { - my ($q,$is_pattern) = escape_search_string_pattern(shift); + my ($q, $op) = parse_search_string(shift); { 'attribute.attribute' => 'display_name', - 'voip_usr_preferences.value' => { like => $q } + 'voip_usr_preferences.value' => { $op => $q } }; }, diff --git a/lib/NGCP/Panel/Controller/API/PbxUsers.pm b/lib/NGCP/Panel/Controller/API/PbxUsers.pm index b0625929cf..c3e6dfd56c 100644 --- a/lib/NGCP/Panel/Controller/API/PbxUsers.pm +++ b/lib/NGCP/Panel/Controller/API/PbxUsers.pm @@ -27,8 +27,8 @@ sub query_params { description => 'Filter for subscribers of contracts with a specific primary number pattern', query => { first => sub { - my ($q,$is_pattern) = escape_search_string_pattern(shift,0,1,1); - { \['concat(primary_number.cc, primary_number.ac, primary_number.sn) like ?', $q ] }; + my ($q, $op) = parse_search_string(shift); + { \["concat(primary_number.cc, primary_number.ac, primary_number.sn) $op ?", $q ] }; }, second => sub { @@ -41,8 +41,8 @@ sub query_params { description => 'Filter for subscribers of contracts with a specific PBX extension', query => { first => sub { - my ($q,$is_pattern) = escape_search_string_pattern(shift,0,1,1); - { 'provisioning_voip_subscriber.pbx_extension' => { like => $q } }; + my ($q, $op) = parse_search_string(shift); + { 'provisioning_voip_subscriber.pbx_extension' => { $op => $q } }; }, second => sub { @@ -55,10 +55,10 @@ sub query_params { description => 'Filter for subscribers of contracts with a specific display name', query => { first => sub { - my ($q,$is_pattern) = escape_search_string_pattern(shift); + my ($q, $op) = parse_search_string(shift); { 'attribute.attribute' => 'display_name', - 'voip_usr_preferences.value' => { like => $q } + 'voip_usr_preferences.value' => { $op => $q } }; }, diff --git a/lib/NGCP/Panel/Controller/API/ProfilePackages.pm b/lib/NGCP/Panel/Controller/API/ProfilePackages.pm index 8f0a9b00af..bbaa39d8c3 100644 --- a/lib/NGCP/Panel/Controller/API/ProfilePackages.pm +++ b/lib/NGCP/Panel/Controller/API/ProfilePackages.pm @@ -43,8 +43,8 @@ sub query_params { description => 'Filter for profile packages containing a billing profile with specific name pattern', query => { first => sub { - my ($q,$is_pattern) = escape_search_string_pattern(shift); - { 'billing_profile.name' => { like => $q } }; + my ($q, $op) = parse_search_string(shift); + { 'billing_profile.name' => { $op => $q } }; }, second => sub { return { join => { profiles => 'billing_profile' }, @@ -57,8 +57,8 @@ sub query_params { description => 'Filter for profile packages containing a billing network with specific name pattern', query => { first => sub { - my ($q,$is_pattern) = escape_search_string_pattern(shift); - { 'billing_network.name' => { like => $q } }; + my ($q, $op) = parse_search_string(shift); + { 'billing_network.name' => { $op => $q } }; }, second => sub { return { join => { profiles => 'billing_network' }, diff --git a/lib/NGCP/Panel/Controller/API/Subscribers.pm b/lib/NGCP/Panel/Controller/API/Subscribers.pm index 1f203b1920..20d252e7ce 100644 --- a/lib/NGCP/Panel/Controller/API/Subscribers.pm +++ b/lib/NGCP/Panel/Controller/API/Subscribers.pm @@ -80,8 +80,8 @@ sub query_params { description => 'Filter for subscribers in specific domain pattern', query => { first => sub { - my ($q,$is_pattern) = escape_search_string_pattern(shift); - return { 'domain.domain' => { like => $q } }; + my ($q, $op) = parse_search_string(shift); + return { 'domain.domain' => { $op => $q } }; }, second => sub { my $q = shift; @@ -107,8 +107,8 @@ sub query_params { description => 'Filter for subscribers of a specific customer external_id pattern', query => { first => sub { - my ($q,$is_pattern) = escape_search_string_pattern(shift); - return { 'contract.external_id' => { like => $q } }; + my ($q, $op) = parse_search_string(shift); + return { 'contract.external_id' => { $op => $q } }; }, second => sub { return { join => 'contract' }; @@ -120,8 +120,8 @@ sub query_params { description => 'Filter for subscribers by subscriber\'s external_id pattern', query => { first => sub { - my ($q,$is_pattern) = escape_search_string_pattern(shift); - return { 'me.external_id' => { like => $q } }; + my ($q, $op) = parse_search_string(shift); + return { 'me.external_id' => { $op => $q } }; }, second => sub { return { }; @@ -184,9 +184,9 @@ sub query_params { description => 'Filter for subscribers who has specified alias pattern', query => { first => sub { - my ($q,$is_pattern) = escape_search_string_pattern(shift,0,1,1); + my ($q, $op) = parse_search_string(shift); { - 'voip_dbaliases.username' => { like => $q }, + 'voip_dbaliases.username' => { $op => $q }, }; }, second => sub { @@ -226,9 +226,8 @@ sub query_params { description => 'Filter for subscribers of contracts with a specific primary number pattern', query => { first => sub { - my ($q,$is_pattern) = escape_search_string_pattern(shift,0,1,1); - { \['concat(primary_number.cc, primary_number.ac, primary_number.sn) like ?', $q ] }; - + my ($q, $op) = parse_search_string(shift); + { \["concat(primary_number.cc, primary_number.ac, primary_number.sn) $op ?", $q ] }; }, second => sub { return { join => 'primary_number' } @@ -240,8 +239,8 @@ sub query_params { description => 'Filter for subscribers of contracts with a specific PBX extension', query => { first => sub { - my ($q,$is_pattern) = escape_search_string_pattern(shift,0,1,1); - { 'provisioning_voip_subscriber.pbx_extension' => { like => $q } }; + my ($q, $op) = parse_search_string(shift); + { 'provisioning_voip_subscriber.pbx_extension' => { $op => $q } }; }, second => sub { @@ -254,10 +253,10 @@ sub query_params { description => 'Filter for subscribers of contracts with a specific display name', query => { first => sub { - my ($q,$is_pattern) = escape_search_string_pattern(shift); + my ($q, $op) = parse_search_string(shift); { 'attribute.attribute' => 'display_name', - 'voip_usr_preferences.value' => { like => $q } + 'voip_usr_preferences.value' => { $op => $q } }; }, diff --git a/lib/NGCP/Panel/Controller/API/VoicemailGreetings.pm b/lib/NGCP/Panel/Controller/API/VoicemailGreetings.pm index 25383add27..d86edfe8ad 100644 --- a/lib/NGCP/Panel/Controller/API/VoicemailGreetings.pm +++ b/lib/NGCP/Panel/Controller/API/VoicemailGreetings.pm @@ -62,10 +62,9 @@ sub query_params { description => 'Filter for the greeting type', query => { first => sub { - my $q = shift; - ($q, my $is_pattern) = escape_search_string_pattern('/var/spool/asterisk/voicemail/*/' . $q); + my ($q, $op) = parse_search_string('/var/spool/asterisk/voicemail/*/' . shift); return { - 'me.dir' => { like => $q }, + 'me.dir' => { $op => $q }, }; }, second => sub {}, diff --git a/lib/NGCP/Panel/Controller/API/Voicemails.pm b/lib/NGCP/Panel/Controller/API/Voicemails.pm index d7137df0a5..8a98d2d096 100644 --- a/lib/NGCP/Panel/Controller/API/Voicemails.pm +++ b/lib/NGCP/Panel/Controller/API/Voicemails.pm @@ -45,10 +45,9 @@ sub query_params { description => 'Filter for voicemails in a specific folder (one of INBOX, Old, Friends, Family, Cust1 to Cust4)', query => { first => sub { - my $q = shift; - ($q, my $is_pattern) = escape_search_string_pattern('*/' . $q); + my ($str, $op) = parse_search_string('*/' . shift); # join is already done in get_item_rs - { 'me.dir' => { like => $q } }; + { 'me.dir' => { $op => $q } }; }, second => sub { }, }, diff --git a/lib/NGCP/Panel/Utils/Generic.pm b/lib/NGCP/Panel/Utils/Generic.pm index 8f0b7dc9a2..b6dc5ba6d4 100644 --- a/lib/NGCP/Panel/Utils/Generic.pm +++ b/lib/NGCP/Panel/Utils/Generic.pm @@ -8,9 +8,9 @@ use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS); $VERSION = 1.00; @ISA = qw(Exporter); @EXPORT = (); -@EXPORT_OK = qw(is_int is_integer is_decimal merge compare is_false is_true get_inflated_columns_all hash2obj mime_type_to_extension extension_to_mime_type array_to_map escape_js escape_uri trim escape_search_string_pattern run_module_method get_module_var); -%EXPORT_TAGS = ( DEFAULT => [qw(&is_int &is_integer &is_decimal &merge &compare &is_false &is_true &mime_type_to_extension &extension_to_mime_type &array_to_map &escape_js &escape_uri &trim &escape_search_string_pattern &run_module_method &get_module_var)], - all => [qw(&is_int &is_integer &is_decimal &merge &compare &is_false &is_true &get_inflated_columns_all &hash2obj &mime_type_to_extension &extension_to_mime_type &array_to_map &escape_js &escape_uri &trim &escape_search_string_pattern &run_module_method &get_module_var)]); +@EXPORT_OK = qw(is_int is_integer is_decimal merge compare is_false is_true get_inflated_columns_all hash2obj mime_type_to_extension extension_to_mime_type array_to_map escape_js escape_uri trim escape_search_string_pattern parse_search_string run_module_method get_module_var); +%EXPORT_TAGS = ( DEFAULT => [qw(&is_int &is_integer &is_decimal &merge &compare &is_false &is_true &mime_type_to_extension &extension_to_mime_type &array_to_map &escape_js &escape_uri &trim &escape_search_string_pattern &parse_search_string &run_module_method &get_module_var)], + all => [qw(&is_int &is_integer &is_decimal &merge &compare &is_false &is_true &get_inflated_columns_all &hash2obj &mime_type_to_extension &extension_to_mime_type &array_to_map &escape_js &escape_uri &trim &escape_search_string_pattern &parse_search_string &run_module_method &get_module_var)]); use Hash::Merge; use Data::Compare qw//; @@ -263,6 +263,22 @@ sub escape_search_string_pattern { } +sub parse_search_string { + my $str = shift; + + my $has_wildcards = $str =~ /(?