use {
crate::{
binary_package_control::BinaryPackageControlFile,
control::ControlParagraphReader,
error::{DebianError, Result},
},
std::{
io::{Cursor, Read},
ops::{Deref, DerefMut},
},
};
fn reader_from_filename(extension: &str, data: std::io::Cursor<Vec<u8>>) -> Result<Box<dyn Read>> {
match extension {
"" => Ok(Box::new(data)),
".gz" => Ok(Box::new(libflate::gzip::Decoder::new(data)?)),
".xz" => Ok(Box::new(xz2::read::XzDecoder::new(data))),
".zst" => Ok(Box::new(zstd::Decoder::new(data)?)),
_ => Err(DebianError::DebUnknownCompression(extension.to_string())),
}
}
fn reader_from_filename_async(
extension: &str,
data: futures::io::Cursor<Vec<u8>>,
) -> Result<Box<dyn futures::AsyncRead + Unpin>> {
match extension {
"" => Ok(Box::new(data)),
".gz" => Ok(Box::new(
async_compression::futures::bufread::GzipDecoder::new(data),
)),
".xz" => Ok(Box::new(
async_compression::futures::bufread::XzDecoder::new(data),
)),
".zst" => Ok(Box::new(
async_compression::futures::bufread::ZstdDecoder::new(data),
)),
_ => Err(DebianError::DebUnknownCompression(extension.to_string())),
}
}
pub struct BinaryPackageReader<R: Read> {
archive: ar::Archive<R>,
}
impl<R: Read> BinaryPackageReader<R> {
pub fn new(reader: R) -> Result<Self> {
Ok(Self {
archive: ar::Archive::new(reader),
})
}
pub fn next_entry(&mut self) -> Option<Result<BinaryPackageEntry>> {
if let Some(entry) = self.archive.next_entry() {
match entry {
Ok(mut entry) => {
let filename = String::from_utf8_lossy(entry.header().identifier()).to_string();
let mut data = vec![];
match entry.read_to_end(&mut data) {
Ok(_) => {}
Err(e) => {
return Some(Err(e.into()));
}
}
if filename == "debian-binary" {
Some(Ok(BinaryPackageEntry::DebianBinary(std::io::Cursor::new(
data,
))))
} else if let Some(tail) = filename.strip_prefix("control.tar") {
match reader_from_filename(tail, std::io::Cursor::new(data)) {
Ok(res) => Some(Ok(BinaryPackageEntry::Control(ControlTarReader {
archive: tar::Archive::new(res),
}))),
Err(e) => Some(Err(e)),
}
} else if let Some(tail) = filename.strip_prefix("data.tar") {
match reader_from_filename_async(tail, futures::io::Cursor::new(data)) {
Ok(res) => Some(Ok(BinaryPackageEntry::Data(DataTarReader {
archive: async_tar::Archive::new(res),
}))),
Err(e) => Some(Err(e)),
}
} else {
Some(Err(DebianError::DebUnknownBinaryPackageEntry(
filename.to_string(),
)))
}
}
Err(e) => Some(Err(e.into())),
}
} else {
None
}
}
}
pub enum BinaryPackageEntry {
DebianBinary(std::io::Cursor<Vec<u8>>),
Control(ControlTarReader),
Data(DataTarReader),
}
pub struct ControlTarReader {
archive: tar::Archive<Box<dyn Read>>,
}
impl Deref for ControlTarReader {
type Target = tar::Archive<Box<dyn Read>>;
fn deref(&self) -> &Self::Target {
&self.archive
}
}
impl DerefMut for ControlTarReader {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.archive
}
}
impl ControlTarReader {
pub fn entries(&mut self) -> Result<ControlTarEntries<'_>> {
let entries = self.archive.entries()?;
Ok(ControlTarEntries { entries })
}
}
pub struct ControlTarEntries<'a> {
entries: tar::Entries<'a, Box<dyn Read>>,
}
impl<'a> Iterator for ControlTarEntries<'a> {
type Item = Result<ControlTarEntry<'a>>;
fn next(&mut self) -> Option<Self::Item> {
match self.entries.next() {
Some(Ok(entry)) => Some(Ok(ControlTarEntry { inner: entry })),
Some(Err(e)) => Some(Err(e.into())),
None => None,
}
}
}
pub struct ControlTarEntry<'a> {
inner: tar::Entry<'a, Box<dyn Read>>,
}
impl<'a> Deref for ControlTarEntry<'a> {
type Target = tar::Entry<'a, Box<dyn Read>>;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl<'a> DerefMut for ControlTarEntry<'a> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.inner
}
}
impl<'a> ControlTarEntry<'a> {
pub fn to_control_file(&mut self) -> Result<(&'_ tar::Header, ControlTarFile)> {
let path_bytes = self.inner.path_bytes().to_vec();
let path = String::from_utf8_lossy(&path_bytes);
let mut data = vec![];
self.inner.read_to_end(&mut data)?;
match path.trim_start_matches("./") {
"control" => {
let mut reader = ControlParagraphReader::new(Cursor::new(data));
let paragraph = reader.next().ok_or(DebianError::ControlFileNoParagraph)??;
let control = BinaryPackageControlFile::from(paragraph);
Ok((self.inner.header(), ControlTarFile::Control(control)))
}
"conffiles" => Ok((self.inner.header(), ControlTarFile::Conffiles(data))),
"triggers" => Ok((self.inner.header(), ControlTarFile::Triggers(data))),
"shlibs" => Ok((self.inner.header(), ControlTarFile::Shlibs(data))),
"symbols" => Ok((self.inner.header(), ControlTarFile::Symbols(data))),
"preinst" => Ok((self.inner.header(), ControlTarFile::Preinst(data))),
"postinst" => Ok((self.inner.header(), ControlTarFile::Postinst(data))),
"prerm" => Ok((self.inner.header(), ControlTarFile::Prerm(data))),
"postrm" => Ok((self.inner.header(), ControlTarFile::Postrm(data))),
_ => Ok((self.inner.header(), ControlTarFile::Other(path_bytes, data))),
}
}
}
pub enum ControlTarFile {
Control(BinaryPackageControlFile<'static>),
Conffiles(Vec<u8>),
Triggers(Vec<u8>),
Shlibs(Vec<u8>),
Symbols(Vec<u8>),
Preinst(Vec<u8>),
Postinst(Vec<u8>),
Prerm(Vec<u8>),
Postrm(Vec<u8>),
Other(Vec<u8>, Vec<u8>),
}
pub struct DataTarReader {
archive: async_tar::Archive<Box<dyn futures::io::AsyncRead + Unpin>>,
}
impl Deref for DataTarReader {
type Target = async_tar::Archive<Box<dyn futures::io::AsyncRead + Unpin>>;
fn deref(&self) -> &Self::Target {
&self.archive
}
}
impl DerefMut for DataTarReader {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.archive
}
}
impl DataTarReader {
pub fn into_inner(self) -> async_tar::Archive<Box<dyn futures::io::AsyncRead + Unpin>> {
self.archive
}
}
pub fn resolve_control_file(reader: impl Read) -> Result<BinaryPackageControlFile<'static>> {
let mut reader = BinaryPackageReader::new(reader)?;
while let Some(entry) = reader.next_entry() {
if let BinaryPackageEntry::Control(mut control) = entry? {
for entry in control.entries()? {
if let ControlTarFile::Control(control) = entry?.to_control_file()?.1 {
return Ok(control);
}
}
}
}
Err(DebianError::ControlFileNotFound)
}