tokio_tar/
entry.rs

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