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 let mtime = FileTime::from_unix_time(mtime as i64, 0); 614 filetime::set_file_handle_times(&f, Some(mtime), Some(mtime)).map_err(|e| { 615 TarError::new(&format!("failed to set mtime for `{}`", dst.display()), e) 616 })?; 617 } 618 } 619 if let Ok(mode) = self.header.mode() { 620 set_perms(dst, Some(&mut f), mode, self.preserve_permissions)?; 621 } 622 if self.unpack_xattrs { 623 set_xattrs(self, dst)?; 624 } 625 return Ok(Unpacked::File(f)); 626 627 fn set_perms( 628 dst: &Path, 629 f: Option<&mut std::fs::File>, 630 mode: u32, 631 preserve: bool, 632 ) -> Result<(), TarError> { 633 _set_perms(dst, f, mode, preserve).map_err(|e| { 634 TarError::new( 635 &format!( 636 "failed to set permissions to {:o} \ 637 for `{}`", 638 mode, 639 dst.display() 640 ), 641 e, 642 ) 643 }) 644 } 645 646 #[cfg(unix)] 647 fn _set_perms( 648 dst: &Path, 649 f: Option<&mut std::fs::File>, 650 mode: u32, 651 preserve: bool, 652 ) -> io::Result<()> { 653 use std::os::unix::prelude::*; 654 655 let mode = if preserve { mode } else { mode & 0o777 }; 656 let perm = fs::Permissions::from_mode(mode as _); 657 match f { 658 Some(f) => f.set_permissions(perm), 659 None => fs::set_permissions(dst, perm), 660 } 661 } 662 663 #[cfg(windows)] 664 fn _set_perms( 665 dst: &Path, 666 f: Option<&mut std::fs::File>, 667 mode: u32, 668 _preserve: bool, 669 ) -> io::Result<()> { 670 if mode & 0o200 == 0o200 { 671 return Ok(()); 672 } 673 match f { 674 Some(f) => { 675 let mut perm = f.metadata()?.permissions(); 676 perm.set_readonly(true); 677 f.set_permissions(perm) 678 } 679 None => { 680 let mut perm = fs::metadata(dst)?.permissions(); 681 perm.set_readonly(true); 682 fs::set_permissions(dst, perm) 683 } 684 } 685 } 686 687 #[cfg(target_arch = "wasm32")] 688 #[allow(unused_variables)] 689 fn _set_perms( 690 dst: &Path, 691 f: Option<&mut std::fs::File>, 692 mode: u32, 693 _preserve: bool, 694 ) -> io::Result<()> { 695 Err(io::Error::new(io::ErrorKind::Other, "Not implemented")) 696 } 697 698 #[cfg(all(unix, feature = "xattr"))] 699 fn set_xattrs(me: &mut EntryFields, dst: &Path) -> io::Result<()> { 700 use std::ffi::OsStr; 701 use std::os::unix::prelude::*; 702 703 let exts = match me.pax_extensions() { 704 Ok(Some(e)) => e, 705 _ => return Ok(()), 706 }; 707 let exts = exts 708 .filter_map(|e| e.ok()) 709 .filter_map(|e| { 710 let key = e.key_bytes(); 711 let prefix = b"SCHILY.xattr."; 712 if key.starts_with(prefix) { 713 Some((&key[prefix.len()..], e)) 714 } else { 715 None 716 } 717 }) 718 .map(|(key, e)| (OsStr::from_bytes(key), e.value_bytes())); 719 720 for (key, value) in exts { 721 xattr::set(dst, key, value).map_err(|e| { 722 TarError::new( 723 &format!( 724 "failed to set extended \ 725 attributes to {}. \ 726 Xattrs: key={:?}, value={:?}.", 727 dst.display(), 728 key, 729 String::from_utf8_lossy(value) 730 ), 731 e, 732 ) 733 })?; 734 } 735 736 Ok(()) 737 } 738 // Windows does not completely support posix xattrs 739 // https://en.wikipedia.org/wiki/Extended_file_attributes#Windows_NT 740 #[cfg(any(windows, not(feature = "xattr"), target_arch = "wasm32"))] 741 fn set_xattrs(_: &mut EntryFields, _: &Path) -> io::Result<()> { 742 Ok(()) 743 } 744 } 745 validate_inside_dst(&self, dst: &Path, file_dst: &Path) -> io::Result<PathBuf>746 fn validate_inside_dst(&self, dst: &Path, file_dst: &Path) -> io::Result<PathBuf> { 747 // Abort if target (canonical) parent is outside of `dst` 748 let canon_parent = file_dst.canonicalize().map_err(|err| { 749 Error::new( 750 err.kind(), 751 format!("{} while canonicalizing {}", err, file_dst.display()), 752 ) 753 })?; 754 let canon_target = dst.canonicalize().map_err(|err| { 755 Error::new( 756 err.kind(), 757 format!("{} while canonicalizing {}", err, dst.display()), 758 ) 759 })?; 760 if !canon_parent.starts_with(&canon_target) { 761 let err = TarError::new( 762 &format!( 763 "trying to unpack outside of destination path: {}", 764 canon_target.display() 765 ), 766 // TODO: use ErrorKind::InvalidInput here? (minor breaking change) 767 Error::new(ErrorKind::Other, "Invalid argument"), 768 ); 769 return Err(err.into()); 770 } 771 Ok(canon_target) 772 } 773 } 774 775 impl<'a> Read for EntryFields<'a> { read(&mut self, into: &mut [u8]) -> io::Result<usize>776 fn read(&mut self, into: &mut [u8]) -> io::Result<usize> { 777 loop { 778 match self.data.get_mut(0).map(|io| io.read(into)) { 779 Some(Ok(0)) => { 780 self.data.remove(0); 781 } 782 Some(r) => return r, 783 None => return Ok(0), 784 } 785 } 786 } 787 } 788 789 impl<'a> Read for EntryIo<'a> { read(&mut self, into: &mut [u8]) -> io::Result<usize>790 fn read(&mut self, into: &mut [u8]) -> io::Result<usize> { 791 match *self { 792 EntryIo::Pad(ref mut io) => io.read(into), 793 EntryIo::Data(ref mut io) => io.read(into), 794 } 795 } 796 } 797