use chrono::{offset::Utc, DateTime, TimeZone};
use num_enum::{FromPrimitive, IntoPrimitive};
use ownable::{IntoOwned, ToOwned};
use winnow::{binary::le_u16, PResult, Partial};
use crate::{
encoding::Encoding,
parse::{Mode, Version},
};
use super::{zero_datetime, ExtraField, NtfsAttr};
pub struct Archive {
pub(crate) size: u64,
pub(crate) encoding: Encoding,
pub(crate) entries: Vec<Entry>,
pub(crate) comment: String,
}
impl Archive {
#[inline(always)]
pub fn size(&self) -> u64 {
self.size
}
pub fn entries(&self) -> impl Iterator<Item = &Entry> {
self.entries.iter()
}
pub fn by_name<N: AsRef<str>>(&self, name: N) -> Option<&Entry> {
self.entries.iter().find(|&x| x.name == name.as_ref())
}
#[inline(always)]
pub fn encoding(&self) -> Encoding {
self.encoding
}
#[inline(always)]
pub fn comment(&self) -> &str {
&self.comment
}
}
#[derive(Clone)]
pub struct Entry {
pub name: String,
pub method: Method,
pub comment: String,
pub modified: DateTime<Utc>,
pub created: Option<DateTime<Utc>>,
pub accessed: Option<DateTime<Utc>>,
pub header_offset: u64,
pub reader_version: Version,
pub flags: u16,
pub uid: Option<u32>,
pub gid: Option<u32>,
pub crc32: u32,
pub compressed_size: u64,
pub uncompressed_size: u64,
pub mode: Mode,
}
impl Entry {
pub fn sanitized_name(&self) -> Option<&str> {
let name = self.name.as_str();
if name.contains("..") {
return None;
}
#[cfg(windows)]
{
if name.contains(":\\") || name.starts_with("\\") {
return None;
}
Some(name)
}
#[cfg(not(windows))]
{
let mut entry_chars = name.chars();
let mut name = name;
while name.starts_with('/') {
entry_chars.next();
name = entry_chars.as_str()
}
Some(name)
}
}
pub(crate) fn set_extra_field(&mut self, ef: &ExtraField) {
match &ef {
ExtraField::Zip64(z64) => {
self.uncompressed_size = z64.uncompressed_size;
self.compressed_size = z64.compressed_size;
self.header_offset = z64.header_offset;
}
ExtraField::Timestamp(ts) => {
self.modified = Utc
.timestamp_opt(ts.mtime as i64, 0)
.single()
.unwrap_or_else(zero_datetime);
}
ExtraField::Ntfs(nf) => {
for attr in &nf.attrs {
if let NtfsAttr::Attr1(attr) = attr {
self.modified = attr.mtime.to_datetime().unwrap_or_else(zero_datetime);
self.created = attr.ctime.to_datetime();
self.accessed = attr.atime.to_datetime();
}
}
}
ExtraField::Unix(uf) => {
self.modified = Utc
.timestamp_opt(uf.mtime as i64, 0)
.single()
.unwrap_or_else(zero_datetime);
if self.uid.is_none() {
self.uid = Some(uf.uid as u32);
}
if self.gid.is_none() {
self.gid = Some(uf.gid as u32);
}
}
ExtraField::NewUnix(uf) => {
self.uid = Some(uf.uid as u32);
self.gid = Some(uf.uid as u32);
}
_ => {}
};
}
}
#[derive(Debug)]
pub enum EntryKind {
Directory,
File,
Symlink,
}
impl Entry {
pub fn kind(&self) -> EntryKind {
if self.mode.has(Mode::SYMLINK) {
EntryKind::Symlink
} else if self.mode.has(Mode::DIR) {
EntryKind::Directory
} else {
EntryKind::File
}
}
}
#[derive(
Debug, Clone, Copy, PartialEq, Eq, Hash, IntoPrimitive, FromPrimitive, IntoOwned, ToOwned,
)]
#[repr(u16)]
pub enum Method {
Store = 0,
Deflate = 8,
Deflate64 = 9,
Bzip2 = 12,
Lzma = 14,
Zstd = 93,
Mp3 = 94,
Xz = 95,
Jpeg = 96,
WavPack = 97,
Ppmd = 98,
Aex = 99,
#[num_enum(catch_all)]
Unrecognized(u16),
}
impl Method {
pub fn parser(i: &mut Partial<&[u8]>) -> PResult<Self> {
le_u16(i).map(From::from)
}
}