Skip to main content

archive_trait/
name.rs

1/// A predicate that accepts or rejects one UTF-8 archive name.
2pub type NameValidator = fn(&str) -> bool;
3
4/// Applies the default archive name policy.
5///
6/// The default rejects ASCII control characters, including NUL and DEL, colons,
7/// and leading or trailing ASCII whitespace. It deliberately does not impose
8/// extraction containment rules such as rejecting absolute paths or parent
9/// components.
10#[inline]
11pub fn default_name_validator(name: &str) -> bool {
12    let bytes = name.as_bytes();
13    !bytes.first().is_some_and(u8::is_ascii_whitespace)
14        && !bytes.last().is_some_and(u8::is_ascii_whitespace)
15        && !bytes.iter().any(u8::is_ascii_control)
16        && !bytes.contains(&b':')
17}
18
19#[derive(Clone, Copy, Debug, Default)]
20pub(crate) enum NameValidation {
21    #[default]
22    Default,
23    Disabled,
24    Custom(NameValidator),
25}
26
27impl NameValidation {
28    pub(crate) fn from_validator(validator: Option<NameValidator>) -> Self {
29        match validator {
30            Some(validator) => Self::Custom(validator),
31            None => Self::Disabled,
32        }
33    }
34
35    #[inline]
36    pub(crate) fn accepts(self, name: &str) -> bool {
37        match self {
38            Self::Default => default_name_validator(name),
39            Self::Disabled => true,
40            Self::Custom(validator) => validator(name),
41        }
42    }
43}
44
45#[cfg(test)]
46mod tests {
47    use super::*;
48
49    #[test]
50    fn default_validator_rejects_ascii_controls_colons_and_boundary_whitespace() {
51        for name in [
52            "",
53            "/absolute",
54            r"back\slash",
55            ".",
56            "..",
57            "a//b",
58            "interior space",
59            "\u{a0}name\u{a0}",
60        ] {
61            assert!(default_name_validator(name), "{name:?}");
62        }
63        for name in [
64            " leading",
65            "trailing ",
66            "\tname",
67            "name\n",
68            "inside\nname",
69            "name:stream",
70        ] {
71            assert!(!default_name_validator(name), "{name:?}");
72        }
73    }
74}