diff --git a/c2pa-native-version.txt b/c2pa-native-version.txt index c418262f..5bbbbe60 100644 --- a/c2pa-native-version.txt +++ b/c2pa-native-version.txt @@ -1 +1 @@ -c2pa-v0.77.0 +c2pa-v0.77.1 diff --git a/src/c2pa/c2pa.py b/src/c2pa/c2pa.py index 1e519b25..0cb76ac9 100644 --- a/src/c2pa/c2pa.py +++ b/src/c2pa/c2pa.py @@ -49,6 +49,7 @@ 'c2pa_reader_from_context', 'c2pa_reader_with_stream', 'c2pa_reader_with_fragment', + 'c2pa_reader_with_manifest_data_and_stream', 'c2pa_reader_is_embedded', 'c2pa_reader_remote_url', 'c2pa_reader_supported_mime_types', @@ -705,6 +706,13 @@ def _setup_function(func, argtypes, restype=None): ctypes.POINTER(C2paStream)], ctypes.POINTER(C2paReader) ) +_setup_function( + _lib.c2pa_reader_with_manifest_data_and_stream, + [ctypes.POINTER(C2paReader), ctypes.c_char_p, + ctypes.POINTER(C2paStream), + ctypes.POINTER(ctypes.c_ubyte), ctypes.c_size_t], + ctypes.POINTER(C2paReader), +) _setup_function( _lib.c2pa_reader_with_fragment, [ctypes.POINTER(C2paReader), ctypes.c_char_p, @@ -2241,6 +2249,7 @@ def __init__( if context is not None: self._init_from_context( context, format_or_path, stream, + manifest_data, ) return @@ -2331,7 +2340,7 @@ def _init_from_file(self, path, format_bytes, Reader._ERROR_MESSAGES['io_error'].format(str(e))) def _init_from_context(self, context, format_or_path, - stream): + stream, manifest_data=None): """Initialize Reader from a Context object implementing the ContextProvider interface/abstract base class. """ @@ -2362,7 +2371,7 @@ def _init_from_context(self, context, format_or_path, self._own_stream = Stream(stream) try: - # Create base reader from context + # Create reader from context reader_ptr = _lib.c2pa_reader_from_context( context.execution_context, ) @@ -2372,13 +2381,35 @@ def _init_from_context(self, context, format_or_path, ].format("Unknown error") ) - # Consume-and-return: reader_ptr is consumed, - # new_ptr is the valid pointer going forward - new_ptr = _lib.c2pa_reader_with_stream( - reader_ptr, format_bytes, - self._own_stream._stream, - ) - # reader_ptr has been invalidated(consumed) + if manifest_data is not None: + if not isinstance(manifest_data, bytes): + raise TypeError( + Reader._ERROR_MESSAGES[ + 'manifest_error']) + manifest_array = ( + ctypes.c_ubyte * + len(manifest_data))( + *manifest_data) + # Consume current reader, + # with manifest data and stream (C FFI pattern), + # to create a new one (switch out) + new_ptr = ( + _lib.c2pa_reader_with_manifest_data_and_stream( + reader_ptr, + format_bytes, + self._own_stream._stream, + manifest_array, + len(manifest_data), + ) + ) + # reader_ptr has been invalidated(consumed) + else: + # Consume reader with stream + new_ptr = _lib.c2pa_reader_with_stream( + reader_ptr, format_bytes, + self._own_stream._stream, + ) + # reader_ptr has been invalidated(consumed) _check_ffi_operation_result(new_ptr, Reader._ERROR_MESSAGES[ diff --git a/tests/test_unit_tests.py b/tests/test_unit_tests.py index 5602e550..7d6744c4 100644 --- a/tests/test_unit_tests.py +++ b/tests/test_unit_tests.py @@ -70,7 +70,7 @@ def load_test_settings_json(): class TestC2paSdk(unittest.TestCase): def test_sdk_version(self): # This test verifies the native libraries used match the expected version. - self.assertIn("0.77.0", sdk_version()) + self.assertIn("0.77.1", sdk_version()) class TestReader(unittest.TestCase): @@ -6122,6 +6122,42 @@ def test_archive_sign_with_ingredient_trusted_via_context(self): context.close() settings.close() + def test_remote_sign_trusted_via_context(self): + trust_dict = load_test_settings_json() + settings = Settings.from_dict(trust_dict) + context = Context(settings=settings) + signer = self._ctx_make_signer() + builder = Builder( + self.test_manifest, context=context, + ) + builder.set_no_embed() + with open(DEFAULT_TEST_FILE, "rb") as source: + with io.BytesIO() as output_buffer: + manifest_data = builder.sign( + signer, "image/jpeg", + source, output_buffer, + ) + output_buffer.seek(0) + read_buffer = io.BytesIO( + output_buffer.getvalue() + ) + reader = Reader( + "image/jpeg", read_buffer, + manifest_data, context=context, + ) + validation_state = ( + reader.get_validation_state() + ) + self.assertEqual( + validation_state, "Trusted", + ) + reader.close() + read_buffer.close() + builder.close() + signer.close() + context.close() + settings.close() + def test_sign_callback_signer_in_ctx(self): signer = self._ctx_make_callback_signer() context = Context(signer=signer)