async_tar_wasm/entry.rs
1use std::{
2 borrow::Cow,
3 cmp, fmt, marker,
4 pin::Pin,
5 task::{Context, Poll},
6};
7
8#[cfg(feature = "fs")]
9use async_std::{
10 fs,
11 fs::OpenOptions,
12 io::{Error, ErrorKind, SeekFrom},
13 path::{Component, PathBuf},
14};
15use async_std::{
16 io::{self, prelude::*},
17 path::Path,
18};
19use pin_project::pin_project;
20
21#[cfg(feature = "fs")]
22use filetime::{self, FileTime};
23
24use crate::{header::bytes2path, pax::pax_extensions, Archive, Header, PaxExtensions};
25
26#[cfg(feature = "fs")]
27use crate::{error::TarError, other};
28
29/// A read-only view into an entry of an archive.
30///
31/// This structure is a window into a portion of a borrowed archive which can
32/// be inspected. It acts as a file handle by implementing the Reader trait. An
33/// entry cannot be rewritten once inserted into an archive.
34#[pin_project]
35pub struct Entry<R: Read + Unpin> {
36 #[pin]
37 fields: EntryFields<R>,
38 _ignored: marker::PhantomData<Archive<R>>,
39}
40
41impl<R: Read + Unpin> fmt::Debug for Entry<R> {
42 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
43 f.debug_struct("Entry")
44 .field("fields", &self.fields)
45 .finish()
46 }
47}
48
49// private implementation detail of `Entry`, but concrete (no type parameters)
50// and also all-public to be constructed from other modules.
51#[pin_project]
52pub struct EntryFields<R: Read + Unpin> {
53 pub long_pathname: Option<Vec<u8>>,
54 pub long_linkname: Option<Vec<u8>>,
55 pub pax_extensions: Option<Vec<u8>>,
56 pub header: Header,
57 pub size: u64,
58 pub header_pos: u64,
59 pub file_pos: u64,
60 #[pin]
61 pub data: Vec<EntryIo<R>>,
62 pub unpack_xattrs: bool,
63 pub preserve_permissions: bool,
64 pub preserve_mtime: bool,
65 #[pin]
66 pub(crate) read_state: Option<EntryIo<R>>,
67}
68
69impl<R: Read + Unpin> fmt::Debug for EntryFields<R> {
70 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
71 f.debug_struct("EntryFields")
72 .field("long_pathname", &self.long_pathname)
73 .field("long_linkname", &self.long_linkname)
74 .field("pax_extensions", &self.pax_extensions)
75 .field("header", &self.header)
76 .field("size", &self.size)
77 .field("header_pos", &self.header_pos)
78 .field("file_pos", &self.file_pos)
79 .field("data", &self.data)
80 .field("unpack_xattrs", &self.unpack_xattrs)
81 .field("preserve_permissions", &self.preserve_permissions)
82 .field("preserve_mtime", &self.preserve_mtime)
83 .field("read_state", &self.read_state)
84 .finish()
85 }
86}
87
88#[pin_project(project = EntryIoProject)]
89pub enum EntryIo<R: Read + Unpin> {
90 Pad(#[pin] io::Take<io::Repeat>),
91 Data(#[pin] io::Take<R>),
92}
93
94impl<R: Read + Unpin> fmt::Debug for EntryIo<R> {
95 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
96 match self {
97 EntryIo::Pad(_) => write!(f, "EntryIo::Pad"),
98 EntryIo::Data(_) => write!(f, "EntryIo::Data"),
99 }
100 }
101}
102
103/// When unpacking items the unpacked thing is returned to allow custom
104/// additional handling by users. Today the File is returned, in future
105/// the enum may be extended with kinds for links, directories etc.
106#[cfg(feature = "fs")]
107#[derive(Debug)]
108#[non_exhaustive]
109pub enum Unpacked {
110 /// A file was unpacked.
111 File(fs::File),
112 /// A directory, hardlink, symlink, or other node was unpacked.
113 Other,
114}
115
116impl<R: Read + Unpin> Entry<R> {
117 /// Returns the path name for this entry.
118 ///
119 /// This method may fail if the pathname is not valid Unicode and this is
120 /// called on a Windows platform.
121 ///
122 /// Note that this function will convert any `\` characters to directory
123 /// separators, and it will not always return the same value as
124 /// `self.header().path()` as some archive formats have support for longer
125 /// path names described in separate entries.
126 ///
127 /// It is recommended to use this method instead of inspecting the `header`
128 /// directly to ensure that various archive formats are handled correctly.
129 pub fn path(&self) -> io::Result<Cow<Path>> {
130 self.fields.path()
131 }
132
133 /// Returns the raw bytes listed for this entry.
134 ///
135 /// Note that this function will convert any `\` characters to directory
136 /// separators, and it will not always return the same value as
137 /// `self.header().path_bytes()` as some archive formats have support for
138 /// longer path names described in separate entries.
139 pub fn path_bytes(&self) -> Cow<[u8]> {
140 self.fields.path_bytes()
141 }
142
143 /// Returns the link name for this entry, if any is found.
144 ///
145 /// This method may fail if the pathname is not valid Unicode and this is
146 /// called on a Windows platform. `Ok(None)` being returned, however,
147 /// indicates that the link name was not present.
148 ///
149 /// Note that this function will convert any `\` characters to directory
150 /// separators, and it will not always return the same value as
151 /// `self.header().link_name()` as some archive formats have support for
152 /// longer path names described in separate entries.
153 ///
154 /// It is recommended to use this method instead of inspecting the `header`
155 /// directly to ensure that various archive formats are handled correctly.
156 pub fn link_name(&self) -> io::Result<Option<Cow<Path>>> {
157 self.fields.link_name()
158 }
159
160 /// Returns the link name for this entry, in bytes, if listed.
161 ///
162 /// Note that this will not always return the same value as
163 /// `self.header().link_name_bytes()` as some archive formats have support for
164 /// longer path names described in separate entries.
165 pub fn link_name_bytes(&self) -> Option<Cow<[u8]>> {
166 self.fields.link_name_bytes()
167 }
168
169 /// Returns an iterator over the pax extensions contained in this entry.
170 ///
171 /// Pax extensions are a form of archive where extra metadata is stored in
172 /// key/value pairs in entries before the entry they're intended to
173 /// describe. For example this can be used to describe long file name or
174 /// other metadata like atime/ctime/mtime in more precision.
175 ///
176 /// The returned iterator will yield key/value pairs for each extension.
177 ///
178 /// `None` will be returned if this entry does not indicate that it itself
179 /// contains extensions, or if there were no previous extensions describing
180 /// it.
181 ///
182 /// Note that global pax extensions are intended to be applied to all
183 /// archive entries.
184 ///
185 /// Also note that this function will read the entire entry if the entry
186 /// itself is a list of extensions.
187 pub async fn pax_extensions(&mut self) -> io::Result<Option<PaxExtensions<'_>>> {
188 self.fields.pax_extensions().await
189 }
190
191 /// Returns access to the header of this entry in the archive.
192 ///
193 /// This provides access to the metadata for this entry in the archive.
194 pub fn header(&self) -> &Header {
195 &self.fields.header
196 }
197
198 /// Returns the starting position, in bytes, of the header of this entry in
199 /// the archive.
200 ///
201 /// The header is always a contiguous section of 512 bytes, so if the
202 /// underlying reader implements `Seek`, then the slice from `header_pos` to
203 /// `header_pos + 512` contains the raw header bytes.
204 pub fn raw_header_position(&self) -> u64 {
205 self.fields.header_pos
206 }
207
208 /// Returns the starting position, in bytes, of the file of this entry in
209 /// the archive.
210 ///
211 /// If the file of this entry is continuous (e.g. not a sparse file), and
212 /// if the underlying reader implements `Seek`, then the slice from
213 /// `file_pos` to `file_pos + entry_size` contains the raw file bytes.
214 pub fn raw_file_position(&self) -> u64 {
215 self.fields.file_pos
216 }
217
218 /// Writes this file to the specified location.
219 ///
220 /// This function will write the entire contents of this file into the
221 /// location specified by `dst`. Metadata will also be propagated to the
222 /// path `dst`.
223 ///
224 /// This function will create a file at the path `dst`, and it is required
225 /// that the intermediate directories are created. Any existing file at the
226 /// location `dst` will be overwritten.
227 ///
228 /// > **Note**: This function does not have as many sanity checks as
229 /// > `Archive::unpack` or `Entry::unpack_in`. As a result if you're
230 /// > thinking of unpacking untrusted tarballs you may want to review the
231 /// > implementations of the previous two functions and perhaps implement
232 /// > similar logic yourself.
233 ///
234 /// # Examples
235 ///
236 /// ```no_run
237 /// # fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> { async_std::task::block_on(async {
238 /// #
239 /// use async_std::fs::File;
240 /// use async_std::prelude::*;
241 /// use async_tar::Archive;
242 ///
243 /// let mut ar = Archive::new(File::open("foo.tar").await?);
244 /// let mut entries = ar.entries()?;
245 /// let mut i = 0;
246 /// while let Some(file) = entries.next().await {
247 /// let mut file = file?;
248 /// file.unpack(format!("file-{}", i)).await?;
249 /// i += 1;
250 /// }
251 /// #
252 /// # Ok(()) }) }
253 /// ```
254 #[cfg(feature = "fs")]
255 pub async fn unpack<P: AsRef<Path>>(&mut self, dst: P) -> io::Result<Unpacked> {
256 self.fields.unpack(None, dst.as_ref()).await
257 }
258
259 /// Extracts this file under the specified path, avoiding security issues.
260 ///
261 /// This function will write the entire contents of this file into the
262 /// location obtained by appending the path of this file in the archive to
263 /// `dst`, creating any intermediate directories if needed. Metadata will
264 /// also be propagated to the path `dst`. Any existing file at the location
265 /// `dst` will be overwritten.
266 ///
267 /// This function carefully avoids writing outside of `dst`. If the file has
268 /// a '..' in its path, this function will skip it and return false.
269 ///
270 /// # Examples
271 ///
272 /// ```no_run
273 /// # fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> { async_std::task::block_on(async {
274 /// #
275 /// use async_std::fs::File;
276 /// use async_tar::Archive;
277 /// use async_std::prelude::*;
278 ///
279 /// let mut ar = Archive::new(File::open("foo.tar").await?);
280 /// let mut entries = ar.entries()?;
281 /// let mut i = 0;
282 /// while let Some(file) = entries.next().await {
283 /// let mut file = file.unwrap();
284 /// file.unpack_in("target").await?;
285 /// i += 1;
286 /// }
287 /// #
288 /// # Ok(()) }) }
289 /// ```
290 #[cfg(feature = "fs")]
291 pub async fn unpack_in<P: AsRef<Path>>(&mut self, dst: P) -> io::Result<bool> {
292 self.fields.unpack_in(dst.as_ref()).await
293 }
294
295 /// Indicate whether extended file attributes (xattrs on Unix) are preserved
296 /// when unpacking this entry.
297 ///
298 /// This flag is disabled by default and is currently only implemented on
299 /// Unix using xattr support. This may eventually be implemented for
300 /// Windows, however, if other archive implementations are found which do
301 /// this as well.
302 pub fn set_unpack_xattrs(&mut self, unpack_xattrs: bool) {
303 self.fields.unpack_xattrs = unpack_xattrs;
304 }
305
306 /// Indicate whether extended permissions (like suid on Unix) are preserved
307 /// when unpacking this entry.
308 ///
309 /// This flag is disabled by default and is currently only implemented on
310 /// Unix.
311 pub fn set_preserve_permissions(&mut self, preserve: bool) {
312 self.fields.preserve_permissions = preserve;
313 }
314
315 /// Indicate whether access time information is preserved when unpacking
316 /// this entry.
317 ///
318 /// This flag is enabled by default.
319 pub fn set_preserve_mtime(&mut self, preserve: bool) {
320 self.fields.preserve_mtime = preserve;
321 }
322}
323
324impl<R: Read + Unpin> Read for Entry<R> {
325 fn poll_read(
326 self: Pin<&mut Self>,
327 cx: &mut Context<'_>,
328 into: &mut [u8],
329 ) -> Poll<io::Result<usize>> {
330 let mut this = self.project();
331 Pin::new(&mut *this.fields).poll_read(cx, into)
332 }
333}
334
335impl<R: Read + Unpin> EntryFields<R> {
336 pub fn from(entry: Entry<R>) -> Self {
337 entry.fields
338 }
339
340 pub fn into_entry(self) -> Entry<R> {
341 Entry {
342 fields: self,
343 _ignored: marker::PhantomData,
344 }
345 }
346
347 pub(crate) fn poll_read_all(
348 self: Pin<&mut Self>,
349 cx: &mut Context<'_>,
350 ) -> Poll<io::Result<Vec<u8>>> {
351 // Preallocate some data but don't let ourselves get too crazy now.
352 let cap = cmp::min(self.size, 128 * 1024);
353 let mut buf = Vec::with_capacity(cap as usize);
354
355 // Copied from futures::ReadToEnd
356 match async_std::task::ready!(poll_read_all_internal(self, cx, &mut buf)) {
357 Ok(_) => Poll::Ready(Ok(buf)),
358 Err(err) => Poll::Ready(Err(err)),
359 }
360 }
361
362 pub async fn read_all(&mut self) -> io::Result<Vec<u8>> {
363 // Preallocate some data but don't let ourselves get too crazy now.
364 let cap = cmp::min(self.size, 128 * 1024);
365 let mut v = Vec::with_capacity(cap as usize);
366 self.read_to_end(&mut v).await.map(|_| v)
367 }
368
369 fn path(&self) -> io::Result<Cow<'_, Path>> {
370 bytes2path(self.path_bytes())
371 }
372
373 fn path_bytes(&self) -> Cow<[u8]> {
374 if let Some(ref bytes) = self.long_pathname {
375 if let Some(&0) = bytes.last() {
376 Cow::Borrowed(&bytes[..bytes.len() - 1])
377 } else {
378 Cow::Borrowed(bytes)
379 }
380 } else {
381 if let Some(ref pax) = self.pax_extensions {
382 let pax = pax_extensions(pax)
383 .filter_map(Result::ok)
384 .find(|f| f.key_bytes() == b"path")
385 .map(|f| f.value_bytes());
386 if let Some(field) = pax {
387 return Cow::Borrowed(field);
388 }
389 }
390 self.header.path_bytes()
391 }
392 }
393
394 /// Gets the path in a "lossy" way, used for error reporting ONLY.
395 #[cfg(feature = "fs")]
396 fn path_lossy(&self) -> String {
397 String::from_utf8_lossy(&self.path_bytes()).to_string()
398 }
399
400 fn link_name(&self) -> io::Result<Option<Cow<Path>>> {
401 match self.link_name_bytes() {
402 Some(bytes) => bytes2path(bytes).map(Some),
403 None => Ok(None),
404 }
405 }
406
407 fn link_name_bytes(&self) -> Option<Cow<[u8]>> {
408 match self.long_linkname {
409 Some(ref bytes) => {
410 if let Some(&0) = bytes.last() {
411 Some(Cow::Borrowed(&bytes[..bytes.len() - 1]))
412 } else {
413 Some(Cow::Borrowed(bytes))
414 }
415 }
416 None => self.header.link_name_bytes(),
417 }
418 }
419
420 async fn pax_extensions(&mut self) -> io::Result<Option<PaxExtensions<'_>>> {
421 if self.pax_extensions.is_none() {
422 if !self.header.entry_type().is_pax_global_extensions()
423 && !self.header.entry_type().is_pax_local_extensions()
424 {
425 return Ok(None);
426 }
427 self.pax_extensions = Some(self.read_all().await?);
428 }
429 Ok(Some(pax_extensions(self.pax_extensions.as_ref().unwrap())))
430 }
431
432 #[cfg(feature = "fs")]
433 async fn unpack_in(&mut self, dst: &Path) -> io::Result<bool> {
434 // Notes regarding bsdtar 2.8.3 / libarchive 2.8.3:
435 // * Leading '/'s are trimmed. For example, `///test` is treated as
436 // `test`.
437 // * If the filename contains '..', then the file is skipped when
438 // extracting the tarball.
439 // * '//' within a filename is effectively skipped. An error is
440 // logged, but otherwise the effect is as if any two or more
441 // adjacent '/'s within the filename were consolidated into one
442 // '/'.
443 //
444 // Most of this is handled by the `path` module of the standard
445 // library, but we specially handle a few cases here as well.
446
447 let mut file_dst = dst.to_path_buf();
448 {
449 let path = self.path().map_err(|e| {
450 TarError::new(
451 &format!("invalid path in entry header: {}", self.path_lossy()),
452 e,
453 )
454 })?;
455 for part in path.components() {
456 match part {
457 // Leading '/' characters, root paths, and '.'
458 // components are just ignored and treated as "empty
459 // components"
460 Component::Prefix(..) | Component::RootDir | Component::CurDir => continue,
461
462 // If any part of the filename is '..', then skip over
463 // unpacking the file to prevent directory traversal
464 // security issues. See, e.g.: CVE-2001-1267,
465 // CVE-2002-0399, CVE-2005-1918, CVE-2007-4131
466 Component::ParentDir => return Ok(false),
467
468 Component::Normal(part) => file_dst.push(part),
469 }
470 }
471 }
472
473 // Skip cases where only slashes or '.' parts were seen, because
474 // this is effectively an empty filename.
475 if *dst == *file_dst {
476 return Ok(true);
477 }
478
479 // Skip entries without a parent (i.e. outside of FS root)
480 let parent = match file_dst.parent() {
481 Some(p) => p,
482 None => return Ok(false),
483 };
484
485 self.ensure_dir_created(dst, parent)
486 .await
487 .map_err(|e| TarError::new(&format!("failed to create `{}`", parent.display()), e))?;
488
489 let canon_target = self.validate_inside_dst(dst, parent).await?;
490
491 self.unpack(Some(&canon_target), &file_dst)
492 .await
493 .map_err(|e| TarError::new(&format!("failed to unpack `{}`", file_dst.display()), e))?;
494
495 Ok(true)
496 }
497
498 /// Unpack as destination directory `dst`.
499 #[cfg(feature = "fs")]
500 async fn unpack_dir(&mut self, dst: &Path) -> io::Result<()> {
501 // If the directory already exists just let it slide
502 match fs::create_dir(dst).await {
503 Ok(()) => Ok(()),
504 Err(err) => {
505 if err.kind() == ErrorKind::AlreadyExists {
506 let prev = fs::metadata(dst).await;
507 if prev.map(|m| m.is_dir()).unwrap_or(false) {
508 return Ok(());
509 }
510 }
511 Err(Error::new(
512 err.kind(),
513 format!("{} when creating dir {}", err, dst.display()),
514 ))
515 }
516 }
517 }
518
519 /// Returns access to the header of this entry in the archive.
520 #[cfg(feature = "fs")]
521 async fn unpack(&mut self, target_base: Option<&Path>, dst: &Path) -> io::Result<Unpacked> {
522 let kind = self.header.entry_type();
523
524 if kind.is_dir() {
525 self.unpack_dir(dst).await?;
526 if let Ok(mode) = self.header.mode() {
527 set_perms(dst, None, mode, self.preserve_permissions).await?;
528 }
529 return Ok(Unpacked::Other);
530 } else if kind.is_hard_link() || kind.is_symlink() {
531 let src = match self.link_name()? {
532 Some(name) => name,
533 None => {
534 return Err(other(&format!(
535 "hard link listed for {} but no link name found",
536 String::from_utf8_lossy(self.header.as_bytes())
537 )));
538 }
539 };
540
541 if src.iter().count() == 0 {
542 return Err(other(&format!(
543 "symlink destination for {} is empty",
544 String::from_utf8_lossy(self.header.as_bytes())
545 )));
546 }
547
548 if kind.is_hard_link() {
549 let link_src = match target_base {
550 // If we're unpacking within a directory then ensure that
551 // the destination of this hard link is both present and
552 // inside our own directory. This is needed because we want
553 // to make sure to not overwrite anything outside the root.
554 //
555 // Note that this logic is only needed for hard links
556 // currently. With symlinks the `validate_inside_dst` which
557 // happens before this method as part of `unpack_in` will
558 // use canonicalization to ensure this guarantee. For hard
559 // links though they're canonicalized to their existing path
560 // so we need to validate at this time.
561 Some(p) => {
562 let link_src = p.join(src);
563 self.validate_inside_dst(p, &link_src).await?;
564 link_src
565 }
566 None => src.into_owned(),
567 };
568 fs::hard_link(&link_src, dst).await.map_err(|err| {
569 Error::new(
570 err.kind(),
571 format!(
572 "{} when hard linking {} to {}",
573 err,
574 link_src.display(),
575 dst.display()
576 ),
577 )
578 })?;
579 } else {
580 symlink(&src, dst).await.map_err(|err| {
581 Error::new(
582 err.kind(),
583 format!(
584 "{} when symlinking {} to {}",
585 err,
586 src.display(),
587 dst.display()
588 ),
589 )
590 })?;
591 };
592 return Ok(Unpacked::Other);
593
594 #[cfg(target_arch = "wasm32")]
595 #[allow(unused_variables)]
596 async fn symlink(src: &Path, dst: &Path) -> io::Result<()> {
597 Err(io::Error::new(io::ErrorKind::Other, "Not implemented"))
598 }
599
600 #[cfg(target_family = "windows")]
601 async fn symlink(src: &Path, dst: &Path) -> io::Result<()> {
602 async_std::os::windows::fs::symlink_file(src, dst).await
603 }
604
605 #[cfg(any(target_family = "unix", target_os = "redox"))]
606 async fn symlink(src: &Path, dst: &Path) -> io::Result<()> {
607 async_std::os::unix::fs::symlink(src, dst).await
608 }
609 } else if kind.is_pax_global_extensions()
610 || kind.is_pax_local_extensions()
611 || kind.is_gnu_longname()
612 || kind.is_gnu_longlink()
613 {
614 return Ok(Unpacked::Other);
615 };
616
617 // Old BSD-tar compatibility.
618 // Names that have a trailing slash should be treated as a directory.
619 // Only applies to old headers.
620 if self.header.as_ustar().is_none() && self.path_bytes().ends_with(b"/") {
621 self.unpack_dir(dst).await?;
622 if let Ok(mode) = self.header.mode() {
623 set_perms(dst, None, mode, self.preserve_permissions).await?;
624 }
625 return Ok(Unpacked::Other);
626 }
627
628 // Note the lack of `else` clause above. According to the FreeBSD
629 // documentation:
630 //
631 // > A POSIX-compliant implementation must treat any unrecognized
632 // > typeflag value as a regular file.
633 //
634 // As a result if we don't recognize the kind we just write out the file
635 // as we would normally.
636
637 // Ensure we write a new file rather than overwriting in-place which
638 // is attackable; if an existing file is found unlink it.
639 async fn open(dst: &Path) -> io::Result<fs::File> {
640 OpenOptions::new()
641 .write(true)
642 .create_new(true)
643 .open(dst)
644 .await
645 }
646 let mut f = async {
647 let mut f = match open(dst).await {
648 Ok(f) => Ok(f),
649 Err(err) => {
650 if err.kind() == ErrorKind::AlreadyExists {
651 match fs::remove_file(dst).await {
652 Ok(()) => open(dst).await,
653 Err(ref e) if e.kind() == io::ErrorKind::NotFound => open(dst).await,
654 Err(e) => Err(e),
655 }
656 } else {
657 Err(err)
658 }
659 }
660 }?;
661 for io in self.data.drain(..) {
662 match io {
663 EntryIo::Data(mut d) => {
664 let expected = d.limit();
665 if io::copy(&mut d, &mut f).await? != expected {
666 return Err(other("failed to write entire file"));
667 }
668 }
669 EntryIo::Pad(d) => {
670 // TODO: checked cast to i64
671 let to = SeekFrom::Current(d.limit() as i64);
672 let size = f.seek(to).await?;
673 f.set_len(size).await?;
674 }
675 }
676 }
677 Ok::<fs::File, io::Error>(f)
678 }
679 .await
680 .map_err(|e| {
681 let header = self.header.path_bytes();
682 TarError::new(
683 &format!(
684 "failed to unpack `{}` into `{}`",
685 String::from_utf8_lossy(&header),
686 dst.display()
687 ),
688 e,
689 )
690 })?;
691
692 if self.preserve_mtime {
693 if let Ok(mtime) = self.header.mtime() {
694 let mtime = FileTime::from_unix_time(mtime as i64, 0);
695 filetime::set_file_times(&dst, mtime, mtime).map_err(|e| {
696 TarError::new(&format!("failed to set mtime for `{}`", dst.display()), e)
697 })?;
698 }
699 }
700 if let Ok(mode) = self.header.mode() {
701 set_perms(dst, Some(&mut f), mode, self.preserve_permissions).await?;
702 }
703 if self.unpack_xattrs {
704 set_xattrs(self, dst).await?;
705 }
706 return Ok(Unpacked::File(f));
707
708 async fn set_perms(
709 dst: &Path,
710 f: Option<&mut fs::File>,
711 mode: u32,
712 preserve: bool,
713 ) -> Result<(), TarError> {
714 _set_perms(dst, f, mode, preserve).await.map_err(|e| {
715 TarError::new(
716 &format!(
717 "failed to set permissions to {:o} \
718 for `{}`",
719 mode,
720 dst.display()
721 ),
722 e,
723 )
724 })
725 }
726
727 #[cfg(any(unix, target_os = "redox"))]
728 async fn _set_perms(
729 dst: &Path,
730 f: Option<&mut fs::File>,
731 mode: u32,
732 preserve: bool,
733 ) -> io::Result<()> {
734 use std::os::unix::prelude::*;
735
736 let mode = if preserve { mode } else { mode & 0o777 };
737 let perm = fs::Permissions::from_mode(mode as _);
738 match f {
739 Some(f) => f.set_permissions(perm).await,
740 None => fs::set_permissions(dst, perm).await,
741 }
742 }
743
744 #[cfg(windows)]
745 async fn _set_perms(
746 dst: &Path,
747 f: Option<&mut fs::File>,
748 mode: u32,
749 _preserve: bool,
750 ) -> io::Result<()> {
751 if mode & 0o200 == 0o200 {
752 return Ok(());
753 }
754 match f {
755 Some(f) => {
756 let mut perm = f.metadata().await?.permissions();
757 perm.set_readonly(true);
758 f.set_permissions(perm).await
759 }
760 None => {
761 let mut perm = fs::metadata(dst).await?.permissions();
762 perm.set_readonly(true);
763 fs::set_permissions(dst, perm).await
764 }
765 }
766 }
767
768 #[cfg(target_arch = "wasm32")]
769 #[allow(unused_variables)]
770 async fn _set_perms(
771 dst: &Path,
772 f: Option<&mut fs::File>,
773 mode: u32,
774 _preserve: bool,
775 ) -> io::Result<()> {
776 Err(io::Error::new(io::ErrorKind::Other, "Not implemented"))
777 }
778
779 #[cfg(all(unix, feature = "xattr"))]
780 async fn set_xattrs<R: Read + Unpin>(
781 me: &mut EntryFields<R>,
782 dst: &Path,
783 ) -> io::Result<()> {
784 use std::{ffi::OsStr, os::unix::prelude::*};
785
786 let exts = match me.pax_extensions().await {
787 Ok(Some(e)) => e,
788 _ => return Ok(()),
789 };
790 let exts = exts
791 .filter_map(Result::ok)
792 .filter_map(|e| {
793 let key = e.key_bytes();
794 let prefix = b"SCHILY.xattr.";
795 if key.starts_with(prefix) {
796 Some((&key[prefix.len()..], e))
797 } else {
798 None
799 }
800 })
801 .map(|(key, e)| (OsStr::from_bytes(key), e.value_bytes()));
802
803 for (key, value) in exts {
804 xattr::set(dst, key, value).map_err(|e| {
805 TarError::new(
806 &format!(
807 "failed to set extended \
808 attributes to {}. \
809 Xattrs: key={:?}, value={:?}.",
810 dst.display(),
811 key,
812 String::from_utf8_lossy(value)
813 ),
814 e,
815 )
816 })?;
817 }
818
819 Ok(())
820 }
821 // Windows does not completely support posix xattrs
822 // https://en.wikipedia.org/wiki/Extended_file_attributes#Windows_NT
823 #[cfg(any(
824 windows,
825 target_os = "redox",
826 not(feature = "xattr"),
827 target_arch = "wasm32"
828 ))]
829 async fn set_xattrs<R: Read + Unpin>(_: &mut EntryFields<R>, _: &Path) -> io::Result<()> {
830 Ok(())
831 }
832 }
833
834 #[cfg(feature = "fs")]
835 async fn ensure_dir_created(&self, dst: &Path, dir: &Path) -> io::Result<()> {
836 let mut ancestor = dir;
837 let mut dirs_to_create = Vec::new();
838 while ancestor.symlink_metadata().await.is_err() {
839 dirs_to_create.push(ancestor);
840 if let Some(parent) = ancestor.parent() {
841 ancestor = parent;
842 } else {
843 break;
844 }
845 }
846 for ancestor in dirs_to_create.into_iter().rev() {
847 if let Some(parent) = ancestor.parent() {
848 self.validate_inside_dst(dst, parent).await?;
849 }
850 fs::create_dir(ancestor).await?;
851 }
852 Ok(())
853 }
854
855 #[cfg(feature = "fs")]
856 async fn validate_inside_dst(&self, dst: &Path, file_dst: &Path) -> io::Result<PathBuf> {
857 // Abort if target (canonical) parent is outside of `dst`
858 let canon_parent = file_dst.canonicalize().await.map_err(|err| {
859 Error::new(
860 err.kind(),
861 format!("{} while canonicalizing {}", err, file_dst.display()),
862 )
863 })?;
864 let canon_target = dst.canonicalize().await.map_err(|err| {
865 Error::new(
866 err.kind(),
867 format!("{} while canonicalizing {}", err, dst.display()),
868 )
869 })?;
870 if !canon_parent.starts_with(&canon_target) {
871 let err = TarError::new(
872 &format!(
873 "trying to unpack outside of destination path: {}",
874 canon_target.display()
875 ),
876 // TODO: use ErrorKind::InvalidInput here? (minor breaking change)
877 Error::new(ErrorKind::Other, "Invalid argument"),
878 );
879 return Err(err.into());
880 }
881 Ok(canon_target)
882 }
883}
884
885impl<R: Read + Unpin> Read for EntryFields<R> {
886 fn poll_read(
887 self: Pin<&mut Self>,
888 cx: &mut Context<'_>,
889 into: &mut [u8],
890 ) -> Poll<io::Result<usize>> {
891 let mut this = self.project();
892 loop {
893 if this.read_state.is_none() {
894 if this.data.as_ref().is_empty() {
895 *this.read_state = None;
896 } else {
897 let data = &mut *this.data;
898 *this.read_state = Some(data.remove(0));
899 }
900 }
901
902 if let Some(ref mut io) = &mut *this.read_state {
903 let ret = Pin::new(io).poll_read(cx, into);
904 match ret {
905 Poll::Ready(Ok(0)) => {
906 *this.read_state = None;
907 if this.data.as_ref().is_empty() {
908 return Poll::Ready(Ok(0));
909 }
910 continue;
911 }
912 Poll::Ready(Ok(val)) => {
913 return Poll::Ready(Ok(val));
914 }
915 Poll::Ready(Err(err)) => {
916 return Poll::Ready(Err(err));
917 }
918 Poll::Pending => {
919 return Poll::Pending;
920 }
921 }
922 }
923 // Unable to pull another value from `data`, so we are done.
924 return Poll::Ready(Ok(0));
925 }
926 }
927}
928
929impl<R: Read + Unpin> Read for EntryIo<R> {
930 fn poll_read(
931 self: Pin<&mut Self>,
932 cx: &mut Context<'_>,
933 into: &mut [u8],
934 ) -> Poll<io::Result<usize>> {
935 match self.project() {
936 EntryIoProject::Pad(io) => io.poll_read(cx, into),
937 EntryIoProject::Data(io) => io.poll_read(cx, into),
938 }
939 }
940}
941
942struct Guard<'a> {
943 buf: &'a mut Vec<u8>,
944 len: usize,
945}
946
947impl Drop for Guard<'_> {
948 fn drop(&mut self) {
949 unsafe {
950 self.buf.set_len(self.len);
951 }
952 }
953}
954
955fn poll_read_all_internal<R: Read + ?Sized>(
956 mut rd: Pin<&mut R>,
957 cx: &mut Context<'_>,
958 buf: &mut Vec<u8>,
959) -> Poll<io::Result<usize>> {
960 let mut g = Guard {
961 len: buf.len(),
962 buf,
963 };
964 let ret;
965 loop {
966 if g.len == g.buf.len() {
967 unsafe {
968 g.buf.reserve(32);
969 let capacity = g.buf.capacity();
970 g.buf.set_len(capacity);
971
972 let buf = &mut g.buf[g.len..];
973 std::ptr::write_bytes(buf.as_mut_ptr(), 0, buf.len());
974 }
975 }
976
977 match async_std::task::ready!(rd.as_mut().poll_read(cx, &mut g.buf[g.len..])) {
978 Ok(0) => {
979 ret = Poll::Ready(Ok(g.len));
980 break;
981 }
982 Ok(n) => g.len += n,
983 Err(e) => {
984 ret = Poll::Ready(Err(e));
985 break;
986 }
987 }
988 }
989
990 ret
991}