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}