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