From 032c5f21bd5c9775de9ecabdc154fc7c2038901a Mon Sep 17 00:00:00 2001 From: Tania Mathern Date: Mon, 13 Oct 2025 16:57:46 -0700 Subject: [PATCH 1/3] fix: CLean up errors --- src/c2pa/c2pa.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/c2pa/c2pa.py b/src/c2pa/c2pa.py index b0b7e5db..3626d578 100644 --- a/src/c2pa/c2pa.py +++ b/src/c2pa/c2pa.py @@ -601,6 +601,19 @@ def _convert_to_py_string(value) -> str: return py_string +def _clear_error_state(): + """Clear any existing error state from the C library. + + This function should be called at the beginning of object initialization + to ensure that stale error states from previous operations don't interfere + with new objects being created. + """ + error = _lib.c2pa_error() + if error: + # Free the error to clear the state + _lib.c2pa_string_free(error) + + def _parse_operation_result_for_error( result: ctypes.c_void_p | None, check_error: bool = True) -> Optional[str]: @@ -1375,6 +1388,9 @@ def __init__(self, C2paError.Encoding: If any of the string inputs contain invalid UTF-8 characters """ + # Native libs plumbing: + # Clear any stale error state from previous operations + _clear_error_state() self._closed = False self._initialized = False @@ -2095,6 +2111,10 @@ def __init__(self, signer_ptr: ctypes.POINTER(C2paSigner)): Raises: C2paError: If the signer pointer is invalid """ + # Native libs plumbing: + # Clear any stale error state from previous operations + _clear_error_state() + # Validate pointer before assignment if not signer_ptr: raise C2paError("Invalid signer pointer: pointer is null") @@ -2352,6 +2372,10 @@ def __init__(self, manifest_json: Any): C2paError.Encoding: If manifest JSON contains invalid UTF-8 chars C2paError.Json: If the manifest JSON cannot be serialized """ + # Native libs plumbing: + # Clear any stale error state from previous operations + _clear_error_state() + self._closed = False self._initialized = False self._builder = None From 2e3447c6da37e12251a04d95439d99502777f042 Mon Sep 17 00:00:00 2001 From: Tania Mathern Date: Mon, 13 Oct 2025 17:10:58 -0700 Subject: [PATCH 2/3] fix: Error cleanup on isntantiate/new use --- src/c2pa/c2pa.py | 50 ++++++++++++++++++++++++++++++++++-------------- 1 file changed, 36 insertions(+), 14 deletions(-) diff --git a/src/c2pa/c2pa.py b/src/c2pa/c2pa.py index 3626d578..c74a6847 100644 --- a/src/c2pa/c2pa.py +++ b/src/c2pa/c2pa.py @@ -244,6 +244,19 @@ class C2paStream(ctypes.Structure): ] +def _clear_error_state(): + """Clear any existing error state from the C library. + + This function should be called at the beginning of object initialization + and before any operations that could potentially raise an error, + to ensure that stale error states from previous operations don't interfere + with new objects being created. + """ + error = _lib.c2pa_error() + if error: + # Free the error to clear the state + _lib.c2pa_string_free(error) + class C2paSignerInfo(ctypes.Structure): """Configuration for a Signer.""" _fields_ = [ @@ -264,6 +277,7 @@ def __init__(self, alg, sign_cert, private_key, ta_url): private_key: The private key as a string ta_url: The timestamp authority URL as bytes """ + _clear_error_state() if sign_cert is None: raise ValueError("sign_cert must be set") @@ -601,19 +615,6 @@ def _convert_to_py_string(value) -> str: return py_string -def _clear_error_state(): - """Clear any existing error state from the C library. - - This function should be called at the beginning of object initialization - to ensure that stale error states from previous operations don't interfere - with new objects being created. - """ - error = _lib.c2pa_error() - if error: - # Free the error to clear the state - _lib.c2pa_string_free(error) - - def _parse_operation_result_for_error( result: ctypes.c_void_p | None, check_error: bool = True) -> Optional[str]: @@ -705,6 +706,8 @@ def load_settings(settings: Union[str, dict], format: str = "json") -> None: Raises: C2paError: If there was an error loading the settings """ + _clear_error_state() + # Convert to JSON string as necessary try: if isinstance(settings, dict): @@ -789,6 +792,8 @@ def read_ingredient_file( stacklevel=2, ) + _clear_error_state() + container = _StringContainer() container._path_str = str(path).encode('utf-8') @@ -834,6 +839,8 @@ def read_file(path: Union[str, Path], stacklevel=2, ) + _clear_error_state() + container = _StringContainer() container._path_str = str(path).encode('utf-8') @@ -920,6 +927,8 @@ def sign_file( stacklevel=2, ) + _clear_error_state() + try: # Determine if we have a signer or signer info if isinstance(signer_or_info, C2paSignerInfo): @@ -1946,6 +1955,10 @@ def from_info(cls, signer_info: C2paSignerInfo) -> 'Signer': Raises: C2paError: If there was an error creating the signer """ + # Native libs plumbing: + # Clear any stale error state from previous operations + _clear_error_state() + signer_ptr = _lib.c2pa_signer_from_info(ctypes.byref(signer_info)) if not signer_ptr: @@ -2071,6 +2084,10 @@ def wrapped_callback( cls._ERROR_MESSAGES['encoding_error'].format( str(e))) + # Native libs plumbing: + # Clear any stale error state from previous operations + _clear_error_state() + # Create the callback object using the callback function callback_cb = SignerCallback(wrapped_callback) @@ -2114,7 +2131,7 @@ def __init__(self, signer_ptr: ctypes.POINTER(C2paSigner)): # Native libs plumbing: # Clear any stale error state from previous operations _clear_error_state() - + # Validate pointer before assignment if not signer_ptr: raise C2paError("Invalid signer pointer: pointer is null") @@ -2347,6 +2364,7 @@ def from_archive(cls, stream: Any) -> 'Builder': """ builder = cls({}) stream_obj = Stream(stream) + builder._builder = _lib.c2pa_builder_from_archive(stream_obj._stream) if not builder._builder: @@ -2908,6 +2926,8 @@ def format_embeddable(format: str, manifest_bytes: bytes) -> tuple[int, bytes]: Raises: C2paError: If there was an error converting the manifest """ + _clear_error_state() + format_str = format.encode('utf-8') manifest_array = (ctypes.c_ubyte * len(manifest_bytes))(*manifest_bytes) result_bytes_ptr = ctypes.POINTER(ctypes.c_ubyte)() @@ -3017,6 +3037,8 @@ def ed25519_sign(data: bytes, private_key: str) -> bytes: C2paError: If there was an error signing the data C2paError.Encoding: If the private key contains invalid UTF-8 chars """ + _clear_error_state() + if not data: raise C2paError("Data to sign cannot be empty") From 947cf46b3bff7a3361493082e0a3331a45ad504f Mon Sep 17 00:00:00 2001 From: tmathern <60901087+tmathern@users.noreply.github.com> Date: Mon, 13 Oct 2025 17:23:29 -0700 Subject: [PATCH 3/3] fix: Clarify comment --- src/c2pa/c2pa.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/c2pa/c2pa.py b/src/c2pa/c2pa.py index c74a6847..390e015c 100644 --- a/src/c2pa/c2pa.py +++ b/src/c2pa/c2pa.py @@ -250,7 +250,7 @@ def _clear_error_state(): This function should be called at the beginning of object initialization and before any operations that could potentially raise an error, to ensure that stale error states from previous operations don't interfere - with new objects being created. + with new objects being created, or independent function calls. """ error = _lib.c2pa_error() if error: