Skip to main content

tar/
entry.rs

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