S3: fix Range request handling (#6778)
This commit is contained in:
parent
4ea51d8795
commit
3e928bcad6
@ -1169,25 +1169,35 @@ class S3Response(BaseResponse):
|
|||||||
) -> TYPE_RESPONSE:
|
) -> TYPE_RESPONSE:
|
||||||
length = len(response_content)
|
length = len(response_content)
|
||||||
last = length - 1
|
last = length - 1
|
||||||
|
|
||||||
_, rspec = request.headers.get("range").split("=")
|
_, rspec = request.headers.get("range").split("=")
|
||||||
if "," in rspec:
|
if "," in rspec:
|
||||||
raise NotImplementedError("Multiple range specifiers not supported")
|
return 200, response_headers, response_content
|
||||||
|
|
||||||
def toint(i: Any) -> Optional[int]:
|
try:
|
||||||
return int(i) if i else None
|
begin, end = [int(i) if i else None for i in rspec.split("-")]
|
||||||
|
except ValueError:
|
||||||
|
# if we can't parse the Range header, S3 just treat the request as a non-range request
|
||||||
|
return 200, response_headers, response_content
|
||||||
|
|
||||||
|
if (begin is None and end == 0) or (begin is not None and begin > last):
|
||||||
|
raise InvalidRange(
|
||||||
|
actual_size=str(length), range_requested=request.headers.get("range")
|
||||||
|
)
|
||||||
|
|
||||||
begin, end = map(toint, rspec.split("-"))
|
|
||||||
if begin is not None: # byte range
|
if begin is not None: # byte range
|
||||||
end = last if end is None else min(end, last)
|
end = last if end is None else min(end, last)
|
||||||
elif end is not None: # suffix byte range
|
elif end is not None: # suffix byte range
|
||||||
begin = length - min(end, length)
|
begin = length - min(end, length)
|
||||||
end = last
|
end = last
|
||||||
else:
|
else:
|
||||||
return 400, response_headers, ""
|
# Treat as non-range request
|
||||||
if begin < 0 or end > last or begin > min(end, last):
|
return 200, response_headers, response_content
|
||||||
raise InvalidRange(
|
|
||||||
actual_size=str(length), range_requested=request.headers.get("range")
|
if begin > min(end, last):
|
||||||
)
|
# Treat as non-range request if after the logic is applied, the start of the range is greater than the end
|
||||||
|
return 200, response_headers, response_content
|
||||||
|
|
||||||
response_headers["content-range"] = f"bytes {begin}-{end}/{length}"
|
response_headers["content-range"] = f"bytes {begin}-{end}/{length}"
|
||||||
content = response_content[begin : end + 1]
|
content = response_content[begin : end + 1]
|
||||||
response_headers["content-length"] = len(content)
|
response_headers["content-length"] = len(content)
|
||||||
|
@ -1004,6 +1004,51 @@ def test_ranged_get():
|
|||||||
|
|
||||||
assert key.content_length == 100
|
assert key.content_length == 100
|
||||||
|
|
||||||
|
assert key.get(Range="bytes=0-0")["Body"].read() == b"0"
|
||||||
|
assert key.get(Range="bytes=1-1")["Body"].read() == b"1"
|
||||||
|
|
||||||
|
range_req = key.get(Range="bytes=1-0")
|
||||||
|
assert range_req["Body"].read() == rep * 10
|
||||||
|
# assert that the request was not treated as a range request
|
||||||
|
assert range_req["ResponseMetadata"]["HTTPStatusCode"] == 200
|
||||||
|
assert "ContentRange" not in range_req
|
||||||
|
|
||||||
|
range_req = key.get(Range="bytes=-1-")
|
||||||
|
assert range_req["Body"].read() == rep * 10
|
||||||
|
assert range_req["ResponseMetadata"]["HTTPStatusCode"] == 200
|
||||||
|
|
||||||
|
range_req = key.get(Range="bytes=0--1")
|
||||||
|
assert range_req["Body"].read() == rep * 10
|
||||||
|
assert range_req["ResponseMetadata"]["HTTPStatusCode"] == 200
|
||||||
|
|
||||||
|
range_req = key.get(Range="bytes=0-1,3-4,7-9")
|
||||||
|
assert range_req["Body"].read() == rep * 10
|
||||||
|
assert range_req["ResponseMetadata"]["HTTPStatusCode"] == 200
|
||||||
|
|
||||||
|
range_req = key.get(Range="bytes=-")
|
||||||
|
assert range_req["Body"].read() == rep * 10
|
||||||
|
assert range_req["ResponseMetadata"]["HTTPStatusCode"] == 200
|
||||||
|
|
||||||
|
with pytest.raises(ClientError) as ex:
|
||||||
|
key.get(Range="bytes=-0")
|
||||||
|
assert ex.value.response["Error"]["Code"] == "InvalidRange"
|
||||||
|
assert (
|
||||||
|
ex.value.response["Error"]["Message"]
|
||||||
|
== "The requested range is not satisfiable"
|
||||||
|
)
|
||||||
|
assert ex.value.response["Error"]["ActualObjectSize"] == "100"
|
||||||
|
assert ex.value.response["Error"]["RangeRequested"] == "bytes=-0"
|
||||||
|
|
||||||
|
with pytest.raises(ClientError) as ex:
|
||||||
|
key.get(Range="bytes=101-200")
|
||||||
|
assert ex.value.response["Error"]["Code"] == "InvalidRange"
|
||||||
|
assert (
|
||||||
|
ex.value.response["Error"]["Message"]
|
||||||
|
== "The requested range is not satisfiable"
|
||||||
|
)
|
||||||
|
assert ex.value.response["Error"]["ActualObjectSize"] == "100"
|
||||||
|
assert ex.value.response["Error"]["RangeRequested"] == "bytes=101-200"
|
||||||
|
|
||||||
|
|
||||||
@mock_s3
|
@mock_s3
|
||||||
def test_policy():
|
def test_policy():
|
||||||
|
Loading…
Reference in New Issue
Block a user