#[cfg(feature = "std")]
use std::path::PathBuf;
#[cfg(feature = "std")]
use std::borrow::Cow;
#[cfg(not(feature = "std"))]
use alloc::{boxed::Box, string::String, borrow::Cow};
use chrono::{LocalResult, prelude::*};
mod compression;
mod ostype;
mod msdos;
mod parser;
mod timestamp;
use parser::ext::*;
pub use msdos::*;
pub use compression::*;
pub use ostype::*;
pub use parser::*;
pub use timestamp::*;
#[derive(Debug, Clone)]
pub struct LhaHeader {
pub level: u8,
pub compression: [u8;5],
pub compressed_size: u64,
pub original_size: u64,
pub filename: Box<[u8]>,
pub msdos_attrs: MsDosAttrs,
pub last_modified: u32,
pub os_type: u8,
pub file_crc: u16,
pub extended_area: Box<[u8]>,
pub first_header_len: u32,
pub extra_headers: Box<[u8]>,
}
impl Default for LhaHeader {
fn default() -> Self {
LhaHeader {
level: 0,
compression: [0;5],
compressed_size: 0,
original_size: 0,
filename: Box::new([]),
msdos_attrs: MsDosAttrs::ARCHIVE,
last_modified: 0,
os_type: 0,
file_crc: 0,
extended_area: Box::new([]),
first_header_len: 0,
extra_headers: Box::new([]),
}
}
}
impl LhaHeader {
pub fn is_directory(&self) -> bool {
self.compression_method().ok()
.filter(CompressionMethod::is_directory)
.is_some()
}
pub fn parse_os_type(&self) -> Result<OsType, UnrecognizedOsType> {
OsType::try_from(self.os_type)
}
pub fn parse_last_modified(&self) -> TimestampResult {
for header in self.iter_extra() {
match header {
[EXT_HEADER_UNIX_TIME, data @ ..] => {
if let Some(ts) = data.get(0..4).and_then(read_u32) {
return Utc.timestamp_opt(ts as i64, 0).into()
}
}
[EXT_HEADER_MSDOS_TIME, data @ ..] if data.len() == 24 => {
if let Some(mtime) = read_u64(&data[8..16]) {
return parse_win_filetime(mtime).into()
}
}
_ => {}
}
}
if self.level < 2 {
match self.parse_os_type() {
Ok(OsType::Unix)|Ok(OsType::Osk) => {
if let Some(ts) = self.extended_area.get(1..5).and_then(read_u32) {
return Utc.timestamp_opt(ts as i64, 0).into()
}
}
_ => {}
}
parse_msdos_datetime(self.last_modified).into()
}
else {
Utc.timestamp_opt(self.last_modified as i64, 0).into()
}
}
pub fn compression_method(&self) -> Result<CompressionMethod, UnrecognizedCompressionMethod> {
CompressionMethod::try_from(&self.compression)
}
#[cfg(feature = "std")]
pub fn parse_pathname(&self) -> PathBuf {
let mut path = PathBuf::new();
let mut filename = Cow::Borrowed("");
let nilterm = self.parse_os_type() == Ok(OsType::Amiga);
for header in self.iter_extra() {
match header {
[EXT_HEADER_FILENAME, data @ ..] => {
filename = parse_str_nilterm(data, nilterm, false);
},
[EXT_HEADER_PATH, data @ ..] => {
parse_pathname(data, &mut path);
}
_ => {}
}
}
if filename.is_empty() {
let data = if nilterm {
split_data_at_nil_or_end(&self.filename).0
}
else {
&self.filename
};
parse_pathname(data, &mut path);
}
else {
path.push(filename.as_ref());
}
path
}
pub fn parse_pathname_to_str(&self) -> String {
let mut path = String::new();
let mut filename = Cow::Borrowed("");
let nilterm = self.parse_os_type() == Ok(OsType::Amiga);
for header in self.iter_extra() {
match header {
[EXT_HEADER_FILENAME, data @ ..] => {
filename = parse_str_nilterm(data, nilterm, false);
},
[EXT_HEADER_PATH, data @ ..] => {
parse_pathname_to_str(data, &mut path);
}
_ => {}
}
}
if filename.is_empty() {
let data = if nilterm {
split_data_at_nil_or_end(&self.filename).0
}
else {
&self.filename
};
parse_pathname_to_str(data, &mut path);
}
else {
if !path.is_empty() {
path.push('/');
}
path.push_str(filename.as_ref());
}
path
}
pub fn parse_comment(&self) -> Option<Cow<'_, str>> {
let mut raw_filename = &self.filename[..];
for header in self.iter_extra() {
match header {
[EXT_HEADER_FILENAME, data @ ..] => {
raw_filename = data;
},
[EXT_HEADER_COMMENT, data @ ..] => {
let comment = parse_str_nilterm(data, false, true);
if !comment.is_empty() {
return Some(comment)
}
}
_ => {}
}
}
if self.parse_os_type() == Ok(OsType::Amiga) {
split_data_at_nil_or_end(raw_filename)
.1
.map(|data| parse_str_nilterm(data, false, true))
}
else {
None
}
}
}
pub fn parse_msdos_datetime(ts: u32) -> Option<NaiveDateTime> {
let sec = ts << 1 & 0x3e;
let min = ts >> 5 & 0x3f;
let hour = ts >> 11 & 0x1f;
let day = ts >> 16 & 0x1f;
let mon = ts >> 21 & 0xf;
let year = 1980 + (ts >> 25 & 0x7f) as i32;
NaiveDate::from_ymd_opt(year, mon, day).and_then(|d| d.and_hms_opt(hour, min, sec))
}
pub fn parse_win_filetime(filetime: u64) -> LocalResult<DateTime<Utc>> {
if let Some(ft) = i64::try_from(filetime).ok().and_then(|ft|
ft.checked_sub(116_444_736_000_000_000))
{
let secs = ft / 10_000_000;
let nanos = (ft % 10_000_000) as u32 * 100;
return Utc.timestamp_opt(secs, nanos)
}
LocalResult::None
}