diff --git a/moto/s3/exceptions.py b/moto/s3/exceptions.py index 3b33791c5..cb8661035 100644 --- a/moto/s3/exceptions.py +++ b/moto/s3/exceptions.py @@ -424,3 +424,15 @@ class InvalidRange(S3ClientError): actual_size=actual_size, **kwargs ) + + +class InvalidContinuationToken(S3ClientError): + code = 400 + + def __init__(self, *args, **kwargs): + super(InvalidContinuationToken, self).__init__( + "InvalidArgument", + "The continuation token provided is incorrect", + *args, + **kwargs + ) diff --git a/moto/s3/responses.py b/moto/s3/responses.py index b01bed1fb..c2d41b04b 100644 --- a/moto/s3/responses.py +++ b/moto/s3/responses.py @@ -25,6 +25,7 @@ from moto.s3bucket_path.utils import ( from .exceptions import ( BucketAlreadyExists, DuplicateTagKeys, + InvalidContinuationToken, S3ClientError, MissingBucket, MissingKey, @@ -529,6 +530,10 @@ class ResponseObject(_TemplateEnvironmentMixin, ActionAuthenticatorMixin): template = self.response_template(S3_BUCKET_GET_RESPONSE_V2) bucket = self.backend.get_bucket(bucket_name) + continuation_token = querystring.get("continuation-token", [None])[0] + if continuation_token is not None and continuation_token == "": + raise InvalidContinuationToken() + prefix = querystring.get("prefix", [None])[0] if prefix and isinstance(prefix, six.binary_type): prefix = prefix.decode("utf-8") @@ -539,7 +544,6 @@ class ResponseObject(_TemplateEnvironmentMixin, ActionAuthenticatorMixin): fetch_owner = querystring.get("fetch-owner", [False])[0] max_keys = int(querystring.get("max-keys", [1000])[0]) - continuation_token = querystring.get("continuation-token", [None])[0] start_after = querystring.get("start-after", [None])[0] # sort the combination of folders and keys into lexicographical order diff --git a/tests/test_s3/test_s3.py b/tests/test_s3/test_s3.py index bac03ed6a..aa857d089 100644 --- a/tests/test_s3/test_s3.py +++ b/tests/test_s3/test_s3.py @@ -1809,6 +1809,32 @@ def test_boto3_list_objects_v2_common_prefix_pagination(): assert prefixes == [k[: k.rindex("/") + 1] for k in keys] +@mock_s3 +def test_boto3_list_objects_v2_common_invalid_continuation_token(): + s3 = boto3.client("s3", region_name=DEFAULT_REGION_NAME) + s3.create_bucket(Bucket="mybucket") + + max_keys = 1 + keys = ["test/{i}/{i}".format(i=i) for i in range(3)] + for key in keys: + s3.put_object(Bucket="mybucket", Key=key, Body=b"v") + + args = { + "Bucket": "mybucket", + "Delimiter": "/", + "Prefix": "test/", + "MaxKeys": max_keys, + "ContinuationToken": "", + } + + with pytest.raises(botocore.exceptions.ClientError) as exc: + s3.list_objects_v2(**args) + exc.value.response["Error"]["Code"].should.equal("InvalidArgument") + exc.value.response["Error"]["Message"].should.equal( + "The continuation token provided is incorrect" + ) + + @mock_s3 def test_boto3_list_objects_v2_truncated_response(): s3 = boto3.client("s3", region_name=DEFAULT_REGION_NAME)