c2pa-rs

A fork of https://github.com/contentauth/c2pa-rs/
git clone git://archive.git.mtrnord.blog/mtrnords-photography-manager/c2pa-rs.git
Log | Files | Refs | README

commit d75eca3ec7501ac5a101fd77583740cd11924ead
parent 2f6f8bd901aa7eb953660fb601592aa734319e4e
Author: Gavin  Peacock <gpeacock@adobe.com>
Date:   Wed,  3 May 2023 12:25:10 -0700

(MINOR) Added ResourceNotFound error (#244)

* Added ResourceNotFound error
returned if thumbnails or c2pa manifests are missing
reports the identifier or path of missing resource
* Catch errors on manifest thumbnails too
* make Errors non-exhaustive
ensure folders are created on embed
Diffstat:
Msdk/src/error.rs | 4++++
Msdk/src/ingredient.rs | 18+++++++++++-------
Msdk/src/manifest.rs | 35+++++++++++++++++++++++++----------
Msdk/src/resource_store.rs | 7+++++--
4 files changed, 45 insertions(+), 19 deletions(-)

diff --git a/sdk/src/error.rs b/sdk/src/error.rs @@ -17,6 +17,7 @@ use thiserror::Error; /// `Error` enumerates errors returned by most C2PA toolkit operations. #[derive(Debug, Error)] +#[non_exhaustive] pub enum Error { // --- c2pa errors --- /// Could not find a claim with this label. @@ -198,6 +199,9 @@ pub enum Error { #[error("file not found: {0}")] FileNotFound(String), + #[error("resource not found: {0}")] + ResourceNotFound(String), + #[error("XMP read error")] XmpReadError, diff --git a/sdk/src/ingredient.rs b/sdk/src/ingredient.rs @@ -855,8 +855,8 @@ impl Ingredient { // add the ingredient manifest_data to the claim // this is how any existing claims are added to the new store - let c2pa_manifest = match self.manifest_data() { - Some(buffer) => { + let c2pa_manifest = match self.manifest_data_ref() { + Some(resource_ref) => { let manifest_label = self .active_manifest .clone() @@ -873,9 +873,12 @@ impl Ingredient { false => None, }; + // get the c2pa manifest bytes + let data = self.resources.get(&resource_ref.identifier)?; + // have Store check and load ingredients and add them to a claim let ingredient_store = - Store::load_ingredient_to_claim(claim, &manifest_label, &buffer, redactions)?; + Store::load_ingredient_to_claim(claim, &manifest_label, &data, redactions)?; // get the ingredient map loaded in previous match claim.claim_ingredient(&manifest_label) { @@ -931,10 +934,11 @@ impl Ingredient { // if the ingredient defines a thumbnail, add it to the claim // otherwise use the parent claim thumbnail if available - if let Some((format, data)) = self.thumbnail() { + if let Some(thumb_ref) = self.thumbnail_ref() { + let data = self.thumbnail_bytes()?; let hash_url = claim.add_assertion(&Thumbnail::new( - &labels::add_thumbnail_format(labels::INGREDIENT_THUMBNAIL, format), - data.to_vec(), + &labels::add_thumbnail_format(labels::INGREDIENT_THUMBNAIL, &thumb_ref.format), + data.into_owned(), ))?; thumbnail = Some(hash_url); } @@ -1371,7 +1375,7 @@ mod tests_file_io { println!("ingredient = {ingredient}"); assert_eq!(ingredient.validation_status(), None); - // verify we can't set a references that don't exist + // verify we can't set references that don't exist assert!(ingredient .set_thumbnail_ref(ResourceRef::new("Foo", "bar")) .is_err()); diff --git a/sdk/src/manifest.rs b/sdk/src/manifest.rs @@ -11,9 +11,9 @@ // specific language governing permissions and limitations under // each license. -#[cfg(feature = "file_io")] -use std::path::Path; use std::{borrow::Cow, collections::HashMap, io::Cursor}; +#[cfg(feature = "file_io")] +use std::{fs::create_dir_all, path::Path}; use log::{debug, error, warn}; use serde::{de::DeserializeOwned, Deserialize, Serialize}; @@ -483,7 +483,7 @@ impl Manifest { /// Ingredients resources will also be relative to this path #[cfg(feature = "file_io")] pub fn with_base_path<P: AsRef<Path>>(&mut self, base_path: P) -> Result<&Self> { - std::fs::create_dir_all(&base_path)?; + create_dir_all(&base_path)?; self.resources.set_base_path(base_path.as_ref()); for i in 0..self.ingredients.len() { // todo: create different subpath for each ingredient? @@ -626,7 +626,7 @@ impl Manifest { } // if a thumbnail is not already defined, create one here - if self.thumbnail().is_none() { + if self.thumbnail_ref().is_none() { #[cfg(feature = "add_thumbnails")] if let Ok((format, image)) = crate::utils::thumbnail::make_thumbnail(path.as_ref()) { // Do not write this as a file when reading from files @@ -669,10 +669,12 @@ impl Manifest { } claim.format = self.format().to_owned(); claim.instance_id = self.instance_id().to_owned(); - if let Some((format, data)) = self.thumbnail() { + + if let Some(thumb_ref) = self.thumbnail_ref() { + let data = self.resources.get(&thumb_ref.identifier)?; claim.add_assertion(&Thumbnail::new( - &labels::add_thumbnail_format(labels::CLAIM_THUMBNAIL, format), - data.to_vec(), + &labels::add_thumbnail_format(labels::CLAIM_THUMBNAIL, &thumb_ref.format), + data.into_owned(), ))?; } @@ -804,6 +806,10 @@ impl Manifest { } // we need to copy the source to target before setting the asset info if !dest_path.as_ref().exists() { + // ensure the path to the file exists + if let Some(output_dir) = dest_path.as_ref().parent() { + create_dir_all(output_dir)?; + } std::fs::copy(&source_path, &dest_path)?; copied = true; } @@ -1769,10 +1775,14 @@ pub(crate) mod tests { #[test] #[cfg(feature = "file_io")] fn test_create_file_based_ingredient() { - let mut folder = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")); - folder.push("tests/fixtures"); + let mut fixtures = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")); + fixtures.push("tests/fixtures"); + + let temp_dir = tempdir().expect("temp dir"); + let output = temp_fixture_path(&temp_dir, TEST_SMALL_JPEG); + let mut manifest = Manifest::new("claim_generator"); - manifest.resources.set_base_path(folder); + manifest.with_base_path(fixtures).expect("with_base"); // verify we can't set a references that don't exist assert!(manifest .set_thumbnail_ref(ResourceRef::new("image/jpg", "foo")) @@ -1783,5 +1793,10 @@ pub(crate) mod tests { .set_thumbnail_ref(ResourceRef::new("image/jpg", "C.jpg")) .is_ok()); assert!(manifest.thumbnail_ref().is_some()); + + let signer = temp_signer(); + manifest + .embed(&output, &output, signer.as_ref()) + .expect("embed"); } } diff --git a/sdk/src/resource_store.rs b/sdk/src/resource_store.rs @@ -135,10 +135,13 @@ impl ResourceStore { Some(base) => { // read the file, save in Map and then return a reference let path = base.join(id); - let value = std::fs::read(path)?; + let value = std::fs::read(path).map_err(|_| { + let path = base.join(id).to_string_lossy().into_owned(); + Error::ResourceNotFound(path) + })?; return Ok(Cow::Owned(value)); } - None => return Err(Error::NotFound), + None => return Err(Error::ResourceNotFound(id.to_string())), } } self.resources