use std::io::{Read, Seek};
use std::path::Path;
use toml_edit::DocumentMut;
use zip::ZipArchive;
use crate::error::{EntryKind, Error, Malformed, Result, Unsupported};
use crate::{central, metadata, name, METADATA_MEMBER, VERSION};
pub(crate) struct Entry {
raw: Vec<u8>,
kind: EntryKind,
size: u64,
encrypted: bool,
unsupported_method: Option<u16>,
}
fn entries_of<R: Read + Seek>(archive: &mut ZipArchive<R>) -> Result<Vec<Entry>> {
let mut entries = Vec::with_capacity(archive.len());
for i in 0..archive.len() {
let f = archive.by_index_raw(i)?;
entries.push(Entry {
raw: f.name_raw().to_owned(),
kind: EntryKind::from_mode(f.unix_mode()),
size: f.size(),
encrypted: f.encrypted(),
unsupported_method: unsupported_method(f.compression()),
});
}
Ok(entries)
}
#[allow(deprecated)]
fn unsupported_method(method: zip::CompressionMethod) -> Option<u16> {
match method {
zip::CompressionMethod::Unsupported(id) => Some(id),
_ => None,
}
}
pub(crate) enum Located {
One(usize),
None,
Several(usize),
}
pub(crate) fn locate(entries: &[Entry], names: &[central::RawName], want: &str) -> Located {
let mut matched = names.iter().filter(|n| n.decodes_to(want));
let Some(first) = matched.next() else {
return Located::None;
};
let count = 1 + matched.count();
if count > 1 {
return Located::Several(count);
}
match entries.iter().position(|e| e.raw == first.bytes) {
Some(i) => Located::One(i),
None => Located::None,
}
}
pub struct Container<R> {
pub(crate) archive: ZipArchive<R>,
pub(crate) entries: Vec<Entry>,
pub(crate) names: Vec<central::RawName>,
pub(crate) metadata_index: usize,
doc: DocumentMut,
bytes: Vec<u8>,
version: String,
payload_file: String,
pub(crate) payload_index: Option<usize>,
}
impl Container<std::fs::File> {
pub fn open<P: AsRef<Path>>(path: P) -> Result<Self> {
Self::read(std::fs::File::open(path)?)
}
}
impl<R: Read + Seek> Container<R> {
pub fn read(mut reader: R) -> Result<Self> {
let names = central::names(&mut reader)?;
reader.rewind()?;
let mut archive = ZipArchive::new(reader)?;
let entries = entries_of(&mut archive)?;
let meta_index = match locate(&entries, &names, METADATA_MEMBER) {
Located::One(i) => i,
Located::None => return Err(Malformed::NoMetadataMember.into()),
Located::Several(n) => return Err(Malformed::DuplicateMetadataMember(n).into()),
};
let mut bytes = Vec::new();
archive.by_index(meta_index)?.read_to_end(&mut bytes)?;
let (doc, keys) = metadata::parse(&bytes)?;
let crate::metadata::Keys {
version,
payload_file,
} = keys;
let payload_index = if version == VERSION {
Some(locate_payload(&entries, &names, &payload_file)?)
} else {
None
};
Ok(Self {
archive,
entries,
names,
metadata_index: meta_index,
doc,
bytes,
version,
payload_file,
payload_index,
})
}
pub fn version(&self) -> &str {
&self.version
}
pub fn payload_name(&self) -> &str {
&self.payload_file
}
pub fn metadata(&self) -> &DocumentMut {
&self.doc
}
pub fn metadata_mut(&mut self) -> &mut DocumentMut {
&mut self.doc
}
pub fn metadata_bytes(&self) -> &[u8] {
&self.bytes
}
pub(crate) fn version_is_recognised(&self) -> bool {
self.payload_index.is_some()
}
pub fn payload_size(&self) -> Result<u64> {
let i = self
.payload_index
.ok_or_else(|| Unsupported::Version(self.version.clone()))?;
Ok(self.entries[i].size)
}
pub fn check_payload_readable(&self) -> std::result::Result<(), Unsupported> {
let i = self
.payload_index
.ok_or_else(|| Unsupported::Version(self.version.clone()))?;
if self.entries[i].encrypted {
return Err(Unsupported::Encrypted);
}
if let Some(m) = self.entries[i].unsupported_method {
return Err(Unsupported::Compression(m));
}
Ok(())
}
pub fn payload(&mut self) -> Result<impl Read + '_> {
let i = self
.payload_index
.ok_or_else(|| Unsupported::Version(self.version.clone()))?;
Ok(self.archive.by_index(i)?)
}
}
pub fn metadata_of<R: Read + Seek>(mut reader: R) -> Result<DocumentMut> {
let names = central::names(&mut reader)?;
reader.rewind()?;
let mut archive = ZipArchive::new(reader)?;
let entries = entries_of(&mut archive)?;
let i = match locate(&entries, &names, METADATA_MEMBER) {
Located::One(i) => i,
Located::None => return Err(Malformed::NoMetadataMember.into()),
Located::Several(n) => return Err(Malformed::DuplicateMetadataMember(n).into()),
};
let mut bytes = Vec::new();
archive.by_index(i)?.read_to_end(&mut bytes)?;
metadata::document(&bytes)
}
pub(crate) fn locate_payload(
entries: &[Entry],
names: &[central::RawName],
payload_file: &str,
) -> Result<usize> {
name::check_payload_name(payload_file)?;
let i = match locate(entries, names, payload_file) {
Located::One(i) => i,
Located::None => return Err(Malformed::NoPayloadMember(payload_file.to_owned()).into()),
Located::Several(count) => {
return Err(Malformed::DuplicatePayloadMember {
name: payload_file.to_owned(),
count,
}
.into())
}
};
if entries[i].kind != EntryKind::Regular {
return Err(Error::Malformed(Malformed::PayloadNotARegularFile {
name: payload_file.to_owned(),
kind: entries[i].kind,
}));
}
Ok(i)
}