Browse Source

Merge "py3: Fix s3api header casing"

changes/38/682138/33
Zuul 2 weeks ago
parent
commit
d215fef8fa
3 changed files with 49 additions and 50 deletions
  1. +19
    -18
      swift/common/header_key_dict.py
  2. +12
    -32
      swift/common/middleware/s3api/s3response.py
  3. +18
    -0
      test/unit/common/middleware/s3api/test_s3response.py

+ 19
- 18
swift/common/header_key_dict.py View File

@@ -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)

+ 12
- 32
swift/common/middleware/s3api/s3response.py View File

@@ -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

+ 18
- 0
test/unit/common/middleware/s3api/test_s3response.py View File

@@ -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(

Loading…
Cancel
Save