use std::io::{Read, Seek};
use std::path::Path;
use toml_edit::DocumentMut;
use zip::ZipArchive;
use crate::error::{Error, Malformed, Result, Unsupported};
use crate::{metadata, name, METADATA_MEMBER, VERSION};
pub(crate) struct Entry {
name: String,
raw: Vec<u8>,
symlink: bool,
}
pub struct Container<R> {
pub(crate) archive: ZipArchive<R>,
pub(crate) entries: Vec<Entry>,
pub(crate) metadata_index: usize,
doc: DocumentMut,
bytes: Vec<u8>,
version: String,
payload_file: String,
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(reader: R) -> Result<Self> {
let mut archive = ZipArchive::new(reader)?;
let mut entries = Vec::with_capacity(archive.len());
for i in 0..archive.len() {
let f = archive.by_index_raw(i)?;
entries.push(Entry {
name: f.name().to_owned(),
raw: f.name_raw().to_owned(),
symlink: f.is_symlink(),
});
}
let meta_index = entries
.iter()
.position(|e| name::matches(&e.name, &e.raw, METADATA_MEMBER))
.ok_or(Malformed::NoMetadataMember)?;
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, &payload_file)?)
} else {
None
};
Ok(Self {
archive,
entries,
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(&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(crate) fn locate_payload(entries: &[Entry], payload_file: &str) -> Result<usize> {
name::check_payload_name(payload_file)?;
let i = entries
.iter()
.position(|e| name::matches(&e.name, &e.raw, payload_file))
.ok_or_else(|| Malformed::NoPayloadMember(payload_file.to_owned()))?;
if entries[i].symlink {
return Err(Error::Malformed(Malformed::PayloadIsSymlink(
payload_file.to_owned(),
)));
}
Ok(i)
}