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 "." | ".." | "../" => 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 _ => depth = None,
56 }
57 }
58
59 true
60}
61
62#[derive(Debug, Clone, Copy, PartialEq, Eq)]
64pub enum FileType {
65 File,
67 Directory,
69 Symlink,
71}
72
73impl FileType {
74 #[inline]
76 pub fn is_file(&self) -> bool {
77 matches!(self, FileType::File)
78 }
79
80 #[inline]
82 pub fn is_directory(&self) -> bool {
83 matches!(self, FileType::Directory)
84 }
85
86 #[inline]
88 pub fn is_symlink(&self) -> bool {
89 matches!(self, FileType::Symlink)
90 }
91}
92
93#[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 #[inline]
104 pub fn now() -> Self {
105 Self::from_std(SystemTime::now())
106 }
107
108 pub fn from_ntfs(time: u64) -> Self {
112 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 #[inline]
122 pub fn from_unix(time: u64) -> Self {
123 Self(time)
124 }
125
126 #[inline]
130 pub fn from_std(t: SystemTime) -> Self {
131 Self(t.duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs())
132 }
133
134 #[inline]
136 pub fn to_unix(self) -> u64 {
137 self.0
138 }
139
140 #[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 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 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}