1#[cfg(feature = "async_support")]
67pub mod async_support;
68mod error;
69mod ffi;
70#[cfg(feature = "futures_support")]
71pub mod futures_support;
72mod iterator;
73#[cfg(feature = "tokio_support")]
74pub mod tokio_support;
75mod zip_preflight;
76
77use error::{archive_result, archive_result_strict};
78pub use error::{Error, Result};
79use io::{Seek, SeekFrom};
80pub use iterator::{ArchiveContents, ArchiveIterator, ArchiveIteratorBuilder, ArchivePassword};
81use std::{
82 ffi::{CStr, CString},
83 io::{self, Read, Write},
84 os::raw::{c_int, c_void},
85 path::{Component, Path},
86 slice,
87};
88
89const READER_BUFFER_SIZE: usize = 16384;
90
91#[cfg(not(target_os = "windows"))]
94pub use libc::stat;
95
96#[cfg(target_os = "windows")]
103#[derive(Copy, Clone)]
104#[repr(C)]
105#[allow(non_camel_case_types)]
106pub struct stat {
107 pub st_dev: libc::dev_t,
108 pub st_ino: libc::ino_t,
109 pub st_mode: u16,
110 pub st_nlink: libc::c_short,
111 pub st_uid: libc::c_short,
112 pub st_gid: libc::c_short,
113 pub st_rdev: libc::dev_t,
114 pub st_size: i32,
115 pub st_atime: libc::time_t,
116 pub st_mtime: libc::time_t,
117 pub st_ctime: libc::time_t,
118}
119
120#[derive(Clone, Debug)]
126pub struct ArchiveEntryInfo {
127 pub path: String,
128 pub size: u64,
129}
130
131#[derive(Clone, Copy, Debug)]
133pub enum Ownership {
134 Preserve,
136 Ignore,
139}
140
141struct ReaderPipe<'a> {
142 reader: &'a mut dyn Read,
143 buffer: &'a mut [u8],
144}
145
146trait ReadAndSeek: Read + Seek {}
147impl<T> ReadAndSeek for T where T: Read + Seek {}
148
149struct SeekableReaderPipe<'a> {
150 reader: &'a mut dyn ReadAndSeek,
151 buffer: &'a mut [u8],
152}
153
154pub type DecodeCallback = fn(&[u8]) -> Result<String>;
155
156pub(crate) fn decode_utf8(bytes: &[u8]) -> Result<String> {
157 Ok(std::str::from_utf8(bytes)?.to_owned())
158}
159
160pub fn list_archive_files_with_encoding<R>(source: R, decode: DecodeCallback) -> Result<Vec<String>>
176where
177 R: Read + Seek,
178{
179 Ok(list_archive_entries_with_encoding(source, decode)?
180 .into_iter()
181 .map(|e| e.path)
182 .collect())
183}
184
185pub fn list_archive_files<R>(source: R) -> Result<Vec<String>>
200where
201 R: Read + Seek,
202{
203 list_archive_files_with_encoding(source, decode_utf8)
204}
205
206pub fn list_archive_entries_with_encoding<R>(
228 source: R,
229 decode: DecodeCallback,
230) -> Result<Vec<ArchiveEntryInfo>>
231where
232 R: Read + Seek,
233{
234 let _utf8_guard = ffi::UTF8LocaleGuard::new();
235 run_with_archive(
236 Ownership::Ignore,
237 source,
238 |archive_reader, _, mut entry| unsafe {
239 let mut entries = Vec::new();
240 loop {
241 match ffi::archive_read_next_header(archive_reader, &mut entry) {
242 ffi::ARCHIVE_EOF => return Ok(entries),
243 value => archive_result(value, archive_reader)?,
244 }
245
246 let _utf8_guard = ffi::WindowsUTF8LocaleGuard::new();
247 let cstr = libarchive_entry_pathname(entry)?;
248 let path = decode(cstr.to_bytes())?;
249 let size = libarchive_entry_size(entry);
250 entries.push(ArchiveEntryInfo { path, size });
251 }
252 },
253 )
254}
255
256pub fn list_archive_entries<R>(source: R) -> Result<Vec<ArchiveEntryInfo>>
277where
278 R: Read + Seek,
279{
280 list_archive_entries_with_encoding(source, decode_utf8)
281}
282
283pub fn uncompress_data<R, W>(source: R, target: W) -> Result<usize>
315where
316 R: Read,
317 W: Write,
318{
319 let _utf8_guard = ffi::UTF8LocaleGuard::new();
320 run_with_unseekable_archive(source, |archive_reader, _, mut entry| unsafe {
321 archive_result(
322 ffi::archive_read_next_header(archive_reader, &mut entry),
323 archive_reader,
324 )?;
325 libarchive_write_data_block(archive_reader, target)
326 })
327}
328
329pub fn uncompress_archive_with_encoding<R>(
349 source: R,
350 dest: &Path,
351 ownership: Ownership,
352 decode: DecodeCallback,
353) -> Result<()>
354where
355 R: Read + Seek,
356{
357 let _utf8_guard = ffi::UTF8LocaleGuard::new();
358 run_with_archive(
359 ownership,
360 source,
361 |archive_reader, archive_writer, mut entry| unsafe {
362 loop {
363 match ffi::archive_read_next_header(archive_reader, &mut entry) {
364 ffi::ARCHIVE_EOF => return Ok(()),
365 value => archive_result(value, archive_reader)?,
366 }
367
368 let _utf8_guard = ffi::WindowsUTF8LocaleGuard::new();
369 let cstr = libarchive_entry_pathname(entry)?;
370 let target_path = CString::new(
371 dest.join(sanitize_destination_path(Path::new(&decode(
372 cstr.to_bytes(),
373 )?))?)
374 .to_str()
375 .unwrap(),
376 )
377 .unwrap();
378
379 ffi::archive_entry_set_pathname(entry, target_path.as_ptr());
380
381 let link_name = ffi::archive_entry_hardlink(entry);
382 if !link_name.is_null() {
383 let target_path = CString::new(
384 dest.join(sanitize_destination_path(Path::new(&decode(
385 CStr::from_ptr(link_name).to_bytes(),
386 )?))?)
387 .to_str()
388 .unwrap(),
389 )
390 .unwrap();
391
392 ffi::archive_entry_set_hardlink(entry, target_path.as_ptr());
393 }
394
395 archive_result_strict(
396 ffi::archive_write_header(archive_writer, entry),
397 archive_writer,
398 )?;
399 if !libarchive_entry_is_dir(entry) {
400 libarchive_copy_data(archive_reader, archive_writer)?;
401 }
402
403 archive_result_strict(
404 ffi::archive_write_finish_entry(archive_writer),
405 archive_writer,
406 )?;
407 }
408 },
409 )
410}
411
412pub fn uncompress_archive<R>(source: R, dest: &Path, ownership: Ownership) -> Result<()>
431where
432 R: Read + Seek,
433{
434 uncompress_archive_with_encoding(source, dest, ownership, decode_utf8)
435}
436
437pub fn uncompress_archive_file_with_encoding<R, W>(
457 source: R,
458 target: W,
459 path: &str,
460 decode: DecodeCallback,
461) -> Result<usize>
462where
463 R: Read + Seek,
464 W: Write,
465{
466 let _utf8_guard = ffi::UTF8LocaleGuard::new();
467 run_with_archive(
468 Ownership::Ignore,
469 source,
470 |archive_reader, _, mut entry| unsafe {
471 loop {
472 match ffi::archive_read_next_header(archive_reader, &mut entry) {
473 ffi::ARCHIVE_EOF => {
474 return Err(io::Error::new(
475 io::ErrorKind::NotFound,
476 format!("path {} doesn't exist inside archive", path),
477 )
478 .into())
479 }
480 value => archive_result(value, archive_reader)?,
481 }
482
483 let _utf8_guard = ffi::WindowsUTF8LocaleGuard::new();
484 let cstr = libarchive_entry_pathname(entry)?;
485 let file_name = decode(cstr.to_bytes())?;
486 if file_name == path {
487 break;
488 }
489 }
490
491 if libarchive_entry_is_dir(entry) {
492 return Ok(0);
493 }
494 libarchive_write_data_block(archive_reader, target)
495 },
496 )
497}
498
499pub fn uncompress_archive_file<R, W>(source: R, target: W, path: &str) -> Result<usize>
518where
519 R: Read + Seek,
520 W: Write,
521{
522 uncompress_archive_file_with_encoding(source, target, path, decode_utf8)
523}
524
525fn run_with_archive<F, R, T>(ownership: Ownership, mut reader: R, f: F) -> Result<T>
526where
527 F: FnOnce(*mut ffi::archive, *mut ffi::archive, *mut ffi::archive_entry) -> Result<T>,
528 R: Read + Seek,
529{
530 let _utf8_guard = ffi::UTF8LocaleGuard::new();
531 reader.seek(SeekFrom::Start(0))?;
533 zip_preflight::reject_unsupported_zip_methods(&mut reader)?;
534 unsafe {
535 let archive_entry: *mut ffi::archive_entry = std::ptr::null_mut();
536 let archive_reader = ffi::archive_read_new();
537 let archive_writer = ffi::archive_write_disk_new();
538
539 let res = (|| {
540 archive_result(
541 ffi::archive_read_support_filter_all(archive_reader),
542 archive_reader,
543 )?;
544
545 archive_result(
546 ffi::archive_read_set_seek_callback(archive_reader, Some(libarchive_seek_callback)),
547 archive_reader,
548 )?;
549
550 let mut writer_flags = ffi::ARCHIVE_EXTRACT_TIME
551 | ffi::ARCHIVE_EXTRACT_PERM
552 | ffi::ARCHIVE_EXTRACT_ACL
553 | ffi::ARCHIVE_EXTRACT_FFLAGS
554 | ffi::ARCHIVE_EXTRACT_XATTR;
555
556 if let Ownership::Preserve = ownership {
557 writer_flags |= ffi::ARCHIVE_EXTRACT_OWNER;
558 };
559
560 archive_result(
561 ffi::archive_write_disk_set_options(archive_writer, writer_flags as i32),
562 archive_writer,
563 )?;
564 archive_result(
565 ffi::archive_write_disk_set_standard_lookup(archive_writer),
566 archive_writer,
567 )?;
568 archive_result(
569 ffi::archive_read_support_format_all(archive_reader),
570 archive_reader,
571 )?;
572
573 if archive_reader.is_null() || archive_writer.is_null() {
574 return Err(Error::NullArchive);
575 }
576
577 let mut pipe = SeekableReaderPipe {
578 reader: &mut reader,
579 buffer: &mut [0; READER_BUFFER_SIZE],
580 };
581
582 archive_result(
583 ffi::archive_read_open(
584 archive_reader,
585 std::ptr::addr_of_mut!(pipe) as *mut c_void,
586 None,
587 Some(libarchive_seekable_read_callback),
588 None,
589 ),
590 archive_reader,
591 )?;
592
593 f(archive_reader, archive_writer, archive_entry)
594 })();
595
596 archive_result(ffi::archive_read_close(archive_reader), archive_reader)?;
597 archive_result(ffi::archive_read_free(archive_reader), archive_reader)?;
598
599 archive_result(ffi::archive_write_close(archive_writer), archive_writer)?;
600 archive_result(ffi::archive_write_free(archive_writer), archive_writer)?;
601
602 ffi::archive_entry_free(archive_entry);
603
604 res
605 }
606}
607
608fn run_with_unseekable_archive<F, R, T>(mut reader: R, f: F) -> Result<T>
609where
610 F: FnOnce(*mut ffi::archive, *mut ffi::archive, *mut ffi::archive_entry) -> Result<T>,
611 R: Read,
612{
613 let _utf8_guard = ffi::UTF8LocaleGuard::new();
614 unsafe {
615 let archive_entry: *mut ffi::archive_entry = std::ptr::null_mut();
616 let archive_reader = ffi::archive_read_new();
617 let archive_writer = ffi::archive_write_disk_new();
618
619 let res = (|| {
620 archive_result(
621 ffi::archive_read_support_filter_all(archive_reader),
622 archive_reader,
623 )?;
624
625 archive_result(
626 ffi::archive_read_support_format_raw(archive_reader),
627 archive_reader,
628 )?;
629
630 if archive_reader.is_null() || archive_writer.is_null() {
631 return Err(Error::NullArchive);
632 }
633
634 let mut pipe = ReaderPipe {
635 reader: &mut reader,
636 buffer: &mut [0; READER_BUFFER_SIZE],
637 };
638
639 archive_result(
640 ffi::archive_read_open(
641 archive_reader,
642 std::ptr::addr_of_mut!(pipe) as *mut c_void,
643 None,
644 Some(libarchive_read_callback),
645 None,
646 ),
647 archive_reader,
648 )?;
649
650 f(archive_reader, archive_writer, archive_entry)
651 })();
652
653 archive_result(ffi::archive_read_close(archive_reader), archive_reader)?;
654 archive_result(ffi::archive_read_free(archive_reader), archive_reader)?;
655
656 archive_result(ffi::archive_write_close(archive_writer), archive_writer)?;
657 archive_result(ffi::archive_write_free(archive_writer), archive_writer)?;
658
659 ffi::archive_entry_free(archive_entry);
660
661 res
662 }
663}
664
665fn sanitize_destination_path(dest: &Path) -> Result<&Path> {
672 let dest = dest.strip_prefix("/").unwrap_or(dest);
673
674 dest.components()
675 .find(|c| c == &Component::ParentDir)
676 .map_or(Ok(dest), |_| {
677 Err(std::io::Error::new(
678 std::io::ErrorKind::InvalidData,
679 "cannot use relative destination directory",
680 )
681 .into())
682 })
683}
684
685fn libarchive_copy_data(
686 archive_reader: *mut ffi::archive,
687 archive_writer: *mut ffi::archive,
688) -> Result<()> {
689 let mut buffer = std::ptr::null();
690 let mut offset = 0;
691 let mut size = 0;
692
693 unsafe {
694 loop {
695 match ffi::archive_read_data_block(archive_reader, &mut buffer, &mut size, &mut offset)
696 {
697 ffi::ARCHIVE_EOF => return Ok(()),
698 value => archive_result(value, archive_reader)?,
699 }
700
701 archive_result_strict(
702 match ffi::archive_write_data_block(archive_writer, buffer, size, offset) {
706 x if x >= 0 => 0,
707 x => i32::try_from(x).unwrap(),
708 },
709 archive_writer,
710 )?;
711 }
712 }
713}
714
715fn libarchive_entry_size(entry: *mut ffi::archive_entry) -> u64 {
716 let size = unsafe { (*ffi::archive_entry_stat(entry)).st_size } as i64;
719 size.max(0) as u64
720}
721
722pub(crate) fn libarchive_entry_is_dir(entry: *mut ffi::archive_entry) -> bool {
725 const S_IFMT: u32 = 0o170000;
726 const S_IFDIR: u32 = 0o040000;
727 let mode = unsafe { (*ffi::archive_entry_stat(entry)).st_mode } as u32;
728 (mode & S_IFMT) == S_IFDIR
729}
730
731fn libarchive_entry_pathname<'a>(entry: *mut ffi::archive_entry) -> Result<&'a CStr> {
732 let pathname = unsafe { ffi::archive_entry_pathname(entry) };
733 if pathname.is_null() {
734 return Err(io::Error::new(
735 io::ErrorKind::InvalidData,
736 "archive entry has unreadable filename.".to_string(),
737 )
738 .into());
739 }
740
741 Ok(unsafe { CStr::from_ptr(pathname) })
742}
743
744unsafe fn libarchive_write_data_block<W>(
745 archive_reader: *mut ffi::archive,
746 mut target: W,
747) -> Result<usize>
748where
749 W: Write,
750{
751 let mut buffer = std::ptr::null();
752 let mut offset = 0;
753 let mut size = 0;
754 let mut written = 0;
755
756 loop {
757 match ffi::archive_read_data_block(archive_reader, &mut buffer, &mut size, &mut offset) {
758 ffi::ARCHIVE_EOF => return Ok(written),
759 value => archive_result(value, archive_reader)?,
760 }
761
762 if size == 0 {
763 continue;
764 }
765
766 let content = slice::from_raw_parts(buffer as *const u8, size);
767 target.write_all(content)?;
768 written += size;
769 }
770}
771
772unsafe extern "C" fn libarchive_seek_callback(
773 _: *mut ffi::archive,
774 client_data: *mut c_void,
775 offset: ffi::la_int64_t,
776 whence: c_int,
777) -> i64 {
778 let pipe = (client_data as *mut SeekableReaderPipe).as_mut().unwrap();
779 let whence = match whence {
780 0 => SeekFrom::Start(offset as u64),
781 1 => SeekFrom::Current(offset),
782 2 => SeekFrom::End(offset),
783 _ => return -1,
784 };
785
786 match pipe.reader.seek(whence) {
787 Ok(offset) => offset as i64,
788 Err(_) => -1,
789 }
790}
791
792unsafe extern "C" fn libarchive_seekable_read_callback(
793 archive: *mut ffi::archive,
794 client_data: *mut c_void,
795 buffer: *mut *const c_void,
796) -> ffi::la_ssize_t {
797 let pipe = (client_data as *mut SeekableReaderPipe).as_mut().unwrap();
798
799 *buffer = pipe.buffer.as_ptr() as *const c_void;
800
801 match pipe.reader.read(pipe.buffer) {
802 Ok(size) => size as ffi::la_ssize_t,
803 Err(e) => {
804 let description = CString::new(e.to_string()).unwrap();
805
806 ffi::archive_set_error(archive, e.raw_os_error().unwrap_or(0), description.as_ptr());
807
808 -1
809 }
810 }
811}
812
813unsafe extern "C" fn libarchive_read_callback(
814 archive: *mut ffi::archive,
815 client_data: *mut c_void,
816 buffer: *mut *const c_void,
817) -> ffi::la_ssize_t {
818 let pipe = (client_data as *mut ReaderPipe).as_mut().unwrap();
819
820 *buffer = pipe.buffer.as_ptr() as *const c_void;
821
822 match pipe.reader.read(pipe.buffer) {
823 Ok(size) => size as ffi::la_ssize_t,
824 Err(e) => {
825 let description = CString::new(e.to_string()).unwrap();
826
827 ffi::archive_set_error(archive, e.raw_os_error().unwrap_or(0), description.as_ptr());
828
829 -1
830 }
831 }
832}