c2pa-python 0.32.13__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.13/src/c2pa_python.egg-info → c2pa_python-0.34.0}/PKG-INFO +1 -1
- {c2pa_python-0.32.13 → c2pa_python-0.34.0}/pyproject.toml +1 -1
- {c2pa_python-0.32.13 → c2pa_python-0.34.0}/scripts/download_artifacts.py +1 -1
- {c2pa_python-0.32.13 → c2pa_python-0.34.0}/setup.py +2 -1
- {c2pa_python-0.32.13 → c2pa_python-0.34.0}/src/c2pa/__init__.py +0 -2
- {c2pa_python-0.32.13 → c2pa_python-0.34.0}/src/c2pa/c2pa.py +81 -282
- {c2pa_python-0.32.13 → c2pa_python-0.34.0}/src/c2pa/lib.py +5 -2
- {c2pa_python-0.32.13 → c2pa_python-0.34.0/src/c2pa_python.egg-info}/PKG-INFO +1 -1
- {c2pa_python-0.32.13 → c2pa_python-0.34.0}/tests/test_unit_tests.py +14 -487
- {c2pa_python-0.32.13 → c2pa_python-0.34.0}/LICENSE-APACHE +0 -0
- {c2pa_python-0.32.13 → c2pa_python-0.34.0}/LICENSE-MIT +0 -0
- {c2pa_python-0.32.13 → c2pa_python-0.34.0}/MANIFEST.in +0 -0
- {c2pa_python-0.32.13 → c2pa_python-0.34.0}/README.md +0 -0
- {c2pa_python-0.32.13 → c2pa_python-0.34.0}/requirements.txt +0 -0
- {c2pa_python-0.32.13 → c2pa_python-0.34.0}/setup.cfg +0 -0
- {c2pa_python-0.32.13 → c2pa_python-0.34.0}/src/c2pa/build.py +0 -0
- {c2pa_python-0.32.13 → c2pa_python-0.34.0}/src/c2pa_python.egg-info/SOURCES.txt +0 -0
- {c2pa_python-0.32.13 → c2pa_python-0.34.0}/src/c2pa_python.egg-info/dependency_links.txt +0 -0
- {c2pa_python-0.32.13 → c2pa_python-0.34.0}/src/c2pa_python.egg-info/entry_points.txt +0 -0
- {c2pa_python-0.32.13 → c2pa_python-0.34.0}/src/c2pa_python.egg-info/requires.txt +0 -0
- {c2pa_python-0.32.13 → c2pa_python-0.34.0}/src/c2pa_python.egg-info/top_level.txt +0 -0
- {c2pa_python-0.32.13 → 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',
|
|
@@ -48,6 +46,7 @@ _REQUIRED_FUNCTIONS = [
|
|
|
48
46
|
'c2pa_reader_from_manifest_data_and_stream',
|
|
49
47
|
'c2pa_reader_json',
|
|
50
48
|
'c2pa_reader_detailed_json',
|
|
49
|
+
'c2pa_reader_crjson',
|
|
51
50
|
'c2pa_reader_resource_to_stream',
|
|
52
51
|
'c2pa_reader_from_context',
|
|
53
52
|
'c2pa_reader_with_stream',
|
|
@@ -242,8 +241,13 @@ class ManagedResource:
|
|
|
242
241
|
|
|
243
242
|
@staticmethod
|
|
244
243
|
def _free_native_ptr(ptr):
|
|
245
|
-
"""Free a native pointer by
|
|
246
|
-
|
|
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)
|
|
247
251
|
|
|
248
252
|
def _ensure_valid_state(self):
|
|
249
253
|
"""Raise if the resource is closed or uninitialized."""
|
|
@@ -561,6 +565,9 @@ _setup_function(
|
|
|
561
565
|
_setup_function(
|
|
562
566
|
_lib.c2pa_reader_detailed_json, [
|
|
563
567
|
ctypes.POINTER(C2paReader)], ctypes.c_void_p)
|
|
568
|
+
_setup_function(
|
|
569
|
+
_lib.c2pa_reader_crjson, [
|
|
570
|
+
ctypes.POINTER(C2paReader)], ctypes.c_void_p)
|
|
564
571
|
_setup_function(_lib.c2pa_reader_resource_to_stream, [ctypes.POINTER(
|
|
565
572
|
C2paReader), ctypes.c_char_p, ctypes.POINTER(C2paStream)], ctypes.c_int64)
|
|
566
573
|
_setup_function(
|
|
@@ -641,12 +648,6 @@ _setup_function(_lib.c2pa_signer_create,
|
|
|
641
648
|
_setup_function(_lib.c2pa_signer_from_info,
|
|
642
649
|
[ctypes.POINTER(C2paSignerInfo)],
|
|
643
650
|
ctypes.POINTER(C2paSigner))
|
|
644
|
-
_setup_function(
|
|
645
|
-
_lib.c2pa_read_file, [
|
|
646
|
-
ctypes.c_char_p, ctypes.c_char_p], ctypes.c_void_p)
|
|
647
|
-
_setup_function(
|
|
648
|
-
_lib.c2pa_read_ingredient_file, [
|
|
649
|
-
ctypes.c_char_p, ctypes.c_char_p], ctypes.c_void_p)
|
|
650
651
|
|
|
651
652
|
# Set up Signer function prototypes
|
|
652
653
|
_setup_function(
|
|
@@ -885,33 +886,51 @@ class _StringContainer:
|
|
|
885
886
|
self._data_dir_str = ""
|
|
886
887
|
|
|
887
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
|
+
|
|
888
903
|
def _convert_to_py_string(value) -> str:
|
|
889
904
|
if value is None:
|
|
890
905
|
return ""
|
|
891
906
|
|
|
892
907
|
py_string = ""
|
|
893
908
|
|
|
894
|
-
# Validate pointer before
|
|
895
|
-
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:
|
|
896
917
|
return ""
|
|
897
918
|
|
|
898
919
|
try:
|
|
899
|
-
|
|
920
|
+
raw = ctypes.string_at(address)
|
|
900
921
|
|
|
901
|
-
|
|
902
|
-
|
|
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
|
|
903
928
|
try:
|
|
904
|
-
|
|
929
|
+
_lib.c2pa_string_free(value)
|
|
905
930
|
except Exception:
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
try:
|
|
910
|
-
_lib.c2pa_string_free(value)
|
|
911
|
-
except Exception:
|
|
912
|
-
# Ignore clean up issues
|
|
913
|
-
pass
|
|
914
|
-
except (ctypes.ArgumentError, TypeError, ValueError):
|
|
931
|
+
# Ignore clean up issues
|
|
932
|
+
pass
|
|
933
|
+
except (ctypes.ArgumentError, TypeError, ValueError, OSError):
|
|
915
934
|
# Invalid pointer type or value
|
|
916
935
|
return ""
|
|
917
936
|
|
|
@@ -999,8 +1018,7 @@ def _parse_operation_result_for_error(
|
|
|
999
1018
|
if check_error:
|
|
1000
1019
|
error = _lib.c2pa_error()
|
|
1001
1020
|
if error:
|
|
1002
|
-
error_str = ctypes.
|
|
1003
|
-
error, ctypes.c_char_p).value.decode('utf-8')
|
|
1021
|
+
error_str = ctypes.string_at(error).decode('utf-8')
|
|
1004
1022
|
_lib.c2pa_string_free(error)
|
|
1005
1023
|
_raise_typed_c2pa_error(error_str)
|
|
1006
1024
|
return None
|
|
@@ -1187,208 +1205,6 @@ def _get_mime_type_from_path(path: Union[str, Path]) -> str:
|
|
|
1187
1205
|
return mime_type
|
|
1188
1206
|
|
|
1189
1207
|
|
|
1190
|
-
def read_ingredient_file(
|
|
1191
|
-
path: Union[str, Path], data_dir: Union[str, Path]) -> str:
|
|
1192
|
-
"""Read a file as C2PA ingredient (deprecated).
|
|
1193
|
-
This creates the JSON string that would be used as the ingredient JSON.
|
|
1194
|
-
|
|
1195
|
-
.. deprecated:: 0.11.0
|
|
1196
|
-
This function is deprecated and will be removed in a future version.
|
|
1197
|
-
To read C2PA metadata, use the :class:`c2pa.c2pa.Reader` class.
|
|
1198
|
-
To add ingredients to a manifest,
|
|
1199
|
-
use :meth:`c2pa.c2pa.Builder.add_ingredient` instead.
|
|
1200
|
-
|
|
1201
|
-
Args:
|
|
1202
|
-
path: Path to the file to read
|
|
1203
|
-
data_dir: Directory to write binary resources to
|
|
1204
|
-
|
|
1205
|
-
Returns:
|
|
1206
|
-
The ingredient as a JSON string
|
|
1207
|
-
|
|
1208
|
-
Raises:
|
|
1209
|
-
C2paError: If there was an error reading the file
|
|
1210
|
-
"""
|
|
1211
|
-
warnings.warn(
|
|
1212
|
-
"The read_ingredient_file function is deprecated and will be "
|
|
1213
|
-
"removed in a future version. Please use Reader(path).json() for "
|
|
1214
|
-
"reading C2PA metadata instead, or "
|
|
1215
|
-
"Builder.add_ingredient(json, format, stream) to add ingredients "
|
|
1216
|
-
"to a manifest.",
|
|
1217
|
-
DeprecationWarning,
|
|
1218
|
-
stacklevel=2,
|
|
1219
|
-
)
|
|
1220
|
-
|
|
1221
|
-
_clear_error_state()
|
|
1222
|
-
|
|
1223
|
-
container = _StringContainer()
|
|
1224
|
-
|
|
1225
|
-
container._path_str = str(path).encode('utf-8')
|
|
1226
|
-
container._data_dir_str = str(data_dir).encode('utf-8')
|
|
1227
|
-
|
|
1228
|
-
result = _lib.c2pa_read_ingredient_file(
|
|
1229
|
-
container._path_str, container._data_dir_str)
|
|
1230
|
-
|
|
1231
|
-
_check_ffi_operation_result(
|
|
1232
|
-
result, "Error reading ingredient file {}".format(path))
|
|
1233
|
-
|
|
1234
|
-
return _convert_to_py_string(result)
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
def read_file(path: Union[str, Path],
|
|
1238
|
-
data_dir: Union[str, Path]) -> str:
|
|
1239
|
-
"""Read a C2PA manifest from a file (deprecated).
|
|
1240
|
-
|
|
1241
|
-
.. deprecated:: 0.10.0
|
|
1242
|
-
This function is deprecated and will be removed in a future version.
|
|
1243
|
-
To read C2PA metadata, use the :class:`c2pa.c2pa.Reader` class.
|
|
1244
|
-
|
|
1245
|
-
Args:
|
|
1246
|
-
path: Path to the file to read
|
|
1247
|
-
data_dir: Directory to write binary resources to
|
|
1248
|
-
|
|
1249
|
-
Returns:
|
|
1250
|
-
The manifest as a JSON string
|
|
1251
|
-
|
|
1252
|
-
Raises:
|
|
1253
|
-
C2paError: If there was an error reading the file
|
|
1254
|
-
"""
|
|
1255
|
-
warnings.warn(
|
|
1256
|
-
"The read_file function is deprecated and will be removed in a "
|
|
1257
|
-
"future version. Please use the Reader class for reading C2PA "
|
|
1258
|
-
"metadata instead.",
|
|
1259
|
-
DeprecationWarning,
|
|
1260
|
-
stacklevel=2,
|
|
1261
|
-
)
|
|
1262
|
-
|
|
1263
|
-
_clear_error_state()
|
|
1264
|
-
|
|
1265
|
-
container = _StringContainer()
|
|
1266
|
-
|
|
1267
|
-
container._path_str = str(path).encode('utf-8')
|
|
1268
|
-
container._data_dir_str = str(data_dir).encode('utf-8')
|
|
1269
|
-
|
|
1270
|
-
result = _lib.c2pa_read_file(container._path_str, container._data_dir_str)
|
|
1271
|
-
_check_ffi_operation_result(
|
|
1272
|
-
result, "Error during read of manifest from file {}".format(path))
|
|
1273
|
-
|
|
1274
|
-
return _convert_to_py_string(result)
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
@overload
|
|
1278
|
-
def sign_file(
|
|
1279
|
-
source_path: Union[str, Path],
|
|
1280
|
-
dest_path: Union[str, Path],
|
|
1281
|
-
manifest: str,
|
|
1282
|
-
signer_info: C2paSignerInfo,
|
|
1283
|
-
return_manifest_as_bytes: bool = False
|
|
1284
|
-
) -> Union[str, bytes]:
|
|
1285
|
-
"""Sign a file with a C2PA manifest using signer info.
|
|
1286
|
-
"""
|
|
1287
|
-
...
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
@overload
|
|
1291
|
-
def sign_file(
|
|
1292
|
-
source_path: Union[str, Path],
|
|
1293
|
-
dest_path: Union[str, Path],
|
|
1294
|
-
manifest: str,
|
|
1295
|
-
signer: 'Signer',
|
|
1296
|
-
return_manifest_as_bytes: bool = False
|
|
1297
|
-
) -> Union[str, bytes]:
|
|
1298
|
-
"""Sign a file with a C2PA manifest using a signer.
|
|
1299
|
-
"""
|
|
1300
|
-
...
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
def sign_file(
|
|
1304
|
-
source_path: Union[str, Path],
|
|
1305
|
-
dest_path: Union[str, Path],
|
|
1306
|
-
manifest: str,
|
|
1307
|
-
signer_or_info: Union[C2paSignerInfo, 'Signer'],
|
|
1308
|
-
return_manifest_as_bytes: bool = False
|
|
1309
|
-
) -> Union[str, bytes]:
|
|
1310
|
-
"""Sign a file with a C2PA manifest (deprecated).
|
|
1311
|
-
For now, this function is left here to provide a backwards-compatible API.
|
|
1312
|
-
|
|
1313
|
-
.. deprecated:: 0.13.0
|
|
1314
|
-
This function is deprecated and will be removed in a future version.
|
|
1315
|
-
Use :meth:`Builder.sign` instead.
|
|
1316
|
-
|
|
1317
|
-
Args:
|
|
1318
|
-
source_path: Path to the source file. We will attempt
|
|
1319
|
-
to guess the mimetype of the source file based on
|
|
1320
|
-
the extension.
|
|
1321
|
-
dest_path: Path to write the signed file to
|
|
1322
|
-
manifest: The manifest JSON string
|
|
1323
|
-
signer_or_info: Either a signer configuration or a signer object
|
|
1324
|
-
return_manifest_as_bytes: If True, return manifest bytes instead
|
|
1325
|
-
of JSON string
|
|
1326
|
-
|
|
1327
|
-
Returns:
|
|
1328
|
-
The signed manifest as a JSON string or bytes, depending
|
|
1329
|
-
on return_manifest_as_bytes
|
|
1330
|
-
|
|
1331
|
-
Raises:
|
|
1332
|
-
C2paError: If there was an error signing the file
|
|
1333
|
-
C2paError.Encoding: If any of the string inputs contain
|
|
1334
|
-
invalid UTF-8 characters
|
|
1335
|
-
C2paError.NotSupported: If the file type cannot be determined
|
|
1336
|
-
"""
|
|
1337
|
-
|
|
1338
|
-
warnings.warn(
|
|
1339
|
-
"The sign_file function is deprecated and will be removed in a "
|
|
1340
|
-
"future version. Please use the Builder object and Builder.sign() "
|
|
1341
|
-
"instead.",
|
|
1342
|
-
DeprecationWarning,
|
|
1343
|
-
stacklevel=2,
|
|
1344
|
-
)
|
|
1345
|
-
|
|
1346
|
-
_clear_error_state()
|
|
1347
|
-
|
|
1348
|
-
try:
|
|
1349
|
-
# Determine if we have a signer or signer info
|
|
1350
|
-
if isinstance(signer_or_info, C2paSignerInfo):
|
|
1351
|
-
signer = Signer.from_info(signer_or_info)
|
|
1352
|
-
own_signer = True
|
|
1353
|
-
else:
|
|
1354
|
-
signer = signer_or_info
|
|
1355
|
-
own_signer = False
|
|
1356
|
-
|
|
1357
|
-
# Create a builder from the manifest
|
|
1358
|
-
builder = Builder(manifest)
|
|
1359
|
-
|
|
1360
|
-
manifest_bytes = builder.sign_file(
|
|
1361
|
-
source_path,
|
|
1362
|
-
dest_path,
|
|
1363
|
-
signer
|
|
1364
|
-
)
|
|
1365
|
-
|
|
1366
|
-
if return_manifest_as_bytes:
|
|
1367
|
-
return manifest_bytes
|
|
1368
|
-
else:
|
|
1369
|
-
# Read the signed manifest from the destination file
|
|
1370
|
-
with Reader(dest_path) as reader:
|
|
1371
|
-
return reader.json()
|
|
1372
|
-
|
|
1373
|
-
except Exception as e:
|
|
1374
|
-
# Clean up destination file if it exists and there was an error
|
|
1375
|
-
if os.path.exists(dest_path):
|
|
1376
|
-
try:
|
|
1377
|
-
os.remove(dest_path)
|
|
1378
|
-
except OSError:
|
|
1379
|
-
logger.warning("Failed to remove destination file")
|
|
1380
|
-
pass # Ignore cleanup errors
|
|
1381
|
-
|
|
1382
|
-
# Re-raise the error
|
|
1383
|
-
raise C2paError(f"Error signing file: {str(e)}") from e
|
|
1384
|
-
finally:
|
|
1385
|
-
# Ensure resources are cleaned up
|
|
1386
|
-
if 'builder' in locals():
|
|
1387
|
-
builder.close()
|
|
1388
|
-
if 'signer' in locals() and own_signer:
|
|
1389
|
-
signer.close()
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
1208
|
class ContextProvider(ABC):
|
|
1393
1209
|
"""Abstract base class for types that provide a C2PA context.
|
|
1394
1210
|
|
|
@@ -1823,10 +1639,21 @@ class Stream:
|
|
|
1823
1639
|
readinto = getattr(stream, "readinto", None)
|
|
1824
1640
|
if readinto is not None:
|
|
1825
1641
|
# Most streams have readinto
|
|
1826
|
-
buf = (
|
|
1827
|
-
ctypes.addressof(data.contents))
|
|
1828
|
-
|
|
1829
|
-
|
|
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
|
|
1830
1657
|
|
|
1831
1658
|
# Fallback for streams without readinto.
|
|
1832
1659
|
buffer = stream.read(length)
|
|
@@ -2631,6 +2458,27 @@ class Reader(ManagedResource):
|
|
|
2631
2458
|
|
|
2632
2459
|
return _convert_to_py_string(result)
|
|
2633
2460
|
|
|
2461
|
+
def crjson(self) -> str:
|
|
2462
|
+
"""Get the manifest store as a crJSON string.
|
|
2463
|
+
|
|
2464
|
+
crJSON is a standardized JSON format for C2PA manifest data. This
|
|
2465
|
+
call yields empty JSON ("{}") when there are no Content Credentials.
|
|
2466
|
+
|
|
2467
|
+
Returns:
|
|
2468
|
+
The manifest store as a crJSON string.
|
|
2469
|
+
|
|
2470
|
+
Raises:
|
|
2471
|
+
C2paError: If the Reader has been closed or the underlying C
|
|
2472
|
+
call returns null.
|
|
2473
|
+
"""
|
|
2474
|
+
|
|
2475
|
+
self._ensure_valid_state()
|
|
2476
|
+
|
|
2477
|
+
result = _lib.c2pa_reader_crjson(self._handle)
|
|
2478
|
+
_check_ffi_operation_result(result, "Error parsing crJSON")
|
|
2479
|
+
|
|
2480
|
+
return _convert_to_py_string(result)
|
|
2481
|
+
|
|
2634
2482
|
def _get_manifest_field(self, extractor):
|
|
2635
2483
|
"""Extract a field from (cached) manifest data, or None if unavailable.
|
|
2636
2484
|
|
|
@@ -3386,52 +3234,6 @@ class Builder(ManagedResource):
|
|
|
3386
3234
|
Builder._ERROR_MESSAGES['ingredient_error'].format("Unknown error"),
|
|
3387
3235
|
check=lambda r: r != 0)
|
|
3388
3236
|
|
|
3389
|
-
def add_ingredient_from_file_path(
|
|
3390
|
-
self,
|
|
3391
|
-
ingredient_json: Union[str, dict],
|
|
3392
|
-
format: str,
|
|
3393
|
-
filepath: Union[str, Path]):
|
|
3394
|
-
"""Add an ingredient from a file path to the builder (deprecated).
|
|
3395
|
-
This is a legacy method.
|
|
3396
|
-
|
|
3397
|
-
.. deprecated:: 0.13.0
|
|
3398
|
-
This method is deprecated and will be removed in a future version.
|
|
3399
|
-
Use :meth:`add_ingredient` with a file stream instead.
|
|
3400
|
-
|
|
3401
|
-
Args:
|
|
3402
|
-
ingredient_json: The JSON ingredient definition
|
|
3403
|
-
(either a JSON string or a dictionary)
|
|
3404
|
-
format: The MIME type or extension of the ingredient
|
|
3405
|
-
filepath: The path to the file containing the ingredient data
|
|
3406
|
-
(can be a string or Path object)
|
|
3407
|
-
|
|
3408
|
-
Raises:
|
|
3409
|
-
C2paError: If there was an error adding the ingredient
|
|
3410
|
-
C2paError.Encoding: If the ingredient JSON or format
|
|
3411
|
-
contains invalid UTF-8 characters
|
|
3412
|
-
FileNotFoundError: If the file at the specified path does not exist
|
|
3413
|
-
"""
|
|
3414
|
-
warnings.warn(
|
|
3415
|
-
"add_ingredient_from_file_path is deprecated and will "
|
|
3416
|
-
"be removed in a future version. Use add_ingredient "
|
|
3417
|
-
"with a file stream instead.",
|
|
3418
|
-
DeprecationWarning,
|
|
3419
|
-
stacklevel=2,
|
|
3420
|
-
)
|
|
3421
|
-
|
|
3422
|
-
try:
|
|
3423
|
-
# Convert Path object to string if necessary
|
|
3424
|
-
filepath_str = str(filepath)
|
|
3425
|
-
|
|
3426
|
-
# Does the stream handling to use add_ingredient_from_stream
|
|
3427
|
-
with open(filepath_str, 'rb') as file_stream:
|
|
3428
|
-
self.add_ingredient_from_stream(
|
|
3429
|
-
ingredient_json, format, file_stream)
|
|
3430
|
-
except FileNotFoundError as e:
|
|
3431
|
-
raise C2paError.FileNotFound(f"File not found: {filepath}") from e
|
|
3432
|
-
except Exception as e:
|
|
3433
|
-
raise C2paError.Other(f"Could not add ingredient: {e}") from e
|
|
3434
|
-
|
|
3435
3237
|
def add_action(self, action_json: Union[str, dict]) -> None:
|
|
3436
3238
|
"""Add an action to the builder, that will be placed
|
|
3437
3239
|
in the actions assertion array in the generated manifest.
|
|
@@ -3966,9 +3768,6 @@ __all__ = [
|
|
|
3966
3768
|
'Builder',
|
|
3967
3769
|
'Signer',
|
|
3968
3770
|
'load_settings',
|
|
3969
|
-
'read_file',
|
|
3970
|
-
'read_ingredient_file',
|
|
3971
|
-
'sign_file',
|
|
3972
3771
|
'format_embeddable',
|
|
3973
3772
|
'version',
|
|
3974
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()
|
|
@@ -271,6 +271,19 @@ class TestReader(unittest.TestCase):
|
|
|
271
271
|
title = manifest_store["manifests"][manifest_store["active_manifest"]]["claim"]["dc:title"]
|
|
272
272
|
self.assertEqual(title, DEFAULT_TEST_FILE_NAME)
|
|
273
273
|
|
|
274
|
+
def test_stream_read_crjson_and_parse(self):
|
|
275
|
+
with open(self.testPath, "rb") as file:
|
|
276
|
+
reader = Reader("image/jpeg", file)
|
|
277
|
+
crjson = reader.crjson()
|
|
278
|
+
self.assertTrue(crjson)
|
|
279
|
+
json.loads(crjson)
|
|
280
|
+
|
|
281
|
+
def test_stream_read_crjson_path_only(self):
|
|
282
|
+
with Reader(self.testPath) as reader:
|
|
283
|
+
crjson = reader.crjson()
|
|
284
|
+
self.assertTrue(crjson)
|
|
285
|
+
json.loads(crjson)
|
|
286
|
+
|
|
274
287
|
def test_stream_read_string_stream_path_only(self):
|
|
275
288
|
with Reader(self.testPath) as reader:
|
|
276
289
|
json_data = reader.json()
|
|
@@ -4774,9 +4787,6 @@ class TestStream(unittest.TestCase):
|
|
|
4774
4787
|
class TestLegacyAPI(unittest.TestCase):
|
|
4775
4788
|
def setUp(self):
|
|
4776
4789
|
# Filter specific deprecation warnings for legacy API tests
|
|
4777
|
-
warnings.filterwarnings("ignore", message="The read_file function is deprecated")
|
|
4778
|
-
warnings.filterwarnings("ignore", message="The sign_file function is deprecated")
|
|
4779
|
-
warnings.filterwarnings("ignore", message="The read_ingredient_file function is deprecated")
|
|
4780
4790
|
warnings.filterwarnings("ignore", message="The create_signer function is deprecated")
|
|
4781
4791
|
warnings.filterwarnings("ignore", message="The create_signer_from_info function is deprecated")
|
|
4782
4792
|
warnings.filterwarnings("ignore", message="load_settings\\(\\) is deprecated")
|
|
@@ -4850,489 +4860,6 @@ class TestLegacyAPI(unittest.TestCase):
|
|
|
4850
4860
|
if os.path.exists(self.temp_data_dir):
|
|
4851
4861
|
shutil.rmtree(self.temp_data_dir)
|
|
4852
4862
|
|
|
4853
|
-
def test_read_ingredient_file(self):
|
|
4854
|
-
"""Test reading a C2PA ingredient from a file."""
|
|
4855
|
-
# Test reading ingredient from file with data_dir
|
|
4856
|
-
temp_data_dir = os.path.join(self.data_dir, "temp_data")
|
|
4857
|
-
os.makedirs(temp_data_dir, exist_ok=True)
|
|
4858
|
-
|
|
4859
|
-
ingredient_json_with_dir = read_ingredient_file(self.testPath, temp_data_dir)
|
|
4860
|
-
|
|
4861
|
-
# Verify some fields
|
|
4862
|
-
ingredient_data = json.loads(ingredient_json_with_dir)
|
|
4863
|
-
self.assertEqual(ingredient_data["title"], DEFAULT_TEST_FILE_NAME)
|
|
4864
|
-
self.assertEqual(ingredient_data["format"], "image/jpeg")
|
|
4865
|
-
self.assertIn("thumbnail", ingredient_data)
|
|
4866
|
-
|
|
4867
|
-
def test_read_ingredient_file_who_has_no_manifest(self):
|
|
4868
|
-
"""Test reading a C2PA ingredient from a file."""
|
|
4869
|
-
# Test reading ingredient from file with data_dir
|
|
4870
|
-
temp_data_dir = os.path.join(self.data_dir, "temp_data")
|
|
4871
|
-
os.makedirs(temp_data_dir, exist_ok=True)
|
|
4872
|
-
|
|
4873
|
-
# Load settings first, before they need to be used
|
|
4874
|
-
load_settings('{"builder": { "thumbnail": {"enabled": false}}}')
|
|
4875
|
-
|
|
4876
|
-
ingredient_json_with_dir = read_ingredient_file(self.testPath2, temp_data_dir)
|
|
4877
|
-
|
|
4878
|
-
# Verify some fields
|
|
4879
|
-
ingredient_data = json.loads(ingredient_json_with_dir)
|
|
4880
|
-
self.assertEqual(ingredient_data["title"], INGREDIENT_TEST_FILE_NAME)
|
|
4881
|
-
self.assertEqual(ingredient_data["format"], "image/jpeg")
|
|
4882
|
-
self.assertNotIn("thumbnail", ingredient_data)
|
|
4883
|
-
|
|
4884
|
-
# Reset setting
|
|
4885
|
-
load_settings('{"builder": { "thumbnail": {"enabled": true}}}')
|
|
4886
|
-
|
|
4887
|
-
def test_compare_read_ingredient_file_with_builder_added_ingredient(self):
|
|
4888
|
-
"""Test reading a C2PA ingredient from a file."""
|
|
4889
|
-
# Test reading ingredient from file with data_dir
|
|
4890
|
-
temp_data_dir = os.path.join(self.data_dir, "temp_data")
|
|
4891
|
-
os.makedirs(temp_data_dir, exist_ok=True)
|
|
4892
|
-
|
|
4893
|
-
ingredient_json_with_dir = read_ingredient_file(self.testPath2, temp_data_dir)
|
|
4894
|
-
|
|
4895
|
-
# Ingredient fields from read_ingredient_file
|
|
4896
|
-
ingredient_data = json.loads(ingredient_json_with_dir)
|
|
4897
|
-
|
|
4898
|
-
# Compare with ingredient added by Builder
|
|
4899
|
-
builder = Builder.from_json(self.manifestDefinition)
|
|
4900
|
-
# Only the title is needed (filename), since title not extracted or guessed from filename
|
|
4901
|
-
ingredient_json = '{ "title" : "A.jpg" }'
|
|
4902
|
-
with open(self.testPath2, 'rb') as f:
|
|
4903
|
-
builder.add_ingredient(ingredient_json, "image/jpeg", f)
|
|
4904
|
-
|
|
4905
|
-
with open(self.testPath2, "rb") as file:
|
|
4906
|
-
output = io.BytesIO(bytearray())
|
|
4907
|
-
builder.sign(self.signer, "image/jpeg", file, output)
|
|
4908
|
-
output.seek(0)
|
|
4909
|
-
|
|
4910
|
-
# Get ingredient fields from signed manifest
|
|
4911
|
-
reader = Reader("image/jpeg", output)
|
|
4912
|
-
json_data = reader.json()
|
|
4913
|
-
manifest_data = json.loads(json_data)
|
|
4914
|
-
active_manifest_id = manifest_data["active_manifest"]
|
|
4915
|
-
active_manifest = manifest_data["manifests"][active_manifest_id]
|
|
4916
|
-
only_ingredient = active_manifest["ingredients"][0]
|
|
4917
|
-
|
|
4918
|
-
self.assertEqual(ingredient_data["title"], only_ingredient["title"])
|
|
4919
|
-
self.assertEqual(ingredient_data["format"], only_ingredient["format"])
|
|
4920
|
-
self.assertEqual(ingredient_data["document_id"], only_ingredient["document_id"])
|
|
4921
|
-
self.assertEqual(ingredient_data["instance_id"], only_ingredient["instance_id"])
|
|
4922
|
-
self.assertEqual(ingredient_data["relationship"], only_ingredient["relationship"])
|
|
4923
|
-
|
|
4924
|
-
def test_read_file(self):
|
|
4925
|
-
"""Test reading a C2PA ingredient from a file."""
|
|
4926
|
-
temp_data_dir = os.path.join(self.data_dir, "temp_data")
|
|
4927
|
-
os.makedirs(temp_data_dir, exist_ok=True)
|
|
4928
|
-
|
|
4929
|
-
# self.testPath has C2PA metadata to read
|
|
4930
|
-
file_json_with_dir = read_file(self.testPath, temp_data_dir)
|
|
4931
|
-
|
|
4932
|
-
# Parse the JSON and verify specific fields
|
|
4933
|
-
file_data = json.loads(file_json_with_dir)
|
|
4934
|
-
expected_manifest_id = "contentauth:urn:uuid:c85a2b90-f1a0-4aa4-b17f-f938b475804e"
|
|
4935
|
-
|
|
4936
|
-
# Verify some fields
|
|
4937
|
-
self.assertEqual(file_data["active_manifest"], expected_manifest_id)
|
|
4938
|
-
self.assertIn("manifests", file_data)
|
|
4939
|
-
self.assertIn(expected_manifest_id, file_data["manifests"])
|
|
4940
|
-
|
|
4941
|
-
def test_sign_file(self):
|
|
4942
|
-
"""Test signing a file with C2PA manifest."""
|
|
4943
|
-
# Set up test paths
|
|
4944
|
-
temp_data_dir = os.path.join(self.data_dir, "temp_data")
|
|
4945
|
-
os.makedirs(temp_data_dir, exist_ok=True)
|
|
4946
|
-
output_path = os.path.join(temp_data_dir, "signed_output.jpg")
|
|
4947
|
-
|
|
4948
|
-
# Load test certificates and key
|
|
4949
|
-
with open(os.path.join(self.data_dir, "es256_certs.pem"), "rb") as cert_file:
|
|
4950
|
-
certs = cert_file.read()
|
|
4951
|
-
with open(os.path.join(self.data_dir, "es256_private.key"), "rb") as key_file:
|
|
4952
|
-
key = key_file.read()
|
|
4953
|
-
|
|
4954
|
-
# Create signer info
|
|
4955
|
-
signer_info = C2paSignerInfo(
|
|
4956
|
-
alg=b"es256",
|
|
4957
|
-
sign_cert=certs,
|
|
4958
|
-
private_key=key,
|
|
4959
|
-
ta_url=b"http://timestamp.digicert.com"
|
|
4960
|
-
)
|
|
4961
|
-
|
|
4962
|
-
# Create a simple manifest
|
|
4963
|
-
manifest = {
|
|
4964
|
-
"claim_generator": "python_internals_test",
|
|
4965
|
-
"claim_generator_info": [{
|
|
4966
|
-
"name": "python_internals_test",
|
|
4967
|
-
"version": "0.0.1",
|
|
4968
|
-
}],
|
|
4969
|
-
# Claim version has become mandatory for signing v1 claims
|
|
4970
|
-
"claim_version": 1,
|
|
4971
|
-
"format": "image/jpeg",
|
|
4972
|
-
"title": "Python Test Signed Image",
|
|
4973
|
-
"ingredients": [],
|
|
4974
|
-
"assertions": [
|
|
4975
|
-
{
|
|
4976
|
-
"label": "c2pa.actions",
|
|
4977
|
-
"data": {
|
|
4978
|
-
"actions": [
|
|
4979
|
-
{
|
|
4980
|
-
"action": "c2pa.opened"
|
|
4981
|
-
}
|
|
4982
|
-
]
|
|
4983
|
-
}
|
|
4984
|
-
}
|
|
4985
|
-
]
|
|
4986
|
-
}
|
|
4987
|
-
|
|
4988
|
-
# Convert manifest to JSON string
|
|
4989
|
-
manifest_json = json.dumps(manifest)
|
|
4990
|
-
|
|
4991
|
-
try:
|
|
4992
|
-
sign_file(
|
|
4993
|
-
self.testPath,
|
|
4994
|
-
output_path,
|
|
4995
|
-
manifest_json,
|
|
4996
|
-
signer_info
|
|
4997
|
-
)
|
|
4998
|
-
|
|
4999
|
-
finally:
|
|
5000
|
-
# Clean up
|
|
5001
|
-
if os.path.exists(output_path):
|
|
5002
|
-
os.remove(output_path)
|
|
5003
|
-
|
|
5004
|
-
def test_sign_file_does_not_exist_errors(self):
|
|
5005
|
-
"""Test signing a file with C2PA manifest."""
|
|
5006
|
-
# Set up test paths
|
|
5007
|
-
temp_data_dir = os.path.join(self.data_dir, "temp_data")
|
|
5008
|
-
os.makedirs(temp_data_dir, exist_ok=True)
|
|
5009
|
-
output_path = os.path.join(temp_data_dir, "signed_output.jpg")
|
|
5010
|
-
|
|
5011
|
-
# Load test certificates and key
|
|
5012
|
-
with open(os.path.join(self.data_dir, "es256_certs.pem"), "rb") as cert_file:
|
|
5013
|
-
certs = cert_file.read()
|
|
5014
|
-
with open(os.path.join(self.data_dir, "es256_private.key"), "rb") as key_file:
|
|
5015
|
-
key = key_file.read()
|
|
5016
|
-
|
|
5017
|
-
# Create signer info
|
|
5018
|
-
signer_info = C2paSignerInfo(
|
|
5019
|
-
alg=b"es256",
|
|
5020
|
-
sign_cert=certs,
|
|
5021
|
-
private_key=key,
|
|
5022
|
-
ta_url=b"http://timestamp.digicert.com"
|
|
5023
|
-
)
|
|
5024
|
-
|
|
5025
|
-
# Create a simple manifest
|
|
5026
|
-
manifest = {
|
|
5027
|
-
"claim_generator": "python_internals_test",
|
|
5028
|
-
"claim_generator_info": [{
|
|
5029
|
-
"name": "python_internals_test",
|
|
5030
|
-
"version": "0.0.1",
|
|
5031
|
-
}],
|
|
5032
|
-
# Claim version has become mandatory for signing v1 claims
|
|
5033
|
-
"claim_version": 1,
|
|
5034
|
-
"format": "image/jpeg",
|
|
5035
|
-
"title": "Python Test Signed Image",
|
|
5036
|
-
"ingredients": [],
|
|
5037
|
-
"assertions": [
|
|
5038
|
-
{
|
|
5039
|
-
"label": "c2pa.actions",
|
|
5040
|
-
"data": {
|
|
5041
|
-
"actions": [
|
|
5042
|
-
{
|
|
5043
|
-
"action": "c2pa.opened"
|
|
5044
|
-
}
|
|
5045
|
-
]
|
|
5046
|
-
}
|
|
5047
|
-
}
|
|
5048
|
-
]
|
|
5049
|
-
}
|
|
5050
|
-
|
|
5051
|
-
# Convert manifest to JSON string
|
|
5052
|
-
manifest_json = json.dumps(manifest)
|
|
5053
|
-
|
|
5054
|
-
try:
|
|
5055
|
-
with self.assertRaises(Error):
|
|
5056
|
-
sign_file(
|
|
5057
|
-
"this-file-does-not-exist",
|
|
5058
|
-
output_path,
|
|
5059
|
-
manifest_json,
|
|
5060
|
-
signer_info
|
|
5061
|
-
)
|
|
5062
|
-
|
|
5063
|
-
finally:
|
|
5064
|
-
# Clean up
|
|
5065
|
-
if os.path.exists(output_path):
|
|
5066
|
-
os.remove(output_path)
|
|
5067
|
-
|
|
5068
|
-
def test_builder_sign_with_ingredient_from_file(self):
|
|
5069
|
-
"""Test Builder class operations with an ingredient added from file path."""
|
|
5070
|
-
|
|
5071
|
-
builder = Builder.from_json(self.manifestDefinition)
|
|
5072
|
-
|
|
5073
|
-
# Test adding ingredient from file path
|
|
5074
|
-
ingredient_json = '{"title": "Test Ingredient From File"}'
|
|
5075
|
-
# Suppress the specific deprecation warning for this test, as this is a legacy method
|
|
5076
|
-
with warnings.catch_warnings():
|
|
5077
|
-
warnings.simplefilter("ignore", DeprecationWarning)
|
|
5078
|
-
builder.add_ingredient_from_file_path(ingredient_json, "image/jpeg", self.testPath3)
|
|
5079
|
-
|
|
5080
|
-
with open(self.testPath2, "rb") as file:
|
|
5081
|
-
output = io.BytesIO(bytearray())
|
|
5082
|
-
builder.sign(self.signer, "image/jpeg", file, output)
|
|
5083
|
-
output.seek(0)
|
|
5084
|
-
reader = Reader("image/jpeg", output)
|
|
5085
|
-
json_data = reader.json()
|
|
5086
|
-
manifest_data = json.loads(json_data)
|
|
5087
|
-
|
|
5088
|
-
# Verify active manifest exists
|
|
5089
|
-
self.assertIn("active_manifest", manifest_data)
|
|
5090
|
-
active_manifest_id = manifest_data["active_manifest"]
|
|
5091
|
-
|
|
5092
|
-
# Verify active manifest object exists
|
|
5093
|
-
self.assertIn("manifests", manifest_data)
|
|
5094
|
-
self.assertIn(active_manifest_id, manifest_data["manifests"])
|
|
5095
|
-
active_manifest = manifest_data["manifests"][active_manifest_id]
|
|
5096
|
-
|
|
5097
|
-
# Verify ingredients array exists in active manifest
|
|
5098
|
-
self.assertIn("ingredients", active_manifest)
|
|
5099
|
-
self.assertIsInstance(active_manifest["ingredients"], list)
|
|
5100
|
-
self.assertTrue(len(active_manifest["ingredients"]) > 0)
|
|
5101
|
-
|
|
5102
|
-
# Verify the first ingredient's title matches what we set
|
|
5103
|
-
first_ingredient = active_manifest["ingredients"][0]
|
|
5104
|
-
self.assertEqual(first_ingredient["title"], "Test Ingredient From File")
|
|
5105
|
-
|
|
5106
|
-
builder.close()
|
|
5107
|
-
|
|
5108
|
-
def test_builder_sign_with_ingredient_dict_from_file(self):
|
|
5109
|
-
"""Test Builder class operations with an ingredient added from file path using a dictionary."""
|
|
5110
|
-
|
|
5111
|
-
builder = Builder.from_json(self.manifestDefinition)
|
|
5112
|
-
|
|
5113
|
-
# Test adding ingredient from file path with a dictionary
|
|
5114
|
-
ingredient_dict = {"title": "Test Ingredient From File"}
|
|
5115
|
-
# Suppress the specific deprecation warning for this test, as this is a legacy method
|
|
5116
|
-
with warnings.catch_warnings():
|
|
5117
|
-
warnings.simplefilter("ignore", DeprecationWarning)
|
|
5118
|
-
builder.add_ingredient_from_file_path(ingredient_dict, "image/jpeg", self.testPath3)
|
|
5119
|
-
|
|
5120
|
-
with open(self.testPath2, "rb") as file:
|
|
5121
|
-
output = io.BytesIO(bytearray())
|
|
5122
|
-
builder.sign(self.signer, "image/jpeg", file, output)
|
|
5123
|
-
output.seek(0)
|
|
5124
|
-
reader = Reader("image/jpeg", output)
|
|
5125
|
-
json_data = reader.json()
|
|
5126
|
-
manifest_data = json.loads(json_data)
|
|
5127
|
-
|
|
5128
|
-
# Verify active manifest exists
|
|
5129
|
-
self.assertIn("active_manifest", manifest_data)
|
|
5130
|
-
active_manifest_id = manifest_data["active_manifest"]
|
|
5131
|
-
|
|
5132
|
-
# Verify active manifest object exists
|
|
5133
|
-
self.assertIn("manifests", manifest_data)
|
|
5134
|
-
self.assertIn(active_manifest_id, manifest_data["manifests"])
|
|
5135
|
-
active_manifest = manifest_data["manifests"][active_manifest_id]
|
|
5136
|
-
|
|
5137
|
-
# Verify ingredients array exists in active manifest
|
|
5138
|
-
self.assertIn("ingredients", active_manifest)
|
|
5139
|
-
self.assertIsInstance(active_manifest["ingredients"], list)
|
|
5140
|
-
self.assertTrue(len(active_manifest["ingredients"]) > 0)
|
|
5141
|
-
|
|
5142
|
-
# Verify the first ingredient's title matches what we set
|
|
5143
|
-
first_ingredient = active_manifest["ingredients"][0]
|
|
5144
|
-
self.assertEqual(first_ingredient["title"], "Test Ingredient From File")
|
|
5145
|
-
|
|
5146
|
-
builder.close()
|
|
5147
|
-
|
|
5148
|
-
def test_builder_add_ingredient_from_file_path(self):
|
|
5149
|
-
"""Test Builder class add_ingredient_from_file_path method."""
|
|
5150
|
-
|
|
5151
|
-
# Suppress the specific deprecation warning for this test, as this is a legacy method
|
|
5152
|
-
with warnings.catch_warnings():
|
|
5153
|
-
warnings.simplefilter("ignore", DeprecationWarning)
|
|
5154
|
-
|
|
5155
|
-
builder = Builder.from_json(self.manifestDefinition)
|
|
5156
|
-
|
|
5157
|
-
# Test adding ingredient from file path
|
|
5158
|
-
ingredient_json = '{"test": "ingredient_from_file_path"}'
|
|
5159
|
-
builder.add_ingredient_from_file_path(ingredient_json, "image/jpeg", self.testPath)
|
|
5160
|
-
|
|
5161
|
-
builder.close()
|
|
5162
|
-
|
|
5163
|
-
def test_builder_add_ingredient_from_file_path_not_found(self):
|
|
5164
|
-
"""Test Builder class add_ingredient_from_file_path method."""
|
|
5165
|
-
|
|
5166
|
-
# Suppress the specific deprecation warning for this test, as this is a legacy method
|
|
5167
|
-
with warnings.catch_warnings():
|
|
5168
|
-
warnings.simplefilter("ignore", DeprecationWarning)
|
|
5169
|
-
|
|
5170
|
-
builder = Builder.from_json(self.manifestDefinition)
|
|
5171
|
-
|
|
5172
|
-
# Test adding ingredient from file path
|
|
5173
|
-
ingredient_json = '{"test": "ingredient_from_file_path"}'
|
|
5174
|
-
|
|
5175
|
-
with self.assertRaises(Error.FileNotFound):
|
|
5176
|
-
builder.add_ingredient_from_file_path(ingredient_json, "image/jpeg", "this-file-path-does-not-exist")
|
|
5177
|
-
|
|
5178
|
-
def test_sign_file_using_callback_signer_overloads(self):
|
|
5179
|
-
"""Test signing a file using the sign_file function with a Signer object."""
|
|
5180
|
-
# Create a temporary directory for the test
|
|
5181
|
-
temp_dir = tempfile.mkdtemp()
|
|
5182
|
-
|
|
5183
|
-
try:
|
|
5184
|
-
# Create a temporary output file path
|
|
5185
|
-
output_path = os.path.join(temp_dir, "signed_output_callback.jpg")
|
|
5186
|
-
|
|
5187
|
-
# Create signer with callback
|
|
5188
|
-
signer = Signer.from_callback(
|
|
5189
|
-
callback=self.callback_signer_es256,
|
|
5190
|
-
alg=SigningAlg.ES256,
|
|
5191
|
-
certs=self.certs.decode('utf-8'),
|
|
5192
|
-
tsa_url="http://timestamp.digicert.com"
|
|
5193
|
-
)
|
|
5194
|
-
|
|
5195
|
-
# Overload that returns a JSON string
|
|
5196
|
-
result_json = sign_file(
|
|
5197
|
-
self.testPath,
|
|
5198
|
-
output_path,
|
|
5199
|
-
self.manifestDefinition,
|
|
5200
|
-
signer,
|
|
5201
|
-
False
|
|
5202
|
-
)
|
|
5203
|
-
|
|
5204
|
-
# Verify the output file was created
|
|
5205
|
-
self.assertTrue(os.path.exists(output_path))
|
|
5206
|
-
|
|
5207
|
-
# Verify the result is JSON
|
|
5208
|
-
self.assertIsInstance(result_json, str)
|
|
5209
|
-
self.assertGreater(len(result_json), 0)
|
|
5210
|
-
|
|
5211
|
-
manifest_data = json.loads(result_json)
|
|
5212
|
-
self.assertIn("manifests", manifest_data)
|
|
5213
|
-
self.assertIn("active_manifest", manifest_data)
|
|
5214
|
-
|
|
5215
|
-
output_path_bytes = os.path.join(temp_dir, "signed_output_callback_bytes.jpg")
|
|
5216
|
-
# Overload that returns bytes
|
|
5217
|
-
result_bytes = sign_file(
|
|
5218
|
-
self.testPath,
|
|
5219
|
-
output_path_bytes,
|
|
5220
|
-
self.manifestDefinition,
|
|
5221
|
-
signer,
|
|
5222
|
-
True
|
|
5223
|
-
)
|
|
5224
|
-
|
|
5225
|
-
# Verify the output file was created
|
|
5226
|
-
self.assertTrue(os.path.exists(output_path_bytes))
|
|
5227
|
-
|
|
5228
|
-
# Verify the result is bytes
|
|
5229
|
-
self.assertIsInstance(result_bytes, bytes)
|
|
5230
|
-
self.assertGreater(len(result_bytes), 0)
|
|
5231
|
-
|
|
5232
|
-
# Read the signed file and verify the manifest contains expected content
|
|
5233
|
-
with open(output_path, "rb") as file:
|
|
5234
|
-
reader = Reader("image/jpeg", file)
|
|
5235
|
-
file_manifest_json = reader.json()
|
|
5236
|
-
self.assertIn("Python Test", file_manifest_json)
|
|
5237
|
-
|
|
5238
|
-
finally:
|
|
5239
|
-
shutil.rmtree(temp_dir)
|
|
5240
|
-
|
|
5241
|
-
def test_sign_file_overloads(self):
|
|
5242
|
-
"""Test that the overloaded sign_file function works with both parameter types."""
|
|
5243
|
-
# Create a temporary directory for the test
|
|
5244
|
-
temp_dir = tempfile.mkdtemp()
|
|
5245
|
-
try:
|
|
5246
|
-
# Test with C2paSignerInfo
|
|
5247
|
-
output_path_1 = os.path.join(temp_dir, "signed_output_1.jpg")
|
|
5248
|
-
|
|
5249
|
-
# Load test certificates and key
|
|
5250
|
-
with open(os.path.join(self.data_dir, "es256_certs.pem"), "rb") as cert_file:
|
|
5251
|
-
certs = cert_file.read()
|
|
5252
|
-
with open(os.path.join(self.data_dir, "es256_private.key"), "rb") as key_file:
|
|
5253
|
-
key = key_file.read()
|
|
5254
|
-
|
|
5255
|
-
# Create signer info
|
|
5256
|
-
signer_info = C2paSignerInfo(
|
|
5257
|
-
alg=b"es256",
|
|
5258
|
-
sign_cert=certs,
|
|
5259
|
-
private_key=key,
|
|
5260
|
-
ta_url=b"http://timestamp.digicert.com"
|
|
5261
|
-
)
|
|
5262
|
-
|
|
5263
|
-
# Test with C2paSignerInfo parameter - JSON return
|
|
5264
|
-
result_1 = sign_file(
|
|
5265
|
-
self.testPath,
|
|
5266
|
-
output_path_1,
|
|
5267
|
-
self.manifestDefinition,
|
|
5268
|
-
signer_info,
|
|
5269
|
-
False
|
|
5270
|
-
)
|
|
5271
|
-
|
|
5272
|
-
self.assertIsInstance(result_1, str)
|
|
5273
|
-
self.assertTrue(os.path.exists(output_path_1))
|
|
5274
|
-
|
|
5275
|
-
# Test with C2paSignerInfo parameter - bytes return
|
|
5276
|
-
output_path_1_bytes = os.path.join(temp_dir, "signed_output_1_bytes.jpg")
|
|
5277
|
-
result_1_bytes = sign_file(
|
|
5278
|
-
self.testPath,
|
|
5279
|
-
output_path_1_bytes,
|
|
5280
|
-
self.manifestDefinition,
|
|
5281
|
-
signer_info,
|
|
5282
|
-
True
|
|
5283
|
-
)
|
|
5284
|
-
|
|
5285
|
-
self.assertIsInstance(result_1_bytes, bytes)
|
|
5286
|
-
self.assertTrue(os.path.exists(output_path_1_bytes))
|
|
5287
|
-
|
|
5288
|
-
# Test with Signer object
|
|
5289
|
-
output_path_2 = os.path.join(temp_dir, "signed_output_2.jpg")
|
|
5290
|
-
|
|
5291
|
-
# Create a signer from the signer info
|
|
5292
|
-
signer = Signer.from_info(signer_info)
|
|
5293
|
-
|
|
5294
|
-
# Test with Signer parameter - JSON return
|
|
5295
|
-
result_2 = sign_file(
|
|
5296
|
-
self.testPath,
|
|
5297
|
-
output_path_2,
|
|
5298
|
-
self.manifestDefinition,
|
|
5299
|
-
signer,
|
|
5300
|
-
False
|
|
5301
|
-
)
|
|
5302
|
-
|
|
5303
|
-
self.assertIsInstance(result_2, str)
|
|
5304
|
-
self.assertTrue(os.path.exists(output_path_2))
|
|
5305
|
-
|
|
5306
|
-
# Test with Signer parameter - bytes return
|
|
5307
|
-
output_path_2_bytes = os.path.join(temp_dir, "signed_output_2_bytes.jpg")
|
|
5308
|
-
result_2_bytes = sign_file(
|
|
5309
|
-
self.testPath,
|
|
5310
|
-
output_path_2_bytes,
|
|
5311
|
-
self.manifestDefinition,
|
|
5312
|
-
signer,
|
|
5313
|
-
True
|
|
5314
|
-
)
|
|
5315
|
-
|
|
5316
|
-
self.assertIsInstance(result_2_bytes, bytes)
|
|
5317
|
-
self.assertTrue(os.path.exists(output_path_2_bytes))
|
|
5318
|
-
|
|
5319
|
-
# Both JSON results should be similar (same manifest structure)
|
|
5320
|
-
manifest_1 = json.loads(result_1)
|
|
5321
|
-
manifest_2 = json.loads(result_2)
|
|
5322
|
-
|
|
5323
|
-
self.assertIn("manifests", manifest_1)
|
|
5324
|
-
self.assertIn("manifests", manifest_2)
|
|
5325
|
-
self.assertIn("active_manifest", manifest_1)
|
|
5326
|
-
self.assertIn("active_manifest", manifest_2)
|
|
5327
|
-
|
|
5328
|
-
# Both bytes results should be non-empty
|
|
5329
|
-
self.assertGreater(len(result_1_bytes), 0)
|
|
5330
|
-
self.assertGreater(len(result_2_bytes), 0)
|
|
5331
|
-
|
|
5332
|
-
finally:
|
|
5333
|
-
# Clean up the temporary directory
|
|
5334
|
-
shutil.rmtree(temp_dir)
|
|
5335
|
-
|
|
5336
4863
|
def test_sign_file_callback_signer_reports_error(self):
|
|
5337
4864
|
"""Test signing a file using the sign_file method with a callback that reports an error."""
|
|
5338
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
|