eazip/utils/
mod.rs

1pub mod cp437;
2mod crc32;
3
4pub use crc32::{Crc32Checker, Crc32Writer};
5
6use std::{fmt, io, time::SystemTime};
7
8#[must_use]
9pub(crate) fn validate_name(name: &str) -> Option<Box<str>> {
10    if name.starts_with('/')
11        || name.contains('\\')
12        || name.contains('\0')
13        || (cfg!(windows) && name.contains(':'))
14    {
15        return None;
16    }
17
18    let mut dst = String::with_capacity(name.len());
19    for part in name.split_inclusive('/') {
20        match part {
21            // Forbid parent parts as they have weird interactions with symlinks
22            "." | ".." | "../" => return None,
23            "/" | "./" => (),
24            _ => dst.push_str(part),
25        }
26    }
27
28    if dst.is_empty() {
29        return None;
30    }
31
32    Some(dst.into_boxed_str())
33}
34
35pub(crate) fn validate_symlink(name: &str, target: &str) -> bool {
36    if target.starts_with('/')
37        || target.contains('\\')
38        || target.contains('\0')
39        || (cfg!(windows) && target.contains(':'))
40    {
41        return false;
42    }
43
44    let mut depth = Some(name.split('/').count() - 1);
45
46    for part in target.split('/') {
47        match part {
48            "" | "." => (),
49            ".." => match depth.and_then(|d| d.checked_sub(1)) {
50                Some(d) => depth = Some(d),
51                None => return false,
52            },
53            // Once the link goes down, forbid it going up again (eg "a/../b")
54            // to prevent it using another link as a "trampoline" to escape.
55            _ => depth = None,
56        }
57    }
58
59    true
60}
61
62/// The type of an entry in an archive.
63#[derive(Debug, Clone, Copy, PartialEq, Eq)]
64pub enum FileType {
65    /// A file.
66    File,
67    /// A directory.
68    Directory,
69    /// A symlink.
70    Symlink,
71}
72
73impl FileType {
74    /// Returns whether `self` is `FileType::File`.
75    #[inline]
76    pub fn is_file(&self) -> bool {
77        matches!(self, FileType::File)
78    }
79
80    /// Returns whether `self` is `FileType::Directory`.
81    #[inline]
82    pub fn is_directory(&self) -> bool {
83        matches!(self, FileType::Directory)
84    }
85
86    /// Returns whether `self` is `FileType::Symlink`.
87    #[inline]
88    pub fn is_symlink(&self) -> bool {
89        matches!(self, FileType::Symlink)
90    }
91}
92
93/// A timestamp for an entry in an archive.
94///
95/// It is stored as a 64-bits UNIX timestamp, and therefore has second precision.
96#[derive(Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
97pub struct Timestamp(u64);
98
99impl Timestamp {
100    pub const UNIX_EPOCH: Self = Self(0);
101
102    /// Returns the timestamp corresponding to "now".
103    #[inline]
104    pub fn now() -> Self {
105        Self::from_std(SystemTime::now())
106    }
107
108    /// Returns a `Timestamp` from a NTFS timestamp.
109    ///
110    /// Sub-second precision is lost in the process.
111    pub fn from_ntfs(time: u64) -> Self {
112        /// Time in seconds between NT and Unix epochs
113        const NT_EPOCH: u64 = 11_644_473_600;
114
115        let time = time.saturating_sub(NT_EPOCH * 10_000_000);
116
117        Self(time / 10_000_000)
118    }
119
120    /// Returns a `Timestamp` from an UNIX timestamp.
121    #[inline]
122    pub fn from_unix(time: u64) -> Self {
123        Self(time)
124    }
125
126    /// Returns a `Timestamp` from a [`SystemTime`].
127    ///
128    /// Sub-second precision is lost in the process.
129    #[inline]
130    pub fn from_std(t: SystemTime) -> Self {
131        Self(t.duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs())
132    }
133
134    /// Converts this timestamp to an UNIX timestamp.
135    #[inline]
136    pub fn to_unix(self) -> u64 {
137        self.0
138    }
139
140    /// Converts this timestamp to a [`SystemTime`].
141    #[inline]
142    pub fn to_std(self) -> SystemTime {
143        SystemTime::UNIX_EPOCH + std::time::Duration::from_secs(self.0)
144    }
145}
146
147impl fmt::Debug for Timestamp {
148    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
149        write!(f, "Timestamp({})", self.0)
150    }
151}
152
153#[derive(Default)]
154pub(crate) struct Counter<T> {
155    pub amt: u64,
156    pub inner: T,
157}
158
159impl<T> Counter<T> {
160    #[inline]
161    pub const fn new(inner: T) -> Self {
162        Self { amt: 0, inner }
163    }
164
165    pub(crate) fn advance(&mut self, amt: u64) -> io::Result<()>
166    where
167        T: io::Seek,
168    {
169        #[cold]
170        fn out_of_range() -> io::Error {
171            io::Error::new(io::ErrorKind::InvalidInput, "seek out of range")
172        }
173
174        let offset = amt.try_into().map_err(|_| out_of_range())?;
175        self.amt = self.amt.checked_add(amt).ok_or_else(out_of_range)?;
176        self.inner.seek_relative(offset)
177    }
178}
179
180impl<R: io::Read> io::Read for Counter<R> {
181    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
182        let n = self.inner.read(buf)?;
183        self.amt += n as u64;
184        Ok(n)
185    }
186}
187
188impl<R: io::BufRead> io::BufRead for Counter<R> {
189    #[inline]
190    fn fill_buf(&mut self) -> io::Result<&[u8]> {
191        self.inner.fill_buf()
192    }
193
194    #[inline]
195    fn consume(&mut self, amount: usize) {
196        self.amt += amount as u64;
197        self.inner.consume(amount);
198    }
199}
200
201impl<W: io::Write> io::Write for Counter<W> {
202    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
203        let n = self.inner.write(buf)?;
204        self.amt += n as u64;
205        Ok(n)
206    }
207
208    fn write_vectored(&mut self, bufs: &[io::IoSlice<'_>]) -> io::Result<usize> {
209        let n = self.inner.write_vectored(bufs)?;
210        self.amt += n as u64;
211        Ok(n)
212    }
213
214    #[inline]
215    fn flush(&mut self) -> io::Result<()> {
216        self.inner.flush()
217    }
218}
219
220#[cold]
221fn bad_length() -> io::Error {
222    io::Error::new(io::ErrorKind::InvalidData, "unexpected file length")
223}
224
225pub(crate) struct LengthChecker<R> {
226    expected: u64,
227    reader: R,
228}
229
230impl<R> LengthChecker<R> {
231    #[inline]
232    pub fn new(reader: R, expected: u64) -> Self {
233        Self { expected, reader }
234    }
235}
236
237impl<R: io::Read> io::Read for LengthChecker<R> {
238    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
239        let n = self.reader.read(buf)?;
240        if n == 0 && self.expected != 0 {
241            return Err(bad_length());
242        }
243        self.expected = self.expected.checked_sub(n as u64).ok_or_else(bad_length)?;
244        Ok(n)
245    }
246
247    fn read_to_end(&mut self, buf: &mut Vec<u8>) -> io::Result<usize> {
248        let size = self
249            .expected
250            .try_into()
251            .map_err(|_| io::ErrorKind::OutOfMemory)?;
252        buf.try_reserve(size)?;
253
254        let initial_len = buf.len();
255        buf.extend((0..size).map(|_| 0));
256        self.read_exact(&mut buf[initial_len..])?;
257
258        // Check that we really are at EOF
259        self.read(&mut [0])?;
260
261        Ok(size)
262    }
263
264    fn read_to_string(&mut self, buf: &mut String) -> io::Result<usize> {
265        let size = self
266            .expected
267            .try_into()
268            .map_err(|_| io::ErrorKind::OutOfMemory)?;
269        buf.try_reserve(size)?;
270
271        // Forward to the default implementation of `read_to_string`
272
273        struct Reader<R>(R);
274        impl<R: io::Read> io::Read for Reader<R> {
275            fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
276                self.0.read(buf)
277            }
278            fn read_to_end(&mut self, buf: &mut Vec<u8>) -> io::Result<usize> {
279                self.0.read_to_end(buf)
280            }
281        }
282
283        Reader(self).read_to_string(buf)
284    }
285}
286
287#[test]
288fn symlink_validation() {
289    assert!(validate_symlink("a/b", "../c"));
290    assert!(!validate_symlink("a/b", "../../c"));
291    assert!(!validate_symlink("a/b", "/c"));
292    assert!(!validate_symlink("a/b", ".//////../../c"));
293    assert!(!validate_symlink("a/b", "a/../c"));
294    #[cfg(windows)]
295    assert!(!validate_symlink("a/b", "C:/e"));
296}