ic_http_certification/utils/
wildcard_paths.rs

1/// The prefix for all paths in an HTTP certification tree.
2pub const PATH_PREFIX: &str = "http_expr";
3
4/// The prefix for all paths in an HTTP certification tree, as bytes.
5pub const PATH_PREFIX_BYTES: &[u8] = PATH_PREFIX.as_bytes();
6
7/// A trailing slash used to indicate a directory in an HTTP certification tree.
8pub const PATH_DIR_SEPARATOR: &str = "";
9
10/// A trailing slash used to indicate a directory in an HTTP certification tree, as bytes.
11pub const PATH_DIR_SEPARATOR_BYTES: &[u8] = PATH_DIR_SEPARATOR.as_bytes();
12
13/// A terminator used to indicate the end of an exact path in an HTTP certification tree.
14pub const EXACT_PATH_TERMINATOR: &str = "<$>";
15
16/// A terminator used to indicate the end of an exact path in an HTTP certification tree, as bytes.
17pub const EXACT_PATH_TERMINATOR_BYTES: &[u8] = EXACT_PATH_TERMINATOR.as_bytes();
18
19/// A terminator used to indicate the end of a wildcard path in an HTTP certification tree.
20pub const WILDCARD_PATH_TERMINATOR: &str = "<*>";
21
22/// A terminator used to indicate the end of a wildcard path in an HTTP certification tree, as bytes.
23pub const WILDCARD_PATH_TERMINATOR_BYTES: &[u8] = WILDCARD_PATH_TERMINATOR.as_bytes();
24
25/// Returns whether the given wildcard path is valid for the given request path.
26pub fn is_wildcard_path_valid_for_request_path(
27    wildcard_path: &[Vec<u8>],
28    request_path: &[Vec<u8>],
29) -> bool {
30    // request_path must be a superset of wildcard_path
31    if request_path.starts_with(wildcard_path) {
32        return true;
33    }
34
35    // if the wildcard path includes a trailing slash then remove it and try the same check again
36    // request paths will not include trailing slashes between path elements
37    if wildcard_path.ends_with(&[PATH_DIR_SEPARATOR_BYTES.to_vec()]) {
38        return request_path.starts_with(&wildcard_path[..wildcard_path.len() - 1]);
39    }
40
41    false
42}
43
44fn strip_path_affixes(path: &mut Vec<Vec<u8>>) {
45    // strip any leading `http_expr` segments
46    if matches!(
47        path.first(),
48        Some(first) if first == PATH_PREFIX_BYTES,
49    ) {
50        path.remove(0);
51    }
52
53    // strip any trailing `<*>` or `<$>` segments
54    if matches!(path.last(), Some(last) if
55        last == EXACT_PATH_TERMINATOR_BYTES ||
56        last == WILDCARD_PATH_TERMINATOR_BYTES)
57    {
58        path.pop();
59    }
60
61    // after stripping out prefixes and suffixes,
62    // the only path that should have a leading `/`,
63    // is a single segment path (i.e. the root path)
64    if path.len() > 1
65        && matches!(
66            path.first(),
67            Some(first) if first == PATH_DIR_SEPARATOR_BYTES,
68        )
69    {
70        path.remove(0);
71    }
72}
73
74/// Returns a list of wildcard paths that are more specific than the responding
75/// wildcard path. A wildcard path is more specific than another if it has more
76/// segments and it contains the requested path as a prefix. The responding
77/// wildcard path is expected to be a prefix of the requested path.
78///
79/// For example, if the requested path is `["a", "b", "c"]` and the responding
80/// wildcard path is `["a", "b"]`, then the more specific wildcard paths are
81/// `["a", "b", "c", "<*>"]` and `["a", "b", "/", "<*>"]`.
82pub fn more_specific_wildcards_for(
83    requested_path: &[Vec<u8>],
84    responding_wildcard_path: &[Vec<u8>],
85) -> Vec<Vec<Vec<u8>>> {
86    let mut valid_wildcards: Vec<Vec<Vec<u8>>> = vec![];
87
88    let mut potential_path = requested_path.to_vec();
89    strip_path_affixes(&mut potential_path);
90
91    let mut responding_wildcard_path = responding_wildcard_path.to_vec();
92    strip_path_affixes(&mut responding_wildcard_path);
93
94    // if the responding wildcard path is not a valid prefix of the requested path,
95    // then we start from an empty path so that we can return all valid wildcards
96    if !is_wildcard_path_valid_for_request_path(&responding_wildcard_path, &potential_path) {
97        responding_wildcard_path = vec![];
98    }
99
100    while potential_path.len() > responding_wildcard_path.len()
101        || potential_path.last() != responding_wildcard_path.last()
102    {
103        potential_path.push(WILDCARD_PATH_TERMINATOR_BYTES.to_vec());
104        valid_wildcards.push(potential_path.clone());
105        potential_path.pop(); // remove the wildcard terminator
106
107        // if we didn't have a trailing slash in this round,
108        // add it so we can handle it in the next round
109        if potential_path.ends_with(&[PATH_DIR_SEPARATOR_BYTES.to_vec()]) {
110            potential_path.pop(); // remove the last segment of the path
111        } else {
112            potential_path.pop(); // remove the last segment of the path
113            potential_path.push(PATH_DIR_SEPARATOR_BYTES.to_vec());
114        }
115    }
116
117    valid_wildcards
118}
119
120#[cfg(test)]
121mod tests {
122    use super::*;
123    use rstest::*;
124
125    #[rstest]
126    #[case(responding_path_a(), more_specific_paths_a())]
127    #[case(responding_path_b(), more_specific_paths_b())]
128    #[case(responding_path_c(), more_specific_paths_c())]
129    #[case(responding_path_d(), more_specific_paths_d())]
130    #[case(responding_path_e(), more_specific_paths_e())]
131    #[case(responding_path_f(), more_specific_paths_f())]
132    #[case(responding_path_g(), more_specific_paths_g())]
133    #[case(responding_path_h(), more_specific_paths_h())]
134    fn test_more_specific_wildcards_for(
135        requested_path: Vec<Vec<u8>>,
136        #[case] responding_path: Vec<Vec<u8>>,
137        #[case] expected: Vec<Vec<Vec<u8>>>,
138    ) {
139        let more_specific_paths = more_specific_wildcards_for(&requested_path, &responding_path);
140
141        assert_eq!(more_specific_paths, expected);
142    }
143
144    #[fixture]
145    fn requested_path() -> Vec<Vec<u8>> {
146        vec![b"a".to_vec(), b"b".to_vec(), b"c".to_vec()]
147    }
148
149    #[fixture]
150    fn responding_path_a() -> Vec<Vec<u8>> {
151        vec![b"a".to_vec(), b"b".to_vec(), b"c".to_vec()]
152    }
153
154    #[fixture]
155    fn more_specific_paths_a() -> Vec<Vec<Vec<u8>>> {
156        vec![]
157    }
158
159    #[fixture]
160    fn responding_path_b() -> Vec<Vec<u8>> {
161        vec![
162            b"a".to_vec(),
163            b"b".to_vec(),
164            PATH_DIR_SEPARATOR_BYTES.to_vec(),
165        ]
166    }
167
168    #[fixture]
169    fn more_specific_paths_b() -> Vec<Vec<Vec<u8>>> {
170        vec![vec![
171            b"a".to_vec(),
172            b"b".to_vec(),
173            b"c".to_vec(),
174            WILDCARD_PATH_TERMINATOR_BYTES.to_vec(),
175        ]]
176    }
177
178    #[fixture]
179    fn responding_path_c() -> Vec<Vec<u8>> {
180        vec![b"a".to_vec(), b"b".to_vec()]
181    }
182
183    #[fixture]
184    fn more_specific_paths_c() -> Vec<Vec<Vec<u8>>> {
185        vec![
186            vec![
187                b"a".to_vec(),
188                b"b".to_vec(),
189                b"c".to_vec(),
190                WILDCARD_PATH_TERMINATOR_BYTES.to_vec(),
191            ],
192            vec![
193                b"a".to_vec(),
194                b"b".to_vec(),
195                PATH_DIR_SEPARATOR_BYTES.to_vec(),
196                WILDCARD_PATH_TERMINATOR_BYTES.to_vec(),
197            ],
198        ]
199    }
200
201    #[fixture]
202    fn responding_path_d() -> Vec<Vec<u8>> {
203        vec![b"a".to_vec(), PATH_DIR_SEPARATOR_BYTES.to_vec()]
204    }
205
206    #[fixture]
207    fn more_specific_paths_d() -> Vec<Vec<Vec<u8>>> {
208        vec![
209            vec![
210                b"a".to_vec(),
211                b"b".to_vec(),
212                b"c".to_vec(),
213                WILDCARD_PATH_TERMINATOR_BYTES.to_vec(),
214            ],
215            vec![
216                b"a".to_vec(),
217                b"b".to_vec(),
218                PATH_DIR_SEPARATOR_BYTES.to_vec(),
219                WILDCARD_PATH_TERMINATOR_BYTES.to_vec(),
220            ],
221            vec![
222                b"a".to_vec(),
223                b"b".to_vec(),
224                WILDCARD_PATH_TERMINATOR_BYTES.to_vec(),
225            ],
226        ]
227    }
228
229    #[fixture]
230    fn responding_path_e() -> Vec<Vec<u8>> {
231        vec![b"a".to_vec()]
232    }
233
234    #[fixture]
235    fn more_specific_paths_e() -> Vec<Vec<Vec<u8>>> {
236        vec![
237            vec![
238                b"a".to_vec(),
239                b"b".to_vec(),
240                b"c".to_vec(),
241                WILDCARD_PATH_TERMINATOR_BYTES.to_vec(),
242            ],
243            vec![
244                b"a".to_vec(),
245                b"b".to_vec(),
246                PATH_DIR_SEPARATOR_BYTES.to_vec(),
247                WILDCARD_PATH_TERMINATOR_BYTES.to_vec(),
248            ],
249            vec![
250                b"a".to_vec(),
251                b"b".to_vec(),
252                WILDCARD_PATH_TERMINATOR_BYTES.to_vec(),
253            ],
254            vec![
255                b"a".to_vec(),
256                PATH_DIR_SEPARATOR_BYTES.to_vec(),
257                WILDCARD_PATH_TERMINATOR_BYTES.to_vec(),
258            ],
259        ]
260    }
261
262    #[fixture]
263    fn responding_path_f() -> Vec<Vec<u8>> {
264        vec![PATH_DIR_SEPARATOR_BYTES.to_vec()]
265    }
266
267    #[fixture]
268    fn more_specific_paths_f() -> Vec<Vec<Vec<u8>>> {
269        vec![
270            vec![
271                b"a".to_vec(),
272                b"b".to_vec(),
273                b"c".to_vec(),
274                WILDCARD_PATH_TERMINATOR_BYTES.to_vec(),
275            ],
276            vec![
277                b"a".to_vec(),
278                b"b".to_vec(),
279                PATH_DIR_SEPARATOR_BYTES.to_vec(),
280                WILDCARD_PATH_TERMINATOR_BYTES.to_vec(),
281            ],
282            vec![
283                b"a".to_vec(),
284                b"b".to_vec(),
285                WILDCARD_PATH_TERMINATOR_BYTES.to_vec(),
286            ],
287            vec![
288                b"a".to_vec(),
289                PATH_DIR_SEPARATOR_BYTES.to_vec(),
290                WILDCARD_PATH_TERMINATOR_BYTES.to_vec(),
291            ],
292            vec![b"a".to_vec(), WILDCARD_PATH_TERMINATOR_BYTES.to_vec()],
293        ]
294    }
295
296    #[fixture]
297    fn responding_path_g() -> Vec<Vec<u8>> {
298        vec![]
299    }
300
301    #[fixture]
302    fn more_specific_paths_g() -> Vec<Vec<Vec<u8>>> {
303        vec![
304            vec![
305                b"a".to_vec(),
306                b"b".to_vec(),
307                b"c".to_vec(),
308                WILDCARD_PATH_TERMINATOR_BYTES.to_vec(),
309            ],
310            vec![
311                b"a".to_vec(),
312                b"b".to_vec(),
313                PATH_DIR_SEPARATOR_BYTES.to_vec(),
314                WILDCARD_PATH_TERMINATOR_BYTES.to_vec(),
315            ],
316            vec![
317                b"a".to_vec(),
318                b"b".to_vec(),
319                WILDCARD_PATH_TERMINATOR_BYTES.to_vec(),
320            ],
321            vec![
322                b"a".to_vec(),
323                PATH_DIR_SEPARATOR_BYTES.to_vec(),
324                WILDCARD_PATH_TERMINATOR_BYTES.to_vec(),
325            ],
326            vec![b"a".to_vec(), WILDCARD_PATH_TERMINATOR_BYTES.to_vec()],
327            vec![
328                PATH_DIR_SEPARATOR_BYTES.to_vec(),
329                WILDCARD_PATH_TERMINATOR_BYTES.to_vec(),
330            ],
331        ]
332    }
333
334    #[fixture]
335    fn responding_path_h() -> Vec<Vec<u8>> {
336        vec![b"d".to_vec(), b"e".to_vec(), b"f".to_vec()]
337    }
338
339    #[fixture]
340    fn more_specific_paths_h() -> Vec<Vec<Vec<u8>>> {
341        vec![
342            vec![
343                b"a".to_vec(),
344                b"b".to_vec(),
345                b"c".to_vec(),
346                WILDCARD_PATH_TERMINATOR_BYTES.to_vec(),
347            ],
348            vec![
349                b"a".to_vec(),
350                b"b".to_vec(),
351                PATH_DIR_SEPARATOR_BYTES.to_vec(),
352                WILDCARD_PATH_TERMINATOR_BYTES.to_vec(),
353            ],
354            vec![
355                b"a".to_vec(),
356                b"b".to_vec(),
357                WILDCARD_PATH_TERMINATOR_BYTES.to_vec(),
358            ],
359            vec![
360                b"a".to_vec(),
361                PATH_DIR_SEPARATOR_BYTES.to_vec(),
362                WILDCARD_PATH_TERMINATOR_BYTES.to_vec(),
363            ],
364            vec![b"a".to_vec(), WILDCARD_PATH_TERMINATOR_BYTES.to_vec()],
365            vec![
366                PATH_DIR_SEPARATOR_BYTES.to_vec(),
367                WILDCARD_PATH_TERMINATOR_BYTES.to_vec(),
368            ],
369        ]
370    }
371}