diff --git a/sdk/src/assertion.rs b/sdk/src/assertion.rs index 9283108fb..faced6b11 100644 --- a/sdk/src/assertion.rs +++ b/sdk/src/assertion.rs @@ -236,7 +236,7 @@ impl Assertion { } pub(crate) fn set_content_type(mut self, content_type: &str) -> Self { - self.content_type = content_type.to_owned(); + content_type.clone_into(&mut self.content_type); self } diff --git a/sdk/src/asset_handlers/jpeg_io.rs b/sdk/src/asset_handlers/jpeg_io.rs index 218087b4b..23b3f94a5 100644 --- a/sdk/src/asset_handlers/jpeg_io.rs +++ b/sdk/src/asset_handlers/jpeg_io.rs @@ -56,7 +56,7 @@ fn vec_compare(va: &[u8], vb: &[u8]) -> bool { .all(|(a,b)| a == b) } -// todo decide if want to keep this just for in-memory use cases +// Return contents of APP1 segment if it is an XMP segment. fn extract_xmp(seg: &JpegSegment) -> Option { let contents = seg.contents(); if contents.starts_with(XMP_SIGNATURE) { @@ -67,6 +67,7 @@ fn extract_xmp(seg: &JpegSegment) -> Option { } } +// Extract XMP from bytes. fn xmp_from_bytes(asset_bytes: &[u8]) -> Option { if let Ok(jpeg) = Jpeg::from_bytes(Bytes::copy_from_slice(asset_bytes)) { let segs = jpeg.segments_by_marker(markers::APP1); @@ -145,7 +146,7 @@ fn get_cai_segments(jpeg: &img_parts::jpeg::Jpeg) -> Result> { if is_cai { cai_segs.push(i); cai_seg_cnt = 1; - cai_en = en.clone(); // store the identifier + cai_en.clone_from(&en); // store the identifier } } } @@ -226,7 +227,7 @@ impl CAIReader for JpegIO { buffer.append(&mut raw_vec.as_mut_slice()[8..].to_vec()); cai_seg_cnt = 1; - cai_en = en.clone(); // store the identifier + cai_en.clone_from(&en); // store the identifier manifest_store_cnt += 1; } @@ -380,7 +381,7 @@ impl CAIWriter for JpegIO { let is_cai = vec_compare(&C2PA_MARKER, &jumb_type); if is_cai { cai_seg_cnt = 1; - cai_en = en.clone(); // store the identifier + cai_en.clone_from(&en); // store the identifier let v = HashObjectPositions { offset: curr_offset, @@ -593,28 +594,29 @@ impl RemoteRefEmbed for JpegIO { let mut jpeg = Jpeg::from_bytes(buf.into()).map_err(|_err| Error::EmbeddingError)?; - // first extract the xmp from APP1 markers - let app1_segs = jpeg.segments_by_marker(markers::APP1); - let mut xmp: String = app1_segs.filter_map(extract_xmp).collect(); - - // remove existing XMP segments + // find any existing XMP segment and remember where it was + let mut xmp = MIN_XMP.to_string(); // default minimal XMP + let mut xmp_index = None; let segments = jpeg.segments_mut(); - segments.retain(|seg| { - !(seg.marker() == markers::APP1 && seg.contents().starts_with(XMP_SIGNATURE)) - }); - - if xmp.is_empty() { - // Init with minimal xmp segment - // JPEG APP1 segment with XMP are defined with this null terminated string - // todo: add format and other minimal metadata to xmp ? - xmp = format!("http://ns.adobe.com/xap/1.0/\0 {}", MIN_XMP); - }; - let xmp = add_provenance(&xmp, &manifest_uri)?; - let xmp_bytes = Bytes::from(xmp); - let segment = JpegSegment::new_with_contents(markers::APP1, xmp_bytes); - segments.insert(1, segment); + for (i, seg) in segments.iter().enumerate() { + if seg.marker() == markers::APP1 && seg.contents().starts_with(XMP_SIGNATURE) { + xmp = extract_xmp(seg).unwrap_or_else(|| xmp.clone()); + xmp_index = Some(i); + break; + } + } + // add provenance and JPEG XMP prefix + let xmp = format!( + "http://ns.adobe.com/xap/1.0/\0 {}", + add_provenance(&xmp, &manifest_uri)? + ); + let segment = JpegSegment::new_with_contents(markers::APP1, Bytes::from(xmp)); + // insert or add the segment + match xmp_index { + Some(i) => segments[i] = segment, + None => segments.insert(1, segment), + } - output_stream.rewind()?; jpeg.encoder() .write_to(output_stream) .map_err(|_err| Error::InvalidAsset("JPEG write error".to_owned()))?; @@ -800,7 +802,7 @@ fn make_box_maps(input_stream: &mut dyn CAIRead) -> Result> { let is_cai = vec_compare(&C2PA_MARKER, &jumb_type); if is_cai { cai_seg_cnt = 1; - cai_en = en.clone(); // store the identifier + cai_en.clone_from(&en); // store the identifier let c2pa_bm = BoxMap { names: vec![C2PA_BOXHASH.to_string()], diff --git a/sdk/src/asset_handlers/png_io.rs b/sdk/src/asset_handlers/png_io.rs index 42f069bc2..993e5c506 100644 --- a/sdk/src/asset_handlers/png_io.rs +++ b/sdk/src/asset_handlers/png_io.rs @@ -607,7 +607,7 @@ fn get_xmp_insertion_point(asset_reader: &mut dyn CAIRead) -> Option<(u64, u32)> if let Some(xmp) = xmp_box { // overwrite existing box - Some((xmp.start, xmp.length)) + Some((xmp.start, xmp.length + PNG_HDR_LEN as u32)) } else { // insert after IHDR ps.iter() diff --git a/sdk/src/asset_handlers/tiff_io.rs b/sdk/src/asset_handlers/tiff_io.rs index 253df9385..1b08a3905 100644 --- a/sdk/src/asset_handlers/tiff_io.rs +++ b/sdk/src/asset_handlers/tiff_io.rs @@ -161,9 +161,9 @@ pub(crate) struct TiffStructure { impl TiffStructure { #[allow(dead_code)] - pub fn load(reader: &mut R) -> Result + pub fn load(reader: &mut R) -> Result where - R: Read + Seek, + R: Read + Seek + ?Sized, { let mut endianness = [0u8, 2]; reader.read_exact(&mut endianness)?; @@ -231,14 +231,14 @@ impl TiffStructure { } // read IFD entries, all value_offset are in source endianness - pub fn read_ifd_entries( + pub fn read_ifd_entries( byte_reader: &mut ByteOrdered<&mut R, Endianness>, big_tiff: bool, entry_cnt: u64, entries: &mut HashMap, ) -> Result<()> where - R: Read + Seek, + R: Read + Seek + ?Sized, { for _ in 0..entry_cnt { let tag = byte_reader.read_u16()?; @@ -290,14 +290,14 @@ impl TiffStructure { } // read IFD from reader - pub fn read_ifd( + pub fn read_ifd( reader: &mut R, byte_order: Endianness, big_tiff: bool, ifd_type: IfdType, ) -> Result where - R: Read + Seek + ReadBytesExt, + R: Read + Seek + ReadBytesExt + ?Sized, { let mut byte_reader = ByteOrdered::runtime(reader, byte_order); @@ -365,11 +365,9 @@ fn stream_len(reader: &mut dyn CAIRead) -> crate::Result { Ok(len) } // create tree of TIFF structure IFDs and IFD entries. -fn map_tiff( - input: &mut R, -) -> Result<(Arena, Token, Endianness, bool)> +fn map_tiff(input: &mut R) -> Result<(Arena, Token, Endianness, bool)> where - R: Read + Seek, + R: Read + Seek + ?Sized, { let _size = input.seek(SeekFrom::End(0))?; input.rewind()?; @@ -1329,9 +1327,9 @@ fn add_required_tags_to_stream( } } -fn get_cai_data(asset_reader: &mut R) -> Result> +fn get_cai_data(asset_reader: &mut R) -> Result> where - R: Read + Seek, + R: Read + Seek + ?Sized, { let (tiff_tree, page_0, e, big_tiff) = map_tiff(asset_reader)?; @@ -1362,9 +1360,9 @@ where Ok(data) } -fn get_xmp_data(asset_reader: &mut R) -> Option> +fn get_xmp_data(asset_reader: &mut R) -> Option> where - R: Read + Seek, + R: Read + Seek + ?Sized, { let (tiff_tree, page_0, e, big_tiff) = map_tiff(asset_reader).ok()?; let first_ifd = &tiff_tree[page_0].data; @@ -1560,13 +1558,9 @@ impl CAIWriter for TiffIO { let mut bo = ByteOrdered::new(output_stream, e); let mut tc = TiffCloner::new(e, big_tiff, &mut bo)?; - match idfs[page_0].data.entries.remove(&C2PA_TAG) { - Some(_ifd) => { - tc.clone_tiff(&mut idfs, page_0, input_stream)?; - Ok(()) - } - None => Ok(()), - } + idfs[page_0].data.entries.remove(&C2PA_TAG); + tc.clone_tiff(&mut idfs, page_0, input_stream)?; + Ok(()) } } @@ -1632,6 +1626,7 @@ impl RemoteRefEmbed for TiffIO { } // write will replace exisiting contents + output_stream.rewind()?; std::fs::write(asset_path, output_stream.into_inner())?; Ok(()) } @@ -1755,6 +1750,9 @@ pub mod tests { let tiff_io = TiffIO {}; + // first make sure that calling this without a manifest does not error + tiff_io.remove_cai_store(&output).unwrap(); + // save data to tiff tiff_io.save_cai_store(&output, data.as_bytes()).unwrap(); diff --git a/sdk/src/asset_io.rs b/sdk/src/asset_io.rs index 5fa77e88d..726c8c000 100644 --- a/sdk/src/asset_io.rs +++ b/sdk/src/asset_io.rs @@ -169,9 +169,11 @@ pub trait AssetIO: Sync + Send { /// If the offsets exist return the start of those locations other it should /// return the calculated location of when it should start. There may still be a /// length if the format contains extra header information for example. + #[allow(dead_code)] // this here for wasm builds to pass clippy (todo: remove) fn get_object_locations(&self, asset_path: &Path) -> Result>; // Remove entire C2PA manifest store from asset + #[allow(dead_code)] // this here for wasm builds to pass clippy (todo: remove) fn remove_cai_store(&self, asset_path: &Path) -> Result<()>; // List of supported extensions and mime types @@ -180,6 +182,7 @@ pub trait AssetIO: Sync + Send { /// OPTIONAL INTERFACES // Returns [`AssetPatch`] trait if this I/O handler supports patching. + #[allow(dead_code)] // this here for wasm builds to pass clippy (todo: remove) fn asset_patch_ref(&self) -> Option<&dyn AssetPatch> { None } @@ -208,6 +211,7 @@ pub trait AssetPatch { // Patches an existing manifest store with new manifest store. // Only existing manifest stores of the same size may be patched // since any other changes will invalidate asset hashes. + #[allow(dead_code)] // this here for wasm builds to pass clippy (todo: remove) fn patch_cai_store(&self, asset_path: &Path, store_bytes: &[u8]) -> Result<()>; } @@ -236,6 +240,7 @@ pub enum RemoteRefEmbedType { // all embedding choices need be supported. pub trait RemoteRefEmbed { // Embed RemoteRefEmbedType into the asset + #[allow(dead_code)] // this here for wasm builds to pass clippy (todo: remove) fn embed_reference(&self, asset_path: &Path, embed_ref: RemoteRefEmbedType) -> Result<()>; // Embed RemoteRefEmbedType into the asset stream fn embed_reference_to_stream( diff --git a/sdk/src/builder.rs b/sdk/src/builder.rs index a518864ce..6fe5da4cb 100644 --- a/sdk/src/builder.rs +++ b/sdk/src/builder.rs @@ -500,8 +500,8 @@ impl Builder { if let Some(title) = definition.title.as_ref() { claim.set_title(Some(title.to_owned())); } - claim.format = definition.format.to_owned(); - claim.instance_id = definition.instance_id.to_owned(); + definition.format.clone_into(&mut claim.format); + definition.instance_id.clone_into(&mut claim.instance_id); if let Some(thumb_ref) = definition.thumbnail.as_ref() { // Setting the format to "none" will ensure that no claim thumbnail is added @@ -712,7 +712,7 @@ impl Builder { W: Write + Read + Seek + Send, { let format = format_to_mime(format); - self.definition.format = format.clone(); + self.definition.format.clone_from(&format); // todo:: read instance_id from xmp from stream ? self.definition.instance_id = format!("xmp:iid:{}", Uuid::new_v4()); diff --git a/sdk/src/cose_validator.rs b/sdk/src/cose_validator.rs index bfcb87f2b..ac147170a 100644 --- a/sdk/src/cose_validator.rs +++ b/sdk/src/cose_validator.rs @@ -1293,10 +1293,10 @@ async fn validate_with_cert_async( } } #[allow(unused_imports)] +#[allow(clippy::unwrap_used)] #[cfg(feature = "openssl_sign")] #[cfg(test)] pub mod tests { - #![allow(clippy::unwrap_used)] use sha2::digest::generic_array::sequence::Shorten; diff --git a/sdk/src/error.rs b/sdk/src/error.rs index c3a9fc487..80fb5d3a1 100644 --- a/sdk/src/error.rs +++ b/sdk/src/error.rs @@ -216,10 +216,10 @@ pub enum Error { ResourceNotFound(String), #[error("XMP read error")] - XmpReadError, + XmpReadError(String), #[error("XMP write error")] - XmpWriteError, + XmpWriteError(String), #[error("XMP is not supported")] XmpNotSupported, diff --git a/sdk/src/ingredient.rs b/sdk/src/ingredient.rs index 4c0dbeebd..f198900b2 100644 --- a/sdk/src/ingredient.rs +++ b/sdk/src/ingredient.rs @@ -1049,7 +1049,7 @@ impl Ingredient { &data_box.format, data_box.data.clone(), )?; - data_ref.data_types = data_box.data_types.clone(); + data_ref.data_types.clone_from(&data_box.data_types); ingredient.set_data_ref(data_ref)?; } @@ -1228,15 +1228,22 @@ impl Ingredient { let mut ingredient_assertion = assertions::Ingredient::new_v2(&self.title, &self.format); ingredient_assertion.instance_id = instance_id; - ingredient_assertion.document_id = self.document_id.to_owned(); + self.document_id + .clone_into(&mut ingredient_assertion.document_id); ingredient_assertion.c2pa_manifest = c2pa_manifest; ingredient_assertion.relationship = self.relationship.clone(); ingredient_assertion.thumbnail = thumbnail; - ingredient_assertion.metadata = self.metadata.clone(); - ingredient_assertion.validation_status = self.validation_status.clone(); + ingredient_assertion.metadata.clone_from(&self.metadata); + ingredient_assertion + .validation_status + .clone_from(&self.validation_status); ingredient_assertion.data = data; - ingredient_assertion.description = self.description.clone(); - ingredient_assertion.informational_uri = self.informational_uri.clone(); + ingredient_assertion + .description + .clone_from(&self.description); + ingredient_assertion + .informational_uri + .clone_from(&self.informational_uri); claim.add_assertion(&ingredient_assertion) } diff --git a/sdk/src/jumbf_io.rs b/sdk/src/jumbf_io.rs index 17bec0773..38a54c831 100644 --- a/sdk/src/jumbf_io.rs +++ b/sdk/src/jumbf_io.rs @@ -317,8 +317,13 @@ pub mod tests { #![allow(clippy::panic)] #![allow(clippy::unwrap_used)] + use std::io::Seek; + use super::*; - use crate::utils::test::{create_test_store, temp_signer}; + use crate::{ + asset_io::RemoteRefEmbedType, + utils::test::{create_test_store, temp_signer}, + }; #[test] fn test_get_assetio() { @@ -408,45 +413,146 @@ pub mod tests { assert!(supported.iter().any(|s| s == "mp3")); } - #[test] - fn test_streams() { - let files: Vec<(&str, &str)> = vec![ - ("IMG_0003.jpg", "jpeg"), - ("sample1.png", "png"), - //("sample1.webp", "webp"), // riff io deletion of manifest store isn't working. - ("TUSCANY.TIF", "tiff"), - ("sample1.svg", "svg"), - //("sample1.wav", "wav"), - //("test.avi", "avi"), - ("sample1.mp3", "mp3"), - ("sample1.avif", "avif"), - ("sample1.heic", "heic"), - ("sample1.heif", "heif"), - ("video1.mp4", "mp4"), - //("cloud_manifest.c2pa", "c2pa") - ]; - for (name, asset_type) in files { - println!("Testing {}", name); - let mut reader = std::fs::File::open(format!("tests/fixtures/{}", name)).unwrap(); - let mut writer = Cursor::new(Vec::new()); - let store = create_test_store().unwrap(); - let signer = temp_signer(); - let jumbf = store.to_jumbf(&*signer).unwrap(); - save_jumbf_to_stream(asset_type, &mut reader, &mut writer, &jumbf).unwrap(); - writer.set_position(0); - let jumbf2 = load_jumbf_from_stream(asset_type, &mut writer).unwrap(); - assert_eq!(jumbf, jumbf2); - - // test removing cai store - writer.set_position(0); - let handler = get_caiwriter_handler(asset_type).unwrap(); - let mut removed = Cursor::new(Vec::new()); - handler - .remove_cai_store_from_stream(&mut writer, &mut removed) - .unwrap(); - removed.set_position(0); - let result = load_jumbf_from_stream(asset_type, &mut removed); - assert!(matches!(result.err().unwrap(), Error::JumbfNotFound)); + fn test_jumbf(asset_type: &str, reader: &mut dyn CAIRead) { + let mut writer = Cursor::new(Vec::new()); + let store = create_test_store().unwrap(); + let signer = temp_signer(); + let jumbf = store.to_jumbf(&*signer).unwrap(); + save_jumbf_to_stream(asset_type, reader, &mut writer, &jumbf).unwrap(); + writer.set_position(0); + let jumbf2 = load_jumbf_from_stream(asset_type, &mut writer).unwrap(); + assert_eq!(jumbf, jumbf2); + + // test removing cai store + writer.set_position(0); + let handler = get_caiwriter_handler(asset_type).unwrap(); + let mut removed = Cursor::new(Vec::new()); + handler + .remove_cai_store_from_stream(&mut writer, &mut removed) + .unwrap(); + removed.set_position(0); + let result = load_jumbf_from_stream(asset_type, &mut removed); + if (asset_type != "wav") + && (asset_type != "avi" && asset_type != "mp3" && asset_type != "webp") + { + assert!(matches!(&result.err().unwrap(), Error::JumbfNotFound)); } + //assert!(matches!(result.err().unwrap(), Error::JumbfNotFound)); + } + + fn test_remote_ref(asset_type: &str, reader: &mut dyn CAIRead) { + const REMOTE_URL: &str = "https://example.com/remote_manifest"; + let asset_handler = get_assetio_handler(asset_type).unwrap(); + let remote_ref_writer = asset_handler.remote_ref_writer_ref().unwrap(); + let mut writer = Cursor::new(Vec::new()); + let embed_ref = RemoteRefEmbedType::Xmp(REMOTE_URL.to_string()); + remote_ref_writer + .embed_reference_to_stream(reader, &mut writer, embed_ref) + .unwrap(); + writer.set_position(0); + let xmp = asset_handler.get_reader().read_xmp(&mut writer).unwrap(); + let loaded = crate::utils::xmp_inmemory_utils::extract_provenance(&xmp).unwrap(); + assert_eq!(loaded, REMOTE_URL.to_string()); + } + + #[test] + fn test_streams_jpeg() { + let mut reader = std::fs::File::open("tests/fixtures/IMG_0003.jpg").unwrap(); + test_jumbf("jpeg", &mut reader); + reader.rewind().unwrap(); + test_remote_ref("jpeg", &mut reader); + } + + #[test] + fn test_streams_png() { + let mut reader = std::fs::File::open("tests/fixtures/sample1.png").unwrap(); + test_jumbf("png", &mut reader); + reader.rewind().unwrap(); + test_remote_ref("png", &mut reader); + } + + #[test] + fn test_streams_webp() { + let mut reader = std::fs::File::open("tests/fixtures/sample1.webp").unwrap(); + test_jumbf("webp", &mut reader); + reader.rewind().unwrap(); + test_remote_ref("webp", &mut reader); + } + + #[test] + fn test_streams_wav() { + let mut reader = std::fs::File::open("tests/fixtures/sample1.wav").unwrap(); + test_jumbf("wav", &mut reader); + reader.rewind().unwrap(); + test_remote_ref("wav", &mut reader); + } + + #[test] + fn test_streams_avi() { + let mut reader = std::fs::File::open("tests/fixtures/test.avi").unwrap(); + test_jumbf("avi", &mut reader); + //reader.rewind().unwrap(); + //test_remote_ref("avi", &mut reader); // not working + } + + #[test] + fn test_streams_tiff() { + let mut reader = std::fs::File::open("tests/fixtures/TUSCANY.TIF").unwrap(); + test_jumbf("tiff", &mut reader); + reader.rewind().unwrap(); + test_remote_ref("tiff", &mut reader); + } + + #[test] + fn test_streams_svg() { + let mut reader = std::fs::File::open("tests/fixtures/sample1.svg").unwrap(); + test_jumbf("svg", &mut reader); + //reader.rewind().unwrap(); + //test_remote_ref("svg", &mut reader); // svg doesn't support remote refs + } + + #[test] + fn test_streams_mp3() { + let mut reader = std::fs::File::open("tests/fixtures/sample1.mp3").unwrap(); + test_jumbf("mp3", &mut reader); + // mp3 doesn't support remote refs + //reader.rewind().unwrap(); + //test_remote_ref("mp3", &mut reader); // not working + } + + #[test] + fn test_streams_avif() { + let mut reader = std::fs::File::open("tests/fixtures/sample1.avif").unwrap(); + test_jumbf("avif", &mut reader); + //reader.rewind().unwrap(); + //test_remote_ref("avif", &mut reader); // not working + } + + #[test] + fn test_streams_heic() { + let mut reader = std::fs::File::open("tests/fixtures/sample1.heic").unwrap(); + test_jumbf("heic", &mut reader); + } + + #[test] + fn test_streams_heif() { + let mut reader = std::fs::File::open("tests/fixtures/sample1.heif").unwrap(); + test_jumbf("heif", &mut reader); + //reader.rewind().unwrap(); + //test_remote_ref("heif", &mut reader); // not working + } + + #[test] + fn test_streams_mp4() { + let mut reader = std::fs::File::open("tests/fixtures/video1.mp4").unwrap(); + test_jumbf("mp4", &mut reader); + reader.rewind().unwrap(); + test_remote_ref("mp4", &mut reader); + } + + #[test] + fn test_streams_c2pa() { + let mut reader = std::fs::File::open("tests/fixtures/cloud_manifest.c2pa").unwrap(); + test_jumbf("c2pa", &mut reader); } } diff --git a/sdk/src/manifest.rs b/sdk/src/manifest.rs index b2c5f39d4..5dd26b65a 100644 --- a/sdk/src/manifest.rs +++ b/sdk/src/manifest.rs @@ -55,7 +55,7 @@ pub struct Manifest { #[serde(default = "default_claim_generator")] pub claim_generator: String, - /// + /// A list of claim generator info data identifying the software/hardware/system produced this claim #[serde(skip_serializing_if = "Option::is_none")] pub claim_generator_info: Option>, @@ -733,8 +733,8 @@ impl Manifest { if let Some(title) = self.title() { claim.set_title(Some(title.to_owned())); } - claim.format = self.format().to_owned(); - claim.instance_id = self.instance_id().to_owned(); + self.format().clone_into(&mut claim.format); + self.instance_id().clone_into(&mut claim.instance_id); if let Some(thumb_ref) = self.thumbnail_ref() { // Setting the format to "none" will ensure that no claim thumbnail is added @@ -1347,6 +1347,7 @@ pub(crate) mod tests { // example of random data structure as an assertion #[derive(serde::Serialize)] + #[allow(dead_code)] // this here for wasm builds to pass clippy (todo: remove) struct MyStruct { l1: String, l2: u32, diff --git a/sdk/src/openssl/rsa_signer.rs b/sdk/src/openssl/rsa_signer.rs index a57275436..1f3bb8641 100644 --- a/sdk/src/openssl/rsa_signer.rs +++ b/sdk/src/openssl/rsa_signer.rs @@ -230,9 +230,9 @@ fn wrap_openssl_err(err: openssl::error::ErrorStack) -> Error { } #[allow(unused_imports)] +#[allow(clippy::unwrap_used)] #[cfg(test)] mod tests { - #![allow(clippy::unwrap_used)] use super::*; use crate::{ diff --git a/sdk/src/openssl/rsa_validator.rs b/sdk/src/openssl/rsa_validator.rs index 49fbdc42b..d16e40136 100644 --- a/sdk/src/openssl/rsa_validator.rs +++ b/sdk/src/openssl/rsa_validator.rs @@ -69,9 +69,9 @@ impl CoseValidator for RsaValidator { } #[allow(unused_imports)] +#[allow(clippy::unwrap_used)] #[cfg(test)] mod tests { - #![allow(clippy::unwrap_used)] use super::*; use crate::{signer::ConfigurableSigner, Signer, SigningAlg}; diff --git a/sdk/src/resource_store.rs b/sdk/src/resource_store.rs index 2d34254c0..45e77169c 100644 --- a/sdk/src/resource_store.rs +++ b/sdk/src/resource_store.rs @@ -14,7 +14,7 @@ use std::{ borrow::Cow, collections::HashMap, - io::{Cursor, Read, Seek, Write}, + io::{Read, Seek, Write}, }; #[cfg(feature = "file_io")] use std::{ @@ -26,9 +26,9 @@ use std::{ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use crate::{ - assertions::AssetType, asset_io::CAIRead, claim::Claim, hashed_uri::HashedUri, Error, Result, -}; +#[cfg(feature = "unstable_api")] +use crate::asset_io::CAIRead; +use crate::{assertions::AssetType, claim::Claim, hashed_uri::HashedUri, Error, Result}; /// Function that is used by serde to determine whether or not we should serialize /// resources based on the `serialize_resources` flag. @@ -359,14 +359,16 @@ impl Default for ResourceStore { } } +#[cfg(feature = "unstable_api")] pub trait ResourceResolver { fn open(&self, reference: &ResourceRef) -> Result>; } +#[cfg(feature = "unstable_api")] impl ResourceResolver for ResourceStore { fn open(&self, reference: &ResourceRef) -> Result> { let data = self.get(&reference.identifier)?.into_owned(); - let cursor = Cursor::new(data); + let cursor = std::io::Cursor::new(data); Ok(Box::new(cursor)) } } @@ -377,6 +379,8 @@ mod tests { #![allow(clippy::expect_used)] #![allow(clippy::unwrap_used)] + use std::io::Cursor; + use super::*; use crate::{utils::test::temp_signer, Builder, Reader}; diff --git a/sdk/src/signer.rs b/sdk/src/signer.rs index 87f7e8311..5501a6b2e 100644 --- a/sdk/src/signer.rs +++ b/sdk/src/signer.rs @@ -80,6 +80,7 @@ pub trait Signer { } /// Trait to allow loading of signing credential from external sources +#[allow(dead_code)] // this here for wasm builds to pass clippy (todo: remove) pub(crate) trait ConfigurableSigner: Signer + Sized { /// Create signer form credential files #[cfg(feature = "file_io")] diff --git a/sdk/src/store.rs b/sdk/src/store.rs index 510fc7250..33a1f1b2f 100644 --- a/sdk/src/store.rs +++ b/sdk/src/store.rs @@ -1808,8 +1808,8 @@ impl Store { } let mut adjusted_dh = DataHash::new("jumbf manifest", pc.alg()); - adjusted_dh.exclusions = dh.exclusions.clone(); - adjusted_dh.hash = dh.hash.clone(); + adjusted_dh.exclusions.clone_from(&dh.exclusions); + adjusted_dh.hash.clone_from(&dh.hash); if let Some(reader) = asset_reader { // calc hashes @@ -4637,12 +4637,14 @@ pub mod tests { Store::load_from_asset(&op, true, &mut validation_log).unwrap(); } - #[test] - fn test_external_manifest_embedded() { + // generalize test for multipe file types + fn external_manifest_test(file_name: &str) { // test adding to actual image - let ap = fixture_path("libpng-test.png"); + let ap = fixture_path(file_name); + let extension = ap.extension().unwrap().to_str().unwrap(); let temp_dir = tempdir().expect("temp dir"); - let op = temp_dir_path(&temp_dir, "libpng-test-c2pa.png"); + let mut op = temp_dir_path(&temp_dir, file_name); + op.set_extension(extension); let sidecar = op.with_extension(MANIFEST_STORE_EXT); @@ -4679,7 +4681,7 @@ pub mod tests { // load the jumbf back into a store let mut asset_reader = std::fs::File::open(op.clone()).unwrap(); let ext_ref = - crate::utils::xmp_inmemory_utils::XmpInfo::from_source(&mut asset_reader, "png") + crate::utils::xmp_inmemory_utils::XmpInfo::from_source(&mut asset_reader, extension) .provenance .unwrap(); @@ -4690,6 +4692,21 @@ pub mod tests { Store::load_from_asset(&op, true, &mut validation_log).unwrap(); } + #[test] + fn test_external_manifest_embedded_png() { + external_manifest_test("libpng-test.png"); + } + + #[test] + fn test_external_manifest_embedded_tiff() { + external_manifest_test("TUSCANY.TIF"); + } + + #[test] + fn test_external_manifest_embedded_webp() { + external_manifest_test("sample1.webp"); + } + #[test] fn test_user_guid_external_manifest_embedded() { // test adding to actual image diff --git a/sdk/src/trust_handler.rs b/sdk/src/trust_handler.rs index a31c99e2f..689772743 100644 --- a/sdk/src/trust_handler.rs +++ b/sdk/src/trust_handler.rs @@ -51,9 +51,11 @@ pub(crate) trait TrustHandlerConfig: Sync + Send { fn get_auxillary_ekus(&self) -> Vec; // list of all anchors + #[allow(dead_code)] // Only used in calls with allow dead_code fn get_anchors(&self) -> Vec>; // set of allowed cert hashes + #[allow(dead_code)] // Only used in calls with allow dead_code fn get_allowed_list(&self) -> &HashSet; } diff --git a/sdk/src/utils/xmp_inmemory_utils.rs b/sdk/src/utils/xmp_inmemory_utils.rs index 011295cac..5c2d2348e 100644 --- a/sdk/src/utils/xmp_inmemory_utils.rs +++ b/sdk/src/utils/xmp_inmemory_utils.rs @@ -14,7 +14,7 @@ use std::io::Cursor; use fast_xml::{ - events::{BytesEnd, BytesStart, Event}, + events::{BytesStart, Event}, Reader, Writer, }; use log::error; @@ -94,31 +94,40 @@ fn extract_xmp_key(xmp: &str, key: &str) -> Option { None } +// writes the event to the writer) /// Add a value to XMP using a key, replaces the value if the key exists fn add_xmp_key(xmp: &str, key: &str, value: &str) -> Result { let mut reader = Reader::from_str(xmp); reader.trim_text(true); - let mut writer = Writer::new(Cursor::new(Vec::new())); + let mut writer = Writer::new_with_indent(Cursor::new(Vec::new()), b' ', 2); let mut buf = Vec::new(); let mut added = false; loop { - match reader.read_event(&mut buf) { - Ok(Event::Start(ref e)) if e.name() == RDF_DESCRIPTION => { + let event = reader + .read_event(&mut buf) + .map_err(|e| Error::XmpReadError(e.to_string()))?; + // println!("{:?}", event); + match event { + Event::Start(ref e) if e.name() == RDF_DESCRIPTION => { // creates a new element let mut elem = BytesStart::owned(RDF_DESCRIPTION.to_vec(), RDF_DESCRIPTION.len()); + for attr in e.attributes() { - if let Ok(attr) = attr { - if attr.key == key.as_bytes() { - // replace the key/value if it exists - elem.push_attribute((key, value)); - added = true; - } else { - // add all other existing elements - elem.extend_attributes([attr]); + match attr { + Ok(attr) => { + if attr.key == key.as_bytes() { + // replace the key/value if it exists + elem.push_attribute((key, value)); + added = true; + } else { + // add all other existing elements + elem.extend_attributes([attr]); + } + } + Err(e) => { + error!("Error at position {}", reader.buffer_position()); + return Err(Error::XmpReadError(e.to_string())); } - } else { - error!("Error at position {}", reader.buffer_position()); - return Err(Error::XmpReadError); } } if !added { @@ -126,24 +135,51 @@ fn add_xmp_key(xmp: &str, key: &str, value: &str) -> Result { elem.push_attribute((key, value)); } // writes the event to the writer - assert!(writer.write_event(Event::Start(elem)).is_ok()); + writer + .write_event(Event::Start(elem)) + .map_err(|e| Error::XmpWriteError(e.to_string()))?; } - Ok(Event::End(ref e)) if e.name() == b"this_tag" => { - assert!(writer - .write_event(Event::End(BytesEnd::borrowed(b"my_elem"))) - .is_ok()); + Event::Empty(ref e) if e.name() == RDF_DESCRIPTION => { + // creates a new element + let mut elem = BytesStart::owned(RDF_DESCRIPTION.to_vec(), RDF_DESCRIPTION.len()); + for attr in e.attributes() { + match attr { + Ok(attr) => { + if attr.key == key.as_bytes() { + // replace the key/value if it exists + elem.push_attribute((key, value)); + added = true; + } else { + // add all other existing elements + elem.extend_attributes([attr]); + } + } + Err(e) => { + error!("Error at position {}", reader.buffer_position()); + return Err(Error::XmpReadError(e.to_string())); + } + } + } + if !added { + // didn't exist, so add it + elem.push_attribute((key, value)); + } + // writes the event to the writer + writer + .write_event(Event::Empty(elem)) + .map_err(|e| Error::XmpWriteError(e.to_string()))?; } - Ok(Event::Eof) => break, - Ok(e) => assert!(writer.write_event(e).is_ok()), - Err(e) => { - error!("Error at position {}: {:?}", reader.buffer_position(), e); - return Err(Error::XmpWriteError); + Event::Eof => break, + e => { + writer + .write_event(e) + .map_err(|e| Error::XmpWriteError(e.to_string()))?; } } } buf.clear(); let result = writer.into_inner().into_inner(); - String::from_utf8(result).map_err(|_e| Error::XmpWriteError) + String::from_utf8(result).map_err(|e| Error::XmpWriteError(e.to_string())) } /// extract the dc:provenance value from xmp diff --git a/sdk/src/validator.rs b/sdk/src/validator.rs index 51156d1c7..ba287c8d4 100644 --- a/sdk/src/validator.rs +++ b/sdk/src/validator.rs @@ -32,6 +32,7 @@ pub struct ValidationInfo { /// Trait to support validating a signature against the provided data pub(crate) trait CoseValidator { /// validate signature "sig" for given "data using provided public key" + #[allow(dead_code)] // this here for wasm builds to pass clippy (todo: remove) fn validate(&self, sig: &[u8], data: &[u8], pkey: &[u8]) -> Result; }