use chrono::{offset::Utc, DateTime, TimeZone};
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(Clone, Copy, Debug, Eq, PartialEq)]
pub enum EntryKind {
Directory,
File,
Symlink,
}
impl EntryKind {
pub fn is_dir(self) -> bool {
self == Self::Directory
}
pub fn is_file(self) -> bool {
self == Self::File
}
pub fn is_symlink(self) -> bool {
self == Self::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, IntoOwned, ToOwned)]
#[repr(u16)]
pub enum Method {
Store = Self::STORE,
Deflate = Self::DEFLATE,
Deflate64 = Self::DEFLATE64,
Bzip2 = Self::BZIP2,
Lzma = Self::LZMA,
Zstd = Self::ZSTD,
Mp3 = Self::MP3,
Xz = Self::XZ,
Jpeg = Self::JPEG,
WavPack = Self::WAV_PACK,
Ppmd = Self::PPMD,
Aex = Self::AEX,
Unrecognized(u16),
}
impl Method {
const STORE: u16 = 0;
const DEFLATE: u16 = 8;
const DEFLATE64: u16 = 9;
const BZIP2: u16 = 12;
const LZMA: u16 = 14;
const ZSTD: u16 = 93;
const MP3: u16 = 94;
const XZ: u16 = 95;
const JPEG: u16 = 96;
const WAV_PACK: u16 = 97;
const PPMD: u16 = 98;
const AEX: u16 = 99;
pub fn parser(i: &mut Partial<&[u8]>) -> PResult<Self> {
le_u16(i).map(From::from)
}
}
impl From<u16> for Method {
fn from(u: u16) -> Self {
match u {
Self::STORE => Self::Store,
Self::DEFLATE => Self::Deflate,
Self::DEFLATE64 => Self::Deflate64,
Self::BZIP2 => Self::Bzip2,
Self::LZMA => Self::Lzma,
Self::ZSTD => Self::Zstd,
Self::MP3 => Self::Mp3,
Self::XZ => Self::Xz,
Self::JPEG => Self::Jpeg,
Self::WAV_PACK => Self::WavPack,
Self::PPMD => Self::Ppmd,
Self::AEX => Self::Aex,
u => Self::Unrecognized(u),
}
}
}
impl From<Method> for u16 {
fn from(method: Method) -> Self {
match method {
Method::Store => Method::STORE,
Method::Deflate => Method::DEFLATE,
Method::Deflate64 => Method::DEFLATE64,
Method::Bzip2 => Method::BZIP2,
Method::Lzma => Method::LZMA,
Method::Zstd => Method::ZSTD,
Method::Mp3 => Method::MP3,
Method::Xz => Method::XZ,
Method::Jpeg => Method::JPEG,
Method::WavPack => Method::WAV_PACK,
Method::Ppmd => Method::PPMD,
Method::Aex => Method::AEX,
Method::Unrecognized(u) => u,
}
}
}