S3 - key_name url encoding for listing (#5181)

This commit is contained in:
Cristopher Pinzón 2022-06-01 06:03:40 -05:00 committed by GitHub
parent 18ec060edc
commit 34a98d2c20
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 35 additions and 2 deletions

View File

@ -14,6 +14,7 @@ import pytz
import sys import sys
import time import time
import uuid import uuid
import urllib.parse
from bisect import insort from bisect import insort
from importlib import reload from importlib import reload
@ -153,6 +154,10 @@ class FakeKey(BaseModel):
self.s3_backend = s3_backend self.s3_backend = s3_backend
@property
def safe_name(self):
return urllib.parse.quote(self.name, safe="")
@property @property
def version_id(self): def version_id(self):
return self._version_id return self._version_id

View File

@ -590,6 +590,7 @@ class ResponseObject(_TemplateEnvironmentMixin, ActionAuthenticatorMixin):
result_keys, result_folders = self.backend.list_objects( result_keys, result_folders = self.backend.list_objects(
bucket, prefix, delimiter bucket, prefix, delimiter
) )
encoding_type = querystring.get("encoding-type", [None])[0]
if marker: if marker:
result_keys = self._get_results_from_token(result_keys, marker) result_keys = self._get_results_from_token(result_keys, marker)
@ -611,6 +612,7 @@ class ResponseObject(_TemplateEnvironmentMixin, ActionAuthenticatorMixin):
is_truncated=is_truncated, is_truncated=is_truncated,
next_marker=next_marker, next_marker=next_marker,
max_keys=max_keys, max_keys=max_keys,
encoding_type=encoding_type,
), ),
) )
@ -642,6 +644,7 @@ class ResponseObject(_TemplateEnvironmentMixin, ActionAuthenticatorMixin):
fetch_owner = querystring.get("fetch-owner", [False])[0] fetch_owner = querystring.get("fetch-owner", [False])[0]
max_keys = int(querystring.get("max-keys", [1000])[0]) max_keys = int(querystring.get("max-keys", [1000])[0])
start_after = querystring.get("start-after", [None])[0] start_after = querystring.get("start-after", [None])[0]
encoding_type = querystring.get("encoding-type", [None])[0]
if continuation_token or start_after: if continuation_token or start_after:
limit = continuation_token or start_after limit = continuation_token or start_after
@ -666,6 +669,7 @@ class ResponseObject(_TemplateEnvironmentMixin, ActionAuthenticatorMixin):
is_truncated=is_truncated, is_truncated=is_truncated,
next_continuation_token=next_continuation_token, next_continuation_token=next_continuation_token,
start_after=None if continuation_token else start_after, start_after=None if continuation_token else start_after,
encoding_type=encoding_type,
) )
@staticmethod @staticmethod
@ -2051,13 +2055,16 @@ S3_BUCKET_GET_RESPONSE = """<?xml version="1.0" encoding="UTF-8"?>
{% if delimiter %} {% if delimiter %}
<Delimiter>{{ delimiter }}</Delimiter> <Delimiter>{{ delimiter }}</Delimiter>
{% endif %} {% endif %}
{% if encoding_type %}
<EncodingType>{{ encoding_type }}</EncodingType>
{% endif %}
<IsTruncated>{{ is_truncated }}</IsTruncated> <IsTruncated>{{ is_truncated }}</IsTruncated>
{% if next_marker %} {% if next_marker %}
<NextMarker>{{ next_marker }}</NextMarker> <NextMarker>{{ next_marker }}</NextMarker>
{% endif %} {% endif %}
{% for key in result_keys %} {% for key in result_keys %}
<Contents> <Contents>
<Key>{{ key.name }}</Key> <Key>{{ key.safe_name }}</Key>
<LastModified>{{ key.last_modified_ISO8601 }}</LastModified> <LastModified>{{ key.last_modified_ISO8601 }}</LastModified>
<ETag>{{ key.etag }}</ETag> <ETag>{{ key.etag }}</ETag>
<Size>{{ key.size }}</Size> <Size>{{ key.size }}</Size>
@ -2087,6 +2094,9 @@ S3_BUCKET_GET_RESPONSE_V2 = """<?xml version="1.0" encoding="UTF-8"?>
<KeyCount>{{ key_count }}</KeyCount> <KeyCount>{{ key_count }}</KeyCount>
{% if delimiter %} {% if delimiter %}
<Delimiter>{{ delimiter }}</Delimiter> <Delimiter>{{ delimiter }}</Delimiter>
{% endif %}
{% if encoding_type %}
<EncodingType>{{ encoding_type }}</EncodingType>
{% endif %} {% endif %}
<IsTruncated>{{ is_truncated }}</IsTruncated> <IsTruncated>{{ is_truncated }}</IsTruncated>
{% if next_continuation_token %} {% if next_continuation_token %}
@ -2097,7 +2107,7 @@ S3_BUCKET_GET_RESPONSE_V2 = """<?xml version="1.0" encoding="UTF-8"?>
{% endif %} {% endif %}
{% for key in result_keys %} {% for key in result_keys %}
<Contents> <Contents>
<Key>{{ key.name }}</Key> <Key>{{ key.safe_name }}</Key>
<LastModified>{{ key.last_modified_ISO8601 }}</LastModified> <LastModified>{{ key.last_modified_ISO8601 }}</LastModified>
<ETag>{{ key.etag }}</ETag> <ETag>{{ key.etag }}</ETag>
<Size>{{ key.size }}</Size> <Size>{{ key.size }}</Size>

View File

@ -134,6 +134,24 @@ def test_empty_key():
resp["Body"].read().should.equal(b"") resp["Body"].read().should.equal(b"")
@mock_s3
def test_key_name_encoding_in_listing():
s3 = boto3.resource("s3", region_name=DEFAULT_REGION_NAME)
client = boto3.client("s3", region_name=DEFAULT_REGION_NAME)
s3.create_bucket(Bucket="foobar")
name = "6T7\x159\x12\r\x08.txt"
key = s3.Object("foobar", name)
key.put(Body=b"")
key_received = client.list_objects(Bucket="foobar")["Contents"][0]["Key"]
key_received.should.equal(name)
key_received = client.list_objects_v2(Bucket="foobar")["Contents"][0]["Key"]
key_received.should.equal(name)
@mock_s3 @mock_s3
def test_empty_key_set_on_existing_key(): def test_empty_key_set_on_existing_key():
s3 = boto3.resource("s3", region_name=DEFAULT_REGION_NAME) s3 = boto3.resource("s3", region_name=DEFAULT_REGION_NAME)