async_tar/
entry.rs

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