From 5c81d153b96fdc3a2675faf7ceee4f094646e2d5 Mon Sep 17 00:00:00 2001 From: Gavin Peacock Date: Sat, 28 Sep 2024 12:00:57 -0700 Subject: [PATCH 1/3] feat: Adds Reader.from_manifest_data_and_stream() for validating remote or sidecar manifests --- c2pa/c2pa_api/c2pa_api.py | 7 +++++-- src/c2pa.udl | 7 +++++++ src/lib.rs | 32 ++++++++++++++++++++++++++++++++ tests/unit_tests.py | 22 ++++++++++++++++++++-- 4 files changed, 64 insertions(+), 4 deletions(-) diff --git a/c2pa/c2pa_api/c2pa_api.py b/c2pa/c2pa_api/c2pa_api.py index 3ff64dd3..26fecd86 100644 --- a/c2pa/c2pa_api/c2pa_api.py +++ b/c2pa/c2pa_api/c2pa_api.py @@ -36,9 +36,12 @@ # reader = Reader("image/jpeg", open("test.jpg", "rb")) # json = reader.json() class Reader(api.Reader): - def __init__(self, format, stream): + def __init__(self, format, stream, manifest_data=None): super().__init__() - self.from_stream(format, C2paStream(stream)) + if manifest_data is not None: + self.from_manifest_data_and_stream(format, manifest_data, stream) + else: + self.from_stream(format, C2paStream(stream)) @classmethod def from_file(cls, path: str, format=None): diff --git a/src/c2pa.udl b/src/c2pa.udl index d4c31d50..928dea36 100644 --- a/src/c2pa.udl +++ b/src/c2pa.udl @@ -57,6 +57,9 @@ interface Reader { [Throws=Error] string from_stream([ByRef] string format, [ByRef] Stream reader); + [Throws=Error] + string from_manifest_data_and_stream([ByRef] bytes manifest_data, [ByRef] string format, [ByRef] Stream reader); + [Throws=Error] string json(); @@ -79,6 +82,10 @@ interface Builder { [Throws=Error] void with_json([ByRef] string json); + void set_no_embed(); + + void set_remote_url([ByRef] string url) + [Throws=Error] void add_resource([ByRef] string uri, [ByRef] Stream stream ); diff --git a/src/lib.rs b/src/lib.rs index 4fcf30fd..7e440925 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -77,6 +77,19 @@ impl Reader { Ok(json) } + pub fn from_manifest_data_and_stream(&self, manifest_data: &[u8], format: &str, stream: &dyn Stream) -> Result { + // uniffi doesn't allow mutable parameters, so we we use an adapter + let mut stream = StreamAdapter::from(stream); + let reader = c2pa::Reader::from_manifest_data_and_stream(manifest_data, format, &mut stream)?; + let json = reader.to_string(); + if let Ok(mut st) = self.reader.try_write() { + *st = reader; + } else { + return Err(Error::RwLock); + }; + Ok(json) + } + pub fn json(&self) -> Result { if let Ok(st) = self.reader.try_read() { Ok(st.json()) @@ -121,6 +134,25 @@ impl Builder { Ok(()) } + /// Set to true to disable embedding a manifest + pub fn set_no_embed(&self) -> Result<()> { + if let Ok(mut builder) = self.builder.try_write() { + builder.set_no_embed(true); + } else { + return Err(Error::RwLock); + }; + Ok(()) + } + + pub fn set_remote_url(&self, remote_url: &str) -> Result<()> { + if let Ok(mut builder) = self.builder.try_write() { + builder.set_remote_url(remote_url); + } else { + return Err(Error::RwLock); + }; + Ok(()) + } + /// Add a resource to the builder pub fn add_resource(&self, uri: &str, stream: &dyn Stream) -> Result<()> { if let Ok(mut builder) = self.builder.try_write() { diff --git a/tests/unit_tests.py b/tests/unit_tests.py index 3532bfbb..e9b2e7b9 100644 --- a/tests/unit_tests.py +++ b/tests/unit_tests.py @@ -44,6 +44,14 @@ def test_stream_read_and_parse(self): title = manifest = manifest_store["manifests"][manifest_store["active_manifest"]]["title"] self.assertEqual(title, "C.jpg") + def test_from_manifest_data_and_stream(self): + with open(testPath, "rb") as stream: + with open("tests/fixtures/manifest.c2pa", "r") as manifest_data: + reader = Reader("image/jpeg",stream, manifest_data) + manifest_store = json.loads(reader.json()) + title = manifest = manifest_store["manifests"][manifest_store["active_manifest"]]["title"] + self.assertEqual(title, "C.jpg") + def test_json_decode_err(self): with self.assertRaises(Error.Io): manifest_store = Reader("image/jpeg","foo") @@ -92,7 +100,7 @@ def sign(data: bytes) -> bytes: # Create a local Ps256 signer with certs and a timestamp server signer = create_signer(sign, SigningAlg.PS256, certs, "http://timestamp.digicert.com") - def test_streams_build(self): + def test_streams_sign(self): with open(testPath, "rb") as file: builder = Builder(TestBuilder.manifestDefinition) output = io.BytesIO(bytearray()) @@ -101,7 +109,7 @@ def test_streams_build(self): reader = Reader("image/jpeg", output) self.assertIn("Python Test", reader.json()) - def test_streams_build(self): + def test_archive_sign(self): with open(testPath, "rb") as file: builder = Builder(TestBuilder.manifestDefinition) archive = byte_array = io.BytesIO(bytearray()) @@ -113,5 +121,15 @@ def test_streams_build(self): reader = Reader("image/jpeg", output) self.assertIn("Python Test", reader.json()) + def test_remote_sign(self): + with open(testPath, "rb") as file: + builder = Builder(TestBuilder.manifestDefinition) + builder.set_no_embed() + output = io.BytesIO(bytearray()) + manifest_data = builder.sign(TestBuilder.signer, "image/jpeg", file, output) + output.seek(0) + reader = Reader.from_manifest_data_and_stream(manifest_data, "image/jpeg", output) + self.assertIn("Python Test", reader.json()) + if __name__ == '__main__': unittest.main() \ No newline at end of file From 0aab2bad525734e12e5dcd2e9c22d11e97a82861 Mon Sep 17 00:00:00 2001 From: Gavin Peacock Date: Sat, 28 Sep 2024 12:05:17 -0700 Subject: [PATCH 2/3] test: Removes erroneous test case. --- tests/unit_tests.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/tests/unit_tests.py b/tests/unit_tests.py index e9b2e7b9..e3c8c940 100644 --- a/tests/unit_tests.py +++ b/tests/unit_tests.py @@ -44,13 +44,6 @@ def test_stream_read_and_parse(self): title = manifest = manifest_store["manifests"][manifest_store["active_manifest"]]["title"] self.assertEqual(title, "C.jpg") - def test_from_manifest_data_and_stream(self): - with open(testPath, "rb") as stream: - with open("tests/fixtures/manifest.c2pa", "r") as manifest_data: - reader = Reader("image/jpeg",stream, manifest_data) - manifest_store = json.loads(reader.json()) - title = manifest = manifest_store["manifests"][manifest_store["active_manifest"]]["title"] - self.assertEqual(title, "C.jpg") def test_json_decode_err(self): with self.assertRaises(Error.Io): From 8b072eb06d03cde5fb8b38625d427ea3a70118d6 Mon Sep 17 00:00:00 2001 From: Gavin Peacock Date: Sat, 28 Sep 2024 13:23:06 -0700 Subject: [PATCH 3/3] fix: udl requires throws. --- src/c2pa.udl | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/c2pa.udl b/src/c2pa.udl index 928dea36..61905b7c 100644 --- a/src/c2pa.udl +++ b/src/c2pa.udl @@ -82,9 +82,11 @@ interface Builder { [Throws=Error] void with_json([ByRef] string json); + [Throws=Error] void set_no_embed(); - void set_remote_url([ByRef] string url) + [Throws=Error] + void set_remote_url([ByRef] string url); [Throws=Error] void add_resource([ByRef] string uri, [ByRef] Stream stream );