Skip to content

Commit

Permalink
S3 - Add more detail to error responses
Browse files Browse the repository at this point in the history
  • Loading branch information
bblommers committed Oct 28, 2020
1 parent a5fc14b commit cc27f1e
Show file tree
Hide file tree
Showing 4 changed files with 67 additions and 5 deletions.
20 changes: 20 additions & 0 deletions moto/s3/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@
{% block extra %}<Condition>{{ condition }}</Condition>{% endblock %}
"""

ERROR_WITH_RANGE = """{% extends 'single_error' %}
{% block extra %}<ActualObjectSize>{{ actual_size }}</ActualObjectSize>
<RangeRequested>{{ range_requested }}</RangeRequested>{% endblock %}
"""


class S3ClientError(RESTError):
def __init__(self, *args, **kwargs):
Expand Down Expand Up @@ -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
)
18 changes: 13 additions & 5 deletions moto/s3/responses.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
ObjectNotInActiveTierError,
NoSystemTags,
PreconditionFailed,
InvalidRange,
)
from .models import (
s3_backend,
Expand Down Expand Up @@ -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 :/
Expand All @@ -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):
Expand Down
Binary file added tests/test_s3/red.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
34 changes: 34 additions & 0 deletions tests/test_s3/test_s3.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

0 comments on commit cc27f1e

Please sign in to comment.