From 4e960cc44c08fdc1289f8b4352a7828f305d8f47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Sanchez?= Date: Tue, 8 Oct 2013 23:47:04 +0200 Subject: [PATCH] Fix spaces encoding in parameters OAuth requires spaces to be encoded as %20 instead of + in order to generate a valid signature. --- requests_oauthlib/oauth1_session.py | 35 +++++++++++++++++++++++++++++ tests/test_oauth1_session.py | 9 ++++++++ 2 files changed, 44 insertions(+) diff --git a/requests_oauthlib/oauth1_session.py b/requests_oauthlib/oauth1_session.py index cadb09dc..5588838c 100644 --- a/requests_oauthlib/oauth1_session.py +++ b/requests_oauthlib/oauth1_session.py @@ -7,6 +7,8 @@ from oauthlib.common import add_params_to_uri, urldecode from oauthlib.oauth1 import SIGNATURE_HMAC, SIGNATURE_TYPE_AUTH_HEADER +from requests.utils import to_key_val_list +from urllib import urlencode import requests from . import OAuth1 @@ -264,3 +266,36 @@ def _fetch_token(self, url): token = dict(urldecode(self.post(url).text)) self._populate_attributes(token) return token + + def _urlencode_for_oauth(self, params): + """ + This method does exactly what requests.models.RequestEncodingMixin._encode_params() does, + with one subtle but important difference: instead of replacing spaces by +, they are + replaced by %20, which is needed for OAuth. + + See: http://troy.yort.com/2-common-problems-with-oauth-client-libraries/ + """ + if isinstance(params, (str, bytes)): + return params + elif hasattr(params, 'read'): + return params + elif hasattr(params, '__iter__'): + result = [] + for k, vs in to_key_val_list(params): + if isinstance(vs, basestring) or not hasattr(vs, '__iter__'): + vs = [vs] + for v in vs: + if v is not None: + result.append( + ( + k.encode('utf-8') if isinstance(k, str) else k, + v.encode('utf-8') if isinstance(v, str) else v, + ) + ) + return urlencode(result, doseq=True).replace('+', '%20') + else: + return params + + def request(self, method, url, params=None, *args, **kwargs): + params = self._urlencode_for_oauth(params) + return super(OAuth1Session, self).request(method, url, params, *args, **kwargs) diff --git a/tests/test_oauth1_session.py b/tests/test_oauth1_session.py index 83441c0c..c0a288bc 100644 --- a/tests/test_oauth1_session.py +++ b/tests/test_oauth1_session.py @@ -158,6 +158,15 @@ def test_fetch_access_token(self): self.assertTrue(isinstance(k, unicode_type)) self.assertTrue(isinstance(v, unicode_type)) + def test_params_with_spaces(self): + class RequestIntercepter(OAuth1Session): + def send(self, request, **kwargs): + self.sent_url = request.url + + auth = RequestIntercepter('foo') + auth.get('http://example.com/', params={'q': 'foo bar'}) + self.assertEqual(str('http://example.com/?q=foo%20bar'), auth.sent_url) + def verify_signature(self, signature): def fake_send(r, **kwargs): auth_header = r.headers['Authorization']