2121
2222#include < map>
2323#include < regex>
24+ #include < unordered_map>
2425
2526#include " casbin/exception/illegal_argument_exception.h"
2627#include " casbin/ip_parser/parser/CIDR.h"
@@ -57,6 +58,40 @@ namespace {
5758 std::string intermediate = ReplacePattern (value, " {" , " \\ {" );
5859 return ReplacePattern (intermediate, " }" , " \\ }" );
5960 }
61+
62+ static const char regexSpecialChars[] = " \\ ^$.|?*+()[]{}" ;
63+ static const char regexSpecialCharsAndColon[] = " \\ ^$.|?*+()[]{}:" ;
64+
65+ // MatchWithoutRegex matches key1 against key2 without a regex when key2 is a plain path, optionally ending with "/*".
66+ // It returns false when key2 contains other special characters, then the caller has to fall back to the regex.
67+ bool MatchWithoutRegex (const std::string& key1, const std::string& key2, const char * special_chars, bool & res) {
68+ std::size_t pos = key2.find_first_of (special_chars);
69+ if (pos == std::string::npos) {
70+ res = key1 == key2;
71+ return true ;
72+ }
73+
74+ if (pos > 0 && pos + 1 == key2.size () && key2[pos] == ' *' && key2[pos - 1 ] == ' /' ) {
75+ // Same as "/.*" in the regex: same prefix, and "." does not match line terminators.
76+ res = key1.compare (0 , pos, key2, 0 , pos) == 0 && key1.find_first_of (" \r\n " , pos) == std::string::npos;
77+ return true ;
78+ }
79+
80+ return false ;
81+ }
82+
83+ // GetRegex returns the compiled regex of pattern, each thread compiles a pattern only once.
84+ const std::regex& GetRegex (const std::string& pattern) {
85+ thread_local std::unordered_map<std::string, std::regex> cache;
86+ auto it = cache.find (pattern);
87+ if (it != cache.end ())
88+ return it->second ;
89+
90+ if (cache.size () >= 1024 )
91+ cache.clear ();
92+
93+ return cache.emplace (pattern, std::regex (pattern)).first ->second ;
94+ }
6095}
6196
6297// 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) {
92127// KeyMatch2 determines whether key1 matches the pattern of key2 (similar to RESTful path), key2 can contain a *.
93128// For example, "/foo/bar" matches "/foo/*", "/resource1" matches "/:resource"
94129bool KeyMatch2 (const std::string& key1, const std::string& key2) {
130+ bool res;
131+ if (MatchWithoutRegex (key1, key2, regexSpecialCharsAndColon, res))
132+ return res;
133+
95134 std::string k2 = PrepareWildCardMatching (key2);
96135 k2 = std::regex_replace (k2, capturingColonNonSlashRegex, " $1[^/]+$2" );
97136 k2 = EscapeCurlyBraces (k2);
@@ -121,7 +160,7 @@ std::string KeyGet2(const std::string& key1, const std::string& key2, const std:
121160 k2 = " ^" + k2 + " $" ;
122161
123162 std::smatch values;
124- std::regex_match (key1.begin (), key1.end (), values, std::regex (k2));
163+ std::regex_match (key1.begin (), key1.end (), values, GetRegex (k2));
125164
126165 for (int i = 0 ; i < keys.size (); i++)
127166 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:
133172// KeyMatch3 determines whether key1 matches the pattern of key2 (similar to RESTful path), key2 can contain a *.
134173// For example, "/foo/bar" matches "/foo/*", "/resource1" matches "/{resource}"
135174bool KeyMatch3 (const std::string& key1, const std::string& key2) {
175+ bool res;
176+ if (MatchWithoutRegex (key1, key2, regexSpecialChars, res))
177+ return res;
178+
136179 std::string k2 = PrepareWildCardMatching (key2);
137180 k2 = std::regex_replace (k2, enclosedPlaceHolderRegex, " $1[^/]+$2" );
138181 k2 = EscapeCurlyBraces (k2);
@@ -159,7 +202,7 @@ std::string KeyGet3(const std::string& key1, const std::string& key2, const std:
159202 k2 = " ^" + k2 + " $" ;
160203
161204 std::smatch values;
162- std::regex_match (key1.begin (), key1.end (), values, std::regex (k2));
205+ std::regex_match (key1.begin (), key1.end (), values, GetRegex (k2));
163206
164207 for (int i = 0 ; i < keys.size (); i++)
165208 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:
174217// "/parent/123/child/456" does not match "/parent/{id}/child/{id}"
175218// But KeyMatch3 will match both.
176219bool KeyMatch4 (const std::string& key1, const std::string& key2) {
220+ bool res;
221+ if (MatchWithoutRegex (key1, key2, regexSpecialChars, res))
222+ return res;
223+
177224 std::string k2 = PrepareWildCardMatching (key2);
178225
179226 std::vector<std::string> tokens;
@@ -185,7 +232,7 @@ bool KeyMatch4(const std::string& key1, const std::string& key2) {
185232 k2 = EscapeCurlyBraces (k2);
186233 k2 = " ^" + k2 + " $" ;
187234 std::smatch matches;
188- std::regex_match (key1.begin (), key1.end (), matches, std::regex (k2));
235+ std::regex_match (key1.begin (), key1.end (), matches, GetRegex (k2));
189236 if (matches.empty ())
190237 return false ;
191238 if (tokens.size () != matches.size () - 1 )
@@ -213,8 +260,7 @@ bool KeyMatch5(const std::string& key1, const std::string& key2) {
213260
214261// RegexMatch determines whether key1 matches the pattern of key2 in regular expression.
215262bool RegexMatch (const std::string& key1, const std::string& key2) {
216- std::regex regex_s (key2);
217- return std::regex_match (key1, regex_s);
263+ return std::regex_match (key1, GetRegex (key2));
218264}
219265
220266// IPMatch determines whether IP address ip1 matches the pattern of IP address ip2, ip2 can be an IP address or a CIDR pattern.
0 commit comments