Skip to content

Commit 0ff7cd8

Browse files
authored
feat: use wcmatch.glob for glob_match with "**" support (#402)
1 parent 97d8c8d commit 0ff7cd8

File tree

3 files changed

+96
-64
lines changed

3 files changed

+96
-64
lines changed

casbin/util/builtin_operators.py

Lines changed: 25 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import ipaddress
1616
import re
1717
from datetime import datetime
18+
import wcmatch.glob as glob
1819

1920
KEY_MATCH2_PATTERN = re.compile(r"(.*?):[^\/]+(.*?)")
2021
KEY_MATCH3_PATTERN = re.compile(r"(.*?){[^\/]+?}(.*?)")
@@ -290,70 +291,31 @@ def range_match(pattern, pattern_index, test):
290291
def glob_match(string, pattern):
291292
"""determines whether string matches the pattern in glob expression."""
292293

293-
pattern_len = len(pattern)
294-
string_len = len(string)
295-
if pattern_len == 0:
296-
return string_len == 0
297-
pattern_index = 0
298-
string_index = 0
299-
while True:
300-
if pattern_index == pattern_len:
301-
return string_len == string_index
302-
c = pattern[pattern_index]
303-
pattern_index += 1
304-
if c == "?":
305-
if string_index == string_len:
306-
return False
307-
if string[string_index] == "/":
308-
return False
309-
string_index += 1
310-
continue
311-
if c == "*":
312-
while (pattern_index != pattern_len) and (c == "*"):
313-
c = pattern[pattern_index]
314-
pattern_index += 1
315-
if pattern_index == pattern_len:
316-
return string.find("/", string_index) == -1
294+
def doublestar_to_wcmatch(pattern):
295+
"""
296+
Converts glob patterns with double stars (**) to wcmatch-compatible format.
297+
"""
298+
parts = pattern.split("/")
299+
new_parts = []
300+
for part in parts:
301+
if part == "**":
302+
new_parts.append("**")
317303
else:
318-
if c == "/":
319-
string_index = string.find("/", string_index)
320-
if string_index == -1:
321-
return False
322-
else:
323-
string_index += 1
324-
# General case, use recursion.
325-
while string_index != string_len:
326-
if glob_match(string[string_index:], pattern[pattern_index:]):
327-
return True
328-
if string[string_index] == "/":
329-
break
330-
string_index += 1
331-
continue
332-
if c == "[":
333-
if string_index == string_len:
334-
return False
335-
if string[string_index] == "/":
336-
return False
337-
pattern_index = range_match(pattern, pattern_index, string[string_index])
338-
if pattern_index == -1:
339-
return False
340-
string_index += 1
341-
continue
342-
if c == "\\":
343-
if pattern_index == pattern_len:
344-
c = "\\"
345-
else:
346-
c = pattern[pattern_index]
347-
pattern_index += 1
348-
# fall through
349-
# other cases and c == "\\"
350-
if string_index == string_len:
351-
return False
352-
else:
353-
if c == string[string_index]:
354-
string_index += 1
355-
else:
356-
return False
304+
part = re.sub(r"\*{2,}", "*", part)
305+
new_parts.append(part)
306+
result_pattern = "/".join(new_parts)
307+
308+
if result_pattern.endswith("/**"):
309+
base_pattern = result_pattern[:-3]
310+
result_pattern = "{" + base_pattern + "," + result_pattern + "}"
311+
312+
return result_pattern
313+
314+
if pattern.startswith("*/"):
315+
return glob_match(string, pattern[1:])
316+
317+
pattern = doublestar_to_wcmatch(pattern)
318+
return glob.globmatch(string, pattern, flags=glob.GLOBSTAR | glob.BRACE)
357319

358320

359321
def glob_match_func(*args):

requirements.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
simpleeval >= 1.0.3
1+
simpleeval >= 1.0.3
2+
wcmatch >= 10.1

tests/util/test_builtin_operators.py

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,7 @@ def test_glob_match(self):
249249
self.assertFalse(util.glob_match_func("/foobar", "/foo"))
250250
self.assertTrue(util.glob_match_func("/foobar", "/foo*"))
251251
self.assertFalse(util.glob_match_func("/foobar", "/foo/*"))
252+
252253
self.assertTrue(util.glob_match_func("/foo", "*/foo"))
253254
self.assertTrue(util.glob_match_func("/foo", "*/foo*"))
254255
self.assertFalse(util.glob_match_func("/foo", "*/foo/*"))
@@ -277,6 +278,74 @@ def test_glob_match(self):
277278
self.assertFalse(util.glob_match_func("/prefix/subprefix/foobar", "*/foo*"))
278279
self.assertFalse(util.glob_match_func("/prefix/subprefix/foobar", "*/foo/*"))
279280

281+
self.assertTrue(util.glob_match_func("/foo", "**/foo"))
282+
self.assertTrue(util.glob_match_func("/foo", "**/foo**"))
283+
self.assertTrue(util.glob_match_func("/foo", "**/foo/**"))
284+
self.assertFalse(util.glob_match_func("/foo/bar", "**/foo"))
285+
self.assertFalse(util.glob_match_func("/foo/bar", "**/foo**"))
286+
self.assertTrue(util.glob_match_func("/foo/bar", "**/foo/**"))
287+
self.assertFalse(util.glob_match_func("/foobar", "**/foo"))
288+
self.assertTrue(util.glob_match_func("/foobar", "**/foo**"))
289+
self.assertFalse(util.glob_match_func("/foobar", "**/foo/**"))
290+
291+
self.assertTrue(util.glob_match_func("/prefix/foo", "**/foo"))
292+
self.assertTrue(util.glob_match_func("/prefix/foo", "**/foo**"))
293+
self.assertTrue(util.glob_match_func("/prefix/foo", "**/foo/**"))
294+
self.assertFalse(util.glob_match_func("/prefix/foo/bar", "**/foo"))
295+
self.assertFalse(util.glob_match_func("/prefix/foo/bar", "**/foo**"))
296+
self.assertTrue(util.glob_match_func("/prefix/foo/bar", "**/foo/**"))
297+
self.assertFalse(util.glob_match_func("/prefix/foobar", "**/foo"))
298+
self.assertTrue(util.glob_match_func("/prefix/foobar", "**/foo**"))
299+
self.assertFalse(util.glob_match_func("/prefix/foobar", "**/foo/**"))
300+
301+
self.assertTrue(util.glob_match_func("/prefix/subprefix/foo", "**/foo"))
302+
self.assertTrue(util.glob_match_func("/prefix/subprefix/foo", "**/foo**"))
303+
self.assertTrue(util.glob_match_func("/prefix/subprefix/foo", "**/foo/**"))
304+
self.assertFalse(util.glob_match_func("/prefix/subprefix/foo/bar", "**/foo"))
305+
self.assertFalse(util.glob_match_func("/prefix/subprefix/foo/bar", "**/foo**"))
306+
self.assertTrue(util.glob_match_func("/prefix/subprefix/foo/bar", "**/foo/**"))
307+
self.assertFalse(util.glob_match_func("/prefix/subprefix/foobar", "**/foo"))
308+
self.assertTrue(util.glob_match_func("/prefix/subprefix/foobar", "**/foo**"))
309+
self.assertFalse(util.glob_match_func("/prefix/subprefix/foobar", "**/foo/**"))
310+
311+
self.assertTrue(util.glob_match_func("/foo", "*/foo**"))
312+
self.assertTrue(util.glob_match_func("/foo", "**/foo*"))
313+
self.assertTrue(util.glob_match_func("/foo", "*/foo/**"))
314+
self.assertFalse(util.glob_match_func("/foo", "**/foo/*"))
315+
self.assertFalse(util.glob_match_func("/foo/bar", "*/foo**"))
316+
self.assertFalse(util.glob_match_func("/foo/bar", "**/foo*"))
317+
self.assertTrue(util.glob_match_func("/foo/bar", "*/foo/**"))
318+
self.assertTrue(util.glob_match_func("/foo/bar", "**/foo/*"))
319+
self.assertTrue(util.glob_match_func("/foobar", "*/foo**"))
320+
self.assertTrue(util.glob_match_func("/foobar", "**/foo*"))
321+
self.assertFalse(util.glob_match_func("/foobar", "*/foo/**"))
322+
self.assertFalse(util.glob_match_func("/foobar", "**/foo/*"))
323+
self.assertFalse(util.glob_match_func("/prefix/foo", "*/foo**"))
324+
self.assertTrue(util.glob_match_func("/prefix/foo", "**/foo*"))
325+
self.assertFalse(util.glob_match_func("/prefix/foo", "*/foo/**"))
326+
self.assertFalse(util.glob_match_func("/prefix/foo", "**/foo/*"))
327+
self.assertFalse(util.glob_match_func("/prefix/foo/bar", "*/foo**"))
328+
self.assertFalse(util.glob_match_func("/prefix/foo/bar", "**/foo*"))
329+
self.assertFalse(util.glob_match_func("/prefix/foo/bar", "*/foo/**"))
330+
self.assertTrue(util.glob_match_func("/prefix/foo/bar", "**/foo/*"))
331+
self.assertFalse(util.glob_match_func("/prefix/foobar", "*/foo**"))
332+
self.assertTrue(util.glob_match_func("/prefix/foobar", "**/foo*"))
333+
self.assertFalse(util.glob_match_func("/prefix/foobar", "*/foo/**"))
334+
self.assertFalse(util.glob_match_func("/prefix/foobar", "**/foo/*"))
335+
336+
self.assertFalse(util.glob_match_func("/prefix/subprefix/foo", "*/foo**"))
337+
self.assertTrue(util.glob_match_func("/prefix/subprefix/foo", "**/foo*"))
338+
self.assertFalse(util.glob_match_func("/prefix/subprefix/foo", "*/foo/**"))
339+
self.assertFalse(util.glob_match_func("/prefix/subprefix/foo", "**/foo/*"))
340+
self.assertFalse(util.glob_match_func("/prefix/subprefix/foo/bar", "*/foo**"))
341+
self.assertFalse(util.glob_match_func("/prefix/subprefix/foo/bar", "**/foo*"))
342+
self.assertFalse(util.glob_match_func("/prefix/subprefix/foo/bar", "*/foo/**"))
343+
self.assertTrue(util.glob_match_func("/prefix/subprefix/foo/bar", "**/foo/*"))
344+
self.assertFalse(util.glob_match_func("/prefix/subprefix/foobar", "*/foo**"))
345+
self.assertTrue(util.glob_match_func("/prefix/subprefix/foobar", "**/foo*"))
346+
self.assertFalse(util.glob_match_func("/prefix/subprefix/foobar", "*/foo/**"))
347+
self.assertFalse(util.glob_match_func("/prefix/subprefix/foobar", "**/foo/*"))
348+
280349
self.assertTrue(util.glob_match_func("/f", "/?"))
281350
self.assertTrue(util.glob_match_func("/foobar", "/foo?ar"))
282351
self.assertFalse(util.glob_match_func("/fooar", "/foo?ar"))

0 commit comments

Comments
 (0)