8000 fix S3 parser with empty integer query string parameters (#9603) · codeperl/localstack@0750764 · GitHub
[go: up one dir, main page]

Skip to content

Commit 0750764

Browse files
authored
fix S3 parser with empty integer query string parameters (localstack#9603)
1 parent 4b8518e commit 0750764

File tree

3 files changed

+114
-3
lines changed

3 files changed

+114
-3
lines changed

localstack/aws/protocol/parser.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1076,6 +1076,14 @@ def _parse_shape(
10761076
uri_params["Key"] = uri_params["Key"] + "/"
10771077
return super()._parse_shape(request, shape, node, uri_params)
10781078

1079+
@_text_content
1080+
def _parse_integer(self, _, shape, node: str, ___) -> int | None:
1081+
# S3 accepts empty query string parameters that should be integer
1082+
# to not break other cases, validate that the shape is in the querystring
1083+
if node == "" and shape.serialization.get("location") == "querystring":
1084+
return None
1085+
return int(node)
1086+
10791087

10801088
class SQSQueryRequestParser(QueryRequestParser):
10811089
def _get_serialized_name(self, shape: Shape, default_name: str, node: dict) -> str:

tests/aws/services/s3/test_s3.py

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -837,7 +837,7 @@ def test_list_objects_next_marker(self, s3_bucket, snapshot, aws_client):
837837
snapshot.match("list-objects-marker-empty", resp)
838838

839839
@markers.aws.validated
840-
@pytest.mark.xfail(condition=is_v2_provider, reason="not implemented in moto")
840+
@pytest.mark.xfail(condition=is_v2_provider(), reason="not implemented in moto")
841841
def test_list_multiparts_next_marker(self, s3_bucket, snapshot, aws_client):
842842
snapshot.add_transformer(snapshot.transform.s3_api())
843843
snapshot.add_transformers_list(
@@ -943,7 +943,7 @@ def test_list_multiparts_next_marker(self, s3_bucket, snapshot, aws_client):
943943
snapshot.match("list-multiparts-next-key-empty", response)
944944

945945
@markers.aws.validated
946-
@pytest.mark.xfail(condition=is_v2_provider, reason="not implemented in moto")
946+
@pytest.mark.xfail(condition=is_v2_provider(), reason="not implemented in moto")
947947
def test_list_multiparts_with_prefix_and_delimiter(
948948
self, s3_bucket, snapshot, aws_client, aws_http_client_factory
949949
):
@@ -989,7 +989,7 @@ def test_list_multiparts_with_prefix_and_delimiter(
989989
resp_dict["ListMultipartUploadsResult"].pop("@xmlns", None)
990990
snapshot.match("list-multiparts-no-encoding", resp_dict)
991991

992-
@pytest.mark.xfail(condition=is_v2_provider, reason="not implemented in moto")
992+
@pytest.mark.xfail(condition=is_v2_provider(), reason="not implemented in moto")
993993
@markers.aws.validated
994994
def test_list_parts_pagination(self, s3_bucket, snapshot, aws_client):
995995
snapshot.add_transformer(
@@ -1045,6 +1045,44 @@ def test_list_parts_pagination(self, s3_bucket, snapshot, aws_client):
10451045
)
10461046
snapshot.match("list-parts-wrong-part", response)
10471047

1048+
@pytest.mark.xfail(
1049+
condition=is_v2_provider(), reason="moto does not handle empty query string parameters"
1050+
)
1051+
@markers.aws.validated
1052+
def test_list_parts_empty_part_number_marker(self, s3_bucket, snapshot, aws_client_factory):
1053+
# we need to disable validation for this test
1054+
s3_client = aws_client_factory(config=Config(parameter_validation=False)).s3
1055+
snapshot.add_transformer(
1056+
[
1057+
snapshot.transform.key_value("Bucket", reference_replacement=False),
1058+
snapshot.transform.key_value("Location"),
1059+
snapshot.transform.key_value("UploadId"),
1060+
snapshot.transform.key_value("DisplayName", reference_replacement=False),
1061+
snapshot.transform.key_value("ID", reference_replacement=False),
1062+
]
1063+
)
1064+
object_key = "test-list-part-empty-marker"
1065+
response = s3_client.create_multipart_upload(Bucket=s3_bucket, Key=object_key)
1066+
upload_id = response["UploadId"]
1067+
1068+
s3_client.upload_part(
1069+
Bucket=s3_bucket,
1070+
Key=object_key,
1071+
Body=BytesIO(b"data"),
1072+
PartNumber=1,
1073+
UploadId=upload_id,
1074+
)
1075+
# it seems S3 does not care about empty string for integer query string parameters
1076+
response = s3_client.list_parts(
1077+
Bucket=s3_bucket, UploadId=upload_id, Key=object_key, PartNumberMarker=""
1078+
)
1079+
snapshot.match("list-parts-empty-marker", response)
1080+
1081+
response = s3_client.list_parts(
1082+
Bucket=s3_bucket, UploadId=upload_id, Key=object_key, MaxParts=""
1083+
)
1084+
snapshot.match("list-parts-empty-max-parts", response)
1085+
10481086
@markers.aws.validated
10491087
def test_get_object_no_such_bucket(self, snapshot, aws_client):
10501088
snapshot.add_transformer(snapshot.transform.key_value("BucketName"))

tests/aws/services/s3/test_s3.snapshot.json

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12236,5 +12236,70 @@
1223612236
}
1223712237
}
1223812238
}
12239+
},
12240+
"tests/aws/services/s3/test_s3.py::TestS3::test_list_parts_empty_part_number_marker": {
12241+
"recorded-date": "11-11-2023, 00:20:09",
12242+
"recorded-content": {
12243+
"list-parts-empty-marker": {
12244+
"Bucket": "bucket",
12245+
"Initiator": {
12246+
"DisplayName": "display-name",
12247+
"ID": "i-d"
12248+
},
12249+
"IsTruncated": false,
12250+
"Key": "test-list-part-empty-marker",
12251+
"MaxParts": 1000,
12252+
"NextPartNumberMarker": 1,
12253+
"Owner": {
12254+
"DisplayName": "display-name",
12255+
"ID": "i-d"
12256+
},
12257+
"PartNumberMarker": 0,
12258+
"Parts": [
12259+
{
12260+
"ETag": "\"8d777f385d3dfec8815d20f7496026dc\"",
12261+
"LastModified": "datetime",
12262+
"PartNumber": 1,
12263+
"Size": 4
12264+
}
12265+
],
12266+
"StorageClass": "STANDARD",
12267+
"UploadId": "<upload-id:1>",
12268+
"ResponseMetadata": {
12269+
"HTTPHeaders": {},
12270+
"HTTPStatusCode": 200
12271+
}
12272+
},
12273+
"list-parts-empty-max-parts": {
12274+
"Bucket": "bucket",
12275+
"Initiator": {
12276+
"DisplayName": "display-name",
12277+
"ID": "i-d"
12278+
},
12279+
"IsTruncated": false,
12280+
"Key": "test-list-part-empty-marker",
12281+
"MaxParts": 1000,
12282+
"NextPartNumberMarker": 1,
12283+
"Owner": {
12284+
"DisplayName": "display-name",
12285+
"ID": "i-d"
12286+
},
12287+
"PartNumberMarker": 0,
12288+
"Parts": [
12289+
{
12290+
"ETag": "\"8d777f385d3dfec8815d20f7496026dc\"",
12291+
"LastModified": "datetime",
12292+
"PartNumber": 1,
12293+
"Size": 4
12294+
}
12295+
],
12296+
"StorageClass": "STANDARD",
12297+
"UploadId": "<upload-id:1>",
12298+
"ResponseMetadata": {
12299+
"HTTPHeaders": {},
12300+
"HTTPStatusCode": 200
12301+
}
12302+
}
12303+
}
1223912304
}
1224012305
}

0 commit comments

Comments
 (0)
0