1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
// URL glob matching for client-side waits (e.g. `Frame::wait_for_url`).
//
// Playwright URL globs: `*` matches any run of characters except `/` (one
// path segment), `**` matches any run including `/` (across segments), and
// `.` is a literal dot. Everything else is matched literally. A pattern that
// fails to translate to a valid regex never matches.
/// Returns whether `text` matches the glob `pattern`.
///
/// `*` matches within a path segment, `**` crosses segments, `.` is literal.
/// Returns `false` if the translated pattern is not a valid regex.
pub(crate) fn glob_match(pattern: &str, text: &str) -> bool {
let regex_str = pattern
.replace('.', "\\.")
.replace("**", "\x00") // placeholder so the next step skips `**`
.replace('*', "[^/]*")
.replace('\x00', ".*"); // restore `**` as cross-segment match
let regex_str = format!("^{}$", regex_str);
regex::Regex::new(®ex_str)
.map(|re| re.is_match(text))
.unwrap_or(false)
}
#[cfg(test)]
mod tests {
use super::glob_match;
#[test]
fn exact_match_is_anchored() {
assert!(glob_match("https://example.com/", "https://example.com/"));
// anchored at both ends: a trailing extra segment must not match
assert!(!glob_match("https://example.com/", "https://example.com/x"));
// ...nor a prefix
assert!(!glob_match("example.com", "an.example.com"));
}
#[test]
fn single_star_stays_within_one_segment() {
assert!(glob_match(
"https://example.com/*",
"https://example.com/foo"
));
// `*` does not cross a `/`
assert!(!glob_match(
"https://example.com/*",
"https://example.com/foo/bar"
));
// `*` matches the empty string
assert!(glob_match("https://example.com/*", "https://example.com/"));
}
#[test]
fn double_star_crosses_segments() {
assert!(glob_match(
"https://example.com/**",
"https://example.com/a/b/c"
));
assert!(glob_match(
"**/*.png",
"https://cdn.example.com/img/logo.png"
));
// wrong extension fails
assert!(!glob_match(
"**/*.png",
"https://cdn.example.com/img/logo.jpg"
));
}
#[test]
fn dot_is_literal_not_wildcard() {
assert!(glob_match("https://a.com/x", "https://a.com/x"));
// the literal dot must not match an arbitrary character
assert!(!glob_match("https://a.com/x", "https://axcom/x"));
}
#[test]
fn extension_globs() {
assert!(glob_match("*.png", "logo.png"));
// single `*` stops at `/`
assert!(!glob_match("*.png", "img/logo.png"));
// `**` crosses `/`
assert!(glob_match("**.png", "img/logo.png"));
}
#[test]
fn invalid_translation_does_not_match() {
// An unbalanced regex group from a stray metachar must yield false,
// not panic.
assert!(!glob_match("[unterminated", "anything"));
}
}