Quellcode durchsuchen

Merge "py3: Fix s3api header casing"

Zuul vor 2 Monaten
3 geänderte Dateien mit 49 neuen und 50 gelöschten Zeilen
  1. +19
  2. +12
  3. +18

+ 19
- 18
swift/common/header_key_dict.py Datei anzeigen

@@ -16,13 +16,6 @@
import six

def _title(s):
if six.PY2:
return s.title()
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):

def _title(s):
if six.PY2:
return s.title()
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]
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'))
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 Datei anzeigen

@@ -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':
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 Datei anzeigen

@@ -35,6 +35,24 @@ class TestResponse(unittest.TestCase):
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(