aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Alexandre Alapetite <alexandre@alapetite.fr> 2024-10-22 16:00:07 +0200
committerGravatar GitHub <noreply@github.com> 2024-10-22 16:00:07 +0200
commit60dd22d3b3916f5113954fc1472b1658c3c4245f (patch)
tree1c895a8a6363d5dfb3effb452724d1c0f22fa4ad
parentda4b85624fc4426b94b3ee98e9e11cbb7e9af7f1 (diff)
Allow regex parentheses (#6926)
* Allow regex parentheses While waiting for a new better search parser, auto-escape parentheses in regex expressions to allow them like in `/^(ab|cd)/` * Allow escaped parenthesis in regex * A couple more tests
-rw-r--r--app/Models/BooleanSearch.php15
-rw-r--r--app/Models/Search.php1
-rw-r--r--tests/app/Models/SearchTest.php15
3 files changed, 31 insertions, 0 deletions
diff --git a/app/Models/BooleanSearch.php b/app/Models/BooleanSearch.php
index 10c2321fb..749dbdfa5 100644
--- a/app/Models/BooleanSearch.php
+++ b/app/Models/BooleanSearch.php
@@ -37,6 +37,7 @@ class FreshRSS_BooleanSearch {
if ($level === 0) {
$input = $this->parseUserQueryNames($input, $allowUserQueries);
$input = $this->parseUserQueryIds($input, $allowUserQueries);
+ $input = self::escapeRegexParentheses($input);
$input = trim($input);
}
@@ -133,6 +134,20 @@ class FreshRSS_BooleanSearch {
}
/**
+ * Temporarily escape parentheses used in regex expressions.
+ */
+ public static function escapeRegexParentheses(string $input): string {
+ return preg_replace_callback('#(?<=[\\s(:!-]|^)(?<![\\\\])/.*?(?<!\\\\)/[im]*#',
+ fn(array $matches): string => str_replace(['(', ')'], ['\\u0028', '\\u0029'], $matches[0]),
+ $input
+ ) ?? '';
+ }
+
+ public static function unescapeRegexParentheses(string $input): string {
+ return str_replace(['\\u0028', '\\u0029'], ['(', ')'], $input);
+ }
+
+ /**
* Example: 'ab cd OR ef OR "gh ij"' becomes '(ab cd) OR (ef) OR ("gh ij")'
*/
public static function addOrParentheses(string $input): string {
diff --git a/app/Models/Search.php b/app/Models/Search.php
index 0e2f5da6f..45fa742be 100644
--- a/app/Models/Search.php
+++ b/app/Models/Search.php
@@ -94,6 +94,7 @@ class FreshRSS_Search {
public function __construct(string $input) {
$input = self::cleanSearch($input);
$input = self::unescape($input);
+ $input = FreshRSS_BooleanSearch::unescapeRegexParentheses($input);
$this->raw_input = $input;
$input = $this->parseNotEntryIds($input);
diff --git a/tests/app/Models/SearchTest.php b/tests/app/Models/SearchTest.php
index 3092b43ca..403c2c98e 100644
--- a/tests/app/Models/SearchTest.php
+++ b/tests/app/Models/SearchTest.php
@@ -454,6 +454,21 @@ class SearchTest extends PHPUnit\Framework\TestCase {
'AND ((e.title LIKE ? AND e.title NOT LIKE ? AND e.content NOT LIKE ? AND e.title NOT LIKE ? AND e.content NOT LIKE ? ))',
['%ab%', '%ab%', '%cd%', '%cd%', '%ef%', '%ef%', '%gh%', '%ij%', '%ij%', '%kl%', '%kl%']
],
+ [
+ '/^(ab|cd) [(] \\) (ef|gh)/',
+ '((e.title ~ ? OR e.content ~ ?) )',
+ ['^(ab|cd) [(] \\) (ef|gh)', '^(ab|cd) [(] \\) (ef|gh)']
+ ],
+ [
+ '!/^(ab|cd)/',
+ '(NOT e.title ~ ? AND NOT e.content ~ ? )',
+ ['^(ab|cd)', '^(ab|cd)']
+ ],
+ [
+ 'intitle:/^(ab|cd)/',
+ '(e.title ~ ? )',
+ ['^(ab|cd)']
+ ],
];
}