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