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