1pub fn scope_contains(scope: &str, candidate: &str) -> bool {
24 let Some(scope_segments) = well_formed_segments(scope) else {
25 return false;
26 };
27 let Some(candidate_segments) = well_formed_segments(candidate) else {
28 return false;
29 };
30
31 candidate_segments.len() >= scope_segments.len()
32 && scope_segments == candidate_segments[..scope_segments.len()]
33}
34
35fn well_formed_segments(path: &str) -> Option<Vec<&str>> {
38 let segments: Vec<&str> = path.split('/').collect();
39 if segments
40 .iter()
41 .any(|segment| segment.is_empty() || *segment == "." || *segment == "..")
42 {
43 return None;
44 }
45 Some(segments)
46}
47
48#[cfg(test)]
49mod tests {
50 use super::scope_contains;
51
52 #[test]
53 fn exact_match_allowed() {
54 assert!(scope_contains("a/b", "a/b"));
55 }
56
57 #[test]
58 fn descendant_allowed() {
59 assert!(scope_contains("a/b", "a/b/c"));
60 assert!(scope_contains("a/b", "a/b/c/d"));
61 }
62
63 #[test]
64 fn dotdot_traversal_denied() {
65 assert!(!scope_contains("a/b", "a/b/../c"));
68 assert!(!scope_contains("a/b", "a/b/.."));
69 assert!(!scope_contains("a/b", "a/b/c/../../b/c"));
70 }
71
72 #[test]
73 fn single_dot_denied() {
74 assert!(!scope_contains("a/b", "a/b/./c"));
76 assert!(!scope_contains("a/b", "a/b/."));
77 }
78
79 #[test]
80 fn empty_segments_denied() {
81 assert!(!scope_contains("a/b", "a//b"));
82 assert!(!scope_contains("a/b", "a/b//c"));
83 assert!(!scope_contains("a/b", "a/b/"));
84 assert!(!scope_contains("a/b", "/a/b"));
85 assert!(!scope_contains("a/b", ""));
86 }
87
88 #[test]
89 fn sibling_denied() {
90 assert!(!scope_contains("a/b", "a/c"));
91 }
92
93 #[test]
94 fn upward_access_denied() {
95 assert!(!scope_contains("a/b", "a"));
96 assert!(!scope_contains("a/b/c", "a/b"));
97 }
98
99 #[test]
100 fn non_boundary_prefix_denied() {
101 assert!(!scope_contains("a/b", "a/bc"));
103 assert!(!scope_contains("a/bc", "a/b"));
104 }
105
106 #[test]
107 fn malformed_scope_denies_everything() {
108 assert!(!scope_contains("a/..", "a"));
111 assert!(!scope_contains("a/../b", "a/../b"));
112 assert!(!scope_contains("a//b", "a/b/c"));
113 assert!(!scope_contains("", "a"));
114 assert!(!scope_contains("", ""));
115 }
116}