Skip to content

Commit 6a5b642

Browse files
author
Jussi Kukkonen
authored
Merge pull request #1521 from avelichka/targetfile-from-data
Add an option to create TargetFile from data/file
2 parents 57985a0 + 65fd1aa commit 6a5b642

File tree

2 files changed

+116
-1
lines changed

2 files changed

+116
-1
lines changed

tests/test_api.py

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
import_ed25519_privatekey_from_file
4848
)
4949

50+
from securesystemslib import hash as sslib_hash
5051
from securesystemslib.signer import (
5152
SSlibSigner,
5253
Signature
@@ -621,7 +622,49 @@ def test_length_and_hash_validation(self):
621622
file1_targetfile.length = expected_length
622623
file1_targetfile.hashes = {'sha256': 'incorrecthash'}
623624
self.assertRaises(exceptions.LengthOrHashMismatchError,
624-
file1_targetfile.verify_length_and_hashes, file1)
625+
file1_targetfile.verify_length_and_hashes, file1)
626+
627+
def test_targetfile_from_file(self):
628+
# Test with an existing file and valid hash algorithm
629+
file_path = os.path.join(self.repo_dir, 'targets', 'file1.txt')
630+
targetfile_from_file = TargetFile.from_file(
631+
file_path, file_path, ['sha256']
632+
)
633+
634+
with open(file_path, "rb") as file:
635+
targetfile_from_file.verify_length_and_hashes(file)
636+
637+
# Test with a non-existing file
638+
file_path = os.path.join(self.repo_dir, 'targets', 'file123.txt')
639+
self.assertRaises(
640+
FileNotFoundError,
641+
TargetFile.from_file,
642+
file_path,
643+
file_path,
644+
[sslib_hash.DEFAULT_HASH_ALGORITHM]
645+
)
646+
647+
# Test with an unsupported algorithm
648+
file_path = os.path.join(self.repo_dir, 'targets', 'file1.txt')
649+
self.assertRaises(
650+
exceptions.UnsupportedAlgorithmError,
651+
TargetFile.from_file,
652+
file_path,
653+
file_path,
654+
['123']
655+
)
656+
657+
def test_targetfile_from_data(self):
658+
data = b"Inline test content"
659+
target_file_path = os.path.join(self.repo_dir, 'targets', 'file1.txt')
660+
661+
# Test with a valid hash algorithm
662+
targetfile_from_data = TargetFile.from_data(target_file_path, data, ['sha256'])
663+
targetfile_from_data.verify_length_and_hashes(data)
664+
665+
# Test with no algorithms specified
666+
targetfile_from_data = TargetFile.from_data(target_file_path, data)
667+
targetfile_from_data.verify_length_and_hashes(data)
625668

626669
def test_is_delegated_role(self):
627670
# test path matches

tuf/api/metadata.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1219,6 +1219,78 @@ def to_dict(self) -> Dict[str, Any]:
12191219
**self.unrecognized_fields,
12201220
}
12211221

1222+
@classmethod
1223+
def from_file(
1224+
cls,
1225+
target_file_path: str,
1226+
local_path: str,
1227+
hash_algorithms: Optional[List[str]] = None,
1228+
) -> "TargetFile":
1229+
"""Creates TargetFile object from a file.
1230+
Arguments:
1231+
target_file_path: The TargetFile path.
1232+
local_path: The local path to the file to create TargetFile from.
1233+
hash_algorithms: An optional list of hash algorithms to create
1234+
the hashes with. If not specified the securesystemslib default
1235+
hash algorithm is used.
1236+
Raises:
1237+
FileNotFoundError: The file doesn't exist.
1238+
UnsupportedAlgorithmError: The hash algorithms list
1239+
contains an unsupported algorithm.
1240+
"""
1241+
with open(local_path, "rb") as file:
1242+
return cls.from_data(target_file_path, file, hash_algorithms)
1243+
1244+
@classmethod
1245+
def from_data(
1246+
cls,
1247+
target_file_path: str,
1248+
data: Union[bytes, IO[bytes]],
1249+
hash_algorithms: Optional[List[str]] = None,
1250+
) -> "TargetFile":
1251+
"""Creates TargetFile object from bytes.
1252+
Arguments:
1253+
target_file_path: The TargetFile path.
1254+
data: The data to create TargetFile from.
1255+
hash_algorithms: An optional list of hash algorithms to create
1256+
the hashes with. If not specified the securesystemslib default
1257+
hash algorithm is used.
1258+
Raises:
1259+
UnsupportedAlgorithmError: The hash algorithms list
1260+
contains an unsupported algorithm.
1261+
"""
1262+
if isinstance(data, bytes):
1263+
length = len(data)
1264+
else:
1265+
data.seek(0, io.SEEK_END)
1266+
length = data.tell()
1267+
1268+
hashes = {}
1269+
1270+
if hash_algorithms is None:
1271+
hash_algorithms = [sslib_hash.DEFAULT_HASH_ALGORITHM]
1272+
1273+
for algorithm in hash_algorithms:
1274+
try:
1275+
if isinstance(data, bytes):
1276+
digest_object = sslib_hash.digest(algorithm)
1277+
digest_object.update(data)
1278+
else:
1279+
digest_object = sslib_hash.digest_fileobject(
1280+
data, algorithm
1281+
)
1282+
except (
1283+
sslib_exceptions.UnsupportedAlgorithmError,
1284+
sslib_exceptions.FormatError,
1285+
) as e:
1286+
raise exceptions.UnsupportedAlgorithmError(
1287+
f"Unsupported algorithm '{algorithm}'"
1288+
) from e
1289+
1290+
hashes[algorithm] = digest_object.hexdigest()
1291+
1292+
return cls(length, hashes, target_file_path)
1293+
12221294
def verify_length_and_hashes(self, data: Union[bytes, IO[bytes]]) -> None:
12231295
"""Verifies that length and hashes of "data" match expected values.
12241296

0 commit comments

Comments
 (0)