use std::io::Read;
mod error;
pub use error::Error;
mod control;
pub use control::Control;
mod debian_binary;
use debian_binary::{parse_debian_binary_contents, DebianBinaryVersion};
type Result<T> = std::result::Result<T, Error>;
enum ReadState {
Opened,
ControlRead,
DataRead,
}
pub struct DebPkg<R: Read> {
state: ReadState,
format_version: DebianBinaryVersion,
archive: ar::Archive<R>,
}
fn validate_debian_binary<'a, R: 'a + Read>(
entry: &mut ar::Entry<'a, R>,
) -> Result<DebianBinaryVersion> {
let identifier = "debian-binary";
if entry.header().identifier() == identifier.as_bytes() {
parse_debian_binary_contents(entry)
} else {
Err(Error::MissingDebianBinary)
}
}
impl<'a, R: 'a + Read> DebPkg<R> {
pub fn parse(reader: R) -> Result<DebPkg<R>> {
let mut archive = ar::Archive::new(reader);
let mut debian_binary_entry = match archive.next_entry() {
Some(Ok(entry)) => entry,
Some(Err(err)) => return Err(Error::Io(err)),
None => return Err(Error::MissingDebianBinary),
};
let format_version = validate_debian_binary(&mut debian_binary_entry)?;
drop(debian_binary_entry);
Ok(DebPkg {
state: ReadState::Opened,
format_version,
archive,
})
}
pub fn format_version(&self) -> (u32, u32) {
(self.format_version.major, self.format_version.minor)
}
pub fn control(&'a mut self) -> Result<tar::Archive<Box<dyn Read + 'a>>> {
match self.state {
ReadState::Opened => {
let entry = match self.archive.next_entry() {
Some(entry) => entry?,
None => return Err(Error::MissingControlArchive),
};
self.state = ReadState::ControlRead;
get_tar_from_entry(entry)
}
ReadState::ControlRead | ReadState::DataRead => Err(Error::ControlAlreadyRead),
}
}
pub fn data(&'a mut self) -> Result<tar::Archive<Box<dyn Read + 'a>>> {
match self.control() {
Ok(_) => (),
Err(Error::ControlAlreadyRead) => (),
Err(e) => return Err(e),
};
match self.state {
ReadState::Opened => unreachable!(),
ReadState::ControlRead => {
let entry = match self.archive.next_entry() {
Some(entry) => entry?,
None => return Err(Error::MissingDataArchive),
};
self.state = ReadState::DataRead;
get_tar_from_entry(entry)
}
ReadState::DataRead => Err(Error::DataAlreadyRead),
}
}
}
fn get_tar_from_entry<'a, R: 'a + Read>(
entry: ar::Entry<'a, R>,
) -> Result<tar::Archive<Box<dyn Read + 'a>>> {
let mut reader = entry.take(1024);
let mut first_1kb = vec![];
reader.read_to_end(&mut first_1kb)?;
let is_tar = infer::archive::is_tar(&first_1kb);
let is_gz = infer::archive::is_gz(&first_1kb);
let is_xz = infer::archive::is_xz(&first_1kb);
let is_bz2 = infer::archive::is_bz2(&first_1kb);
let is_zst = infer::archive::is_zst(&first_1kb);
let entry = std::io::Cursor::new(first_1kb).chain(reader.into_inner());
if is_tar {
let entry: Box<dyn Read> = Box::new(entry);
Ok(tar::Archive::new(entry))
} else if is_gz {
let gz: Box<dyn Read> = Box::new(flate2::read::GzDecoder::new(entry));
Ok(tar::Archive::new(gz))
} else if is_xz {
let xz: Box<dyn Read> = Box::new(xz2::read::XzDecoder::new_multi_decoder(entry));
Ok(tar::Archive::new(xz))
} else if is_bz2 {
let bz2: Box<dyn Read> = Box::new(bzip2::read::BzDecoder::new(entry));
Ok(tar::Archive::new(bz2))
} else if is_zst {
let zstd: Box<dyn Read> = Box::new(zstd::stream::read::Decoder::new(entry)?);
Ok(tar::Archive::new(zstd))
} else {
Err(Error::UnknownEntryFormat)
}
}