From fa07140420f24a677c29faa2182e6ef0e2f62e93 Mon Sep 17 00:00:00 2001 From: James Saryerwinnie Date: Wed, 23 Mar 2016 15:44:41 -0700 Subject: [PATCH] Always convert header values to str() When serializing input to rest-* protocols, we need to ensure that any value that's associated with a header value is converted to a string. Fixes #791. --- botocore/serialize.py | 2 +- tests/functional/test_s3.py | 18 +++++++++++++++++ tests/unit/test_serialize.py | 38 ++++++++++++++++++++++++++++++++++++ 3 files changed, 57 insertions(+), 1 deletion(-) diff --git a/botocore/serialize.py b/botocore/serialize.py index 4917264cde..641a73bff1 100644 --- a/botocore/serialize.py +++ b/botocore/serialize.py @@ -483,7 +483,7 @@ def _partition_parameters(self, partitioned, param_name, elif location == 'header': shape = shape_members[param_name] value = self._convert_header_value(shape, param_value) - partitioned['headers'][key_name] = value + partitioned['headers'][key_name] = str(value) elif location == 'headers': # 'headers' is a bit of an oddball. The ``key_name`` # is actually really a prefix for the header names: diff --git a/tests/functional/test_s3.py b/tests/functional/test_s3.py index ddabcc6a6b..f7ba036853 100644 --- a/tests/functional/test_s3.py +++ b/tests/functional/test_s3.py @@ -206,3 +206,21 @@ def test_provided_endpoint_url_for_path_addressing(self): request_sent = mock_send.call_args[0][0] self.assertEqual( 'https://foo.amazonaws.com/mybucket/mykey', request_sent.url) + + +class TestCanSendIntegerHeaders(BaseSessionTest): + + def test_int_values_with_sigv4(self): + s3 = self.session.create_client( + 's3', config=Config(signature_version='s3v4')) + with mock.patch('botocore.endpoint.Session.send') as mock_send: + mock_send.return_value = mock.Mock(status_code=200, + content=b'', + headers={}) + s3.upload_part(Bucket='foo', Key='bar', Body=b'foo', + UploadId='bar', PartNumber=1, ContentLength=3) + headers = mock_send.call_args[0][0].headers + # Verify that the request integer value of 3 has been converted to + # string '3'. This also means we've made it pass the signer which + # expects string values in order to sign properly. + self.assertEqual(headers['Content-Length'], '3') diff --git a/tests/unit/test_serialize.py b/tests/unit/test_serialize.py index 97cfb0b53d..a7399bf431 100644 --- a/tests/unit/test_serialize.py +++ b/tests/unit/test_serialize.py @@ -402,3 +402,41 @@ def test_instantiate_with_validation(self): with self.assertRaises(ParamValidationError): self.assert_serialize_invalid_parameter(request_serializer) + + +class TestHeaderSerialization(BaseModelWithBlob): + def setUp(self): + self.model = { + 'metadata': {'protocol': 'rest-xml', 'apiVersion': '2014-01-01'}, + 'documentation': '', + 'operations': { + 'TestOperation': { + 'name': 'TestOperation', + 'http': { + 'method': 'POST', + 'requestUri': '/', + }, + 'input': {'shape': 'InputShape'}, + } + }, + 'shapes': { + 'InputShape': { + 'type': 'structure', + 'members': { + 'ContentLength': { + 'shape': 'Integer', + 'location': 'header', + 'locationName': 'Content-Length' + }, + } + }, + 'Integer': { + 'type': 'integer' + }, + } + } + self.service_model = ServiceModel(self.model) + + def test_always_serialized_as_str(self): + request = self.serialize_to_request({'ContentLength': 100}) + self.assertEqual(request['headers']['Content-Length'], '100')