Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-s3transfer for openSUSE:Factory checked in at 2025-09-18 21:07:58 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-s3transfer (Old) and /work/SRC/openSUSE:Factory/.python-s3transfer.new.27445 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-s3transfer" Thu Sep 18 21:07:58 2025 rev:40 rq:1305266 version:0.14.0 Changes: -------- --- /work/SRC/openSUSE:Factory/python-s3transfer/python-s3transfer.changes 2025-07-30 11:45:43.383582429 +0200 +++ /work/SRC/openSUSE:Factory/.python-s3transfer.new.27445/python-s3transfer.changes 2025-09-18 21:08:01.788778227 +0200 @@ -1,0 +2,7 @@ +Tue Sep 16 11:53:51 UTC 2025 - John Paul Adrian Glaubitz <[email protected]> + +- Update to version 0.14.0 + * feature:download: Validate requested range matches content + range in response for multipart downloads + +------------------------------------------------------------------- Old: ---- s3transfer-0.13.1.tar.gz New: ---- s3transfer-0.14.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-s3transfer.spec ++++++ --- /var/tmp/diff_new_pack.uaFiwG/_old 2025-09-18 21:08:02.284798965 +0200 +++ /var/tmp/diff_new_pack.uaFiwG/_new 2025-09-18 21:08:02.288799132 +0200 @@ -18,7 +18,7 @@ %{?sle15_python_module_pythons} Name: python-s3transfer -Version: 0.13.1 +Version: 0.14.0 Release: 0 Summary: Python S3 transfer manager License: Apache-2.0 ++++++ s3transfer-0.13.1.tar.gz -> s3transfer-0.14.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/s3transfer-0.13.1/PKG-INFO new/s3transfer-0.14.0/PKG-INFO --- old/s3transfer-0.13.1/PKG-INFO 2025-07-18 20:11:17.464557200 +0200 +++ new/s3transfer-0.14.0/PKG-INFO 2025-09-09 20:09:31.783774000 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: s3transfer -Version: 0.13.1 +Version: 0.14.0 Summary: An Amazon S3 Transfer Manager Home-page: https://github.com/boto/s3transfer Author: Amazon Web Services diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/s3transfer-0.13.1/s3transfer/__init__.py new/s3transfer-0.14.0/s3transfer/__init__.py --- old/s3transfer-0.13.1/s3transfer/__init__.py 2025-07-18 20:11:17.000000000 +0200 +++ new/s3transfer-0.14.0/s3transfer/__init__.py 2025-09-09 20:09:31.000000000 +0200 @@ -146,7 +146,7 @@ from s3transfer.exceptions import RetriesExceededError, S3UploadFailedError __author__ = 'Amazon Web Services' -__version__ = '0.13.1' +__version__ = '0.14.0' logger = logging.getLogger(__name__) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/s3transfer-0.13.1/s3transfer/download.py new/s3transfer-0.14.0/s3transfer/download.py --- old/s3transfer-0.13.1/s3transfer/download.py 2025-07-18 20:07:26.000000000 +0200 +++ new/s3transfer-0.14.0/s3transfer/download.py 2025-09-09 20:09:31.000000000 +0200 @@ -17,7 +17,11 @@ from botocore.exceptions import ClientError from s3transfer.compat import seekable -from s3transfer.exceptions import RetriesExceededError, S3DownloadFailedError +from s3transfer.exceptions import ( + RetriesExceededError, + S3DownloadFailedError, + S3ValidationError, +) from s3transfer.futures import IN_MEMORY_DOWNLOAD_TAG from s3transfer.tasks import SubmissionTask, Task from s3transfer.utils import ( @@ -578,6 +582,10 @@ response = client.get_object( Bucket=bucket, Key=key, **extra_args ) + self._validate_content_range( + extra_args.get('Range'), + response.get('ContentRange'), + ) streaming_body = StreamReaderProgress( response['Body'], callbacks ) @@ -635,6 +643,27 @@ def _handle_io(self, download_output_manager, fileobj, chunk, index): download_output_manager.queue_file_io_task(fileobj, chunk, index) + def _validate_content_range(self, requested_range, content_range): + if not requested_range or not content_range: + return + # Unparsed `ContentRange` looks like `bytes 0-8388607/39542919`, + # where `0-8388607` is the fetched range and `39542919` is + # the total object size. + response_range, total_size = content_range.split('/') + # Subtract `1` because range is 0-indexed. + final_byte = str(int(total_size) - 1) + # If it's the last part, the requested range will not include + # the final byte, eg `bytes=33554432-`. + if requested_range.endswith('-'): + requested_range += final_byte + # Request looks like `bytes=0-8388607`. + # Parsed response looks like `bytes 0-8388607`. + if requested_range[6:] != response_range[6:]: + raise S3ValidationError( + f"Requested range: `{requested_range[6:]}` does not match " + f"content range in response: `{response_range[6:]}`" + ) + class ImmediatelyWriteIOGetObjectTask(GetObjectTask): """GetObjectTask that immediately writes to the provided file object diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/s3transfer-0.13.1/s3transfer/exceptions.py new/s3transfer-0.14.0/s3transfer/exceptions.py --- old/s3transfer-0.13.1/s3transfer/exceptions.py 2025-07-18 20:07:26.000000000 +0200 +++ new/s3transfer-0.14.0/s3transfer/exceptions.py 2025-09-09 20:09:31.000000000 +0200 @@ -39,3 +39,7 @@ """A CancelledError raised from an error in the TransferManager""" pass + + +class S3ValidationError(Exception): + pass diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/s3transfer-0.13.1/s3transfer.egg-info/PKG-INFO new/s3transfer-0.14.0/s3transfer.egg-info/PKG-INFO --- old/s3transfer-0.13.1/s3transfer.egg-info/PKG-INFO 2025-07-18 20:11:17.000000000 +0200 +++ new/s3transfer-0.14.0/s3transfer.egg-info/PKG-INFO 2025-09-09 20:09:31.000000000 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: s3transfer -Version: 0.13.1 +Version: 0.14.0 Summary: An Amazon S3 Transfer Manager Home-page: https://github.com/boto/s3transfer Author: Amazon Web Services diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/s3transfer-0.13.1/tests/functional/test_download.py new/s3transfer-0.14.0/tests/functional/test_download.py --- old/s3transfer-0.13.1/tests/functional/test_download.py 2025-07-18 20:07:26.000000000 +0200 +++ new/s3transfer-0.14.0/tests/functional/test_download.py 2025-09-09 20:09:31.000000000 +0200 @@ -21,7 +21,11 @@ from botocore.exceptions import ClientError from s3transfer.compat import SOCKET_ERROR -from s3transfer.exceptions import RetriesExceededError, S3DownloadFailedError +from s3transfer.exceptions import ( + RetriesExceededError, + S3DownloadFailedError, + S3ValidationError, +) from s3transfer.manager import TransferConfig, TransferManager from tests import ( BaseGeneralInterfaceTest, @@ -109,7 +113,7 @@ self.stubber.add_response(**head_response) def add_successful_get_object_responses( - self, expected_params=None, expected_ranges=None + self, expected_params=None, expected_ranges=None, extras=None ): # Add all get_object responses needed to complete the download. # Should account for both ranged and nonranged downloads. @@ -124,6 +128,8 @@ stubbed_response['expected_params']['Range'] = ( expected_ranges[i] ) + if extras: + stubbed_response['service_response'].update(extras[i]) self.stubber.add_response(**stubbed_response) def add_n_retryable_get_object_responses(self, n, num_reads=0): @@ -511,9 +517,12 @@ 'RequestPayer': 'requester', } expected_ranges = ['bytes=0-3', 'bytes=4-7', 'bytes=8-'] + stubbed_ranges = ['bytes 0-3/10', 'bytes 4-7/10', 'bytes 8-9/10'] self.add_head_object_response(expected_params) self.add_successful_get_object_responses( - {**expected_params, 'IfMatch': self.etag}, expected_ranges + {**expected_params, 'IfMatch': self.etag}, + expected_ranges, + [{"ContentRange": r} for r in stubbed_ranges], ) future = self.manager.download( @@ -547,6 +556,28 @@ with open(self.filename, 'rb') as f: self.assertEqual(self.content, f.read()) + def test_download_raises_if_content_range_mismatch(self): + expected_params = { + 'Bucket': self.bucket, + 'Key': self.key, + } + expected_ranges = ['bytes=0-3', 'bytes=4-7', 'bytes=8-'] + # Note that the final retrieved range should be `bytes 8-9/10`. + stubbed_ranges = ['bytes 0-3/10', 'bytes 4-7/10', 'bytes 7-8/10'] + self.add_head_object_response(expected_params) + self.add_successful_get_object_responses( + {**expected_params, 'IfMatch': self.etag}, + expected_ranges, + [{"ContentRange": r} for r in stubbed_ranges], + ) + + future = self.manager.download( + self.bucket, self.key, self.filename, self.extra_args + ) + with self.assertRaises(S3ValidationError) as e: + future.result() + self.assertIn('does not match content range', str(e.exception)) + def test_download_raises_if_etag_validation_fails(self): expected_params = { 'Bucket': self.bucket,
