From db0ad588530e147b5d151aff4e04d8e720f99a22 Mon Sep 17 00:00:00 2001 From: Casey Rodarmor Date: Sun, 13 Sep 2026 13:22:02 -0700 Subject: [PATCH] Allow embedded files in archives --- src/archive.rs | 70 +++------------------------------------ src/archive_error.rs | 2 -- src/error.rs | 6 ---- src/manifest.rs | 19 ----------- src/relative_path.rs | 15 --------- src/subcommand/archive.rs | 3 +- tests/archive.rs | 29 ---------------- 7 files changed, 7 insertions(+), 137 deletions(-) diff --git a/src/archive.rs b/src/archive.rs index 6daf85d4..5546689c 100644 --- a/src/archive.rs +++ b/src/archive.rs @@ -114,9 +114,8 @@ impl Archive { fn unpack_directory( &self, loose: &mut BTreeSet, - embedded: &mut BTreeMap, + embedded: &mut BTreeMap>, hash: Hash, - prefix: Option<&RelativePath>, expected_totals: Totals, ) -> Result { let directory = self.decode_directory(Some(loose), hash)?; @@ -127,9 +126,9 @@ impl Archive { for (name, entry) in &directory.entries { let crate_entry = match entry { Entry::File { hash, size } => { - if self.files.contains_key(hash) { + if let Some(content) = self.files.get(hash) { loose.remove(hash); - embedded.insert(RelativePath::join_opt(prefix, name), *hash); + embedded.insert(*hash, content.clone()); } DirectoryTreeEntry::File(File { hash: *hash, @@ -137,13 +136,7 @@ impl Archive { }) } Entry::Directory { hash, totals, .. } => { - DirectoryTreeEntry::Directory(self.unpack_directory( - loose, - embedded, - *hash, - Some(&RelativePath::join_opt(prefix, name)), - *totals, - )?) + DirectoryTreeEntry::Directory(self.unpack_directory(loose, embedded, *hash, *totals)?) } }; entries.insert(name.clone(), crate_entry); @@ -195,7 +188,7 @@ impl Archive { let mut embedded = BTreeMap::new(); - let package = self.unpack_directory(&mut loose, &mut embedded, *hash, None, *totals)?; + let package = self.unpack_directory(&mut loose, &mut embedded, *hash, *totals)?; let signatures = { let entry = root @@ -232,24 +225,6 @@ impl Archive { archive_error::LooseFiles { hashes: loose }, } - { - let unexpected = embedded - .keys() - .filter(|path| **path != Metadata::DECO_FILENAME) - .cloned() - .collect::>(); - - ensure! { - unexpected.is_empty(), - archive_error::UnexpectedEmbeddedFiles { paths: unexpected }, - } - } - - let embedded = embedded - .into_values() - .map(|hash| (hash, self.files[&hash].clone())) - .collect(); - Ok(( Manifest { embedded, @@ -798,41 +773,6 @@ mod tests { ); } - #[test] - fn unexpected_embedded_files() { - let content = b"foo"; - - let mut package = DirectoryTree::new(); - for path in &["bar/bob", "baz"] { - package - .create_file(&path.parse().unwrap(), File::new(content)) - .unwrap(); - } - - let mut builder = ArchiveBuilder::new(); - - let package = builder.pack_directory(&package).unwrap(); - - builder.files.insert(Hash::bytes(content), content.to_vec()); - - let signatures = builder.directory(&Directory::default()).unwrap(); - - let mut root = Directory::new(); - root - .insert_entry("package", package) - .insert_entry("signatures", signatures); - - let root = builder.directory(&root).unwrap(); - - let archive = builder.build(root.hash()); - - assert_matches!( - archive.unpack(), - Err(ArchiveError::UnexpectedEmbeddedFiles { paths }) - if paths.to_string() == "`bar/bob`, `baz`", - ); - } - #[test] fn unexpected_entries() { let mut builder = ArchiveBuilder::new(); diff --git a/src/archive_error.rs b/src/archive_error.rs index c05898e5..4096d141 100644 --- a/src/archive_error.rs +++ b/src/archive_error.rs @@ -25,8 +25,6 @@ pub enum ArchiveError { SignaturesMissing, #[snafu(display("expected archive `signatures` entry to be directory but found {ty}"))] SignaturesType { ty: EntryType }, - #[snafu(display("archive contains unexpected embedded files: {paths}"))] - UnexpectedEmbeddedFiles { paths: Ticked }, #[snafu(display("archive contains unexpected entries: {names}"))] UnexpectedEntries { names: Ticked }, } diff --git a/src/error.rs b/src/error.rs index 8891bf44..5de280fa 100644 --- a/src/error.rs +++ b/src/error.rs @@ -593,12 +593,6 @@ pub enum Error { path: DisplayPath, source: ArchiveError, }, - #[snafu(display("manifest `{path}` contains unexpected embedded files: {unexpected}"))] - UnexpectedEmbeddedFiles { - backtrace: Option, - path: DisplayPath, - unexpected: Ticked, - }, #[snafu(display("invalid video `{path}`"))] Video { backtrace: Option, diff --git a/src/manifest.rs b/src/manifest.rs index 2d03c2ba..60b31943 100644 --- a/src/manifest.rs +++ b/src/manifest.rs @@ -49,25 +49,6 @@ impl Manifest { Archive::pack(self).unwrap().fingerprint().unwrap() } - pub(crate) fn from_json(json: &str, path: &Utf8Path) -> Result { - let manifest = - serde_json::from_str::(json).context(error::DeserializeManifest { path: &path })?; - - let mut unexpected = BTreeSet::new(); - for (file_path, file) in manifest.files() { - if manifest.embedded.contains_key(&file.hash) && file_path != Metadata::DECO_FILENAME { - unexpected.insert(file_path); - } - } - - ensure! { - unexpected.is_empty(), - error::UnexpectedEmbeddedFiles { path, unexpected }, - } - - Ok(manifest) - } - pub fn load(path: Option<&Utf8Path>) -> Result { Ok(Self::load_with_opt_path(path)?.1) } diff --git a/src/relative_path.rs b/src/relative_path.rs index 2c4b2f1e..e858f24f 100644 --- a/src/relative_path.rs +++ b/src/relative_path.rs @@ -43,21 +43,6 @@ impl RelativePath { Component::new(self.0.rsplit('/').next().unwrap()).unwrap() } - pub(crate) fn join(&self, component: &Component) -> Self { - let mut path = self.0.clone(); - path.push('/'); - path.push_str(component.as_str()); - Self(path) - } - - pub(crate) fn join_opt(prefix: Option<&Self>, component: &Component) -> Self { - if let Some(prefix) = prefix { - prefix.join(component) - } else { - component.into() - } - } - pub(crate) fn lint(&self, lints: &BTreeSet) -> Option { for component in Utf8Path::new(&self.0).components() { let Utf8Component::Normal(component) = component else { diff --git a/src/subcommand/archive.rs b/src/subcommand/archive.rs index 51956faa..911c7614 100644 --- a/src/subcommand/archive.rs +++ b/src/subcommand/archive.rs @@ -12,7 +12,8 @@ impl Archive { pub(crate) fn run(self) -> Result { let json = filesystem::read_to_string(&self.input)?; - let manifest = Manifest::from_json(&json, &self.input)?; + let manifest = serde_json::from_str::(&json) + .context(error::DeserializeManifest { path: &self.input })?; manifest.save(&self.output)?; diff --git a/tests/archive.rs b/tests/archive.rs index b0c662bf..6c49f51d 100644 --- a/tests/archive.rs +++ b/tests/archive.rs @@ -95,35 +95,6 @@ fn rejects_totals_overflow() { .failure(); } -#[test] -fn rejects_unexpected_embedded_files() { - let content = b"foo"; - let hash = Hash::bytes(content).to_string(); - let hex_content = hex::encode(content); - - Test::new() - .write( - "manifest.json", - json! { - embedded: { - *hash: hex_content - }, - package: { - bar: { - hash: hash, - size: 3 - } - }, - signatures: [], - }, - ) - .args(["archive", "manifest.json", "manifest.filepack"]) - .stderr_regex_path( - "error: manifest `.*manifest.json` contains unexpected embedded files: `bar`\n", - ) - .failure(); -} - #[test] fn round_trip() { Test::new()