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