From 35de6ebe93b94076964f4250bf3fa9b8ff1f8463 Mon Sep 17 00:00:00 2001 From: Brian Rosmaita Date: Mon, 14 May 2018 16:19:46 -0400 Subject: [PATCH] Fix logging of encoded headers A change introduced in 3.5.0 sorts headers, but runs into a problem when the headers are bytes, such as the headers provided by the python-glanceclient. requests expects headers to be str type in both python2 and python3. This means in python2 we need to encode unicode objects as ASCII (the encoding that should be used for HTTP headers) and in python3 we need to decode bytes as ASCII into str. Change-Id: Ib81497c3a873616c22ba68256c596a6fb113e11e Closes-bug: #1766235 --- keystoneauth1/session.py | 24 +++++++++++++++++++ keystoneauth1/tests/unit/test_session.py | 9 ++++++- .../notes/bug-1766235wq-0de60d0f996c6bfb.yaml | 6 +++++ 3 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 releasenotes/notes/bug-1766235wq-0de60d0f996c6bfb.yaml diff --git a/keystoneauth1/session.py b/keystoneauth1/session.py index 6ab1e144..d9e99d32 100644 --- a/keystoneauth1/session.py +++ b/keystoneauth1/session.py @@ -79,6 +79,26 @@ def _mv_legacy_headers_for_service(mv_service_type): return headers +def _sanitize_headers(headers): + """Ensure headers are strings and not bytes.""" + str_dict = {} + for k, v in headers.items(): + if six.PY3: + # requests expects headers to be str type in python3, which means + # if we get a bytes we need to decode it into a str + k = k.decode('ASCII') if isinstance(k, six.binary_type) else k + if v is not None: + v = v.decode('ASCII') if isinstance(v, six.binary_type) else v + else: + # requests expects headers to be str type in python2, which means + # if we get a unicode we need to encode it to ASCII into a str + k = k.encode('ASCII') if isinstance(k, six.text_type) else k + if v is not None: + v = v.encode('ASCII') if isinstance(v, six.text_type) else v + str_dict[k] = v + return str_dict + + class _JSONEncoder(json.JSONEncoder): def default(self, o): @@ -711,6 +731,10 @@ class Session(object): for k, v in self.additional_headers.items(): headers.setdefault(k, v) + # Bug #1766235: some headers may be bytes + headers = _sanitize_headers(headers) + kwargs['headers'] = headers + kwargs.setdefault('verify', self.verify) if requests_auth: diff --git a/keystoneauth1/tests/unit/test_session.py b/keystoneauth1/tests/unit/test_session.py index 94cc7d15..a7165e17 100644 --- a/keystoneauth1/tests/unit/test_session.py +++ b/keystoneauth1/tests/unit/test_session.py @@ -17,6 +17,7 @@ import sys import uuid import mock +from oslo_utils import encodeutils import requests import requests.auth import six @@ -987,7 +988,12 @@ class SessionAuthTests(utils.TestCase): 'X-OpenStack-Request-ID': request_id, }) - resp = sess.get(self.TEST_URL) + resp = sess.get( + self.TEST_URL, + headers={ + encodeutils.safe_encode('x-bytes-header'): + encodeutils.safe_encode('bytes-value') + }) self.assertEqual(response, resp.json()) @@ -998,6 +1004,7 @@ class SessionAuthTests(utils.TestCase): self.assertIn('curl -g -i -X GET {url}'.format(url=self.TEST_URL), request_output) + self.assertIn('-H "x-bytes-header: bytes-value"', request_output) self.assertEqual('[200] Content-Type: application/json ' 'X-OpenStack-Request-ID: ' '{id}'.format(id=request_id), response_output) diff --git a/releasenotes/notes/bug-1766235wq-0de60d0f996c6bfb.yaml b/releasenotes/notes/bug-1766235wq-0de60d0f996c6bfb.yaml new file mode 100644 index 00000000..1c45aedc --- /dev/null +++ b/releasenotes/notes/bug-1766235wq-0de60d0f996c6bfb.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + [`bug 1766235 `_] + Fixed an issue where passing headers in as bytes rather than strings + would cause a sorting issue.