Skip to main content

eazip/utils/
mod.rs

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