This is an automated email from the ASF dual-hosted git repository.
hsluoyz pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/casbin-cpp.git
The following commit(s) were added to refs/heads/master by this push:
new 2e99457 feat: skip regex for plain paths and cache compiled regexes
in keyMatch functions
2e99457 is described below
commit 2e99457c0bc7856e2b11e215c7adf39ca37b254b
Author: Yang Luo <[email protected]>
AuthorDate: Sat Oct 31 10:11:09 2026 +0800
feat: skip regex for plain paths and cache compiled regexes in keyMatch
functions
---
casbin/util/built_in_functions.cpp | 56 ++++++++++++++++++++++++++++++++++----
tests/built_in_functions_test.cpp | 10 +++++++
2 files changed, 61 insertions(+), 5 deletions(-)
diff --git a/casbin/util/built_in_functions.cpp
b/casbin/util/built_in_functions.cpp
index c844b5d..5cb61e5 100644
--- a/casbin/util/built_in_functions.cpp
+++ b/casbin/util/built_in_functions.cpp
@@ -21,6 +21,7 @@
#include <map>
#include <regex>
+#include <unordered_map>
#include "casbin/exception/illegal_argument_exception.h"
#include "casbin/ip_parser/parser/CIDR.h"
@@ -57,6 +58,40 @@ namespace {
std::string intermediate = ReplacePattern(value, "{", "\\{");
return ReplacePattern(intermediate, "}", "\\}");
}
+
+ static const char regexSpecialChars[] = "\\^$.|?*+()[]{}";
+ static const char regexSpecialCharsAndColon[] = "\\^$.|?*+()[]{}:";
+
+ // MatchWithoutRegex matches key1 against key2 without a regex when key2
is a plain path, optionally ending with "/*".
+ // It returns false when key2 contains other special characters, then the
caller has to fall back to the regex.
+ bool MatchWithoutRegex(const std::string& key1, const std::string& key2,
const char* special_chars, bool& res) {
+ std::size_t pos = key2.find_first_of(special_chars);
+ if (pos == std::string::npos) {
+ res = key1 == key2;
+ return true;
+ }
+
+ if (pos > 0 && pos + 1 == key2.size() && key2[pos] == '*' && key2[pos
- 1] == '/') {
+ // Same as "/.*" in the regex: same prefix, and "." does not match
line terminators.
+ res = key1.compare(0, pos, key2, 0, pos) == 0 &&
key1.find_first_of("\r\n", pos) == std::string::npos;
+ return true;
+ }
+
+ return false;
+ }
+
+ // GetRegex returns the compiled regex of pattern, each thread compiles a
pattern only once.
+ const std::regex& GetRegex(const std::string& pattern) {
+ thread_local std::unordered_map<std::string, std::regex> cache;
+ auto it = cache.find(pattern);
+ if (it != cache.end())
+ return it->second;
+
+ if (cache.size() >= 1024)
+ cache.clear();
+
+ return cache.emplace(pattern, std::regex(pattern)).first->second;
+ }
}
// KeyMatch determines whether key1 matches the pattern of key2 (similar to
RESTful path), key2 can contain a *.
@@ -92,6 +127,10 @@ std::string KeyGet(const std::string& key1, const
std::string& key2) {
// KeyMatch2 determines whether key1 matches the pattern of key2 (similar to
RESTful path), key2 can contain a *.
// For example, "/foo/bar" matches "/foo/*", "/resource1" matches "/:resource"
bool KeyMatch2(const std::string& key1, const std::string& key2) {
+ bool res;
+ if (MatchWithoutRegex(key1, key2, regexSpecialCharsAndColon, res))
+ return res;
+
std::string k2 = PrepareWildCardMatching(key2);
k2 = std::regex_replace(k2, capturingColonNonSlashRegex, "$1[^/]+$2");
k2 = EscapeCurlyBraces(k2);
@@ -121,7 +160,7 @@ std::string KeyGet2(const std::string& key1, const
std::string& key2, const std:
k2 = "^" + k2 + "$";
std::smatch values;
- std::regex_match(key1.begin(), key1.end(), values, std::regex(k2));
+ std::regex_match(key1.begin(), key1.end(), values, GetRegex(k2));
for (int i = 0; i < keys.size(); i++)
if (!path_var.compare(keys.at(i).substr(1)))
@@ -133,6 +172,10 @@ std::string KeyGet2(const std::string& key1, const
std::string& key2, const std:
// KeyMatch3 determines whether key1 matches the pattern of key2 (similar to
RESTful path), key2 can contain a *.
// For example, "/foo/bar" matches "/foo/*", "/resource1" matches "/{resource}"
bool KeyMatch3(const std::string& key1, const std::string& key2) {
+ bool res;
+ if (MatchWithoutRegex(key1, key2, regexSpecialChars, res))
+ return res;
+
std::string k2 = PrepareWildCardMatching(key2);
k2 = std::regex_replace(k2, enclosedPlaceHolderRegex, "$1[^/]+$2");
k2 = EscapeCurlyBraces(k2);
@@ -159,7 +202,7 @@ std::string KeyGet3(const std::string& key1, const
std::string& key2, const std:
k2 = "^" + k2 + "$";
std::smatch values;
- std::regex_match(key1.begin(), key1.end(), values, std::regex(k2));
+ std::regex_match(key1.begin(), key1.end(), values, GetRegex(k2));
for (int i = 0; i < keys.size(); i++)
if (!path_var.compare(keys.at(i).substr(1, keys.at(i).length() - 2)))
@@ -174,6 +217,10 @@ std::string KeyGet3(const std::string& key1, const
std::string& key2, const std:
// "/parent/123/child/456" does not match "/parent/{id}/child/{id}"
// But KeyMatch3 will match both.
bool KeyMatch4(const std::string& key1, const std::string& key2) {
+ bool res;
+ if (MatchWithoutRegex(key1, key2, regexSpecialChars, res))
+ return res;
+
std::string k2 = PrepareWildCardMatching(key2);
std::vector<std::string> tokens;
@@ -185,7 +232,7 @@ bool KeyMatch4(const std::string& key1, const std::string&
key2) {
k2 = EscapeCurlyBraces(k2);
k2 = "^" + k2 + "$";
std::smatch matches;
- std::regex_match(key1.begin(), key1.end(), matches, std::regex(k2));
+ std::regex_match(key1.begin(), key1.end(), matches, GetRegex(k2));
if (matches.empty())
return false;
if (tokens.size() != matches.size() - 1)
@@ -213,8 +260,7 @@ bool KeyMatch5(const std::string& key1, const std::string&
key2) {
// RegexMatch determines whether key1 matches the pattern of key2 in regular
expression.
bool RegexMatch(const std::string& key1, const std::string& key2) {
- std::regex regex_s(key2);
- return std::regex_match(key1, regex_s);
+ return std::regex_match(key1, GetRegex(key2));
}
// IPMatch determines whether IP address ip1 matches the pattern of IP address
ip2, ip2 can be an IP address or a CIDR pattern.
diff --git a/tests/built_in_functions_test.cpp
b/tests/built_in_functions_test.cpp
index b536f2a..b594343 100644
--- a/tests/built_in_functions_test.cpp
+++ b/tests/built_in_functions_test.cpp
@@ -93,6 +93,9 @@ TEST(TestBuiltInFunctions, TestKeyMatch2) {
TestKeyMatch2Fn("/foobar", "/foo", false);
TestKeyMatch2Fn("/foobar", "/foo*", false); // different with KeyMatch.
TestKeyMatch2Fn("/foobar", "/foo/*", false);
+ TestKeyMatch2Fn("/foo/", "/foo/*", true);
+ TestKeyMatch2Fn("/foo/bar\nbaz", "/foo/*", false);
+ TestKeyMatch2Fn("/foo.bar", "/foo.bar", true);
TestKeyMatch2Fn("/", "/:resource", false);
TestKeyMatch2Fn("/resource1", "/:resource", true);
@@ -186,6 +189,9 @@ TEST(TestBuiltInFunctions, TestKeyMatch3) {
TestKeyMatch3Fn("/foobar", "/foo", false);
TestKeyMatch3Fn("/foobar", "/foo*", false);
TestKeyMatch3Fn("/foobar", "/foo/*", false);
+ TestKeyMatch3Fn("/foo/", "/foo/*", true);
+ TestKeyMatch3Fn("/foo/bar\nbaz", "/foo/*", false);
+ TestKeyMatch3Fn("/foo:bar", "/foo:bar", true);
TestKeyMatch3Fn("/", "/{resource}", false);
TestKeyMatch3Fn("/resource1", "/{resource}", true);
@@ -280,6 +286,10 @@ TEST(TestBuiltInFunctions, TestKeyMatch4) {
TestKeyMatch4Fn("/parent/123/child/456",
"/parent/{id}/child/{id}/book/{id}", false);
TestKeyMatch4Fn("/parent/123/child/123", "/parent/{i/d}/child/{i/d}",
false);
+
+ TestKeyMatch4Fn("/parent/123", "/parent/123", true);
+ TestKeyMatch4Fn("/parent/123", "/parent/*", true);
+ TestKeyMatch4Fn("/parent", "/parent/*", false);
}
void TestKeyMatch5Fn(std::string key1, std::string key2, bool res) {
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]