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