Skip to content

Commit ad1335b

Browse files
committed
updater: simplify Updater.download_target() call stack
The call stack and code for download_target() is more complex than required: * download_target() : builds target destination filepath, gets length and hashes * _get_target_file() : fixes filenames if consistent snapshots enabled, defines verification callback * _get_file() : iterates mirrors, tries to download files, verifies them Remove the verification callback and collapse the call stack by a single level to make the code easier to follow. Signed-off-by: Joshua Lock <jlock@vmware.com>
1 parent b3ada5b commit ad1335b

2 files changed

Lines changed: 34 additions & 124 deletions

File tree

tests/test_updater.py

Lines changed: 2 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1616,13 +1616,13 @@ def test_9__get_target_hash(self):
16161616

16171617

16181618

1619-
def test_10__hard_check_file_length(self):
1619+
def test_10__check_file_length(self):
16201620
# Test for exception if file object is not equal to trusted file length.
16211621
with tempfile.TemporaryFile() as temp_file_object:
16221622
temp_file_object.write(b'X')
16231623
temp_file_object.seek(0)
16241624
self.assertRaises(tuf.exceptions.DownloadLengthMismatchError,
1625-
self.repository_updater._hard_check_file_length,
1625+
self.repository_updater._check_file_length,
16261626
temp_file_object, 10)
16271627

16281628

@@ -1744,26 +1744,6 @@ def test_11__verify_metadata_file(self):
17441744
metadata_file_object, 'root')
17451745

17461746

1747-
def test_12__get_file(self):
1748-
# Test for an "unsafe" download, where the file is downloaded up to
1749-
# a required length (and no more). The "safe" download approach
1750-
# downloads an exact required length.
1751-
targets_path = os.path.join(self.repository_directory, 'metadata', 'targets.json')
1752-
1753-
file_size, file_hashes = securesystemslib.util.get_file_details(targets_path)
1754-
file_type = 'meta'
1755-
1756-
def verify_target_file(targets_path):
1757-
# Every target file must have its length and hashes inspected.
1758-
self.repository_updater._hard_check_file_length(targets_path, file_size)
1759-
self.repository_updater._check_hashes(targets_path, file_hashes)
1760-
1761-
self.repository_updater._get_file('targets.json', verify_target_file,
1762-
file_type, file_size, download_safely=True).close()
1763-
1764-
self.repository_updater._get_file('targets.json', verify_target_file,
1765-
file_type, file_size, download_safely=False).close()
1766-
17671747
def test_13__targets_of_role(self):
17681748
# Test case where a list of targets is given. By default, the 'targets'
17691749
# parameter is None.

tuf/client/updater.py

Lines changed: 32 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -1217,7 +1217,7 @@ def _check_hashes(self, file_object, trusted_hashes):
12171217

12181218

12191219

1220-
def _hard_check_file_length(self, file_object, trusted_file_length):
1220+
def _check_file_length(self, file_object, trusted_file_length):
12211221
"""
12221222
<Purpose>
12231223
Non-public method that ensures the length of 'file_object' is strictly
@@ -1304,24 +1304,44 @@ def _get_target_file(self, target_filepath, file_length, file_hashes,
13041304
A file object containing the target.
13051305
"""
13061306

1307-
# Define a callable function that is passed as an argument to _get_file()
1308-
# and called. The 'verify_target_file' function ensures the file length
1309-
# and hashes of 'target_filepath' are strictly equal to the trusted values.
1310-
def verify_target_file(target_file_object):
1311-
1312-
# Every target file must have its length and hashes inspected.
1313-
self._hard_check_file_length(target_file_object, file_length)
1314-
self._check_hashes(target_file_object, file_hashes)
1315-
13161307
if self.consistent_snapshot and prefix_filename_with_hash:
13171308
# Note: values() does not return a list in Python 3. Use list()
13181309
# on values() for Python 2+3 compatibility.
13191310
target_digest = list(file_hashes.values()).pop()
13201311
dirname, basename = os.path.split(target_filepath)
13211312
target_filepath = os.path.join(dirname, target_digest + '.' + basename)
13221313

