use std::io::{Error, ErrorKind, Read, Result, Seek, SeekFrom};
use byteorder::{LittleEndian, ReadBytesExt};
use shallow_tees::ShallowTees;
use takes::Ext;
use typed_builder::TypedBuilder;
use super::util::read_find_bstr;
use super::{index, Entry, FileIndex, Section};
use crate::signature::{self, Signature};
use crate::util::{tell, PHAR_TERMINATOR, STUB_TERMINATOR};
#[derive(Debug)]
#[cfg_attr(feature = "docsrs", doc(cfg(feature = "reader")))]
pub struct Reader<R: Read + Seek, FileIndexT: FileIndex = index::NameHashMap> {
stream: R,
stub: Section,
num_files: u32,
api: u16,
flags: u32,
alias: Section,
metadata: Section,
file_index: FileIndexT,
}
impl<R: Read + Seek, FileIndexT: FileIndex> Reader<R, FileIndexT> {
pub fn read(mut read: R, options: Options) -> Result<Self> {
let mut expected_sig = None;
let mut sig_offset = None;
let mut sig = if options.verify_signature {
let _ = read.seek(SeekFrom::End(-4))?;
let mut gbmb = [0u8; 4];
read.read_exact(&mut gbmb[..])?;
if gbmb != PHAR_TERMINATOR {
return Err(Error::new(ErrorKind::Other, "corrupted file"));
}
let _ = read.seek(SeekFrom::End(-8))?;
let discrim = read.read_u32::<LittleEndian>()?;
let sig = Signature::from_u32(discrim).ok_or_else(|| {
Error::new(
ErrorKind::Other,
format!("unsupported signature type {:x}", discrim),
)
})?;
let mut expect = vec![0u8; sig.size().into()];
let offset = read.seek(SeekFrom::End(-8i64 - i64::from(sig.size())))?;
sig_offset = Some(offset);
read.read_exact(&mut expect[..])?;
expected_sig = Some(expect);
signature::MaybeDummy::Real(sig)
} else {
signature::MaybeDummy::Dummy(signature::NullDevice)
};
let _ = read.seek(SeekFrom::Start(0))?;
let mut tee = ShallowTees::new(&mut read, sig.write());
let mut stub = Section::create(options.cache_stub, 0);
read_find_bstr(&mut tee, &mut stub, STUB_TERMINATOR)?;
let manifest_size = tee.read_u32::<LittleEndian>()?;
let mut manifest = (&mut tee).takes(manifest_size.into())?;
let num_files = manifest.read_u32::<LittleEndian>()?;
let api = manifest.read_u16::<LittleEndian>()?;
let flags = manifest.read_u32::<LittleEndian>()?;
let alias_len = manifest.read_u32::<LittleEndian>()?;
let mut alias = Section::create(options.cache_alias, tell(&mut manifest)?);
alias.from_read(&mut manifest, alias_len)?;
let metadata_len = manifest.read_u32::<LittleEndian>()?;
let mut metadata = Section::create(options.cache_metadata, tell(&mut manifest)?);
metadata.from_read(&mut manifest, metadata_len)?;
let mut file_index = FileIndexT::default();
if FileIndexT::scan_files() {
for _ in 0..num_files {
let start = tell(&mut manifest)?;
let entry = Entry::parse(
&mut manifest,
FileIndexT::requires_name(),
FileIndexT::requires_metadata(),
)?;
file_index.feed_entry(start, entry)?;
}
}
file_index.end_of_header(tell(&mut manifest)?);
if let (Some(expected_sig), Some(sig_offset)) = (expected_sig, sig_offset) {
let _ = tee.seek(SeekFrom::Start(sig_offset))?;
drop(tee);
let sig = match sig {
signature::MaybeDummy::Real(sig) => sig,
signature::MaybeDummy::Dummy(_) => {
unreachable!("expected_sig, sig_offset should be None")
}
};
let ret = sig.finalize();
if ret[..] != expected_sig[..] {
return Err(Error::new(ErrorKind::Other, "signature mismatch"));
}
}
Ok(Reader {
stream: read,
stub,
num_files,
api,
flags,
alias,
metadata,
file_index,
})
}
pub fn stub_bytes(&mut self) -> Result<impl AsRef<[u8]> + '_> {
self.stub.as_memory(&mut self.stream)
}
pub fn stub_read(&mut self) -> Result<impl Read + '_> {
self.stub.as_read(&mut self.stream)
}
pub fn metadata_bytes(&mut self) -> Result<impl AsRef<[u8]> + '_> {
self.metadata.as_memory(&mut self.stream)
}
pub fn metadata_read(&mut self) -> Result<impl Read + '_> {
self.metadata.as_read(&mut self.stream)
}
}
impl<R: Read + Seek, FileIndexT: index::Iterable> Reader<R, FileIndexT> {
pub fn for_each_file<F>(&mut self, f: F) -> Result<()>
where
F: FnMut(&[u8], &mut (dyn Read)) -> Result<()>,
{
self.file_index.for_each_file(&mut self.stream, f)
}
pub fn for_each_file_fold<F, G, T, U>(&mut self, f: F, fold: G) -> Result<Option<T>>
where
F: FnMut(&[u8], &mut (dyn Read)) -> Result<U>,
G: FnMut(Option<T>, U) -> T,
{
self.file_index
.for_each_file_fold(&mut self.stream, f, fold)
}
}
#[derive(Default, TypedBuilder)]
pub struct Options {
#[builder(default = true)]
cache_stub: bool,
#[builder(default = true)]
cache_alias: bool,
#[builder(default = true)]
cache_metadata: bool,
#[builder(default = true)]
verify_signature: bool,
}