Skip to content

fix: to_archive does not store resources associated with ingredients #1151

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
76 changes: 75 additions & 1 deletion c_api/src/c_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -889,7 +889,6 @@ pub unsafe extern "C" fn c2pa_builder_sign(

let mut builder = guard_boxed!(builder_ptr);
let c2pa_signer = guard_boxed!(signer_ptr);

let result = builder.sign(
c2pa_signer.signer.as_ref(),
&format,
Expand Down Expand Up @@ -1283,6 +1282,8 @@ unsafe fn c2pa_mime_types_to_c_array(strs: Vec<String>, count: *mut usize) -> *c
mod tests {
use std::{ffi::CString, panic::catch_unwind};

use c2pa::Reader;

use super::*;
use crate::TestC2paStream;

Expand Down Expand Up @@ -1413,6 +1414,79 @@ mod tests {
unsafe { c2pa_builder_free(builder) };
}

#[test]
fn test_to_archive_and_from_archive_with_ingredient_thumbnail() {
let manifest_def = CString::new("{}").unwrap();

let thumbnail = include_bytes!(fixture_path!("A_thumbnail.jpg"));
let mut thumbnail_stream = TestC2paStream::from_bytes(thumbnail.to_vec());

let source_image = include_bytes!(fixture_path!("A.jpg"));
let mut source_stream = TestC2paStream::from_bytes(source_image.to_vec());

let certs = include_str!(fixture_path!("certs/ed25519.pub"));
let private_key = include_bytes!(fixture_path!("certs/ed25519.pem"));
let alg = CString::new("Ed25519").unwrap();
let sign_cert = CString::new(certs).unwrap();
let private_key = CString::new(private_key).unwrap();
let signer_info = C2paSignerInfo {
alg: alg.as_ptr(),
sign_cert: sign_cert.as_ptr(),
private_key: private_key.as_ptr(),
ta_url: std::ptr::null(),
};

let signer = unsafe { c2pa_signer_from_info(&signer_info) };
assert!(!signer.is_null());

let builder = unsafe { c2pa_builder_from_json(manifest_def.as_ptr()) };
assert!(!builder.is_null());

let ingredient_json = CString::new(r#"{"title": "Test Ingredient"}"#).unwrap();
let format = CString::new("image/jpeg").unwrap();

unsafe {
c2pa_builder_add_ingredient_from_stream(
builder,
ingredient_json.as_ptr(),
format.as_ptr(),
&mut thumbnail_stream,
)
};

let dest_vec = Vec::new();
let mut dest_stream = TestC2paStream::new(dest_vec).into_c_stream();

let archive = Vec::new();
let mut archive = TestC2paStream::from_bytes(archive.to_vec());
let res = unsafe { c2pa_builder_to_archive(builder, &mut archive) };

assert_ne!(res, -1);

let builder = unsafe { c2pa_builder_from_archive(&mut archive) };
assert!(!builder.is_null());
let mut manifest_bytes_ptr = std::ptr::null();

let res = unsafe {
c2pa_builder_sign(
builder,
format.as_ptr(),
&mut source_stream,
&mut dest_stream,
signer,
&mut manifest_bytes_ptr,
)
};

assert_ne!(res, -1);

let reader_json = Reader::from_stream("image/jpeg", &mut dest_stream)
.unwrap()
.json();
assert!(reader_json.contains("Test Ingredient"));
assert!(reader_json.contains("thumbnail.ingredient"));
}

#[test]
fn test_c2pa_version() {
let version = unsafe { c2pa_version() };
Expand Down
35 changes: 33 additions & 2 deletions sdk/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ use crate::{
},
claim::Claim,
error::{Error, Result},
jumbf_io,
format_from_path, jumbf_io,
resource_store::{ResourceRef, ResourceResolver, ResourceStore},
salt::DefaultSalt,
store::Store,
Expand Down Expand Up @@ -514,6 +514,13 @@ impl Builder {
zip.start_file("manifests/", options)
.map_err(|e| Error::OtherError(Box::new(e)))?;
for ingredient in self.definition.ingredients.iter() {
// add ingredient resource files to a ingredient folder
for (id, data) in ingredient.resources().resources() {
zip.start_file(format!("ingredient-resources/{}", id), options)
.map_err(|e| Error::OtherError(Box::new(e)))?;
zip.write_all(data)?;
}

if let Some(manifest_label) = ingredient.active_manifest() {
if let Some(manifest_data) = ingredient.manifest_data() {
// Convert to valid archive / file path name
Expand Down Expand Up @@ -567,6 +574,31 @@ impl Builder {
builder.resources.add(id, data)?;
}

if file.name().starts_with("ingredient-resources/")
&& file.name() != "ingredient-resources/"
{
let mut data = Vec::new();
file.read_to_end(&mut data)?;

let id = file
.name()
.split_once('/')
.map(|(_, second)| second)
.ok_or(Error::BadParam("Invalid resource path".to_string()))?;
let format = format_from_path(id)
.ok_or(Error::BadParam("Invalid resource path".to_string()))?;
let id = id.replacen(['-'], ":", 1);
for ingredient in builder.definition.ingredients.iter_mut() {
let base_id = ingredient.instance_id().to_string();
if id.starts_with(&base_id) {
ingredient
.resources_mut()
.add_with(&base_id, &format, data)?;
break;
}
}
}

// Load the c2pa_manifests.
// Adds the manifest data to any ingredient that has a matching active_manfiest label.
if file.name().starts_with("manifests/") && file.name() != "manifests/" {
Expand Down Expand Up @@ -1040,7 +1072,6 @@ impl Builder {
// generate thumbnail if we don't already have one
#[cfg(feature = "add_thumbnails")]
self.maybe_add_thumbnail(&format, source)?;

// convert the manifest to a store
let mut store = self.to_store()?;

Expand Down
Binary file added sdk/tests/fixtures/A.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added sdk/tests/fixtures/A_thumbnail.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.