c2pa-python 0.32.14__tar.gz → 0.34.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {c2pa_python-0.32.14/src/c2pa_python.egg-info → c2pa_python-0.34.0}/PKG-INFO +1 -1
- {c2pa_python-0.32.14 → c2pa_python-0.34.0}/pyproject.toml +1 -1
- {c2pa_python-0.32.14 → c2pa_python-0.34.0}/scripts/download_artifacts.py +1 -1
- {c2pa_python-0.32.14 → c2pa_python-0.34.0}/setup.py +2 -1
- {c2pa_python-0.32.14 → c2pa_python-0.34.0}/src/c2pa/__init__.py +0 -2
- {c2pa_python-0.32.14 → c2pa_python-0.34.0}/src/c2pa/c2pa.py +56 -282
- {c2pa_python-0.32.14 → c2pa_python-0.34.0}/src/c2pa/lib.py +5 -2
- {c2pa_python-0.32.14 → c2pa_python-0.34.0/src/c2pa_python.egg-info}/PKG-INFO +1 -1
- {c2pa_python-0.32.14 → c2pa_python-0.34.0}/tests/test_unit_tests.py +1 -487
- {c2pa_python-0.32.14 → c2pa_python-0.34.0}/LICENSE-APACHE +0 -0
- {c2pa_python-0.32.14 → c2pa_python-0.34.0}/LICENSE-MIT +0 -0
- {c2pa_python-0.32.14 → c2pa_python-0.34.0}/MANIFEST.in +0 -0
- {c2pa_python-0.32.14 → c2pa_python-0.34.0}/README.md +0 -0
- {c2pa_python-0.32.14 → c2pa_python-0.34.0}/requirements.txt +0 -0
- {c2pa_python-0.32.14 → c2pa_python-0.34.0}/setup.cfg +0 -0
- {c2pa_python-0.32.14 → c2pa_python-0.34.0}/src/c2pa/build.py +0 -0
- {c2pa_python-0.32.14 → c2pa_python-0.34.0}/src/c2pa_python.egg-info/SOURCES.txt +0 -0
- {c2pa_python-0.32.14 → c2pa_python-0.34.0}/src/c2pa_python.egg-info/dependency_links.txt +0 -0
- {c2pa_python-0.32.14 → c2pa_python-0.34.0}/src/c2pa_python.egg-info/entry_points.txt +0 -0
- {c2pa_python-0.32.14 → c2pa_python-0.34.0}/src/c2pa_python.egg-info/requires.txt +0 -0
- {c2pa_python-0.32.14 → c2pa_python-0.34.0}/src/c2pa_python.egg-info/top_level.txt +0 -0
- {c2pa_python-0.32.14 → c2pa_python-0.34.0}/tests/test_unit_tests_threaded.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: c2pa-python
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.34.0
|
|
4
4
|
Summary: Python bindings for the C2PA Content Authenticity Initiative (CAI) library
|
|
5
5
|
Author-email: Gavin Peacock <gvnpeacock@adobe.com>, Tania Mathern <mathern@adobe.com>
|
|
6
6
|
Maintainer-email: Gavin Peacock <gpeacock@adobe.com>
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "c2pa-python"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.34.0"
|
|
8
8
|
requires-python = ">=3.10"
|
|
9
9
|
description = "Python bindings for the C2PA Content Authenticity Initiative (CAI) library"
|
|
10
10
|
readme = { file = "README.md", content-type = "text/markdown" }
|
|
@@ -91,7 +91,7 @@ def get_platform_identifier(target_arch=None):
|
|
|
91
91
|
else:
|
|
92
92
|
return "universal-apple-darwin"
|
|
93
93
|
elif system == "windows":
|
|
94
|
-
if target_arch == "arm64":
|
|
94
|
+
if target_arch == "arm64" or machine in ["arm64", "aarch64"]:
|
|
95
95
|
return "aarch64-pc-windows-msvc"
|
|
96
96
|
else:
|
|
97
97
|
return "x86_64-pc-windows-msvc"
|
|
@@ -41,6 +41,7 @@ PLATFORM_FOLDERS = {
|
|
|
41
41
|
'aarch64-apple-darwin': 'dylib',
|
|
42
42
|
'x86_64-apple-darwin': 'dylib',
|
|
43
43
|
'x86_64-pc-windows-msvc': 'dll',
|
|
44
|
+
'aarch64-pc-windows-msvc': 'dll',
|
|
44
45
|
'x86_64-unknown-linux-gnu': 'so',
|
|
45
46
|
'aarch64-unknown-linux-gnu': 'so',
|
|
46
47
|
}
|
|
@@ -78,7 +79,7 @@ def get_platform_identifier(target_arch=None) -> str:
|
|
|
78
79
|
else:
|
|
79
80
|
return "universal-apple-darwin"
|
|
80
81
|
elif system == "windows":
|
|
81
|
-
if target_arch == "arm64":
|
|
82
|
+
if target_arch == "arm64" or platform.machine().lower() in ["arm64", "aarch64"]:
|
|
82
83
|
return "aarch64-pc-windows-msvc"
|
|
83
84
|
else:
|
|
84
85
|
return "x86_64-pc-windows-msvc"
|
|
@@ -32,7 +32,6 @@ from .c2pa import (
|
|
|
32
32
|
ContextBuilder,
|
|
33
33
|
ContextProvider,
|
|
34
34
|
sdk_version,
|
|
35
|
-
read_ingredient_file,
|
|
36
35
|
load_settings
|
|
37
36
|
) # NOQA
|
|
38
37
|
|
|
@@ -52,6 +51,5 @@ __all__ = [
|
|
|
52
51
|
'ContextBuilder',
|
|
53
52
|
'ContextProvider',
|
|
54
53
|
'sdk_version',
|
|
55
|
-
'read_ingredient_file',
|
|
56
54
|
'load_settings'
|
|
57
55
|
]
|
|
@@ -38,8 +38,6 @@ _REQUIRED_FUNCTIONS = [
|
|
|
38
38
|
'c2pa_error',
|
|
39
39
|
# Legacy APIs, deprecated
|
|
40
40
|
'c2pa_load_settings',
|
|
41
|
-
'c2pa_read_file',
|
|
42
|
-
'c2pa_read_ingredient_file',
|
|
43
41
|
# Stream
|
|
44
42
|
'c2pa_create_stream',
|
|
45
43
|
'c2pa_release_stream',
|
|
@@ -243,8 +241,13 @@ class ManagedResource:
|
|
|
243
241
|
|
|
244
242
|
@staticmethod
|
|
245
243
|
def _free_native_ptr(ptr):
|
|
246
|
-
"""Free a native pointer by
|
|
247
|
-
|
|
244
|
+
"""Free a native pointer by passing it to c2pa_free.
|
|
245
|
+
|
|
246
|
+
c2pa_free's argtype is c_void_p, so ctypes converts any pointer
|
|
247
|
+
instance directly. (ctypes.cast(ptr, c_void_p) would do the same
|
|
248
|
+
conversion but leaves a reference cycle behind on every call.)
|
|
249
|
+
"""
|
|
250
|
+
_lib.c2pa_free(ptr)
|
|
248
251
|
|
|
249
252
|
def _ensure_valid_state(self):
|
|
250
253
|
"""Raise if the resource is closed or uninitialized."""
|
|
@@ -645,12 +648,6 @@ _setup_function(_lib.c2pa_signer_create,
|
|
|
645
648
|
_setup_function(_lib.c2pa_signer_from_info,
|
|
646
649
|
[ctypes.POINTER(C2paSignerInfo)],
|
|
647
650
|
ctypes.POINTER(C2paSigner))
|
|
648
|
-
_setup_function(
|
|
649
|
-
_lib.c2pa_read_file, [
|
|
650
|
-
ctypes.c_char_p, ctypes.c_char_p], ctypes.c_void_p)
|
|
651
|
-
_setup_function(
|
|
652
|
-
_lib.c2pa_read_ingredient_file, [
|
|
653
|
-
ctypes.c_char_p, ctypes.c_char_p], ctypes.c_void_p)
|
|
654
651
|
|
|
655
652
|
# Set up Signer function prototypes
|
|
656
653
|
_setup_function(
|
|
@@ -889,33 +886,51 @@ class _StringContainer:
|
|
|
889
886
|
self._data_dir_str = ""
|
|
890
887
|
|
|
891
888
|
|
|
889
|
+
if sys.implementation.name == "cpython":
|
|
890
|
+
_PyMemoryView_FromMemory = ctypes.pythonapi.PyMemoryView_FromMemory
|
|
891
|
+
_PyMemoryView_FromMemory.restype = ctypes.py_object
|
|
892
|
+
_PyMemoryView_FromMemory.argtypes = (
|
|
893
|
+
ctypes.c_void_p, ctypes.c_ssize_t, ctypes.c_int)
|
|
894
|
+
_PyBUF_WRITE = 0x200
|
|
895
|
+
|
|
896
|
+
def _write_buf(address, length):
|
|
897
|
+
return _PyMemoryView_FromMemory(address, length, _PyBUF_WRITE)
|
|
898
|
+
else:
|
|
899
|
+
def _write_buf(address, length):
|
|
900
|
+
return (ctypes.c_char * length).from_address(address)
|
|
901
|
+
|
|
902
|
+
|
|
892
903
|
def _convert_to_py_string(value) -> str:
|
|
893
904
|
if value is None:
|
|
894
905
|
return ""
|
|
895
906
|
|
|
896
907
|
py_string = ""
|
|
897
908
|
|
|
898
|
-
# Validate pointer before
|
|
899
|
-
if
|
|
909
|
+
# Validate and normalize pointer before reading and freeing.
|
|
910
|
+
if isinstance(value, ctypes.c_void_p):
|
|
911
|
+
address = value.value
|
|
912
|
+
elif isinstance(value, int):
|
|
913
|
+
address = value
|
|
914
|
+
else:
|
|
915
|
+
return ""
|
|
916
|
+
if not address:
|
|
900
917
|
return ""
|
|
901
918
|
|
|
902
919
|
try:
|
|
903
|
-
|
|
920
|
+
raw = ctypes.string_at(address)
|
|
904
921
|
|
|
905
|
-
|
|
906
|
-
|
|
922
|
+
try:
|
|
923
|
+
py_string = raw.decode('utf-8', errors='strict')
|
|
924
|
+
except Exception:
|
|
925
|
+
py_string = ""
|
|
926
|
+
finally:
|
|
927
|
+
# Only free if we have a valid pointer
|
|
907
928
|
try:
|
|
908
|
-
|
|
929
|
+
_lib.c2pa_string_free(value)
|
|
909
930
|
except Exception:
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
try:
|
|
914
|
-
_lib.c2pa_string_free(value)
|
|
915
|
-
except Exception:
|
|
916
|
-
# Ignore clean up issues
|
|
917
|
-
pass
|
|
918
|
-
except (ctypes.ArgumentError, TypeError, ValueError):
|
|
931
|
+
# Ignore clean up issues
|
|
932
|
+
pass
|
|
933
|
+
except (ctypes.ArgumentError, TypeError, ValueError, OSError):
|
|
919
934
|
# Invalid pointer type or value
|
|
920
935
|
return ""
|
|
921
936
|
|
|
@@ -1003,8 +1018,7 @@ def _parse_operation_result_for_error(
|
|
|
1003
1018
|
if check_error:
|
|
1004
1019
|
error = _lib.c2pa_error()
|
|
1005
1020
|
if error:
|
|
1006
|
-
error_str = ctypes.
|
|
1007
|
-
error, ctypes.c_char_p).value.decode('utf-8')
|
|
1021
|
+
error_str = ctypes.string_at(error).decode('utf-8')
|
|
1008
1022
|
_lib.c2pa_string_free(error)
|
|
1009
1023
|
_raise_typed_c2pa_error(error_str)
|
|
1010
1024
|
return None
|
|
@@ -1191,208 +1205,6 @@ def _get_mime_type_from_path(path: Union[str, Path]) -> str:
|
|
|
1191
1205
|
return mime_type
|
|
1192
1206
|
|
|
1193
1207
|
|
|
1194
|
-
def read_ingredient_file(
|
|
1195
|
-
path: Union[str, Path], data_dir: Union[str, Path]) -> str:
|
|
1196
|
-
"""Read a file as C2PA ingredient (deprecated).
|
|
1197
|
-
This creates the JSON string that would be used as the ingredient JSON.
|
|
1198
|
-
|
|
1199
|
-
.. deprecated:: 0.11.0
|
|
1200
|
-
This function is deprecated and will be removed in a future version.
|
|
1201
|
-
To read C2PA metadata, use the :class:`c2pa.c2pa.Reader` class.
|
|
1202
|
-
To add ingredients to a manifest,
|
|
1203
|
-
use :meth:`c2pa.c2pa.Builder.add_ingredient` instead.
|
|
1204
|
-
|
|
1205
|
-
Args:
|
|
1206
|
-
path: Path to the file to read
|
|
1207
|
-
data_dir: Directory to write binary resources to
|
|
1208
|
-
|
|
1209
|
-
Returns:
|
|
1210
|
-
The ingredient as a JSON string
|
|
1211
|
-
|
|
1212
|
-
Raises:
|
|
1213
|
-
C2paError: If there was an error reading the file
|
|
1214
|
-
"""
|
|
1215
|
-
warnings.warn(
|
|
1216
|
-
"The read_ingredient_file function is deprecated and will be "
|
|
1217
|
-
"removed in a future version. Please use Reader(path).json() for "
|
|
1218
|
-
"reading C2PA metadata instead, or "
|
|
1219
|
-
"Builder.add_ingredient(json, format, stream) to add ingredients "
|
|
1220
|
-
"to a manifest.",
|
|
1221
|
-
DeprecationWarning,
|
|
1222
|
-
stacklevel=2,
|
|
1223
|
-
)
|
|
1224
|
-
|
|
1225
|
-
_clear_error_state()
|
|
1226
|
-
|
|
1227
|
-
container = _StringContainer()
|
|
1228
|
-
|
|
1229
|
-
container._path_str = str(path).encode('utf-8')
|
|
1230
|
-
container._data_dir_str = str(data_dir).encode('utf-8')
|
|
1231
|
-
|
|
1232
|
-
result = _lib.c2pa_read_ingredient_file(
|
|
1233
|
-
container._path_str, container._data_dir_str)
|
|
1234
|
-
|
|
1235
|
-
_check_ffi_operation_result(
|
|
1236
|
-
result, "Error reading ingredient file {}".format(path))
|
|
1237
|
-
|
|
1238
|
-
return _convert_to_py_string(result)
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
def read_file(path: Union[str, Path],
|
|
1242
|
-
data_dir: Union[str, Path]) -> str:
|
|
1243
|
-
"""Read a C2PA manifest from a file (deprecated).
|
|
1244
|
-
|
|
1245
|
-
.. deprecated:: 0.10.0
|
|
1246
|
-
This function is deprecated and will be removed in a future version.
|
|
1247
|
-
To read C2PA metadata, use the :class:`c2pa.c2pa.Reader` class.
|
|
1248
|
-
|
|
1249
|
-
Args:
|
|
1250
|
-
path: Path to the file to read
|
|
1251
|
-
data_dir: Directory to write binary resources to
|
|
1252
|
-
|
|
1253
|
-
Returns:
|
|
1254
|
-
The manifest as a JSON string
|
|
1255
|
-
|
|
1256
|
-
Raises:
|
|
1257
|
-
C2paError: If there was an error reading the file
|
|
1258
|
-
"""
|
|
1259
|
-
warnings.warn(
|
|
1260
|
-
"The read_file function is deprecated and will be removed in a "
|
|
1261
|
-
"future version. Please use the Reader class for reading C2PA "
|
|
1262
|
-
"metadata instead.",
|
|
1263
|
-
DeprecationWarning,
|
|
1264
|
-
stacklevel=2,
|
|
1265
|
-
)
|
|
1266
|
-
|
|
1267
|
-
_clear_error_state()
|
|
1268
|
-
|
|
1269
|
-
container = _StringContainer()
|
|
1270
|
-
|
|
1271
|
-
container._path_str = str(path).encode('utf-8')
|
|
1272
|
-
container._data_dir_str = str(data_dir).encode('utf-8')
|
|
1273
|
-
|
|
1274
|
-
result = _lib.c2pa_read_file(container._path_str, container._data_dir_str)
|
|
1275
|
-
_check_ffi_operation_result(
|
|
1276
|
-
result, "Error during read of manifest from file {}".format(path))
|
|
1277
|
-
|
|
1278
|
-
return _convert_to_py_string(result)
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
@overload
|
|
1282
|
-
def sign_file(
|
|
1283
|
-
source_path: Union[str, Path],
|
|
1284
|
-
dest_path: Union[str, Path],
|
|
1285
|
-
manifest: str,
|
|
1286
|
-
signer_info: C2paSignerInfo,
|
|
1287
|
-
return_manifest_as_bytes: bool = False
|
|
1288
|
-
) -> Union[str, bytes]:
|
|
1289
|
-
"""Sign a file with a C2PA manifest using signer info.
|
|
1290
|
-
"""
|
|
1291
|
-
...
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
@overload
|
|
1295
|
-
def sign_file(
|
|
1296
|
-
source_path: Union[str, Path],
|
|
1297
|
-
dest_path: Union[str, Path],
|
|
1298
|
-
manifest: str,
|
|
1299
|
-
signer: 'Signer',
|
|
1300
|
-
return_manifest_as_bytes: bool = False
|
|
1301
|
-
) -> Union[str, bytes]:
|
|
1302
|
-
"""Sign a file with a C2PA manifest using a signer.
|
|
1303
|
-
"""
|
|
1304
|
-
...
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
def sign_file(
|
|
1308
|
-
source_path: Union[str, Path],
|
|
1309
|
-
dest_path: Union[str, Path],
|
|
1310
|
-
manifest: str,
|
|
1311
|
-
signer_or_info: Union[C2paSignerInfo, 'Signer'],
|
|
1312
|
-
return_manifest_as_bytes: bool = False
|
|
1313
|
-
) -> Union[str, bytes]:
|
|
1314
|
-
"""Sign a file with a C2PA manifest (deprecated).
|
|
1315
|
-
For now, this function is left here to provide a backwards-compatible API.
|
|
1316
|
-
|
|
1317
|
-
.. deprecated:: 0.13.0
|
|
1318
|
-
This function is deprecated and will be removed in a future version.
|
|
1319
|
-
Use :meth:`Builder.sign` instead.
|
|
1320
|
-
|
|
1321
|
-
Args:
|
|
1322
|
-
source_path: Path to the source file. We will attempt
|
|
1323
|
-
to guess the mimetype of the source file based on
|
|
1324
|
-
the extension.
|
|
1325
|
-
dest_path: Path to write the signed file to
|
|
1326
|
-
manifest: The manifest JSON string
|
|
1327
|
-
signer_or_info: Either a signer configuration or a signer object
|
|
1328
|
-
return_manifest_as_bytes: If True, return manifest bytes instead
|
|
1329
|
-
of JSON string
|
|
1330
|
-
|
|
1331
|
-
Returns:
|
|
1332
|
-
The signed manifest as a JSON string or bytes, depending
|
|
1333
|
-
on return_manifest_as_bytes
|
|
1334
|
-
|
|
1335
|
-
Raises:
|
|
1336
|
-
C2paError: If there was an error signing the file
|
|
1337
|
-
C2paError.Encoding: If any of the string inputs contain
|
|
1338
|
-
invalid UTF-8 characters
|
|
1339
|
-
C2paError.NotSupported: If the file type cannot be determined
|
|
1340
|
-
"""
|
|
1341
|
-
|
|
1342
|
-
warnings.warn(
|
|
1343
|
-
"The sign_file function is deprecated and will be removed in a "
|
|
1344
|
-
"future version. Please use the Builder object and Builder.sign() "
|
|
1345
|
-
"instead.",
|
|
1346
|
-
DeprecationWarning,
|
|
1347
|
-
stacklevel=2,
|
|
1348
|
-
)
|
|
1349
|
-
|
|
1350
|
-
_clear_error_state()
|
|
1351
|
-
|
|
1352
|
-
try:
|
|
1353
|
-
# Determine if we have a signer or signer info
|
|
1354
|
-
if isinstance(signer_or_info, C2paSignerInfo):
|
|
1355
|
-
signer = Signer.from_info(signer_or_info)
|
|
1356
|
-
own_signer = True
|
|
1357
|
-
else:
|
|
1358
|
-
signer = signer_or_info
|
|
1359
|
-
own_signer = False
|
|
1360
|
-
|
|
1361
|
-
# Create a builder from the manifest
|
|
1362
|
-
builder = Builder(manifest)
|
|
1363
|
-
|
|
1364
|
-
manifest_bytes = builder.sign_file(
|
|
1365
|
-
source_path,
|
|
1366
|
-
dest_path,
|
|
1367
|
-
signer
|
|
1368
|
-
)
|
|
1369
|
-
|
|
1370
|
-
if return_manifest_as_bytes:
|
|
1371
|
-
return manifest_bytes
|
|
1372
|
-
else:
|
|
1373
|
-
# Read the signed manifest from the destination file
|
|
1374
|
-
with Reader(dest_path) as reader:
|
|
1375
|
-
return reader.json()
|
|
1376
|
-
|
|
1377
|
-
except Exception as e:
|
|
1378
|
-
# Clean up destination file if it exists and there was an error
|
|
1379
|
-
if os.path.exists(dest_path):
|
|
1380
|
-
try:
|
|
1381
|
-
os.remove(dest_path)
|
|
1382
|
-
except OSError:
|
|
1383
|
-
logger.warning("Failed to remove destination file")
|
|
1384
|
-
pass # Ignore cleanup errors
|
|
1385
|
-
|
|
1386
|
-
# Re-raise the error
|
|
1387
|
-
raise C2paError(f"Error signing file: {str(e)}") from e
|
|
1388
|
-
finally:
|
|
1389
|
-
# Ensure resources are cleaned up
|
|
1390
|
-
if 'builder' in locals():
|
|
1391
|
-
builder.close()
|
|
1392
|
-
if 'signer' in locals() and own_signer:
|
|
1393
|
-
signer.close()
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
1208
|
class ContextProvider(ABC):
|
|
1397
1209
|
"""Abstract base class for types that provide a C2PA context.
|
|
1398
1210
|
|
|
@@ -1827,10 +1639,21 @@ class Stream:
|
|
|
1827
1639
|
readinto = getattr(stream, "readinto", None)
|
|
1828
1640
|
if readinto is not None:
|
|
1829
1641
|
# Most streams have readinto
|
|
1830
|
-
buf = (
|
|
1831
|
-
ctypes.addressof(data.contents))
|
|
1832
|
-
|
|
1833
|
-
|
|
1642
|
+
buf = _write_buf(
|
|
1643
|
+
ctypes.addressof(data.contents), length)
|
|
1644
|
+
try:
|
|
1645
|
+
n = readinto(buf)
|
|
1646
|
+
finally:
|
|
1647
|
+
release = getattr(buf, "release", None)
|
|
1648
|
+
if release is not None:
|
|
1649
|
+
release()
|
|
1650
|
+
if not n:
|
|
1651
|
+
return 0
|
|
1652
|
+
if n > length:
|
|
1653
|
+
raise ValueError(
|
|
1654
|
+
f"readinto returned {n} bytes but buffer length is {length}"
|
|
1655
|
+
)
|
|
1656
|
+
return n
|
|
1834
1657
|
|
|
1835
1658
|
# Fallback for streams without readinto.
|
|
1836
1659
|
buffer = stream.read(length)
|
|
@@ -3411,52 +3234,6 @@ class Builder(ManagedResource):
|
|
|
3411
3234
|
Builder._ERROR_MESSAGES['ingredient_error'].format("Unknown error"),
|
|
3412
3235
|
check=lambda r: r != 0)
|
|
3413
3236
|
|
|
3414
|
-
def add_ingredient_from_file_path(
|
|
3415
|
-
self,
|
|
3416
|
-
ingredient_json: Union[str, dict],
|
|
3417
|
-
format: str,
|
|
3418
|
-
filepath: Union[str, Path]):
|
|
3419
|
-
"""Add an ingredient from a file path to the builder (deprecated).
|
|
3420
|
-
This is a legacy method.
|
|
3421
|
-
|
|
3422
|
-
.. deprecated:: 0.13.0
|
|
3423
|
-
This method is deprecated and will be removed in a future version.
|
|
3424
|
-
Use :meth:`add_ingredient` with a file stream instead.
|
|
3425
|
-
|
|
3426
|
-
Args:
|
|
3427
|
-
ingredient_json: The JSON ingredient definition
|
|
3428
|
-
(either a JSON string or a dictionary)
|
|
3429
|
-
format: The MIME type or extension of the ingredient
|
|
3430
|
-
filepath: The path to the file containing the ingredient data
|
|
3431
|
-
(can be a string or Path object)
|
|
3432
|
-
|
|
3433
|
-
Raises:
|
|
3434
|
-
C2paError: If there was an error adding the ingredient
|
|
3435
|
-
C2paError.Encoding: If the ingredient JSON or format
|
|
3436
|
-
contains invalid UTF-8 characters
|
|
3437
|
-
FileNotFoundError: If the file at the specified path does not exist
|
|
3438
|
-
"""
|
|
3439
|
-
warnings.warn(
|
|
3440
|
-
"add_ingredient_from_file_path is deprecated and will "
|
|
3441
|
-
"be removed in a future version. Use add_ingredient "
|
|
3442
|
-
"with a file stream instead.",
|
|
3443
|
-
DeprecationWarning,
|
|
3444
|
-
stacklevel=2,
|
|
3445
|
-
)
|
|
3446
|
-
|
|
3447
|
-
try:
|
|
3448
|
-
# Convert Path object to string if necessary
|
|
3449
|
-
filepath_str = str(filepath)
|
|
3450
|
-
|
|
3451
|
-
# Does the stream handling to use add_ingredient_from_stream
|
|
3452
|
-
with open(filepath_str, 'rb') as file_stream:
|
|
3453
|
-
self.add_ingredient_from_stream(
|
|
3454
|
-
ingredient_json, format, file_stream)
|
|
3455
|
-
except FileNotFoundError as e:
|
|
3456
|
-
raise C2paError.FileNotFound(f"File not found: {filepath}") from e
|
|
3457
|
-
except Exception as e:
|
|
3458
|
-
raise C2paError.Other(f"Could not add ingredient: {e}") from e
|
|
3459
|
-
|
|
3460
3237
|
def add_action(self, action_json: Union[str, dict]) -> None:
|
|
3461
3238
|
"""Add an action to the builder, that will be placed
|
|
3462
3239
|
in the actions assertion array in the generated manifest.
|
|
@@ -3991,9 +3768,6 @@ __all__ = [
|
|
|
3991
3768
|
'Builder',
|
|
3992
3769
|
'Signer',
|
|
3993
3770
|
'load_settings',
|
|
3994
|
-
'read_file',
|
|
3995
|
-
'read_ingredient_file',
|
|
3996
|
-
'sign_file',
|
|
3997
3771
|
'format_embeddable',
|
|
3998
3772
|
'version',
|
|
3999
3773
|
'sdk_version'
|
|
@@ -37,7 +37,8 @@ def get_platform_identifier() -> str:
|
|
|
37
37
|
- universal-apple-darwin (for Mac universal)
|
|
38
38
|
- aarch64-apple-darwin (for Mac ARM64)
|
|
39
39
|
- x86_64-apple-darwin (for Mac x86_64)
|
|
40
|
-
- x86_64-pc-windows-msvc (for Windows
|
|
40
|
+
- x86_64-pc-windows-msvc (for Windows x64)
|
|
41
|
+
- aarch64-pc-windows-msvc (for Windows ARM64)
|
|
41
42
|
- x86_64-unknown-linux-gnu (for Linux 64-bit)
|
|
42
43
|
- aarch64-unknown-linux-gnu (for Linux ARM)
|
|
43
44
|
"""
|
|
@@ -54,6 +55,8 @@ def get_platform_identifier() -> str:
|
|
|
54
55
|
else:
|
|
55
56
|
return "universal-apple-darwin"
|
|
56
57
|
elif system == "windows":
|
|
58
|
+
if _get_architecture() in [CPUArchitecture.ARM64.value, CPUArchitecture.AARCH64.value]:
|
|
59
|
+
return "aarch64-pc-windows-msvc"
|
|
57
60
|
return "x86_64-pc-windows-msvc"
|
|
58
61
|
elif system == "linux":
|
|
59
62
|
if _get_architecture() in [CPUArchitecture.ARM64.value, CPUArchitecture.AARCH64.value]:
|
|
@@ -81,7 +84,7 @@ def _get_architecture() -> str:
|
|
|
81
84
|
elif sys.platform == "win32":
|
|
82
85
|
# win32 will cover all Windows versions
|
|
83
86
|
# (the 32 is a historical quirk)
|
|
84
|
-
return platform.machine()
|
|
87
|
+
return platform.machine().lower()
|
|
85
88
|
else:
|
|
86
89
|
raise RuntimeError(f"Unsupported platform: {sys.platform}")
|
|
87
90
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: c2pa-python
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.34.0
|
|
4
4
|
Summary: Python bindings for the C2PA Content Authenticity Initiative (CAI) library
|
|
5
5
|
Author-email: Gavin Peacock <gvnpeacock@adobe.com>, Tania Mathern <mathern@adobe.com>
|
|
6
6
|
Maintainer-email: Gavin Peacock <gpeacock@adobe.com>
|
|
@@ -31,7 +31,7 @@ warnings.simplefilter("ignore", category=DeprecationWarning)
|
|
|
31
31
|
|
|
32
32
|
from c2pa import Builder, C2paError as Error, Reader, C2paSigningAlg as SigningAlg, C2paSignerInfo, Signer, sdk_version, C2paBuilderIntent, C2paDigitalSourceType
|
|
33
33
|
from c2pa import Settings, Context, ContextBuilder, ContextProvider
|
|
34
|
-
from c2pa.c2pa import Stream, LifecycleState,
|
|
34
|
+
from c2pa.c2pa import Stream, LifecycleState, load_settings, create_signer, create_signer_from_info, ed25519_sign, format_embeddable
|
|
35
35
|
|
|
36
36
|
|
|
37
37
|
PROJECT_PATH = os.getcwd()
|
|
@@ -4787,9 +4787,6 @@ class TestStream(unittest.TestCase):
|
|
|
4787
4787
|
class TestLegacyAPI(unittest.TestCase):
|
|
4788
4788
|
def setUp(self):
|
|
4789
4789
|
# Filter specific deprecation warnings for legacy API tests
|
|
4790
|
-
warnings.filterwarnings("ignore", message="The read_file function is deprecated")
|
|
4791
|
-
warnings.filterwarnings("ignore", message="The sign_file function is deprecated")
|
|
4792
|
-
warnings.filterwarnings("ignore", message="The read_ingredient_file function is deprecated")
|
|
4793
4790
|
warnings.filterwarnings("ignore", message="The create_signer function is deprecated")
|
|
4794
4791
|
warnings.filterwarnings("ignore", message="The create_signer_from_info function is deprecated")
|
|
4795
4792
|
warnings.filterwarnings("ignore", message="load_settings\\(\\) is deprecated")
|
|
@@ -4863,489 +4860,6 @@ class TestLegacyAPI(unittest.TestCase):
|
|
|
4863
4860
|
if os.path.exists(self.temp_data_dir):
|
|
4864
4861
|
shutil.rmtree(self.temp_data_dir)
|
|
4865
4862
|
|
|
4866
|
-
def test_read_ingredient_file(self):
|
|
4867
|
-
"""Test reading a C2PA ingredient from a file."""
|
|
4868
|
-
# Test reading ingredient from file with data_dir
|
|
4869
|
-
temp_data_dir = os.path.join(self.data_dir, "temp_data")
|
|
4870
|
-
os.makedirs(temp_data_dir, exist_ok=True)
|
|
4871
|
-
|
|
4872
|
-
ingredient_json_with_dir = read_ingredient_file(self.testPath, temp_data_dir)
|
|
4873
|
-
|
|
4874
|
-
# Verify some fields
|
|
4875
|
-
ingredient_data = json.loads(ingredient_json_with_dir)
|
|
4876
|
-
self.assertEqual(ingredient_data["title"], DEFAULT_TEST_FILE_NAME)
|
|
4877
|
-
self.assertEqual(ingredient_data["format"], "image/jpeg")
|
|
4878
|
-
self.assertIn("thumbnail", ingredient_data)
|
|
4879
|
-
|
|
4880
|
-
def test_read_ingredient_file_who_has_no_manifest(self):
|
|
4881
|
-
"""Test reading a C2PA ingredient from a file."""
|
|
4882
|
-
# Test reading ingredient from file with data_dir
|
|
4883
|
-
temp_data_dir = os.path.join(self.data_dir, "temp_data")
|
|
4884
|
-
os.makedirs(temp_data_dir, exist_ok=True)
|
|
4885
|
-
|
|
4886
|
-
# Load settings first, before they need to be used
|
|
4887
|
-
load_settings('{"builder": { "thumbnail": {"enabled": false}}}')
|
|
4888
|
-
|
|
4889
|
-
ingredient_json_with_dir = read_ingredient_file(self.testPath2, temp_data_dir)
|
|
4890
|
-
|
|
4891
|
-
# Verify some fields
|
|
4892
|
-
ingredient_data = json.loads(ingredient_json_with_dir)
|
|
4893
|
-
self.assertEqual(ingredient_data["title"], INGREDIENT_TEST_FILE_NAME)
|
|
4894
|
-
self.assertEqual(ingredient_data["format"], "image/jpeg")
|
|
4895
|
-
self.assertNotIn("thumbnail", ingredient_data)
|
|
4896
|
-
|
|
4897
|
-
# Reset setting
|
|
4898
|
-
load_settings('{"builder": { "thumbnail": {"enabled": true}}}')
|
|
4899
|
-
|
|
4900
|
-
def test_compare_read_ingredient_file_with_builder_added_ingredient(self):
|
|
4901
|
-
"""Test reading a C2PA ingredient from a file."""
|
|
4902
|
-
# Test reading ingredient from file with data_dir
|
|
4903
|
-
temp_data_dir = os.path.join(self.data_dir, "temp_data")
|
|
4904
|
-
os.makedirs(temp_data_dir, exist_ok=True)
|
|
4905
|
-
|
|
4906
|
-
ingredient_json_with_dir = read_ingredient_file(self.testPath2, temp_data_dir)
|
|
4907
|
-
|
|
4908
|
-
# Ingredient fields from read_ingredient_file
|
|
4909
|
-
ingredient_data = json.loads(ingredient_json_with_dir)
|
|
4910
|
-
|
|
4911
|
-
# Compare with ingredient added by Builder
|
|
4912
|
-
builder = Builder.from_json(self.manifestDefinition)
|
|
4913
|
-
# Only the title is needed (filename), since title not extracted or guessed from filename
|
|
4914
|
-
ingredient_json = '{ "title" : "A.jpg" }'
|
|
4915
|
-
with open(self.testPath2, 'rb') as f:
|
|
4916
|
-
builder.add_ingredient(ingredient_json, "image/jpeg", f)
|
|
4917
|
-
|
|
4918
|
-
with open(self.testPath2, "rb") as file:
|
|
4919
|
-
output = io.BytesIO(bytearray())
|
|
4920
|
-
builder.sign(self.signer, "image/jpeg", file, output)
|
|
4921
|
-
output.seek(0)
|
|
4922
|
-
|
|
4923
|
-
# Get ingredient fields from signed manifest
|
|
4924
|
-
reader = Reader("image/jpeg", output)
|
|
4925
|
-
json_data = reader.json()
|
|
4926
|
-
manifest_data = json.loads(json_data)
|
|
4927
|
-
active_manifest_id = manifest_data["active_manifest"]
|
|
4928
|
-
active_manifest = manifest_data["manifests"][active_manifest_id]
|
|
4929
|
-
only_ingredient = active_manifest["ingredients"][0]
|
|
4930
|
-
|
|
4931
|
-
self.assertEqual(ingredient_data["title"], only_ingredient["title"])
|
|
4932
|
-
self.assertEqual(ingredient_data["format"], only_ingredient["format"])
|
|
4933
|
-
self.assertEqual(ingredient_data["document_id"], only_ingredient["document_id"])
|
|
4934
|
-
self.assertEqual(ingredient_data["instance_id"], only_ingredient["instance_id"])
|
|
4935
|
-
self.assertEqual(ingredient_data["relationship"], only_ingredient["relationship"])
|
|
4936
|
-
|
|
4937
|
-
def test_read_file(self):
|
|
4938
|
-
"""Test reading a C2PA ingredient from a file."""
|
|
4939
|
-
temp_data_dir = os.path.join(self.data_dir, "temp_data")
|
|
4940
|
-
os.makedirs(temp_data_dir, exist_ok=True)
|
|
4941
|
-
|
|
4942
|
-
# self.testPath has C2PA metadata to read
|
|
4943
|
-
file_json_with_dir = read_file(self.testPath, temp_data_dir)
|
|
4944
|
-
|
|
4945
|
-
# Parse the JSON and verify specific fields
|
|
4946
|
-
file_data = json.loads(file_json_with_dir)
|
|
4947
|
-
expected_manifest_id = "contentauth:urn:uuid:c85a2b90-f1a0-4aa4-b17f-f938b475804e"
|
|
4948
|
-
|
|
4949
|
-
# Verify some fields
|
|
4950
|
-
self.assertEqual(file_data["active_manifest"], expected_manifest_id)
|
|
4951
|
-
self.assertIn("manifests", file_data)
|
|
4952
|
-
self.assertIn(expected_manifest_id, file_data["manifests"])
|
|
4953
|
-
|
|
4954
|
-
def test_sign_file(self):
|
|
4955
|
-
"""Test signing a file with C2PA manifest."""
|
|
4956
|
-
# Set up test paths
|
|
4957
|
-
temp_data_dir = os.path.join(self.data_dir, "temp_data")
|
|
4958
|
-
os.makedirs(temp_data_dir, exist_ok=True)
|
|
4959
|
-
output_path = os.path.join(temp_data_dir, "signed_output.jpg")
|
|
4960
|
-
|
|
4961
|
-
# Load test certificates and key
|
|
4962
|
-
with open(os.path.join(self.data_dir, "es256_certs.pem"), "rb") as cert_file:
|
|
4963
|
-
certs = cert_file.read()
|
|
4964
|
-
with open(os.path.join(self.data_dir, "es256_private.key"), "rb") as key_file:
|
|
4965
|
-
key = key_file.read()
|
|
4966
|
-
|
|
4967
|
-
# Create signer info
|
|
4968
|
-
signer_info = C2paSignerInfo(
|
|
4969
|
-
alg=b"es256",
|
|
4970
|
-
sign_cert=certs,
|
|
4971
|
-
private_key=key,
|
|
4972
|
-
ta_url=b"http://timestamp.digicert.com"
|
|
4973
|
-
)
|
|
4974
|
-
|
|
4975
|
-
# Create a simple manifest
|
|
4976
|
-
manifest = {
|
|
4977
|
-
"claim_generator": "python_internals_test",
|
|
4978
|
-
"claim_generator_info": [{
|
|
4979
|
-
"name": "python_internals_test",
|
|
4980
|
-
"version": "0.0.1",
|
|
4981
|
-
}],
|
|
4982
|
-
# Claim version has become mandatory for signing v1 claims
|
|
4983
|
-
"claim_version": 1,
|
|
4984
|
-
"format": "image/jpeg",
|
|
4985
|
-
"title": "Python Test Signed Image",
|
|
4986
|
-
"ingredients": [],
|
|
4987
|
-
"assertions": [
|
|
4988
|
-
{
|
|
4989
|
-
"label": "c2pa.actions",
|
|
4990
|
-
"data": {
|
|
4991
|
-
"actions": [
|
|
4992
|
-
{
|
|
4993
|
-
"action": "c2pa.opened"
|
|
4994
|
-
}
|
|
4995
|
-
]
|
|
4996
|
-
}
|
|
4997
|
-
}
|
|
4998
|
-
]
|
|
4999
|
-
}
|
|
5000
|
-
|
|
5001
|
-
# Convert manifest to JSON string
|
|
5002
|
-
manifest_json = json.dumps(manifest)
|
|
5003
|
-
|
|
5004
|
-
try:
|
|
5005
|
-
sign_file(
|
|
5006
|
-
self.testPath,
|
|
5007
|
-
output_path,
|
|
5008
|
-
manifest_json,
|
|
5009
|
-
signer_info
|
|
5010
|
-
)
|
|
5011
|
-
|
|
5012
|
-
finally:
|
|
5013
|
-
# Clean up
|
|
5014
|
-
if os.path.exists(output_path):
|
|
5015
|
-
os.remove(output_path)
|
|
5016
|
-
|
|
5017
|
-
def test_sign_file_does_not_exist_errors(self):
|
|
5018
|
-
"""Test signing a file with C2PA manifest."""
|
|
5019
|
-
# Set up test paths
|
|
5020
|
-
temp_data_dir = os.path.join(self.data_dir, "temp_data")
|
|
5021
|
-
os.makedirs(temp_data_dir, exist_ok=True)
|
|
5022
|
-
output_path = os.path.join(temp_data_dir, "signed_output.jpg")
|
|
5023
|
-
|
|
5024
|
-
# Load test certificates and key
|
|
5025
|
-
with open(os.path.join(self.data_dir, "es256_certs.pem"), "rb") as cert_file:
|
|
5026
|
-
certs = cert_file.read()
|
|
5027
|
-
with open(os.path.join(self.data_dir, "es256_private.key"), "rb") as key_file:
|
|
5028
|
-
key = key_file.read()
|
|
5029
|
-
|
|
5030
|
-
# Create signer info
|
|
5031
|
-
signer_info = C2paSignerInfo(
|
|
5032
|
-
alg=b"es256",
|
|
5033
|
-
sign_cert=certs,
|
|
5034
|
-
private_key=key,
|
|
5035
|
-
ta_url=b"http://timestamp.digicert.com"
|
|
5036
|
-
)
|
|
5037
|
-
|
|
5038
|
-
# Create a simple manifest
|
|
5039
|
-
manifest = {
|
|
5040
|
-
"claim_generator": "python_internals_test",
|
|
5041
|
-
"claim_generator_info": [{
|
|
5042
|
-
"name": "python_internals_test",
|
|
5043
|
-
"version": "0.0.1",
|
|
5044
|
-
}],
|
|
5045
|
-
# Claim version has become mandatory for signing v1 claims
|
|
5046
|
-
"claim_version": 1,
|
|
5047
|
-
"format": "image/jpeg",
|
|
5048
|
-
"title": "Python Test Signed Image",
|
|
5049
|
-
"ingredients": [],
|
|
5050
|
-
"assertions": [
|
|
5051
|
-
{
|
|
5052
|
-
"label": "c2pa.actions",
|
|
5053
|
-
"data": {
|
|
5054
|
-
"actions": [
|
|
5055
|
-
{
|
|
5056
|
-
"action": "c2pa.opened"
|
|
5057
|
-
}
|
|
5058
|
-
]
|
|
5059
|
-
}
|
|
5060
|
-
}
|
|
5061
|
-
]
|
|
5062
|
-
}
|
|
5063
|
-
|
|
5064
|
-
# Convert manifest to JSON string
|
|
5065
|
-
manifest_json = json.dumps(manifest)
|
|
5066
|
-
|
|
5067
|
-
try:
|
|
5068
|
-
with self.assertRaises(Error):
|
|
5069
|
-
sign_file(
|
|
5070
|
-
"this-file-does-not-exist",
|
|
5071
|
-
output_path,
|
|
5072
|
-
manifest_json,
|
|
5073
|
-
signer_info
|
|
5074
|
-
)
|
|
5075
|
-
|
|
5076
|
-
finally:
|
|
5077
|
-
# Clean up
|
|
5078
|
-
if os.path.exists(output_path):
|
|
5079
|
-
os.remove(output_path)
|
|
5080
|
-
|
|
5081
|
-
def test_builder_sign_with_ingredient_from_file(self):
|
|
5082
|
-
"""Test Builder class operations with an ingredient added from file path."""
|
|
5083
|
-
|
|
5084
|
-
builder = Builder.from_json(self.manifestDefinition)
|
|
5085
|
-
|
|
5086
|
-
# Test adding ingredient from file path
|
|
5087
|
-
ingredient_json = '{"title": "Test Ingredient From File"}'
|
|
5088
|
-
# Suppress the specific deprecation warning for this test, as this is a legacy method
|
|
5089
|
-
with warnings.catch_warnings():
|
|
5090
|
-
warnings.simplefilter("ignore", DeprecationWarning)
|
|
5091
|
-
builder.add_ingredient_from_file_path(ingredient_json, "image/jpeg", self.testPath3)
|
|
5092
|
-
|
|
5093
|
-
with open(self.testPath2, "rb") as file:
|
|
5094
|
-
output = io.BytesIO(bytearray())
|
|
5095
|
-
builder.sign(self.signer, "image/jpeg", file, output)
|
|
5096
|
-
output.seek(0)
|
|
5097
|
-
reader = Reader("image/jpeg", output)
|
|
5098
|
-
json_data = reader.json()
|
|
5099
|
-
manifest_data = json.loads(json_data)
|
|
5100
|
-
|
|
5101
|
-
# Verify active manifest exists
|
|
5102
|
-
self.assertIn("active_manifest", manifest_data)
|
|
5103
|
-
active_manifest_id = manifest_data["active_manifest"]
|
|
5104
|
-
|
|
5105
|
-
# Verify active manifest object exists
|
|
5106
|
-
self.assertIn("manifests", manifest_data)
|
|
5107
|
-
self.assertIn(active_manifest_id, manifest_data["manifests"])
|
|
5108
|
-
active_manifest = manifest_data["manifests"][active_manifest_id]
|
|
5109
|
-
|
|
5110
|
-
# Verify ingredients array exists in active manifest
|
|
5111
|
-
self.assertIn("ingredients", active_manifest)
|
|
5112
|
-
self.assertIsInstance(active_manifest["ingredients"], list)
|
|
5113
|
-
self.assertTrue(len(active_manifest["ingredients"]) > 0)
|
|
5114
|
-
|
|
5115
|
-
# Verify the first ingredient's title matches what we set
|
|
5116
|
-
first_ingredient = active_manifest["ingredients"][0]
|
|
5117
|
-
self.assertEqual(first_ingredient["title"], "Test Ingredient From File")
|
|
5118
|
-
|
|
5119
|
-
builder.close()
|
|
5120
|
-
|
|
5121
|
-
def test_builder_sign_with_ingredient_dict_from_file(self):
|
|
5122
|
-
"""Test Builder class operations with an ingredient added from file path using a dictionary."""
|
|
5123
|
-
|
|
5124
|
-
builder = Builder.from_json(self.manifestDefinition)
|
|
5125
|
-
|
|
5126
|
-
# Test adding ingredient from file path with a dictionary
|
|
5127
|
-
ingredient_dict = {"title": "Test Ingredient From File"}
|
|
5128
|
-
# Suppress the specific deprecation warning for this test, as this is a legacy method
|
|
5129
|
-
with warnings.catch_warnings():
|
|
5130
|
-
warnings.simplefilter("ignore", DeprecationWarning)
|
|
5131
|
-
builder.add_ingredient_from_file_path(ingredient_dict, "image/jpeg", self.testPath3)
|
|
5132
|
-
|
|
5133
|
-
with open(self.testPath2, "rb") as file:
|
|
5134
|
-
output = io.BytesIO(bytearray())
|
|
5135
|
-
builder.sign(self.signer, "image/jpeg", file, output)
|
|
5136
|
-
output.seek(0)
|
|
5137
|
-
reader = Reader("image/jpeg", output)
|
|
5138
|
-
json_data = reader.json()
|
|
5139
|
-
manifest_data = json.loads(json_data)
|
|
5140
|
-
|
|
5141
|
-
# Verify active manifest exists
|
|
5142
|
-
self.assertIn("active_manifest", manifest_data)
|
|
5143
|
-
active_manifest_id = manifest_data["active_manifest"]
|
|
5144
|
-
|
|
5145
|
-
# Verify active manifest object exists
|
|
5146
|
-
self.assertIn("manifests", manifest_data)
|
|
5147
|
-
self.assertIn(active_manifest_id, manifest_data["manifests"])
|
|
5148
|
-
active_manifest = manifest_data["manifests"][active_manifest_id]
|
|
5149
|
-
|
|
5150
|
-
# Verify ingredients array exists in active manifest
|
|
5151
|
-
self.assertIn("ingredients", active_manifest)
|
|
5152
|
-
self.assertIsInstance(active_manifest["ingredients"], list)
|
|
5153
|
-
self.assertTrue(len(active_manifest["ingredients"]) > 0)
|
|
5154
|
-
|
|
5155
|
-
# Verify the first ingredient's title matches what we set
|
|
5156
|
-
first_ingredient = active_manifest["ingredients"][0]
|
|
5157
|
-
self.assertEqual(first_ingredient["title"], "Test Ingredient From File")
|
|
5158
|
-
|
|
5159
|
-
builder.close()
|
|
5160
|
-
|
|
5161
|
-
def test_builder_add_ingredient_from_file_path(self):
|
|
5162
|
-
"""Test Builder class add_ingredient_from_file_path method."""
|
|
5163
|
-
|
|
5164
|
-
# Suppress the specific deprecation warning for this test, as this is a legacy method
|
|
5165
|
-
with warnings.catch_warnings():
|
|
5166
|
-
warnings.simplefilter("ignore", DeprecationWarning)
|
|
5167
|
-
|
|
5168
|
-
builder = Builder.from_json(self.manifestDefinition)
|
|
5169
|
-
|
|
5170
|
-
# Test adding ingredient from file path
|
|
5171
|
-
ingredient_json = '{"test": "ingredient_from_file_path"}'
|
|
5172
|
-
builder.add_ingredient_from_file_path(ingredient_json, "image/jpeg", self.testPath)
|
|
5173
|
-
|
|
5174
|
-
builder.close()
|
|
5175
|
-
|
|
5176
|
-
def test_builder_add_ingredient_from_file_path_not_found(self):
|
|
5177
|
-
"""Test Builder class add_ingredient_from_file_path method."""
|
|
5178
|
-
|
|
5179
|
-
# Suppress the specific deprecation warning for this test, as this is a legacy method
|
|
5180
|
-
with warnings.catch_warnings():
|
|
5181
|
-
warnings.simplefilter("ignore", DeprecationWarning)
|
|
5182
|
-
|
|
5183
|
-
builder = Builder.from_json(self.manifestDefinition)
|
|
5184
|
-
|
|
5185
|
-
# Test adding ingredient from file path
|
|
5186
|
-
ingredient_json = '{"test": "ingredient_from_file_path"}'
|
|
5187
|
-
|
|
5188
|
-
with self.assertRaises(Error.FileNotFound):
|
|
5189
|
-
builder.add_ingredient_from_file_path(ingredient_json, "image/jpeg", "this-file-path-does-not-exist")
|
|
5190
|
-
|
|
5191
|
-
def test_sign_file_using_callback_signer_overloads(self):
|
|
5192
|
-
"""Test signing a file using the sign_file function with a Signer object."""
|
|
5193
|
-
# Create a temporary directory for the test
|
|
5194
|
-
temp_dir = tempfile.mkdtemp()
|
|
5195
|
-
|
|
5196
|
-
try:
|
|
5197
|
-
# Create a temporary output file path
|
|
5198
|
-
output_path = os.path.join(temp_dir, "signed_output_callback.jpg")
|
|
5199
|
-
|
|
5200
|
-
# Create signer with callback
|
|
5201
|
-
signer = Signer.from_callback(
|
|
5202
|
-
callback=self.callback_signer_es256,
|
|
5203
|
-
alg=SigningAlg.ES256,
|
|
5204
|
-
certs=self.certs.decode('utf-8'),
|
|
5205
|
-
tsa_url="http://timestamp.digicert.com"
|
|
5206
|
-
)
|
|
5207
|
-
|
|
5208
|
-
# Overload that returns a JSON string
|
|
5209
|
-
result_json = sign_file(
|
|
5210
|
-
self.testPath,
|
|
5211
|
-
output_path,
|
|
5212
|
-
self.manifestDefinition,
|
|
5213
|
-
signer,
|
|
5214
|
-
False
|
|
5215
|
-
)
|
|
5216
|
-
|
|
5217
|
-
# Verify the output file was created
|
|
5218
|
-
self.assertTrue(os.path.exists(output_path))
|
|
5219
|
-
|
|
5220
|
-
# Verify the result is JSON
|
|
5221
|
-
self.assertIsInstance(result_json, str)
|
|
5222
|
-
self.assertGreater(len(result_json), 0)
|
|
5223
|
-
|
|
5224
|
-
manifest_data = json.loads(result_json)
|
|
5225
|
-
self.assertIn("manifests", manifest_data)
|
|
5226
|
-
self.assertIn("active_manifest", manifest_data)
|
|
5227
|
-
|
|
5228
|
-
output_path_bytes = os.path.join(temp_dir, "signed_output_callback_bytes.jpg")
|
|
5229
|
-
# Overload that returns bytes
|
|
5230
|
-
result_bytes = sign_file(
|
|
5231
|
-
self.testPath,
|
|
5232
|
-
output_path_bytes,
|
|
5233
|
-
self.manifestDefinition,
|
|
5234
|
-
signer,
|
|
5235
|
-
True
|
|
5236
|
-
)
|
|
5237
|
-
|
|
5238
|
-
# Verify the output file was created
|
|
5239
|
-
self.assertTrue(os.path.exists(output_path_bytes))
|
|
5240
|
-
|
|
5241
|
-
# Verify the result is bytes
|
|
5242
|
-
self.assertIsInstance(result_bytes, bytes)
|
|
5243
|
-
self.assertGreater(len(result_bytes), 0)
|
|
5244
|
-
|
|
5245
|
-
# Read the signed file and verify the manifest contains expected content
|
|
5246
|
-
with open(output_path, "rb") as file:
|
|
5247
|
-
reader = Reader("image/jpeg", file)
|
|
5248
|
-
file_manifest_json = reader.json()
|
|
5249
|
-
self.assertIn("Python Test", file_manifest_json)
|
|
5250
|
-
|
|
5251
|
-
finally:
|
|
5252
|
-
shutil.rmtree(temp_dir)
|
|
5253
|
-
|
|
5254
|
-
def test_sign_file_overloads(self):
|
|
5255
|
-
"""Test that the overloaded sign_file function works with both parameter types."""
|
|
5256
|
-
# Create a temporary directory for the test
|
|
5257
|
-
temp_dir = tempfile.mkdtemp()
|
|
5258
|
-
try:
|
|
5259
|
-
# Test with C2paSignerInfo
|
|
5260
|
-
output_path_1 = os.path.join(temp_dir, "signed_output_1.jpg")
|
|
5261
|
-
|
|
5262
|
-
# Load test certificates and key
|
|
5263
|
-
with open(os.path.join(self.data_dir, "es256_certs.pem"), "rb") as cert_file:
|
|
5264
|
-
certs = cert_file.read()
|
|
5265
|
-
with open(os.path.join(self.data_dir, "es256_private.key"), "rb") as key_file:
|
|
5266
|
-
key = key_file.read()
|
|
5267
|
-
|
|
5268
|
-
# Create signer info
|
|
5269
|
-
signer_info = C2paSignerInfo(
|
|
5270
|
-
alg=b"es256",
|
|
5271
|
-
sign_cert=certs,
|
|
5272
|
-
private_key=key,
|
|
5273
|
-
ta_url=b"http://timestamp.digicert.com"
|
|
5274
|
-
)
|
|
5275
|
-
|
|
5276
|
-
# Test with C2paSignerInfo parameter - JSON return
|
|
5277
|
-
result_1 = sign_file(
|
|
5278
|
-
self.testPath,
|
|
5279
|
-
output_path_1,
|
|
5280
|
-
self.manifestDefinition,
|
|
5281
|
-
signer_info,
|
|
5282
|
-
False
|
|
5283
|
-
)
|
|
5284
|
-
|
|
5285
|
-
self.assertIsInstance(result_1, str)
|
|
5286
|
-
self.assertTrue(os.path.exists(output_path_1))
|
|
5287
|
-
|
|
5288
|
-
# Test with C2paSignerInfo parameter - bytes return
|
|
5289
|
-
output_path_1_bytes = os.path.join(temp_dir, "signed_output_1_bytes.jpg")
|
|
5290
|
-
result_1_bytes = sign_file(
|
|
5291
|
-
self.testPath,
|
|
5292
|
-
output_path_1_bytes,
|
|
5293
|
-
self.manifestDefinition,
|
|
5294
|
-
signer_info,
|
|
5295
|
-
True
|
|
5296
|
-
)
|
|
5297
|
-
|
|
5298
|
-
self.assertIsInstance(result_1_bytes, bytes)
|
|
5299
|
-
self.assertTrue(os.path.exists(output_path_1_bytes))
|
|
5300
|
-
|
|
5301
|
-
# Test with Signer object
|
|
5302
|
-
output_path_2 = os.path.join(temp_dir, "signed_output_2.jpg")
|
|
5303
|
-
|
|
5304
|
-
# Create a signer from the signer info
|
|
5305
|
-
signer = Signer.from_info(signer_info)
|
|
5306
|
-
|
|
5307
|
-
# Test with Signer parameter - JSON return
|
|
5308
|
-
result_2 = sign_file(
|
|
5309
|
-
self.testPath,
|
|
5310
|
-
output_path_2,
|
|
5311
|
-
self.manifestDefinition,
|
|
5312
|
-
signer,
|
|
5313
|
-
False
|
|
5314
|
-
)
|
|
5315
|
-
|
|
5316
|
-
self.assertIsInstance(result_2, str)
|
|
5317
|
-
self.assertTrue(os.path.exists(output_path_2))
|
|
5318
|
-
|
|
5319
|
-
# Test with Signer parameter - bytes return
|
|
5320
|
-
output_path_2_bytes = os.path.join(temp_dir, "signed_output_2_bytes.jpg")
|
|
5321
|
-
result_2_bytes = sign_file(
|
|
5322
|
-
self.testPath,
|
|
5323
|
-
output_path_2_bytes,
|
|
5324
|
-
self.manifestDefinition,
|
|
5325
|
-
signer,
|
|
5326
|
-
True
|
|
5327
|
-
)
|
|
5328
|
-
|
|
5329
|
-
self.assertIsInstance(result_2_bytes, bytes)
|
|
5330
|
-
self.assertTrue(os.path.exists(output_path_2_bytes))
|
|
5331
|
-
|
|
5332
|
-
# Both JSON results should be similar (same manifest structure)
|
|
5333
|
-
manifest_1 = json.loads(result_1)
|
|
5334
|
-
manifest_2 = json.loads(result_2)
|
|
5335
|
-
|
|
5336
|
-
self.assertIn("manifests", manifest_1)
|
|
5337
|
-
self.assertIn("manifests", manifest_2)
|
|
5338
|
-
self.assertIn("active_manifest", manifest_1)
|
|
5339
|
-
self.assertIn("active_manifest", manifest_2)
|
|
5340
|
-
|
|
5341
|
-
# Both bytes results should be non-empty
|
|
5342
|
-
self.assertGreater(len(result_1_bytes), 0)
|
|
5343
|
-
self.assertGreater(len(result_2_bytes), 0)
|
|
5344
|
-
|
|
5345
|
-
finally:
|
|
5346
|
-
# Clean up the temporary directory
|
|
5347
|
-
shutil.rmtree(temp_dir)
|
|
5348
|
-
|
|
5349
4863
|
def test_sign_file_callback_signer_reports_error(self):
|
|
5350
4864
|
"""Test signing a file using the sign_file method with a callback that reports an error."""
|
|
5351
4865
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|