From cc27f1ef0c2c3c1070f211bb207736e2af29cb0b Mon Sep 17 00:00:00 2001 From: Bert Blommers Date: Wed, 28 Oct 2020 14:22:18 +0000 Subject: [PATCH] S3 - Add more detail to error responses --- moto/s3/exceptions.py | 20 ++++++++++++++++++++ moto/s3/responses.py | 18 +++++++++++++----- tests/test_s3/red.jpg | Bin 0 -> 633 bytes tests/test_s3/test_s3.py | 34 ++++++++++++++++++++++++++++++++++ 4 files changed, 67 insertions(+), 5 deletions(-) create mode 100644 tests/test_s3/red.jpg diff --git a/moto/s3/exceptions.py b/moto/s3/exceptions.py index 7ea21b096190..3b33791c5a37 100644 --- a/moto/s3/exceptions.py +++ b/moto/s3/exceptions.py @@ -14,6 +14,11 @@ {% block extra %}{{ condition }}{% endblock %} """ +ERROR_WITH_RANGE = """{% extends 'single_error' %} +{% block extra %}{{ actual_size }} +{{ range_requested }}{% endblock %} +""" + class S3ClientError(RESTError): def __init__(self, *args, **kwargs): @@ -404,3 +409,18 @@ def __init__(self, failed_condition, **kwargs): condition=failed_condition, **kwargs ) + + +class InvalidRange(S3ClientError): + code = 416 + + def __init__(self, range_requested, actual_size, **kwargs): + kwargs.setdefault("template", "range_error") + self.templates["range_error"] = ERROR_WITH_RANGE + super(InvalidRange, self).__init__( + "InvalidRange", + "The requested range is not satisfiable", + range_requested=range_requested, + actual_size=actual_size, + **kwargs + ) diff --git a/moto/s3/responses.py b/moto/s3/responses.py index 4cb366195505..b01bed1fbd85 100644 --- a/moto/s3/responses.py +++ b/moto/s3/responses.py @@ -37,6 +37,7 @@ ObjectNotInActiveTierError, NoSystemTags, PreconditionFailed, + InvalidRange, ) from .models import ( s3_backend, @@ -936,11 +937,15 @@ def toint(i): else: return 400, response_headers, "" if begin < 0 or end > last or begin > min(end, last): - return 416, response_headers, "" + raise InvalidRange( + actual_size=str(length), range_requested=request.headers.get("range") + ) response_headers["content-range"] = "bytes {0}-{1}/{2}".format( begin, end, length ) - return 206, response_headers, response_content[begin : end + 1] + content = response_content[begin : end + 1] + response_headers["content-length"] = len(content) + return 206, response_headers, content def key_or_control_response(self, request, full_url, headers): # Key and Control are lumped in because splitting out the regex is too much of a pain :/ @@ -967,9 +972,12 @@ def key_or_control_response(self, request, full_url, headers): status_code, response_headers, response_content = response if status_code == 200 and "range" in request.headers: - return self._handle_range_header( - request, response_headers, response_content - ) + try: + return self._handle_range_header( + request, response_headers, response_content + ) + except S3ClientError as s3error: + return s3error.code, {}, s3error.description return status_code, response_headers, response_content def _control_response(self, request, full_url, headers): diff --git a/tests/test_s3/red.jpg b/tests/test_s3/red.jpg new file mode 100644 index 0000000000000000000000000000000000000000..6fb9aed7c88160a18eb8cb0b412f80a7a47ffced GIT binary patch literal 633 zcmex=^(PF6}rMnOeST|r4lSw=>~TvNxu(8R<c1}I=;VrF4wW9Q)H;sz?% zD!{d!pzFb!U9xX3zTPI5o8roG<0MW4oqZMDikqloVbuf*=gfJ(V&YTRE(2~ znmD<{#3dx9RMpfqG__1j&CD$#!=o3Ax_(anF8u!{0FnsMm;e9( literal 0 HcmV?d00001 diff --git a/tests/test_s3/test_s3.py b/tests/test_s3/test_s3.py index b213a9a72624..d8f08e9ef4c7 100644 --- a/tests/test_s3/test_s3.py +++ b/tests/test_s3/test_s3.py @@ -4889,3 +4889,37 @@ def test_presigned_put_url_with_custom_headers(): s3.delete_object(Bucket=bucket, Key=key) s3.delete_bucket(Bucket=bucket) + + +@mock_s3 +def test_request_partial_content_should_contain_content_length(): + bucket = "bucket" + object_key = "key" + s3 = boto3.resource("s3") + s3.create_bucket(Bucket=bucket) + s3.Object(bucket, object_key).put(Body="some text") + + file = s3.Object(bucket, object_key) + response = file.get(Range="bytes=0-1024") + response["ContentLength"].should.equal(9) + + +@mock_s3 +def test_request_partial_content_should_contain_actual_content_length(): + bucket = "bucket" + object_key = "key" + s3 = boto3.resource("s3") + s3.create_bucket(Bucket=bucket) + s3.Object(bucket, object_key).put(Body="some text") + + file = s3.Object(bucket, object_key) + requested_range = "bytes=1024-" + try: + file.get(Range=requested_range) + except botocore.client.ClientError as e: + e.response["Error"]["Code"].should.equal("InvalidRange") + e.response["Error"]["Message"].should.equal( + "The requested range is not satisfiable" + ) + e.response["Error"]["ActualObjectSize"].should.equal("9") + e.response["Error"]["RangeRequested"].should.equal(requested_range)