From b5c9dc1c9f68afe8083b74eaf8080e17a9ef8f8b Mon Sep 17 00:00:00 2001 From: Tim Burke Date: Tue, 19 Nov 2019 09:02:27 -0800 Subject: [PATCH] py3: Fix s3api header casing Closes-Bug: #1853367 Change-Id: Ifb15ce50fc3bcfda9532a2c3dec542c272ea4933 --- swift/common/header_key_dict.py | 37 ++++++++-------- swift/common/middleware/s3api/s3response.py | 44 +++++-------------- .../middleware/s3api/test_s3response.py | 18 ++++++++ 3 files changed, 49 insertions(+), 50 deletions(-) diff --git a/swift/common/header_key_dict.py b/swift/common/header_key_dict.py index aeaf84f7e8..6cabe1ef9e 100644 --- a/swift/common/header_key_dict.py +++ b/swift/common/header_key_dict.py @@ -16,13 +16,6 @@ import six -def _title(s): - if six.PY2: - return s.title() - else: - return s.encode('latin1').title().decode('latin1') - - class HeaderKeyDict(dict): """ A dict that title-cases all keys on the way in, so as to be @@ -36,35 +29,43 @@ class HeaderKeyDict(dict): self.update(base_headers) self.update(kwargs) + @staticmethod + def _title(s): + if six.PY2: + return s.title() + else: + return s.encode('latin1').title().decode('latin1') + def update(self, other): if hasattr(other, 'keys'): for key in other.keys(): - self[_title(key)] = other[key] + self[self._title(key)] = other[key] else: for key, value in other: - self[_title(key)] = value + self[self._title(key)] = value def __getitem__(self, key): - return dict.get(self, _title(key)) + return dict.get(self, self._title(key)) def __setitem__(self, key, value): + key = self._title(key) if value is None: - self.pop(_title(key), None) + self.pop(key, None) elif six.PY2 and isinstance(value, six.text_type): - return dict.__setitem__(self, _title(key), value.encode('utf-8')) + return dict.__setitem__(self, key, value.encode('utf-8')) elif six.PY3 and isinstance(value, six.binary_type): - return dict.__setitem__(self, _title(key), value.decode('latin-1')) + return dict.__setitem__(self, key, value.decode('latin-1')) else: - return dict.__setitem__(self, _title(key), str(value)) + return dict.__setitem__(self, key, str(value)) def __contains__(self, key): - return dict.__contains__(self, _title(key)) + return dict.__contains__(self, self._title(key)) def __delitem__(self, key): - return dict.__delitem__(self, _title(key)) + return dict.__delitem__(self, self._title(key)) def get(self, key, default=None): - return dict.get(self, _title(key), default) + return dict.get(self, self._title(key), default) def setdefault(self, key, value=None): if key not in self: @@ -72,4 +73,4 @@ class HeaderKeyDict(dict): return self[key] def pop(self, key, default=None): - return dict.pop(self, _title(key), default) + return dict.pop(self, self._title(key), default) diff --git a/swift/common/middleware/s3api/s3response.py b/swift/common/middleware/s3api/s3response.py index 07cfef1b04..365bb047c5 100644 --- a/swift/common/middleware/s3api/s3response.py +++ b/swift/common/middleware/s3api/s3response.py @@ -17,6 +17,7 @@ import re from collections import MutableMapping from functools import partial +from swift.common import header_key_dict from swift.common import swob from swift.common.utils import config_true_value from swift.common.request_helpers import is_sys_meta @@ -26,42 +27,21 @@ from swift.common.middleware.s3api.utils import snake_to_camel, \ from swift.common.middleware.s3api.etree import Element, SubElement, tostring -class HeaderKey(str): +class HeaderKeyDict(header_key_dict.HeaderKeyDict): """ - A string object that normalizes string as S3 clients expect with title(). + Similar to the Swift's normal HeaderKeyDict class, but its key name is + normalized as S3 clients expect. """ - def title(self): - if self.lower() == 'etag': + @staticmethod + def _title(s): + s = header_key_dict.HeaderKeyDict._title(s) + if s.lower() == 'etag': # AWS Java SDK expects only 'ETag'. return 'ETag' - if self.lower().startswith('x-amz-'): + if s.lower().startswith('x-amz-'): # AWS headers returned by S3 are lowercase. - return self.lower() - return str.title(self) - - -class HeaderKeyDict(swob.HeaderKeyDict): - """ - Similar to the HeaderKeyDict class in Swift, but its key name is normalized - as S3 clients expect. - """ - def __getitem__(self, key): - return swob.HeaderKeyDict.__getitem__(self, HeaderKey(key)) - - def __setitem__(self, key, value): - return swob.HeaderKeyDict.__setitem__(self, HeaderKey(key), value) - - def __contains__(self, key): - return swob.HeaderKeyDict.__contains__(self, HeaderKey(key)) - - def __delitem__(self, key): - return swob.HeaderKeyDict.__delitem__(self, HeaderKey(key)) - - def get(self, key, default=None): - return swob.HeaderKeyDict.get(self, HeaderKey(key), default) - - def pop(self, key, default=None): - return swob.HeaderKeyDict.pop(self, HeaderKey(key), default) + return swob.bytes_to_wsgi(swob.wsgi_to_bytes(s).lower()) + return s class S3ResponseBase(object): @@ -116,7 +96,7 @@ class S3Response(S3ResponseBase, swob.Response): # Handle swift headers for key, val in sw_headers.items(): - _key = key.lower() + _key = swob.bytes_to_wsgi(swob.wsgi_to_bytes(key).lower()) if _key.startswith('x-object-meta-'): # Note that AWS ignores user-defined headers with '=' in the diff --git a/test/unit/common/middleware/s3api/test_s3response.py b/test/unit/common/middleware/s3api/test_s3response.py index 4c6a854b18..8288a96acb 100644 --- a/test/unit/common/middleware/s3api/test_s3response.py +++ b/test/unit/common/middleware/s3api/test_s3response.py @@ -35,6 +35,24 @@ class TestResponse(unittest.TestCase): else: self.assertEqual('"theetag"', s3resp.headers['ETag']) + def test_response_s3api_user_meta_headers(self): + resp = Response(headers={ + 'X-Object-Meta-Foo': 'Bar', + 'X-Object-Meta-Non-\xdcnicode-Value': '\xff', + 'X-Object-Sysmeta-Baz': 'quux', + 'Etag': 'unquoted', + 'Content-type': 'text/plain', + 'content-length': '0', + }) + s3resp = S3Response.from_swift_resp(resp) + self.assertEqual(dict(s3resp.headers), { + 'x-amz-meta-foo': 'Bar', + 'x-amz-meta-non-\xdcnicode-value': '\xff', + 'ETag': '"unquoted"', + 'Content-Type': 'text/plain', + 'Content-Length': '0', + }) + def test_response_s3api_sysmeta_headers(self): for _server_type in ('object', 'container'): swift_headers = HeaderKeyDict(