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