Skip to content
This repository has been archived by the owner on Nov 5, 2019. It is now read-only.

Switched headers to be normalized as strings, not bytes, in keeping with httplib2. #136

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 26 additions & 3 deletions oauth2client/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -397,9 +397,32 @@ def delete(self):
finally:
self.release_lock()

# Since strings ARE unicode in Python3, "cleaning" the string doesn't mean
# just converting to str.
#
# As per the httplib2 documentation:
#
# ** THE RESPONSE HEADERS ARE STRINGS, BUT THE CONTENT BODY IS BYTES **
#

This comment was marked as spam.

def _clean_header(s):
"""Always returns something of type str. Raise UnicodeEncodeError if

This comment was marked as spam.

unconvertable to ascii."""

if not isinstance(s, str):
# str(b'foo') will return "b'foo'" in Py3, not what we want.
if isinstance(s, bytes):
# Binary string in Py3
s = s.decode('utf-8')
else:
s = str(s)

# We're trying to generate the UnicodeEncodeError here if the string is
# unconvertable, AND keep this a string in both Py2 and Py3.
s = str((s.encode('ascii')).decode('utf-8'))
return s

def clean_headers(headers):
"""Forces header keys and values to be strings, i.e not unicode.
"""Forces header keys and values to be strings suitable for httplib2.

The httplib module just concats the header keys and values in a way that may
make the message header a unicode string, which, if it then tries to
Expand All @@ -414,8 +437,8 @@ def clean_headers(headers):
clean = {}
try:
for k, v in six.iteritems(headers):
clean_k = k if isinstance(k, bytes) else str(k).encode('ascii')
clean_v = v if isinstance(v, bytes) else str(v).encode('ascii')
clean_k = _clean_header(k)
clean_v = _clean_header(v)
clean[clean_k] = clean_v
except UnicodeEncodeError:
raise NonAsciiHeaderError(k + ': ' + v)
Expand Down
6 changes: 5 additions & 1 deletion tests/test_appengine.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,11 @@
import time
import unittest
import urllib
import urlparse

try:
import urlparse
except ImportError:
from urllib.parse import urlparse

This comment was marked as spam.


import dev_appserver
dev_appserver.fix_sys_path()
Expand Down
6 changes: 3 additions & 3 deletions tests/test_jwt.py
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ def test_credentials_good(self):
])
http = credentials.authorize(http)
resp, content = http.request('http://example.org')
self.assertEqual(b'Bearer 1/3w', content[b'Authorization'])
self.assertEqual('Bearer 1/3w', content['Authorization'])

def test_credentials_to_from_json(self):
private_key = datafile('privatekey.%s' % self.format)
Expand Down Expand Up @@ -257,7 +257,7 @@ def test_credentials_refresh_without_storage(self):

content = self._credentials_refresh(credentials)

self.assertEqual(b'Bearer 3/3w', content[b'Authorization'])
self.assertEqual('Bearer 3/3w', content['Authorization'])

def test_credentials_refresh_with_storage(self):
private_key = datafile('privatekey.%s' % self.format)
Expand All @@ -275,7 +275,7 @@ def test_credentials_refresh_with_storage(self):

content = self._credentials_refresh(credentials)

self.assertEqual(b'Bearer 3/3w', content[b'Authorization'])
self.assertEqual('Bearer 3/3w', content['Authorization'])
os.unlink(filename)


Expand Down
26 changes: 17 additions & 9 deletions tests/test_oauth2client.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
Unit tests for oauth2client.
"""

# pylint: disable=bad-indentation
__author__ = '[email protected] (Joe Gregorio)'

import base64
Expand Down Expand Up @@ -531,7 +532,7 @@ def test_token_refresh_success(self):
])
http = self.credentials.authorize(http)
resp, content = http.request('http://example.com')
self.assertEqual(b'Bearer 1/3w', content[b'Authorization'])
self.assertEqual('Bearer 1/3w', content['Authorization'])
self.assertFalse(self.credentials.access_token_expired)
self.assertEqual(token_response, self.credentials.token_response)

Expand Down Expand Up @@ -599,17 +600,17 @@ def test_unicode_header_checks(self):

# First, test that we correctly encode basic objects, making sure
# to include a bytes object. Note that oauth2client will normalize
# everything to bytes, no matter what python version we're in.
# everything to strings, no matter what python version we're in.
http = credentials.authorize(HttpMock(headers={'status': '200'}))
headers = {u'foo': 3, b'bar': True, 'baz': b'abc'}
cleaned_headers = {b'foo': b'3', b'bar': b'True', b'baz': b'abc'}
cleaned_headers = {'foo': '3', 'bar': 'True', 'baz': 'abc'}
http.request(u'http://example.com', method=u'GET', headers=headers)
for k, v in cleaned_headers.items():
self.assertTrue(k in http.headers)
self.assertEqual(v, http.headers[k])

# Next, test that we do fail on unicode.
unicode_str = six.unichr(40960) + 'abcd'
unicode_str = u'\u2602' + 'abcd'
self.assertRaises(
NonAsciiHeaderError,
http.request,
Expand All @@ -631,14 +632,21 @@ def test_no_unicode_in_request_params(self):
http = HttpMock(headers={'status': '200'})
http = credentials.authorize(http)
http.request(u'http://example.com', method=u'GET', headers={u'foo': u'bar'})

# oauth2client uses httplib2 and httplib2 says:
# "** THE RESPONSE HEADERS ARE STRINGS, BUT THE CONTENT BODY IS BYTES **"
# and from https://github.com/jcgregorio/httplib2/wiki/Examples-Python3
# "In httplib2, the response headers are strings..."
#
# So, the headers should be of type str.
for k, v in six.iteritems(http.headers):
self.assertEqual(six.binary_type, type(k))
self.assertEqual(six.binary_type, type(v))
self.assertTrue(isinstance(k, str))
self.assertTrue(isinstance(v, str))

# Test again with unicode strings that can't simply be converted to ASCII.
try:
http.request(
u'http://example.com', method=u'GET', headers={u'foo': u'\N{COMET}'})
u'http://example.com', method=u'GET', headers={u'foo': u'\u2602'})
self.fail('Expected exception to be raised.')
except NonAsciiHeaderError:
pass
Expand Down Expand Up @@ -724,7 +732,7 @@ def test_auth_header_sent(self):
])
http = self.credentials.authorize(http)
resp, content = http.request('http://example.com')
self.assertEqual(b'Bearer foo', content[b'Authorization'])
self.assertEqual('Bearer foo', content['Authorization'])


class TestAssertionCredentials(unittest.TestCase):
Expand Down Expand Up @@ -755,7 +763,7 @@ def test_assertion_refresh(self):
])
http = self.credentials.authorize(http)
resp, content = http.request('http://example.com')
self.assertEqual(b'Bearer 1/3w', content[b'Authorization'])
self.assertEqual('Bearer 1/3w', content['Authorization'])

def test_token_revoke_success(self):
_token_revoke_test_helper(
Expand Down