diff --git a/.changes/next-release/bugfix-generatepresignedurl-96454.json b/.changes/next-release/bugfix-generatepresignedurl-96454.json new file mode 100644 index 0000000000..1f1bed810e --- /dev/null +++ b/.changes/next-release/bugfix-generatepresignedurl-96454.json @@ -0,0 +1,5 @@ +{ + "type": "bugfix", + "category": "``Polly``", + "description": "Remove `Content-Type` header from ``synthesize_speech`` URL presigning." +} diff --git a/botocore/handlers.py b/botocore/handlers.py index 3014123f9b..9546c61128 100644 --- a/botocore/handlers.py +++ b/botocore/handlers.py @@ -1137,6 +1137,14 @@ def customize_endpoint_resolver_builtins( builtins[EndpointResolverBuiltins.AWS_S3_USE_GLOBAL_ENDPOINT] = True +def remove_content_type_header_for_presigning(request, **kwargs): + if ( + request.context.get('is_presign_request') is True + and 'Content-Type' in request.headers + ): + del request.headers['Content-Type'] + + # This is a list of (event_name, handler). # When a Session is created, everything in this list will be # automatically registered with that Session. @@ -1240,6 +1248,10 @@ def customize_endpoint_resolver_builtins( ('before-parameter-build.route53', fix_route53_ids), ('before-parameter-build.glacier', inject_account_id), ('before-sign.s3', remove_arn_from_signing_path), + ( + 'before-sign.polly.SynthesizeSpeech', + remove_content_type_header_for_presigning, + ), ('after-call.s3.ListObjects', decode_list_object), ('after-call.s3.ListObjectsV2', decode_list_object_v2), ('after-call.s3.ListObjectVersions', decode_list_object_versions), diff --git a/tests/unit/test_signers.py b/tests/unit/test_signers.py index 5b05c7fea8..92a3d5da3f 100644 --- a/tests/unit/test_signers.py +++ b/tests/unit/test_signers.py @@ -13,6 +13,7 @@ import datetime import json +import pytest from dateutil.tz import tzutc import botocore @@ -39,6 +40,13 @@ from tests import assert_url_equal, mock, unittest +@pytest.fixture +def polly_client(): + session = botocore.session.get_session() + session.set_credentials('key', 'secret') + return session.create_client('polly', region_name='us-west-2') + + class BaseSignerTest(unittest.TestCase): def setUp(self): self.credentials = Credentials('key', 'secret') @@ -1168,3 +1176,23 @@ def test_custom_region(self): self.assertIn(region, result) # The hostname won't be changed even if a different region is specified self.assertIn(hostname, result) + + +@pytest.mark.parametrize( + 'request_method', + ['GET', 'HEAD', 'OPTIONS', 'POST', 'PUT', 'DELETE', None], +) +def test_generate_presigned_url_content_type_removal_for_polly( + polly_client, + request_method, +): + url = polly_client.generate_presigned_url( + 'synthesize_speech', + Params={ + 'OutputFormat': 'mp3', + 'Text': 'Hello world!', + 'VoiceId': 'Joanna', + }, + HttpMethod=request_method, + ) + assert 'content-type' not in url.lower()