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 "." | ".." | "../" => 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 _ => depth = None,
61 }
62 }
63
64 true
65}
66
67#[derive(Debug, Clone, Copy, PartialEq, Eq)]
69pub enum FileType {
70 File,
72 Directory,
74 Symlink,
76}
77
78impl FileType {
79 #[inline]
81 pub fn is_file(&self) -> bool {
82 matches!(self, FileType::File)
83 }
84
85 #[inline]
87 pub fn is_directory(&self) -> bool {
88 matches!(self, FileType::Directory)
89 }
90
91 #[inline]
93 pub fn is_symlink(&self) -> bool {
94 matches!(self, FileType::Symlink)
95 }
96}
97
98#[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 #[inline]
109 pub fn now() -> Self {
110 Self::from_std(SystemTime::now())
111 }
112
113 pub fn from_ntfs(time: u64) -> Self {
117 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 #[inline]
127 pub fn from_unix(time: u64) -> Self {
128 Self(time)
129 }
130
131 #[inline]
135 pub fn from_std(t: SystemTime) -> Self {
136 Self(t.duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs())
137 }
138
139 #[inline]
141 pub fn to_unix(self) -> u64 {
142 self.0
143 }
144
145 #[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 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 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}