#[cfg(feature = "async_support")]
pub mod async_support;
mod error;
mod ffi;
#[cfg(feature = "futures_support")]
pub mod futures_support;
mod iterator;
#[cfg(feature = "tokio_support")]
pub mod tokio_support;
mod zip_preflight;
use error::{archive_result, archive_result_strict};
pub use error::{Error, Result};
use io::{Seek, SeekFrom};
pub use iterator::{ArchiveContents, ArchiveIterator, ArchiveIteratorBuilder, ArchivePassword};
use std::{
ffi::{CStr, CString},
io::{self, Read, Write},
os::raw::{c_int, c_void},
path::{Component, Path},
slice,
};
const READER_BUFFER_SIZE: usize = 16384;
#[cfg(not(target_os = "windows"))]
pub use libc::stat;
#[cfg(target_os = "windows")]
#[derive(Copy, Clone)]
#[repr(C)]
#[allow(non_camel_case_types)]
pub struct stat {
pub st_dev: libc::dev_t,
pub st_ino: libc::ino_t,
pub st_mode: u16,
pub st_nlink: libc::c_short,
pub st_uid: libc::c_short,
pub st_gid: libc::c_short,
pub st_rdev: libc::dev_t,
pub st_size: i32,
pub st_atime: libc::time_t,
pub st_mtime: libc::time_t,
pub st_ctime: libc::time_t,
}
#[derive(Clone, Debug)]
pub struct ArchiveEntryInfo {
pub path: String,
pub size: u64,
}
#[derive(Clone, Copy, Debug)]
pub enum Ownership {
Preserve,
Ignore,
}
struct ReaderPipe<'a> {
reader: &'a mut dyn Read,
buffer: &'a mut [u8],
}
trait ReadAndSeek: Read + Seek {}
impl<T> ReadAndSeek for T where T: Read + Seek {}
struct SeekableReaderPipe<'a> {
reader: &'a mut dyn ReadAndSeek,
buffer: &'a mut [u8],
}
pub type DecodeCallback = fn(&[u8]) -> Result<String>;
pub(crate) fn decode_utf8(bytes: &[u8]) -> Result<String> {
Ok(std::str::from_utf8(bytes)?.to_owned())
}
pub fn list_archive_files_with_encoding<R>(source: R, decode: DecodeCallback) -> Result<Vec<String>>
where
R: Read + Seek,
{
Ok(list_archive_entries_with_encoding(source, decode)?
.into_iter()
.map(|e| e.path)
.collect())
}
pub fn list_archive_files<R>(source: R) -> Result<Vec<String>>
where
R: Read + Seek,
{
list_archive_files_with_encoding(source, decode_utf8)
}
pub fn list_archive_entries_with_encoding<R>(
source: R,
decode: DecodeCallback,
) -> Result<Vec<ArchiveEntryInfo>>
where
R: Read + Seek,
{
let _utf8_guard = ffi::UTF8LocaleGuard::new();
run_with_archive(
Ownership::Ignore,
source,
|archive_reader, _, mut entry| unsafe {
let mut entries = Vec::new();
loop {
match ffi::archive_read_next_header(archive_reader, &mut entry) {
ffi::ARCHIVE_EOF => return Ok(entries),
value => archive_result(value, archive_reader)?,
}
let _utf8_guard = ffi::WindowsUTF8LocaleGuard::new();
let cstr = libarchive_entry_pathname(entry)?;
let path = decode(cstr.to_bytes())?;
let size = libarchive_entry_size(entry);
entries.push(ArchiveEntryInfo { path, size });
}
},
)
}
pub fn list_archive_entries<R>(source: R) -> Result<Vec<ArchiveEntryInfo>>
where
R: Read + Seek,
{
list_archive_entries_with_encoding(source, decode_utf8)
}
pub fn uncompress_data<R, W>(source: R, target: W) -> Result<usize>
where
R: Read,
W: Write,
{
let _utf8_guard = ffi::UTF8LocaleGuard::new();
run_with_unseekable_archive(source, |archive_reader, _, mut entry| unsafe {
archive_result(
ffi::archive_read_next_header(archive_reader, &mut entry),
archive_reader,
)?;
libarchive_write_data_block(archive_reader, target)
})
}
pub fn uncompress_archive_with_encoding<R>(
source: R,
dest: &Path,
ownership: Ownership,
decode: DecodeCallback,
) -> Result<()>
where
R: Read + Seek,
{
let _utf8_guard = ffi::UTF8LocaleGuard::new();
run_with_archive(
ownership,
source,
|archive_reader, archive_writer, mut entry| unsafe {
loop {
match ffi::archive_read_next_header(archive_reader, &mut entry) {
ffi::ARCHIVE_EOF => return Ok(()),
value => archive_result(value, archive_reader)?,
}
let _utf8_guard = ffi::WindowsUTF8LocaleGuard::new();
let cstr = libarchive_entry_pathname(entry)?;
let target_path = CString::new(
dest.join(sanitize_destination_path(Path::new(&decode(
cstr.to_bytes(),
)?))?)
.to_str()
.unwrap(),
)
.unwrap();
ffi::archive_entry_set_pathname(entry, target_path.as_ptr());
let link_name = ffi::archive_entry_hardlink(entry);
if !link_name.is_null() {
let target_path = CString::new(
dest.join(sanitize_destination_path(Path::new(&decode(
CStr::from_ptr(link_name).to_bytes(),
)?))?)
.to_str()
.unwrap(),
)
.unwrap();
ffi::archive_entry_set_hardlink(entry, target_path.as_ptr());
}
archive_result_strict(
ffi::archive_write_header(archive_writer, entry),
archive_writer,
)?;
if !libarchive_entry_is_dir(entry) {
libarchive_copy_data(archive_reader, archive_writer)?;
}
archive_result_strict(
ffi::archive_write_finish_entry(archive_writer),
archive_writer,
)?;
}
},
)
}
pub fn uncompress_archive<R>(source: R, dest: &Path, ownership: Ownership) -> Result<()>
where
R: Read + Seek,
{
uncompress_archive_with_encoding(source, dest, ownership, decode_utf8)
}
pub fn uncompress_archive_file_with_encoding<R, W>(
source: R,
target: W,
path: &str,
decode: DecodeCallback,
) -> Result<usize>
where
R: Read + Seek,
W: Write,
{
let _utf8_guard = ffi::UTF8LocaleGuard::new();
run_with_archive(
Ownership::Ignore,
source,
|archive_reader, _, mut entry| unsafe {
loop {
match ffi::archive_read_next_header(archive_reader, &mut entry) {
ffi::ARCHIVE_EOF => {
return Err(io::Error::new(
io::ErrorKind::NotFound,
format!("path {} doesn't exist inside archive", path),
)
.into())
}
value => archive_result(value, archive_reader)?,
}
let _utf8_guard = ffi::WindowsUTF8LocaleGuard::new();
let cstr = libarchive_entry_pathname(entry)?;
let file_name = decode(cstr.to_bytes())?;
if file_name == path {
break;
}
}
if libarchive_entry_is_dir(entry) {
return Ok(0);
}
libarchive_write_data_block(archive_reader, target)
},
)
}
pub fn uncompress_archive_file<R, W>(source: R, target: W, path: &str) -> Result<usize>
where
R: Read + Seek,
W: Write,
{
uncompress_archive_file_with_encoding(source, target, path, decode_utf8)
}
fn run_with_archive<F, R, T>(ownership: Ownership, mut reader: R, f: F) -> Result<T>
where
F: FnOnce(*mut ffi::archive, *mut ffi::archive, *mut ffi::archive_entry) -> Result<T>,
R: Read + Seek,
{
let _utf8_guard = ffi::UTF8LocaleGuard::new();
reader.seek(SeekFrom::Start(0))?;
zip_preflight::reject_unsupported_zip_methods(&mut reader)?;
unsafe {
let archive_entry: *mut ffi::archive_entry = std::ptr::null_mut();
let archive_reader = ffi::archive_read_new();
let archive_writer = ffi::archive_write_disk_new();
let res = (|| {
archive_result(
ffi::archive_read_support_filter_all(archive_reader),
archive_reader,
)?;
archive_result(
ffi::archive_read_set_seek_callback(archive_reader, Some(libarchive_seek_callback)),
archive_reader,
)?;
let mut writer_flags = ffi::ARCHIVE_EXTRACT_TIME
| ffi::ARCHIVE_EXTRACT_PERM
| ffi::ARCHIVE_EXTRACT_ACL
| ffi::ARCHIVE_EXTRACT_FFLAGS
| ffi::ARCHIVE_EXTRACT_XATTR;
if let Ownership::Preserve = ownership {
writer_flags |= ffi::ARCHIVE_EXTRACT_OWNER;
};
archive_result(
ffi::archive_write_disk_set_options(archive_writer, writer_flags as i32),
archive_writer,
)?;
archive_result(
ffi::archive_write_disk_set_standard_lookup(archive_writer),
archive_writer,
)?;
archive_result(
ffi::archive_read_support_format_all(archive_reader),
archive_reader,
)?;
if archive_reader.is_null() || archive_writer.is_null() {
return Err(Error::NullArchive);
}
let mut pipe = SeekableReaderPipe {
reader: &mut reader,
buffer: &mut [0; READER_BUFFER_SIZE],
};
archive_result(
ffi::archive_read_open(
archive_reader,
std::ptr::addr_of_mut!(pipe) as *mut c_void,
None,
Some(libarchive_seekable_read_callback),
None,
),
archive_reader,
)?;
f(archive_reader, archive_writer, archive_entry)
})();
archive_result(ffi::archive_read_close(archive_reader), archive_reader)?;
archive_result(ffi::archive_read_free(archive_reader), archive_reader)?;
archive_result(ffi::archive_write_close(archive_writer), archive_writer)?;
archive_result(ffi::archive_write_free(archive_writer), archive_writer)?;
ffi::archive_entry_free(archive_entry);
res
}
}
fn run_with_unseekable_archive<F, R, T>(mut reader: R, f: F) -> Result<T>
where
F: FnOnce(*mut ffi::archive, *mut ffi::archive, *mut ffi::archive_entry) -> Result<T>,
R: Read,
{
let _utf8_guard = ffi::UTF8LocaleGuard::new();
unsafe {
let archive_entry: *mut ffi::archive_entry = std::ptr::null_mut();
let archive_reader = ffi::archive_read_new();
let archive_writer = ffi::archive_write_disk_new();
let res = (|| {
archive_result(
ffi::archive_read_support_filter_all(archive_reader),
archive_reader,
)?;
archive_result(
ffi::archive_read_support_format_raw(archive_reader),
archive_reader,
)?;
if archive_reader.is_null() || archive_writer.is_null() {
return Err(Error::NullArchive);
}
let mut pipe = ReaderPipe {
reader: &mut reader,
buffer: &mut [0; READER_BUFFER_SIZE],
};
archive_result(
ffi::archive_read_open(
archive_reader,
std::ptr::addr_of_mut!(pipe) as *mut c_void,
None,
Some(libarchive_read_callback),
None,
),
archive_reader,
)?;
f(archive_reader, archive_writer, archive_entry)
})();
archive_result(ffi::archive_read_close(archive_reader), archive_reader)?;
archive_result(ffi::archive_read_free(archive_reader), archive_reader)?;
archive_result(ffi::archive_write_close(archive_writer), archive_writer)?;
archive_result(ffi::archive_write_free(archive_writer), archive_writer)?;
ffi::archive_entry_free(archive_entry);
res
}
}
fn sanitize_destination_path(dest: &Path) -> Result<&Path> {
let dest = dest.strip_prefix("/").unwrap_or(dest);
dest.components()
.find(|c| c == &Component::ParentDir)
.map_or(Ok(dest), |_| {
Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
"cannot use relative destination directory",
)
.into())
})
}
fn libarchive_copy_data(
archive_reader: *mut ffi::archive,
archive_writer: *mut ffi::archive,
) -> Result<()> {
let mut buffer = std::ptr::null();
let mut offset = 0;
let mut size = 0;
unsafe {
loop {
match ffi::archive_read_data_block(archive_reader, &mut buffer, &mut size, &mut offset)
{
ffi::ARCHIVE_EOF => return Ok(()),
value => archive_result(value, archive_reader)?,
}
archive_result_strict(
match ffi::archive_write_data_block(archive_writer, buffer, size, offset) {
x if x >= 0 => 0,
x => i32::try_from(x).unwrap(),
},
archive_writer,
)?;
}
}
}
fn libarchive_entry_size(entry: *mut ffi::archive_entry) -> u64 {
let size = unsafe { (*ffi::archive_entry_stat(entry)).st_size } as i64;
size.max(0) as u64
}
pub(crate) fn libarchive_entry_is_dir(entry: *mut ffi::archive_entry) -> bool {
const S_IFMT: u32 = 0o170000;
const S_IFDIR: u32 = 0o040000;
let mode = unsafe { (*ffi::archive_entry_stat(entry)).st_mode } as u32;
(mode & S_IFMT) == S_IFDIR
}
fn libarchive_entry_pathname<'a>(entry: *mut ffi::archive_entry) -> Result<&'a CStr> {
let pathname = unsafe { ffi::archive_entry_pathname(entry) };
if pathname.is_null() {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"archive entry has unreadable filename.".to_string(),
)
.into());
}
Ok(unsafe { CStr::from_ptr(pathname) })
}
unsafe fn libarchive_write_data_block<W>(
archive_reader: *mut ffi::archive,
mut target: W,
) -> Result<usize>
where
W: Write,
{
let mut buffer = std::ptr::null();
let mut offset = 0;
let mut size = 0;
let mut written = 0;
loop {
match ffi::archive_read_data_block(archive_reader, &mut buffer, &mut size, &mut offset) {
ffi::ARCHIVE_EOF => return Ok(written),
value => archive_result(value, archive_reader)?,
}
if size == 0 {
continue;
}
let content = slice::from_raw_parts(buffer as *const u8, size);
target.write_all(content)?;
written += size;
}
}
unsafe extern "C" fn libarchive_seek_callback(
_: *mut ffi::archive,
client_data: *mut c_void,
offset: ffi::la_int64_t,
whence: c_int,
) -> i64 {
let pipe = (client_data as *mut SeekableReaderPipe).as_mut().unwrap();
let whence = match whence {
0 => SeekFrom::Start(offset as u64),
1 => SeekFrom::Current(offset),
2 => SeekFrom::End(offset),
_ => return -1,
};
match pipe.reader.seek(whence) {
Ok(offset) => offset as i64,
Err(_) => -1,
}
}
unsafe extern "C" fn libarchive_seekable_read_callback(
archive: *mut ffi::archive,
client_data: *mut c_void,
buffer: *mut *const c_void,
) -> ffi::la_ssize_t {
let pipe = (client_data as *mut SeekableReaderPipe).as_mut().unwrap();
*buffer = pipe.buffer.as_ptr() as *const c_void;
match pipe.reader.read(pipe.buffer) {
Ok(size) => size as ffi::la_ssize_t,
Err(e) => {
let description = CString::new(e.to_string()).unwrap();
ffi::archive_set_error(archive, e.raw_os_error().unwrap_or(0), description.as_ptr());
-1
}
}
}
unsafe extern "C" fn libarchive_read_callback(
archive: *mut ffi::archive,
client_data: *mut c_void,
buffer: *mut *const c_void,
) -> ffi::la_ssize_t {
let pipe = (client_data as *mut ReaderPipe).as_mut().unwrap();
*buffer = pipe.buffer.as_ptr() as *const c_void;
match pipe.reader.read(pipe.buffer) {
Ok(size) => size as ffi::la_ssize_t,
Err(e) => {
let description = CString::new(e.to_string()).unwrap();
ffi::archive_set_error(archive, e.raw_os_error().unwrap_or(0), description.as_ptr());
-1
}
}
}