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 => self.header.link_name_bytes(),
338         }
339     }
340 
pax_extensions(&mut self) -> io::Result<Option<PaxExtensions>>341     fn pax_extensions(&mut self) -> io::Result<Option<PaxExtensions>> {
342         if self.pax_extensions.is_none() {
343             if !self.header.entry_type().is_pax_global_extensions()
344                 && !self.header.entry_type().is_pax_local_extensions()
345             {
346                 return Ok(None);
347             }
348             self.pax_extensions = Some(self.read_all()?);
349         }
350         Ok(Some(pax_extensions(self.pax_extensions.as_ref().unwrap())))
351     }
352 
unpack_in(&mut self, dst: &Path) -> io::Result<bool>353     fn unpack_in(&mut self, dst: &Path) -> io::Result<bool> {
354         // Notes regarding bsdtar 2.8.3 / libarchive 2.8.3:
355         // * Leading '/'s are trimmed. For example, `///test` is treated as
356         //   `test`.
357         // * If the filename contains '..', then the file is skipped when
358         //   extracting the tarball.
359         // * '//' within a filename is effectively skipped. An error is
360         //   logged, but otherwise the effect is as if any two or more
361         //   adjacent '/'s within the filename were consolidated into one
362         //   '/'.
363         //
364         // Most of this is handled by the `path` module of the standard
365         // library, but we specially handle a few cases here as well.
366 
367         let mut file_dst = dst.to_path_buf();
368         {
369             let path = self.path().map_err(|e| {
370                 TarError::new(
371                     &format!("invalid path in entry header: {}", self.path_lossy()),
372                     e,
373                 )
374             })?;
375             for part in path.components() {
376                 match part {
377                     // Leading '/' characters, root paths, and '.'
378                     // components are just ignored and treated as "empty
379                     // components"
380                     Component::Prefix(..) | Component::RootDir | Component::CurDir => continue,
381 
382                     // If any part of the filename is '..', then skip over
383                     // unpacking the file to prevent directory traversal
384                     // security issues.  See, e.g.: CVE-2001-1267,
385                     // CVE-2002-0399, CVE-2005-1918, CVE-2007-4131
386                     Component::ParentDir => return Ok(false),
387 
388                     Component::Normal(part) => file_dst.push(part),
389                 }
390             }
391         }
392 
393         // Skip cases where only slashes or '.' parts were seen, because
394         // this is effectively an empty filename.
395         if *dst == *file_dst {
396             return Ok(true);
397         }
398 
399         // Skip entries without a parent (i.e. outside of FS root)
400         let parent = match file_dst.parent() {
401             Some(p) => p,
402             None => return Ok(false),
403         };
404 
405         if parent.symlink_metadata().is_err() {
406             fs::create_dir_all(&parent).map_err(|e| {
407                 TarError::new(&format!("failed to create `{}`", parent.display()), e)
408             })?;
409         }
410 
411         let canon_target = self.validate_inside_dst(&dst, parent)?;
412 
413         self.unpack(Some(&canon_target), &file_dst)
414             .map_err(|e| TarError::new(&format!("failed to unpack `{}`", file_dst.display()), e))?;
415 
416         Ok(true)
417     }
418 
419     /// Unpack as destination directory `dst`.
unpack_dir(&mut self, dst: &Path) -> io::Result<()>420     fn unpack_dir(&mut self, dst: &Path) -> io::Result<()> {
421         // If the directory already exists just let it slide
422         fs::create_dir(dst).or_else(|err| {
423             if err.kind() == ErrorKind::AlreadyExists {
424                 let prev = fs::metadata(dst);
425                 if prev.map(|m| m.is_dir()).unwrap_or(false) {
426                     return Ok(());
427                 }
428             }
429             Err(Error::new(
430                 err.kind(),
431                 format!("{} when creating dir {}", err, dst.display()),
432             ))
433         })
434     }
435 
436     /// Returns access to the header of this entry in the archive.
unpack(&mut self, target_base: Option<&Path>, dst: &Path) -> io::Result<Unpacked>437     fn unpack(&mut self, target_base: Option<&Path>, dst: &Path) -> io::Result<Unpacked> {
438         let kind = self.header.entry_type();
439 
440         if kind.is_dir() {
441             self.unpack_dir(dst)?;
442             if let Ok(mode) = self.header.mode() {
443                 set_perms(dst, None, mode, self.preserve_permissions)?;
444             }
445             return Ok(Unpacked::__Nonexhaustive);
446         } else if kind.is_hard_link() || kind.is_symlink() {
447             let src = match self.link_name()? {
448                 Some(name) => name,
449                 None => {
450                     return Err(other(&format!(
451                         "hard link listed for {} but no link name found",
452                         String::from_utf8_lossy(self.header.as_bytes())
453                     )));
454                 }
455             };
456 
457             if src.iter().count() == 0 {
458                 return Err(other(&format!(
459                     "symlink destination for {} is empty",
460                     String::from_utf8_lossy(self.header.as_bytes())
461                 )));
462             }
463 
464             if kind.is_hard_link() {
465                 let link_src = match target_base {
466                     // If we're unpacking within a directory then ensure that
467                     // the destination of this hard link is both present and
468                     // inside our own directory. This is needed because we want
469                     // to make sure to not overwrite anything outside the root.
470                     //
471                     // Note that this logic is only needed for hard links
472                     // currently. With symlinks the `validate_inside_dst` which
473                     // happens before this method as part of `unpack_in` will
474                     // use canonicalization to ensure this guarantee. For hard
475                     // links though they're canonicalized to their existing path
476                     // so we need to validate at this time.
477                     Some(ref p) => {
478                         let link_src = p.join(src);
479                         self.validate_inside_dst(p, &link_src)?;
480                         link_src
481                     }
482                     None => src.into_owned(),
483                 };
484                 fs::hard_link(&link_src, dst).map_err(|err| {
485                     Error::new(
486                         err.kind(),
487                         format!(
488                             "{} when hard linking {} to {}",
489                             err,
490                             link_src.display(),
491                             dst.display()
492                         ),
493                     )
494                 })?;
495             } else {
496                 symlink(&src, dst)
497                     .or_else(|err_io| {
498                         if err_io.kind() == io::ErrorKind::AlreadyExists && self.overwrite {
499                             // remove dest and try once more
500                             std::fs::remove_file(dst).and_then(|()| symlink(&src, dst))
501                         } else {
502                             Err(err_io)
503                         }
504                     })
505                     .map_err(|err| {
506                         Error::new(
507                             err.kind(),
508                             format!(
509                                 "{} when symlinking {} to {}",
510                                 err,
511                                 src.display(),
512                                 dst.display()
513                             ),
514                         )
515                     })?;
516             };
517             return Ok(Unpacked::__Nonexhaustive);
518 
519             #[cfg(target_arch = "wasm32")]
520             #[allow(unused_variables)]
521             fn symlink(src: &Path, dst: &Path) -> io::Result<()> {
522                 Err(io::Error::new(io::ErrorKind::Other, "Not implemented"))
523             }
524 
525             #[cfg(windows)]
526             fn symlink(src: &Path, dst: &Path) -> io::Result<()> {
527                 ::std::os::windows::fs::symlink_file(src, dst)
528             }
529 
530             #[cfg(unix)]
531             fn symlink(src: &Path, dst: &Path) -> io::Result<()> {
532                 ::std::os::unix::fs::symlink(src, dst)
533             }
534         } else if kind.is_pax_global_extensions()
535             || kind.is_pax_local_extensions()
536             || kind.is_gnu_longname()
537             || kind.is_gnu_longlink()
538         {
539             return Ok(Unpacked::__Nonexhaustive);
540         };
541 
542         // Old BSD-tar compatibility.
543         // Names that have a trailing slash should be treated as a directory.
544         // Only applies to old headers.
545         if self.header.as_ustar().is_none() && self.path_bytes().ends_with(b"/") {
546             self.unpack_dir(dst)?;
547             if let Ok(mode) = self.header.mode() {
548                 set_perms(dst, None, mode, self.preserve_permissions)?;
549             }
550             return Ok(Unpacked::__Nonexhaustive);
551         }
552 
553         // Note the lack of `else` clause above. According to the FreeBSD
554         // documentation:
555         //
556         // > A POSIX-compliant implementation must treat any unrecognized
557         // > typeflag value as a regular file.
558         //
559         // As a result if we don't recognize the kind we just write out the file
560         // as we would normally.
561 
562         // Ensure we write a new file rather than overwriting in-place which
563         // is attackable; if an existing file is found unlink it.
564         fn open(dst: &Path) -> io::Result<std::fs::File> {
565             OpenOptions::new().write(true).create_new(true).open(dst)
566         }
567         let mut f = (|| -> io::Result<std::fs::File> {
568             let mut f = open(dst).or_else(|err| {
569                 if err.kind() != ErrorKind::AlreadyExists {
570                     Err(err)
571                 } else if self.overwrite {
572                     match fs::remove_file(dst) {
573                         Ok(()) => open(dst),
574                         Err(ref e) if e.kind() == io::ErrorKind::NotFound => open(dst),
575                         Err(e) => Err(e),
576                     }
577                 } else {
578                     Err(err)
579                 }
580             })?;
581             for io in self.data.drain(..) {
582                 match io {
583                     EntryIo::Data(mut d) => {
584                         let expected = d.limit();
585                         if io::copy(&mut d, &mut f)? != expected {
586                             return Err(other("failed to write entire file"));
587                         }
588                     }
589                     EntryIo::Pad(d) => {
590                         // TODO: checked cast to i64
591                         let to = SeekFrom::Current(d.limit() as i64);
592                         let size = f.seek(to)?;
593                         f.set_len(size)?;
594                     }
595                 }
596             }
597             Ok(f)
598         })()
599         .map_err(|e| {
600             let header = self.header.path_bytes();
601             TarError::new(
602                 &format!(
603                     "failed to unpack `{}` into `{}`",
604                     String::from_utf8_lossy(&header),
605                     dst.display()
606                 ),
607                 e,
608             )
609         })?;
610 
611         if self.preserve_mtime {
612             if let Ok(mtime) = self.header.mtime() {
613                 // For some more information on this see the comments in
614                 // `Header::fill_platform_from`, but the general idea is that
615                 // we're trying to avoid 0-mtime files coming out of archives
616                 // since some tools don't ingest them well. Perhaps one day
617                 // when Cargo stops working with 0-mtime archives we can remove
618                 // this.
619                 let mtime = if mtime == 0 { 1 } else { mtime };
620                 let mtime = FileTime::from_unix_time(mtime as i64, 0);
621                 filetime::set_file_handle_times(&f, Some(mtime), Some(mtime)).map_err(|e| {
622                     TarError::new(&format!("failed to set mtime for `{}`", dst.display()), e)
623                 })?;
624             }
625         }
626         if let Ok(mode) = self.header.mode() {
627             set_perms(dst, Some(&mut f), mode, self.preserve_permissions)?;
628         }
629         if self.unpack_xattrs {
630             set_xattrs(self, dst)?;
631         }
632         return Ok(Unpacked::File(f));
633 
634         fn set_perms(
635             dst: &Path,
636             f: Option<&mut std::fs::File>,
637             mode: u32,
638             preserve: bool,
639         ) -> Result<(), TarError> {
640             _set_perms(dst, f, mode, preserve).map_err(|e| {
641                 TarError::new(
642                     &format!(
643                         "failed to set permissions to {:o} \
644                          for `{}`",
645                         mode,
646                         dst.display()
647                     ),
648                     e,
649                 )
650             })
651         }
652 
653         #[cfg(unix)]
654         fn _set_perms(
655             dst: &Path,
656             f: Option<&mut std::fs::File>,
657             mode: u32,
658             preserve: bool,
659         ) -> io::Result<()> {
660             use std::os::unix::prelude::*;
661 
662             let mode = if preserve { mode } else { mode & 0o777 };
663             let perm = fs::Permissions::from_mode(mode as _);
664             match f {
665                 Some(f) => f.set_permissions(perm),
666                 None => fs::set_permissions(dst, perm),
667             }
668         }
669 
670         #[cfg(windows)]
671         fn _set_perms(
672             dst: &Path,
673             f: Option<&mut std::fs::File>,
674             mode: u32,
675             _preserve: bool,
676         ) -> io::Result<()> {
677             if mode & 0o200 == 0o200 {
678                 return Ok(());
679             }
680             match f {
681                 Some(f) => {
682                     let mut perm = f.metadata()?.permissions();
683                     perm.set_readonly(true);
684                     f.set_permissions(perm)
685                 }
686                 None => {
687                     let mut perm = fs::metadata(dst)?.permissions();
688                     perm.set_readonly(true);
689                     fs::set_permissions(dst, perm)
690                 }
691             }
692         }
693 
694         #[cfg(target_arch = "wasm32")]
695         #[allow(unused_variables)]
696         fn _set_perms(
697             dst: &Path,
698             f: Option<&mut std::fs::File>,
699             mode: u32,
700             _preserve: bool,
701         ) -> io::Result<()> {
702             Err(io::Error::new(io::ErrorKind::Other, "Not implemented"))
703         }
704 
705         #[cfg(all(unix, feature = "xattr"))]
706         fn set_xattrs(me: &mut EntryFields, dst: &Path) -> io::Result<()> {
707             use std::ffi::OsStr;
708             use std::os::unix::prelude::*;
709 
710             let exts = match me.pax_extensions() {
711                 Ok(Some(e)) => e,
712                 _ => return Ok(()),
713             };
714             let exts = exts
715                 .filter_map(|e| e.ok())
716                 .filter_map(|e| {
717                     let key = e.key_bytes();
718                     let prefix = b"SCHILY.xattr.";
719                     if key.starts_with(prefix) {
720                         Some((&key[prefix.len()..], e))
721                     } else {
722                         None
723                     }
724                 })
725                 .map(|(key, e)| (OsStr::from_bytes(key), e.value_bytes()));
726 
727             for (key, value) in exts {
728                 xattr::set(dst, key, value).map_err(|e| {
729                     TarError::new(
730                         &format!(
731                             "failed to set extended \
732                              attributes to {}. \
733                              Xattrs: key={:?}, value={:?}.",
734                             dst.display(),
735                             key,
736                             String::from_utf8_lossy(value)
737                         ),
738                         e,
739                     )
740                 })?;
741             }
742 
743             Ok(())
744         }
745         // Windows does not completely support posix xattrs
746         // https://en.wikipedia.org/wiki/Extended_file_attributes#Windows_NT
747         #[cfg(any(windows, not(feature = "xattr"), target_arch = "wasm32"))]
748         fn set_xattrs(_: &mut EntryFields, _: &Path) -> io::Result<()> {
749             Ok(())
750         }
751     }
752 
validate_inside_dst(&self, dst: &Path, file_dst: &Path) -> io::Result<PathBuf>753     fn validate_inside_dst(&self, dst: &Path, file_dst: &Path) -> io::Result<PathBuf> {
754         // Abort if target (canonical) parent is outside of `dst`
755         let canon_parent = file_dst.canonicalize().map_err(|err| {
756             Error::new(
757                 err.kind(),
758                 format!("{} while canonicalizing {}", err, file_dst.display()),
759             )
760         })?;
761         let canon_target = dst.canonicalize().map_err(|err| {
762             Error::new(
763                 err.kind(),
764                 format!("{} while canonicalizing {}", err, dst.display()),
765             )
766         })?;
767         if !canon_parent.starts_with(&canon_target) {
768             let err = TarError::new(
769                 &format!(
770                     "trying to unpack outside of destination path: {}",
771                     canon_target.display()
772                 ),
773                 // TODO: use ErrorKind::InvalidInput here? (minor breaking change)
774                 Error::new(ErrorKind::Other, "Invalid argument"),
775             );
776             return Err(err.into());
777         }
778         Ok(canon_target)
779     }
780 }
781 
782 impl<'a> Read for EntryFields<'a> {
read(&mut self, into: &mut [u8]) -> io::Result<usize>783     fn read(&mut self, into: &mut [u8]) -> io::Result<usize> {
784         loop {
785             match self.data.get_mut(0).map(|io| io.read(into)) {
786                 Some(Ok(0)) => {
787                     self.data.remove(0);
788                 }
789                 Some(r) => return r,
790                 None => return Ok(0),
791             }
792         }
793     }
794 }
795 
796 impl<'a> Read for EntryIo<'a> {
read(&mut self, into: &mut [u8]) -> io::Result<usize>797     fn read(&mut self, into: &mut [u8]) -> io::Result<usize> {
798         match *self {
799             EntryIo::Pad(ref mut io) => io.read(into),
800             EntryIo::Data(ref mut io) => io.read(into),
801         }
802     }
803 }
804