Skip to main content

use_cab/
lib.rs

1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4//! Microsoft Cabinet archive labels and compression metadata for `RustUse`.
5
6use core::fmt;
7
8/// Common Microsoft Cabinet archive extension.
9pub const CAB_EXTENSION: &str = "cab";
10/// Common CAB-related extensions.
11pub const CAB_EXTENSIONS: &[&str] = &["cab"];
12
13/// Microsoft Cabinet format labels.
14#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
15pub enum CabFormat {
16    /// Cabinet archive label.
17    Cabinet,
18    /// Unknown or intentionally unspecified CAB format.
19    #[default]
20    Unknown,
21}
22
23impl CabFormat {
24    /// Returns a stable lowercase label.
25    #[must_use]
26    pub const fn as_str(self) -> &'static str {
27        match self {
28            Self::Cabinet => "cabinet",
29            Self::Unknown => "unknown",
30        }
31    }
32}
33
34impl fmt::Display for CabFormat {
35    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
36        formatter.write_str(self.as_str())
37    }
38}
39
40/// CAB compression method labels.
41#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
42pub enum CabCompressionMethod {
43    /// No compression.
44    None,
45    /// MSZIP compression.
46    MsZip,
47    /// Quantum compression.
48    Quantum,
49    /// LZX compression.
50    Lzx,
51    /// Unknown or unsupported compression method.
52    #[default]
53    Unknown,
54}
55
56impl CabCompressionMethod {
57    /// Returns a stable lowercase label.
58    #[must_use]
59    pub const fn as_str(self) -> &'static str {
60        match self {
61            Self::None => "none",
62            Self::MsZip => "mszip",
63            Self::Quantum => "quantum",
64            Self::Lzx => "lzx",
65            Self::Unknown => "unknown",
66        }
67    }
68}
69
70impl fmt::Display for CabCompressionMethod {
71    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
72        formatter.write_str(self.as_str())
73    }
74}
75
76/// Returns whether `extension` is a known CAB extension label.
77#[must_use]
78pub fn is_cab_extension(extension: &str) -> bool {
79    normalize_extension(extension) == "cab"
80}
81
82/// Returns whether `name` has a known CAB filename encoding.
83#[must_use]
84pub fn is_cab_filename(name: &str) -> bool {
85    let parts = filename_parts(name);
86
87    matches!(parts.as_slice(), [.., last] if last == "cab")
88}
89
90fn normalize_extension(extension: &str) -> String {
91    extension
92        .trim()
93        .trim_start_matches('.')
94        .to_ascii_lowercase()
95}
96
97fn filename_parts(name: &str) -> Vec<String> {
98    name.trim()
99        .to_ascii_lowercase()
100        .rsplit(['/', '\\'])
101        .next()
102        .unwrap_or_default()
103        .trim_start_matches('.')
104        .split('.')
105        .filter(|part| !part.is_empty())
106        .map(str::to_owned)
107        .collect()
108}
109
110#[cfg(test)]
111mod tests {
112    use super::{
113        CAB_EXTENSIONS, CabCompressionMethod, CabFormat, is_cab_extension, is_cab_filename,
114    };
115
116    #[test]
117    fn detects_cab_extensions() {
118        assert!(is_cab_extension(".cab"));
119        assert_eq!(CAB_EXTENSIONS[0], "cab");
120    }
121
122    #[test]
123    fn detects_cab_filenames() {
124        assert!(is_cab_filename("driver.cab"));
125        assert!(is_cab_filename("DRIVER.CAB"));
126        assert!(!is_cab_filename("bundle.zip"));
127    }
128
129    #[test]
130    fn exposes_default_and_unknown_labels() {
131        assert_eq!(CabFormat::default(), CabFormat::Unknown);
132        assert_eq!(CabFormat::Cabinet.as_str(), "cabinet");
133        assert_eq!(
134            CabCompressionMethod::default(),
135            CabCompressionMethod::Unknown
136        );
137        assert_eq!(CabCompressionMethod::MsZip.as_str(), "mszip");
138    }
139}