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 594a01c5b5c2639331650f12a1ae315e474f78f2
parent 7926b822f695487cb0631d05f5cc48819f23a144
Author: mauricefisher64 <92736594+mauricefisher64@users.noreply.github.com>
Date:   Wed, 14 Dec 2022 10:31:05 -0500

Create external manifests for unknown types (#162)

* Create external manifests for unknown types

* Reduce duplicate code
Diffstat:
Msdk/src/store.rs | 147+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------
Asdk/tests/fixtures/unsupported_type.txt | 2++
2 files changed, 112 insertions(+), 37 deletions(-)

diff --git a/sdk/src/store.rs b/sdk/src/store.rs @@ -1718,8 +1718,16 @@ impl Store { ) -> Result<Vec<u8>> { // clone the source to working copy if requested - get_supported_file_extension(asset_path).ok_or(Error::UnsupportedType)?; // verify extensions - let ext = get_supported_file_extension(dest_path).ok_or(Error::UnsupportedType)?; + // force generate external manifests for unknown types + let ext = match get_supported_file_extension(dest_path) { + Some(ext) => ext, + None => { + let pc = self.provenance_claim_mut().ok_or(Error::ClaimEncoding)?; + pc.set_external_manifest(); // generate external manifests for unknown types + MANIFEST_STORE_EXT.to_owned() + } + }; + if asset_path != dest_path { fs::copy(asset_path, dest_path).map_err(Error::IoError)?; } @@ -1728,26 +1736,30 @@ impl Store { // 1) Add DC provenance XMP let pc = self.provenance_claim().ok_or(Error::ClaimEncoding)?; - let output_path = if cfg!(feature = "xmp_write") { - match pc.remote_manifest() { - crate::claim::RemoteManifest::NoRemote => { - // even though this block is protected by the outer cfg!(feature = "xmp_write") - // the class embedded_xmp is not defined so we have to explicitly exclude it from the build - #[cfg(feature = "xmp_write")] - if let Some(provenance) = self.provenance_path() { - // update XMP info & add xmp hash to provenance claim - embedded_xmp::add_manifest_uri_to_file(dest_path, &provenance)?; - } else { - return Err(Error::XmpWriteError); - } - dest_path.to_path_buf() + let output_path = match pc.remote_manifest() { + crate::claim::RemoteManifest::NoRemote => { + // even though this block is protected by the outer cfg!(feature = "xmp_write") + // the class embedded_xmp is not defined so we have to explicitly exclude it from the build + #[cfg(feature = "xmp_write")] + if let Some(provenance) = self.provenance_path() { + // update XMP info & add xmp hash to provenance claim + embedded_xmp::add_manifest_uri_to_file(dest_path, &provenance)?; + } else { + return Err(Error::XmpWriteError); } - crate::claim::RemoteManifest::SideCar => { - // remove any previous c2pa manifest from the asset - remove_jumbf_from_file(dest_path)?; - dest_path.with_extension(MANIFEST_STORE_EXT) + dest_path.to_path_buf() + } + crate::claim::RemoteManifest::SideCar => { + // remove any previous c2pa manifest from the asset + match remove_jumbf_from_file(dest_path) { + Ok(_) | Err(Error::UnsupportedType) => { + dest_path.with_extension(MANIFEST_STORE_EXT) + } + Err(e) => return Err(e), } - crate::claim::RemoteManifest::Remote(_url) => { + } + crate::claim::RemoteManifest::Remote(_url) => { + if cfg!(feature = "xmp_write") { let d = dest_path.with_extension(MANIFEST_STORE_EXT); // remove any previous c2pa manifest from the asset remove_jumbf_from_file(dest_path)?; @@ -1756,28 +1768,20 @@ impl Store { #[cfg(feature = "xmp_write")] embedded_xmp::add_manifest_uri_to_file(dest_path, &_url)?; d + } else { + return Err(Error::BadParam("requires 'xmp_write' feature".to_string())); } - crate::claim::RemoteManifest::EmbedWithRemote(_url) => { + } + crate::claim::RemoteManifest::EmbedWithRemote(_url) => { + if cfg!(feature = "xmp_write") { // even though this block is protected by the outer cfg!(feature = "xmp_write") // the class embedded_xmp is not defined so we have to explicitly exclude it from the build #[cfg(feature = "xmp_write")] embedded_xmp::add_manifest_uri_to_file(dest_path, &_url)?; dest_path.to_path_buf() - } - } - } else { - // only side car and embedded supported without feature "xmp_write" - match pc.remote_manifest() { - crate::claim::RemoteManifest::NoRemote => dest_path.to_path_buf(), - crate::claim::RemoteManifest::SideCar => { - // remove any previous c2pa manifest from the asset - remove_jumbf_from_file(dest_path)?; - dest_path.with_extension(MANIFEST_STORE_EXT) - } - crate::claim::RemoteManifest::Remote(_) - | crate::claim::RemoteManifest::EmbedWithRemote(_) => { - return Err(Error::BadParam("requires 'xmp_write' feature".to_string())) + } else { + return Err(Error::BadParam("requires 'xmp_write' feature".to_string())); } } }; @@ -2005,11 +2009,19 @@ impl Store { #[cfg(feature = "file_io")] pub fn load_jumbf_from_path(in_path: &Path) -> Result<Vec<u8>> { let external_manifest = in_path.with_extension(MANIFEST_STORE_EXT); + let external_exists = external_manifest.exists(); match load_jumbf_from_file(in_path) { Ok(manifest_bytes) => Ok(manifest_bytes), + Err(Error::UnsupportedType) => { + if external_exists { + std::fs::read(external_manifest).map_err(Error::IoError) + } else { + Err(Error::UnsupportedType) + } + } Err(Error::JumbfNotFound) => { - if external_manifest.exists() { + if external_exists { std::fs::read(external_manifest).map_err(Error::IoError) } else { // check for remote manifest @@ -2419,6 +2431,67 @@ pub mod tests { .expect_err("Should not verify"); } + #[test] + #[cfg(feature = "file_io")] + fn test_unknown_asset_type_generation() { + // test adding to actual image + let ap = fixture_path("unsupported_type.txt"); + let temp_dir = tempdir().expect("temp dir"); + let op = temp_dir_path(&temp_dir, "unsupported_type.txt"); + + // Create claims store. + let mut store = Store::new(); + + // Create a new claim. + let claim1 = create_test_claim().unwrap(); + + // Create a new claim. + let mut claim2 = Claim::new("Photoshop", Some("Adobe")); + create_editing_claim(&mut claim2).unwrap(); + + // Create a 3rd party claim + let mut claim_capture = Claim::new("capture", Some("claim_capture")); + create_capture_claim(&mut claim_capture).unwrap(); + + // Do we generate JUMBF? + let signer = temp_signer(); + + // Move the claim to claims list. Note this is not real, the claims would have to be signed in between commmits + store.commit_claim(claim1).unwrap(); + store.save_to_asset(&ap, &signer, &op).unwrap(); + + // read from new file + let new_store = + Store::load_from_asset(&op, true, &mut OneShotStatusTracker::new()).unwrap(); + + // can we get by the ingredient data back + + // dump store and compare to original + for claim in new_store.claims() { + let _restored_json = claim + .to_json(AssertionStoreJsonFormat::OrderedList, false) + .unwrap(); + let _orig_json = store + .get_claim(claim.label()) + .unwrap() + .to_json(AssertionStoreJsonFormat::OrderedList, false) + .unwrap(); + + println!( + "Claim: {} \n{}", + claim.label(), + claim + .to_json(AssertionStoreJsonFormat::OrderedListNoBinary, true) + .expect("could not restore from json") + ); + + for hashed_uri in claim.assertions() { + let (label, instance) = Claim::assertion_label_from_link(&hashed_uri.url()); + claim.get_claim_assertion(&label, instance).unwrap(); + } + } + } + struct BadSigner {} impl crate::Signer for BadSigner { @@ -2718,7 +2791,7 @@ pub mod tests { } #[test] - fn test_unsupported_type() { + fn test_unsupported_type_without_external_manifest() { let ap = fixture_path("Purple Square.psd"); let mut report = DetailedStatusTracker::new(); let result = Store::load_from_asset(&ap, true, &mut report); diff --git a/sdk/tests/fixtures/unsupported_type.txt b/sdk/tests/fixtures/unsupported_type.txt @@ -0,0 +1 @@ +This is a test of file type that is not supported +\ No newline at end of file