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
This commit is contained in:
Clay Gerrard 2017-08-25 14:14:21 -07:00
parent 87340e5f29
commit 87eaaebd67
2 changed files with 16 additions and 3 deletions

View File

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

View File

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