From 87eaaebd67e7989426d4be2c184bf5b3f61bb45c Mon Sep 17 00:00:00 2001 From: Clay Gerrard Date: Fri, 25 Aug 2017 14:14:21 -0700 Subject: [PATCH] Fix DirectClientExceptions with utf-8 encoded paths Because the direct_client module uses the buffered_http module it's requests are already robust to receiving either unicode or utf-8 paths. The DirectClientException message however encodes the given path with the device key from a ring node - which having come from a json de-serialized ring will be a unicode type. Despite the device key almost always being only ascii characters; python string interpolation with any unicode type will always force all binary strings to be converted to unicode - which will raise an error if any byte strings includes non-ascii characters. To maintain robustness in DirectClientException, when the provided path is not already unicode we decode the bytes as utf-8 before mixing them with the other unicode strings and then normalize everything back to a quoted utf-8 byte string. Change-Id: I162d2e093a3110856d6e1d513de3c7919079c9e7 --- swift/common/direct_client.py | 2 ++ test/unit/common/test_direct_client.py | 17 ++++++++++++++--- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/swift/common/direct_client.py b/swift/common/direct_client.py index 0043e66707..71b3d0799b 100644 --- a/swift/common/direct_client.py +++ b/swift/common/direct_client.py @@ -42,6 +42,8 @@ class DirectClientException(ClientException): # host can be used to override the node ip and port reported in # the exception host = host if host is not None else node + if not isinstance(path, six.text_type): + path = path.decode("utf-8") full_path = quote('/%s/%s%s' % (node['device'], part, path)) msg = '%s server %s:%s direct %s %r gave status %s' % ( stype, host['ip'], host['port'], method, full_path, resp.status) diff --git a/test/unit/common/test_direct_client.py b/test/unit/common/test_direct_client.py index cc39d73f92..7a1197a23a 100644 --- a/test/unit/common/test_direct_client.py +++ b/test/unit/common/test_direct_client.py @@ -99,8 +99,9 @@ def mocked_http_conn(*args, **kwargs): class TestDirectClient(unittest.TestCase): def setUp(self): - self.node = {'ip': '1.2.3.4', 'port': '6200', 'device': 'sda', - 'replication_ip': '1.2.3.5', 'replication_port': '7000'} + self.node = json.loads(json.dumps({ # json roundtrip to ring-like + 'ip': '1.2.3.4', 'port': '6200', 'device': 'sda', + 'replication_ip': '1.2.3.5', 'replication_port': '7000'})) self.part = '0' self.account = u'\u062a account' @@ -837,7 +838,8 @@ class TestDirectClient(unittest.TestCase): self.assertEqual(err_ctx.exception.http_status, 500) self.assertIn('DELETE', err_ctx.exception.message) self.assertIn(quote('/%s/%s/%s/%s/%s' - % (self.node['device'], self.part, self.account, + % (self.node['device'].encode('utf-8'), + self.part, self.account, self.container, self.obj)), err_ctx.exception.message) self.assertIn(self.node['ip'], err_ctx.exception.message) @@ -872,5 +874,14 @@ class TestDirectClient(unittest.TestCase): for line in error_lines: self.assertIn('Kaboom!', line) + +class TestUTF8DirectClient(TestDirectClient): + + def setUp(self): + super(TestUTF8DirectClient, self).setUp() + self.account = self.account.encode('utf-8') + self.container = self.container.encode('utf-8') + self.obj = self.obj.encode('utf-8') + if __name__ == '__main__': unittest.main()