Skip to content

Commit

Permalink
Users should be able to read files from the RPM payload
Browse files Browse the repository at this point in the history
closes #222
  • Loading branch information
dralley committed Jan 17, 2025
1 parent 8a50048 commit 66d7a6f
Show file tree
Hide file tree
Showing 5 changed files with 111 additions and 4 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added

- Added support for ecdsa signatures
- Added `Package::files()` for iterating over the files of an RPM package (metadata & contents).

## 0.16.0

Expand Down
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ itertools = "0.13"
hex = { version = "0.4", features = ["std"] }
zstd = { version = "0.13", optional = true }
xz2 = { version = "0.1", optional = true }
bzip2 = { version = "0.4.4", optional = true }
bzip2 = { version = "0.5.0", optional = true }

[dev-dependencies]
env_logger = "0.11"
Expand Down
36 changes: 34 additions & 2 deletions src/rpm/compressor.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::io::Write;
use std::io;

use crate::errors::*;

Expand All @@ -13,6 +13,18 @@ pub enum CompressionType {
Bzip2,
}

impl std::fmt::Display for CompressionType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::None => write!(f, "none"),
Self::Gzip => write!(f, "gzip"),
Self::Zstd => write!(f, "zstd"),
Self::Xz => write!(f, "xz"),
Self::Bzip2 => write!(f, "bzip2"),
}
}
}

impl std::str::FromStr for CompressionType {
type Err = Error;
fn from_str(raw: &str) -> Result<Self, Self::Err> {
Expand Down Expand Up @@ -80,7 +92,7 @@ impl TryFrom<CompressionWithLevel> for Compressor {
}
}

impl Write for Compressor {
impl io::Write for Compressor {
fn write(&mut self, content: &[u8]) -> Result<usize, std::io::Error> {
match self {
Compressor::None(data) => data.write(content),
Expand Down Expand Up @@ -192,3 +204,23 @@ impl std::fmt::Display for CompressionWithLevel {
}
}
}

pub(crate) fn decompress_stream(
value: CompressionType,
reader: impl io::BufRead + 'static,
) -> Result<Box<dyn io::Read>, Error> {
match value {
CompressionType::None => Ok(Box::new(reader)),
#[cfg(feature = "gzip-compression")]
CompressionType::Gzip => Ok(Box::new(flate2::bufread::GzDecoder::new(reader))),
#[cfg(feature = "zstd-compression")]
CompressionType::Zstd => Ok(Box::new(zstd::stream::Decoder::new(reader)?)),
#[cfg(feature = "xz-compression")]
CompressionType::Xz => Ok(Box::new(xz2::bufread::XzDecoder::new(reader))),
#[cfg(feature = "bzip2-compression")]
CompressionType::Bzip2 => Ok(Box::new(bzip2::read::BzipDecoder::new(reader))),
// This is an issue when building with all compression types enabled
#[allow(unreachable_patterns)]
_ => Err(Error::UnsupportedCompressorType(value.to_string())),
}
}
8 changes: 8 additions & 0 deletions src/rpm/filecaps.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,14 @@ const CAPS: &[&str; 41] = &[
#[derive(Debug, Clone)]
pub struct FileCaps(String);

impl FileCaps {
pub fn new(input: String) -> Result<Self, Error> {
validate_caps_text(&input)?;

Ok(Self(input))
}
}

impl Display for FileCaps {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
Expand Down
68 changes: 67 additions & 1 deletion src/rpm/package.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
use std::{
fs, io,
io::Read,
path::{Path, PathBuf},
str::FromStr,
};

use digest::Digest;
use num_traits::FromPrimitive;

use crate::{constants::*, errors::*, CompressionType};
use crate::{constants::*, decompress_stream, errors::*, CompressionType};

#[cfg(feature = "signature-pgp")]
use crate::signature::pgp::Verifier;
Expand Down Expand Up @@ -61,6 +62,21 @@ impl Package {
self.write(&mut io::BufWriter::new(fs::File::create(path)?))
}

/// Iterate over the file contents of the package payload
pub fn files(&self) -> Result<FileIterator, Error> {
let file_entries = self.metadata.get_file_entries()?;
let archive = decompress_stream(
self.metadata.get_payload_compressor()?,
io::Cursor::new(self.content.clone()),
)?;

Ok(FileIterator {
file_entries,
archive,
count: 0,
})
}

/// Create package signatures using an external key and add them to the signature header
#[cfg(feature = "signature-meta")]
pub fn sign<S>(&mut self, signer: S) -> Result<(), Error>
Expand Down Expand Up @@ -1019,3 +1035,53 @@ impl PackageMetadata {
}
}
}

pub struct FileIterator<'a> {
file_entries: Vec<FileEntry>,
archive: Box<dyn io::Read + 'a>,
count: usize,
}

#[derive(Debug)]
pub struct RpmFile {
pub file_entry: FileEntry,
pub content: Vec<u8>,
}

impl Iterator for FileIterator<'_> {
type Item = Result<RpmFile, Error>;

fn next(&mut self) -> Option<Self::Item> {
if self.count >= self.file_entries.len() {
return None;
}

let file_entry = self.file_entries[self.count].clone();
self.count += 1;

let reader = cpio::NewcReader::new(&mut self.archive);

match reader {
Ok(mut entry_reader) => {
if entry_reader.entry().is_trailer() {
return None;
}

let mut content = Vec::new();

if let Err(e) = entry_reader.read_to_end(&mut content) {
return Some(Err(Error::Io(e)));
}
if let Err(e) = entry_reader.finish() {
return Some(Err(Error::Io(e)));
}

Some(Ok(RpmFile {
file_entry,
content,
}))
}
Err(e) => Some(Err(Error::Io(e)))
}
}
}

0 comments on commit 66d7a6f

Please sign in to comment.