diff --git a/src/c2pa/c2pa.py b/src/c2pa/c2pa.py index b0b7e5db..390e015c 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, or independent function calls. + """ + 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") @@ -692,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): @@ -776,6 +792,8 @@ def read_ingredient_file( stacklevel=2, ) + _clear_error_state() + container = _StringContainer() container._path_str = str(path).encode('utf-8') @@ -821,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') @@ -907,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): @@ -1375,6 +1397,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 @@ -1930,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: @@ -2055,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) @@ -2095,6 +2128,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") @@ -2327,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: @@ -2352,6 +2390,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 @@ -2884,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)() @@ -2993,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")