use super::error::*;
use super::*;
use std::fmt;
use std::os::raw::{c_int, c_uint};
use std::path::{Path, PathBuf};
use std::ptr::NonNull;
bitflags::bitflags! {
#[derive(Debug, Default)]
struct ArchiveFlags: u32 {
const VOLUME = native::ROADF_VOLUME;
const COMMENT = native::ROADF_COMMENT;
const LOCK = native::ROADF_LOCK;
const SOLID = native::ROADF_SOLID;
const NEW_NUMBERING = native::ROADF_NEWNUMBERING;
const SIGNED = native::ROADF_SIGNED;
const RECOVERY = native::ROADF_RECOVERY;
const ENC_HEADERS = native::ROADF_ENCHEADERS;
const FIRST_VOLUME = native::ROADF_FIRSTVOLUME;
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum VolumeInfo {
None,
First,
Subsequent,
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub enum ExtractEvent {
Start {
filename: PathBuf,
size: u64,
},
Ok {
filename: PathBuf,
},
Err {
filename: PathBuf,
error_code: i32,
},
LargeDictWarning {
dict_size_kb: u64,
max_dict_size_kb: u64,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum ExtractStatus {
Completed,
Cancelled,
}
#[derive(Debug)]
struct Handle(NonNull<native::Handle>);
impl Drop for Handle {
fn drop(&mut self) {
unsafe { native::RARCloseArchive(self.0.as_ptr() as *const _) };
}
}
#[derive(Debug)]
pub struct OpenArchive<M: OpenMode, C: Cursor> {
handle: Handle,
flags: ArchiveFlags,
damaged: bool,
extra: C,
marker: std::marker::PhantomData<M>,
}
type Userdata<T> = (T, Option<widestring::WideCString>);
mod private {
use super::native;
pub trait Sealed {}
impl Sealed for super::CursorBeforeHeader {}
impl Sealed for super::CursorBeforeFile {}
impl Sealed for super::List {}
impl Sealed for super::ListSplit {}
impl Sealed for super::Process {}
#[repr(i32)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Operation {
Skip = native::RAR_SKIP,
Test = native::RAR_TEST,
Extract = native::RAR_EXTRACT,
}
#[repr(u32)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OpenModeValue {
Extract = native::RAR_OM_EXTRACT,
List = native::RAR_OM_LIST,
ListIncSplit = native::RAR_OM_LIST_INCSPLIT,
}
}
#[derive(Debug)]
pub struct CursorBeforeHeader;
#[derive(Debug)]
pub struct CursorBeforeFile {
header: FileHeader,
}
pub trait Cursor: private::Sealed {}
impl Cursor for CursorBeforeHeader {}
impl Cursor for CursorBeforeFile {}
#[derive(Debug)]
pub struct Process;
#[derive(Debug)]
pub struct List;
#[derive(Debug)]
pub struct ListSplit;
pub trait OpenMode: private::Sealed {
const VALUE: private::OpenModeValue;
}
impl OpenMode for Process {
const VALUE: private::OpenModeValue = private::OpenModeValue::Extract;
}
impl OpenMode for List {
const VALUE: private::OpenModeValue = private::OpenModeValue::List;
}
impl OpenMode for ListSplit {
const VALUE: private::OpenModeValue = private::OpenModeValue::ListIncSplit;
}
impl<Mode: OpenMode, C: Cursor> OpenArchive<Mode, C> {
pub fn is_locked(&self) -> bool {
self.flags.contains(ArchiveFlags::LOCK)
}
pub fn has_encrypted_headers(&self) -> bool {
self.flags.contains(ArchiveFlags::ENC_HEADERS)
}
pub fn has_recovery_record(&self) -> bool {
self.flags.contains(ArchiveFlags::RECOVERY)
}
pub fn has_comment(&self) -> bool {
self.flags.contains(ArchiveFlags::COMMENT)
}
pub fn is_solid(&self) -> bool {
self.flags.contains(ArchiveFlags::SOLID)
}
pub fn volume_info(&self) -> VolumeInfo {
if self.flags.contains(ArchiveFlags::FIRST_VOLUME) {
VolumeInfo::First
} else if self.flags.contains(ArchiveFlags::VOLUME) {
VolumeInfo::Subsequent
} else {
VolumeInfo::None
}
}
pub fn force_heal(&mut self) {
self.damaged = false;
}
}
impl<Mode: OpenMode> OpenArchive<Mode, CursorBeforeHeader> {
pub(crate) fn new(
filename: &Path,
password: Option<&[u8]>,
recover: Option<&mut Option<Self>>,
) -> UnrarResult<Self> {
let filename = pathed::construct(filename);
let mut data =
native::OpenArchiveDataEx::new(filename.as_ptr() as *const _, Mode::VALUE as u32);
let handle =
NonNull::new(unsafe { native::RAROpenArchiveEx(&mut data as *mut _) } as *mut _);
let arc = handle.and_then(|handle| {
if let Some(pw) = password {
let cpw = std::ffi::CString::new(pw).unwrap();
unsafe { native::RARSetPassword(handle.as_ptr(), cpw.as_ptr() as *const _) }
}
Some(OpenArchive {
handle: Handle(handle),
damaged: false,
flags: ArchiveFlags::from_bits(data.flags).unwrap(),
extra: CursorBeforeHeader,
marker: std::marker::PhantomData,
})
});
let result = Code::from(data.open_result as i32);
match (arc, result) {
(Some(arc), Code::Success) => Ok(arc),
(arc, _) => {
recover.and_then(|recover| arc.and_then(|arc| recover.replace(arc)));
Err(UnrarError::from(result, When::Open))
}
}
}
pub fn read_header(self) -> UnrarResult<Option<OpenArchive<Mode, CursorBeforeFile>>> {
Ok(read_header(&self.handle)?.map(|entry| OpenArchive {
extra: CursorBeforeFile { header: entry },
damaged: self.damaged,
handle: self.handle,
flags: self.flags,
marker: std::marker::PhantomData,
}))
}
}
impl OpenArchive<Process, CursorBeforeHeader> {
pub fn extract_all<P: AsRef<Path>>(self, dest: P) -> UnrarResult<()> {
let dest_path = pathed::construct(dest.as_ref());
let result = pathed::extract_all(self.handle.0.as_ptr(), &dest_path);
match Code::from(result) {
Code::Success => Ok(()),
code => Err(UnrarError::from(code, When::Process)),
}
}
pub fn extract_all_with_callback<P, F>(
self,
dest: P,
mut callback: F,
) -> UnrarResult<ExtractStatus>
where
P: AsRef<Path>,
F: FnMut(ExtractEvent) -> bool,
{
struct CallbackData<'a, F> {
callback: &'a mut F,
cancelled: bool,
}
extern "C" fn extract_callback<F>(
msg: native::UINT,
user_data: native::LPARAM,
p1: native::LPARAM,
p2: native::LPARAM,
) -> c_int
where
F: FnMut(ExtractEvent) -> bool,
{
if user_data == 0 {
return 0;
}
let data = unsafe { &mut *(user_data as *mut CallbackData<'_, F>) };
fn read_filename(p1: native::LPARAM) -> Option<PathBuf> {
if p1 == 0 {
return None;
}
let ptr = p1 as *const native::WCHAR;
if ptr.is_null() {
return None;
}
let mut len = 0usize;
const MAX_LEN: usize = 2048;
unsafe {
while len < MAX_LEN && *ptr.add(len) != 0 {
len += 1;
}
}
if len == 0 {
return None;
}
let slice = unsafe { std::slice::from_raw_parts(ptr, len) };
let path_string: String = slice
.iter()
.filter_map(|&c| char::from_u32(c as u32))
.collect();
Some(PathBuf::from(path_string))
}
match msg {
native::UCM_EXTRACTFILE => {
if let Some(filename) = read_filename(p1) {
let event = ExtractEvent::Start {
filename,
size: p2 as u64,
};
if !(data.callback)(event) {
data.cancelled = true;
return -1; }
}
0
}
native::UCM_EXTRACTFILE_OK => {
if let Some(filename) = read_filename(p1) {
let event = ExtractEvent::Ok { filename };
if !(data.callback)(event) {
data.cancelled = true;
return -1;
}
}
0
}
native::UCM_EXTRACTFILE_ERR => {
if let Some(filename) = read_filename(p1) {
let event = ExtractEvent::Err {
filename,
error_code: p2 as i32,
};
if !(data.callback)(event) {
data.cancelled = true;
return -1;
}
}
0
}
native::UCM_LARGEDICT => {
let event = ExtractEvent::LargeDictWarning {
dict_size_kb: p1 as u64,
max_dict_size_kb: p2 as u64,
};
if (data.callback)(event) { 1 } else { 0 }
}
native::UCM_CHANGEVOLUMEW => {
match p2 {
native::RAR_VOL_ASK => -1,
_ => 0,
}
}
_ => 0,
}
}
let dest_path = pathed::construct(dest.as_ref());
let mut callback_data = CallbackData {
callback: &mut callback,
cancelled: false,
};
unsafe {
native::RARSetCallback(
self.handle.0.as_ptr(),
Some(extract_callback::<F>),
&mut callback_data as *mut _ as native::LPARAM,
);
}
let result = pathed::extract_all(self.handle.0.as_ptr(), &dest_path);
match Code::from(result) {
Code::Success if callback_data.cancelled => Ok(ExtractStatus::Cancelled),
Code::Success => Ok(ExtractStatus::Completed),
code => Err(UnrarError::from(code, When::Process)),
}
}
}
impl Iterator for OpenArchive<List, CursorBeforeHeader> {
type Item = Result<FileHeader, UnrarError>;
fn next(&mut self) -> Option<Self::Item> {
if self.damaged {
return None;
}
match read_header(&self.handle) {
Ok(Some(header)) => {
match Internal::<Skip>::process_file_raw(&self.handle, None, None) {
Ok(_) => Some(Ok(header)),
Err(s) => {
self.damaged = true;
Some(Err(s))
}
}
}
Ok(None) => None,
Err(s) => {
self.damaged = true;
Some(Err(s))
}
}
}
}
impl Iterator for OpenArchive<ListSplit, CursorBeforeHeader> {
type Item = Result<FileHeader, UnrarError>;
fn next(&mut self) -> Option<Self::Item> {
if self.damaged {
return None;
}
match read_header(&self.handle) {
Ok(Some(header)) => {
match Internal::<Skip>::process_file_raw(&self.handle, None, None) {
Ok(_) => Some(Ok(header)),
Err(s) => {
self.damaged = true;
Some(Err(s))
}
}
}
Ok(None) => None,
Err(s) => {
self.damaged = true;
Some(Err(s))
}
}
}
}
impl<M: OpenMode> OpenArchive<M, CursorBeforeFile> {
pub fn entry(&self) -> &FileHeader {
&self.extra.header
}
pub fn skip(self) -> UnrarResult<OpenArchive<M, CursorBeforeHeader>> {
self.process_file::<Skip>(None, None)
}
fn process_file<PM: ProcessMode>(
self,
path: Option<&pathed::RarStr>,
file: Option<&pathed::RarStr>,
) -> UnrarResult<OpenArchive<M, CursorBeforeHeader>> {
Ok(self.process_file_x::<PM>(path, file)?.1)
}
fn process_file_x<PM: ProcessMode>(
self,
path: Option<&pathed::RarStr>,
file: Option<&pathed::RarStr>,
) -> UnrarResult<(PM::Output, OpenArchive<M, CursorBeforeHeader>)> {
let result = Ok((
Internal::<PM>::process_file_raw(&self.handle, path, file)?,
OpenArchive {
extra: CursorBeforeHeader,
damaged: self.damaged,
handle: self.handle,
flags: self.flags,
marker: std::marker::PhantomData,
},
));
result
}
}
impl OpenArchive<Process, CursorBeforeFile> {
pub fn read(self) -> UnrarResult<(Vec<u8>, OpenArchive<Process, CursorBeforeHeader>)> {
Ok(self.process_file_x::<ReadToVec>(None, None)?)
}
pub fn test(self) -> UnrarResult<OpenArchive<Process, CursorBeforeHeader>> {
Ok(self.process_file::<Test>(None, None)?)
}
pub fn extract(self) -> UnrarResult<OpenArchive<Process, CursorBeforeHeader>> {
self.dir_extract(None)
}
pub fn extract_with_base<P: AsRef<Path>>(
self,
base: P,
) -> UnrarResult<OpenArchive<Process, CursorBeforeHeader>> {
self.dir_extract(Some(base.as_ref()))
}
pub fn extract_to<P: AsRef<Path>>(
self,
file: P,
) -> UnrarResult<OpenArchive<Process, CursorBeforeHeader>> {
let dest = pathed::construct(file.as_ref());
self.process_file::<Extract>(None, Some(&dest))
}
fn dir_extract(
self,
base: Option<&Path>,
) -> UnrarResult<OpenArchive<Process, CursorBeforeHeader>> {
let (path, file) = pathed::preprocess_extract(base, &self.entry().filename);
self.process_file::<Extract>(path.as_deref(), file.as_deref())
}
}
fn read_header(handle: &Handle) -> UnrarResult<Option<FileHeader>> {
let mut userdata: Userdata<<Skip as ProcessMode>::Output> = Default::default();
unsafe {
native::RARSetCallback(
handle.0.as_ptr(),
Some(Internal::<Skip>::callback),
&mut userdata as *mut _ as native::LPARAM,
);
}
let mut header = native::HeaderDataEx::default();
let read_result = Code::from(unsafe {
native::RARReadHeaderEx(handle.0.as_ptr(), &mut header as *mut _)
});
match read_result {
Code::Success => Ok(Some(header.into())),
Code::EndArchive => Ok(None),
_ => Err(UnrarError::from(read_result, When::Read)),
}
}
#[derive(Debug)]
struct Skip;
#[derive(Debug)]
struct ReadToVec;
#[derive(Debug)]
struct Extract;
#[derive(Debug)]
struct Test;
trait ProcessMode: core::fmt::Debug {
const OPERATION: private::Operation;
type Output: core::fmt::Debug + std::default::Default;
fn process_data(data: &mut Self::Output, other: &[u8]);
}
impl ProcessMode for Skip {
const OPERATION: private::Operation = private::Operation::Skip;
type Output = ();
fn process_data(_: &mut Self::Output, _: &[u8]) {}
}
impl ProcessMode for ReadToVec {
const OPERATION: private::Operation = private::Operation::Test;
type Output = Vec<u8>;
fn process_data(my: &mut Self::Output, other: &[u8]) {
my.extend_from_slice(other);
}
}
impl ProcessMode for Extract {
const OPERATION: private::Operation = private::Operation::Extract;
type Output = ();
fn process_data(_: &mut Self::Output, _: &[u8]) {}
}
impl ProcessMode for Test {
const OPERATION: private::Operation = private::Operation::Test;
type Output = ();
fn process_data(_: &mut Self::Output, _: &[u8]) {}
}
struct Internal<M: ProcessMode> {
marker: std::marker::PhantomData<M>,
}
impl<M: ProcessMode> Internal<M> {
extern "C" fn callback(
msg: native::UINT,
user_data: native::LPARAM,
p1: native::LPARAM,
p2: native::LPARAM,
) -> c_int {
if user_data == 0 {
return 0;
}
let user_data = unsafe { &mut *(user_data as *mut Userdata<M::Output>) };
match msg {
native::UCM_CHANGEVOLUMEW => {
let next =
unsafe { widestring::WideCString::from_ptr_truncate(p1 as *const _, 2048) };
user_data.1 = Some(next);
match p2 {
native::RAR_VOL_ASK => -1,
_ => 0,
}
}
native::UCM_PROCESSDATA => {
let raw_slice = std::ptr::slice_from_raw_parts(p1 as *const u8, p2 as _);
M::process_data(&mut user_data.0, unsafe { &*raw_slice as &_ });
0
}
_ => 0,
}
}
fn process_file_raw(
handle: &Handle,
path: Option<&pathed::RarStr>,
file: Option<&pathed::RarStr>,
) -> UnrarResult<M::Output> {
let mut user_data: Userdata<M::Output> = Default::default();
unsafe {
native::RARSetCallback(
handle.0.as_ptr(),
Some(Self::callback),
&mut user_data as *mut _ as native::LPARAM,
);
}
let process_result = Code::from(pathed::process_file(
handle.0.as_ptr(),
M::OPERATION as i32,
path,
file,
));
match process_result {
Code::Success => Ok(user_data.0),
_ => Err(UnrarError::from(process_result, When::Process)),
}
}
}
bitflags::bitflags! {
#[derive(Debug)]
struct EntryFlags: u32 {
const SPLIT_BEFORE = 0x1;
const SPLIT_AFTER = 0x2;
const ENCRYPTED = 0x4;
const SOLID = 0x10;
const DIRECTORY = 0x20;
}
}
#[allow(missing_docs)]
#[derive(Debug)]
pub struct FileHeader {
pub filename: PathBuf,
flags: EntryFlags,
pub unpacked_size: u64,
pub file_crc: u32,
pub file_time: u32,
pub method: u32,
pub file_attr: u32,
}
impl FileHeader {
pub fn is_split(&self) -> bool {
self.flags.contains(EntryFlags::SPLIT_BEFORE)
|| self.flags.contains(EntryFlags::SPLIT_AFTER)
}
pub fn is_split_after(&self) -> bool {
self.flags.contains(EntryFlags::SPLIT_AFTER)
}
pub fn is_split_before(&self) -> bool {
self.flags.contains(EntryFlags::SPLIT_BEFORE)
}
pub fn is_directory(&self) -> bool {
self.flags.contains(EntryFlags::DIRECTORY)
}
pub fn is_encrypted(&self) -> bool {
self.flags.contains(EntryFlags::ENCRYPTED)
}
pub fn is_file(&self) -> bool {
!self.is_directory()
}
}
impl fmt::Display for FileHeader {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:?}", self.filename)?;
if self.is_directory() {
write!(f, "/")?
}
if self.is_split() {
write!(f, " (partial)")?
}
Ok(())
}
}
impl From<native::HeaderDataEx> for FileHeader {
fn from(header: native::HeaderDataEx) -> Self {
let filename_w_ptr = &raw const header.filename_w as *const _;
let filename =
unsafe { widestring::WideCString::from_ptr_truncate(filename_w_ptr, 1024) };
let flags = header.flags;
let unp_size = header.unp_size;
let unp_size_high = header.unp_size_high;
let file_crc = header.file_crc;
let file_time = header.file_time;
let method = header.method;
let file_attr = header.file_attr;
FileHeader {
filename: PathBuf::from(filename.to_os_string()),
flags: EntryFlags::from_bits(flags).unwrap(),
unpacked_size: unpack_unp_size(unp_size, unp_size_high),
file_crc,
file_time,
method,
file_attr,
}
}
}
fn unpack_unp_size(unp_size: c_uint, unp_size_high: c_uint) -> u64 {
((unp_size_high as u64) << (8 * std::mem::size_of::<c_uint>())) | (unp_size as u64)
}
#[cfg(test)]
mod tests {
#[test]
fn combine_size() {
use super::unpack_unp_size;
let (high, low) = (1u32, 1464303715u32);
assert_eq!(unpack_unp_size(low, high), 5759271011);
}
}