diff --git a/swift/common/swob.py b/swift/common/swob.py index e0ce9ba9ba..0d272eea06 100644 --- a/swift/common/swob.py +++ b/swift/common/swob.py @@ -209,8 +209,12 @@ def header_to_environ_key(header_name): # Why the to/from wsgi dance? Headers that include something like b'\xff' # on the wire get translated to u'\u00ff' on py3, which gets upper()ed to # u'\u0178', which is nonsense in a WSGI string. - real_header = wsgi_to_str(header_name) - header_name = 'HTTP_' + str_to_wsgi(real_header.upper()).replace('-', '_') + # Note that we have to only get as far as bytes because something like + # b'\xc3\x9f' on the wire would be u'\u00df' as a native string on py3, + # which would upper() to 'SS'. + real_header = wsgi_to_bytes(header_name) + header_name = 'HTTP_' + bytes_to_wsgi( + real_header.upper()).replace('-', '_') if header_name == 'HTTP_CONTENT_LENGTH': return 'CONTENT_LENGTH' if header_name == 'HTTP_CONTENT_TYPE': @@ -257,8 +261,9 @@ class HeaderEnvironProxy(MutableMapping): def keys(self): # See the to/from WSGI comment in header_to_environ_key - keys = [str_to_wsgi(wsgi_to_str(key[5:]).replace('_', '-').title()) - for key in self.environ if key.startswith('HTTP_')] + keys = [ + bytes_to_wsgi(wsgi_to_bytes(key[5:]).replace(b'_', b'-').title()) + for key in self.environ if key.startswith('HTTP_')] if 'CONTENT_LENGTH' in self.environ: keys.append('Content-Length') if 'CONTENT_TYPE' in self.environ: diff --git a/test/unit/common/test_swob.py b/test/unit/common/test_swob.py index 9ebf95c3ce..4984ab344e 100644 --- a/test/unit/common/test_swob.py +++ b/test/unit/common/test_swob.py @@ -32,21 +32,26 @@ class TestHeaderEnvironProxy(unittest.TestCase): def test_proxy(self): environ = {} proxy = swift.common.swob.HeaderEnvironProxy(environ) + self.assertIs(environ, proxy.environ) proxy['Content-Length'] = 20 proxy['Content-Type'] = 'text/plain' proxy['Something-Else'] = 'somevalue' - self.assertEqual( - proxy.environ, {'CONTENT_LENGTH': '20', - 'CONTENT_TYPE': 'text/plain', - 'HTTP_SOMETHING_ELSE': 'somevalue'}) + # NB: WSGI strings + proxy['X-Object-Meta-Unicode-\xff-Bu\xc3\x9fe'] = '\xe2\x98\xb9' + self.assertEqual(proxy.environ, { + 'CONTENT_LENGTH': '20', + 'CONTENT_TYPE': 'text/plain', + 'HTTP_SOMETHING_ELSE': 'somevalue', + 'HTTP_X_OBJECT_META_UNICODE_\xff_BU\xc3\x9fE': '\xe2\x98\xb9'}) self.assertEqual(proxy['content-length'], '20') self.assertEqual(proxy['content-type'], 'text/plain') self.assertEqual(proxy['something-else'], 'somevalue') self.assertEqual(set(['Something-Else', + 'X-Object-Meta-Unicode-\xff-Bu\xc3\x9fE', 'Content-Length', 'Content-Type']), set(proxy.keys())) self.assertEqual(list(iter(proxy)), proxy.keys()) - self.assertEqual(3, len(proxy)) + self.assertEqual(4, len(proxy)) def test_ignored_keys(self): # Constructor doesn't normalize keys @@ -58,6 +63,8 @@ class TestHeaderEnvironProxy(unittest.TestCase): self.assertEqual(0, len(proxy)) self.assertRaises(KeyError, proxy.__getitem__, key) self.assertNotIn(key, proxy) + self.assertIn(key, proxy.environ) + self.assertIs(environ, proxy.environ) proxy['Content-Type'] = 'text/plain' self.assertEqual(['Content-Type'], list(iter(proxy))) @@ -77,6 +84,8 @@ class TestHeaderEnvironProxy(unittest.TestCase): del proxy['Something-Else'] self.assertEqual(proxy.environ, {}) self.assertEqual(0, len(proxy)) + with self.assertRaises(KeyError): + del proxy['Content-Length'] def test_contains(self): environ = {}