1323-
return self._get_file(target_filepath, verify_target_file,
1324-
'target', file_length, download_safely=True)
1314+
file_mirrors = tuf.mirrors.get_list_of_mirrors('target', target_filepath,
1315+
self.mirrors)
1316+
1317+
# file_mirror (URL): error (Exception)
1318+
file_mirror_errors = {}
1319+
file_object = None
1320+
1321+
for file_mirror in file_mirrors:
1322+
try:
1323+
file_object = tuf.download.safe_download(file_mirror, file_length)
1324+
1325+
# Verify 'file_object' against the expected length and hashes.
1326+
self._check_file_length(file_object, file_length)
1327+
self._check_hashes(file_object, file_hashes)
1328+
1329+
except Exception as exception:
1330+
# Remember the error from this mirror, and "reset" the target file.
1331+
logger.debug('Update failed from ' + file_mirror + '.')
1332+
file_mirror_errors[file_mirror] = exception
1333+
file_object = None
1334+
1335+
else:
1336+
break
1337+
1338+
if file_object:
1339+
return file_object
1340+
1341+
else:
1342+
logger.debug('Failed to update ' + repr(target_filepath) + ' from'
1343+
' all mirrors: ' + repr(file_mirror_errors))
1344+
raise tuf.exceptions.NoWorkingMirrorError(file_mirror_errors)
13251345

13261346

13271347

@@ -1605,96 +1625,6 @@ def _get_metadata_file(self, metadata_role, remote_filename,
16051625

16061626

16071627

1608-
def _get_file(self, filepath, verify_file_function, file_type, file_length,
1609-
download_safely=True):
1610-
"""
1611-
<Purpose>
1612-
Non-public method that tries downloading, up to a certain length, a
1613-
metadata or target file from a list of known mirrors. As soon as the first
1614-
valid copy of the file is found, the rest of the mirrors will be skipped.
1615-
1616-
<Arguments>
1617-
filepath:
1618-
The relative metadata or target filepath.
1619-
1620-
verify_file_function:
1621-
A callable function that expects a file object and raises an exception
1622-
if the file is invalid.
1623-
Target files and uncompressed versions of metadata may be verified with
1624-
'verify_file_function'.
1625-
1626-
file_type:
1627-
Type of data needed for download, must correspond to one of the strings
1628-
in the list ['meta', 'target']. 'meta' for metadata file type or
1629-
'target' for target file type. It should correspond to the
1630-
'securesystemslib.formats.NAME_SCHEMA' format.
1631-
1632-
file_length:
1633-
The expected length, or upper bound, of the target or metadata file to
1634-
be downloaded.
1635-
1636-
download_safely:
1637-
A boolean switch to toggle safe or unsafe download of the file.
1638-
1639-
<Exceptions>
1640-
tuf.exceptions.NoWorkingMirrorError:
1641-
The metadata could not be fetched. This is raised only when all known
1642-
mirrors failed to provide a valid copy of the desired metadata file.
1643-
1644-
<Side Effects>
1645-
The file is downloaded from all known repository mirrors in the worst
1646-
case. If a valid copy of the file is found, it is stored in a temporary
1647-
file and returned.
1648-
1649-
<Returns>
1650-
A file object containing the metadata or target.
1651-
"""
1652-
1653-
file_mirrors = tuf.mirrors.get_list_of_mirrors(file_type, filepath,
1654-
self.mirrors)
1655-
1656-
# file_mirror (URL): error (Exception)
1657-
file_mirror_errors = {}
1658-
file_object = None
1659-
1660-
for file_mirror in file_mirrors:
1661-
try:
1662-
# TODO: Instead of the more fragile 'download_safely' switch, unroll
1663-
# the function into two separate ones: one for "safe" download, and the
1664-
# other one for "unsafe" download? This should induce safer and more
1665-
# readable code.
1666-
if download_safely:
1667-
file_object = tuf.download.safe_download(file_mirror, file_length)
1668-
1669-
else:
1670-
file_object = tuf.download.unsafe_download(file_mirror, file_length)
1671-
1672-
# Verify 'file_object' according to the callable function.
1673-
# 'file_object' is also verified if decompressed above (i.e., the
1674-
# uncompressed version).
1675-
verify_file_function(file_object)
1676-
1677-
except Exception as exception:
1678-
# Remember the error from this mirror, and "reset" the target file.
1679-
logger.debug('Update failed from ' + file_mirror + '.')
1680-
file_mirror_errors[file_mirror] = exception
1681-
file_object = None
1682-
1683-
else:
1684-
break
1685-
1686-
if file_object:
1687-
return file_object
1688-
1689-
else:
1690-
logger.debug('Failed to update ' + repr(filepath) + ' from'
1691-
' all mirrors: ' + repr(file_mirror_errors))
1692-
raise tuf.exceptions.NoWorkingMirrorError(file_mirror_errors)
1693-
1694-
1695-
1696-
1697-
16981628
def _update_metadata(self, metadata_role, upperbound_filelength, version=None):
16991629
"""
17001630
<Purpose>

0 commit comments

Comments
 (0)