def test_single_value_lists_are_not_flattened(self): d = parse_qs("a=1&a=2&a=3&b=c") for n, v in d.items(): self.assertTrue(is_bytes(n), "Dictionary key is not bytes.") self.assertTrue(isinstance(v, list), "Dictionary value is not a list.")
def _parse_credentials_response(self, response): """ Parses the entity-body of the OAuth server response to an OAuth credential request. :param response: An instance of :class:`pyoauth.http.ResponseProxy`. :returns: A tuple of the form:: (parameter dictionary, pyoauth.oauth1.Credentials instance) """ if not response.status_code: raise InvalidHttpResponseError("Invalid status code: `%r`" % (response.status_code, )) if not response.status: raise InvalidHttpResponseError("Invalid status message: `%r`" % (response.status, )) if not response.payload: raise InvalidHttpResponseError("Body is invalid or empty: `%r`" % (response.payload, )) if not response.headers: raise InvalidHttpResponseError("Headers are invalid or not specified: `%r`" % (response.headers, )) if response.error: raise HttpError("Could not fetch credentials: HTTP %d - %s" % (response.status_code, response.status,)) # The response body must be URL encoded. if not response.is_body_form_urlencoded(): raise InvalidContentTypeError("OAuth credentials server response must have Content-Type: `%s`" % (CONTENT_TYPE_FORM_URLENCODED, )) params = parse_qs(response.payload) return params, Credentials(identifier=params["oauth_token"][0], shared_secret=params["oauth_token_secret"][0])
def test_ignores_prefixed_question_mark_character_if_included(self): qs = '?b5=%3D%253D&a3=a&c%40=&a2=r%20b&c2&a3=2+q' q = parse_qs(qs) self.assertDictEqual(q, {b('a2'): [b('r b')], b('a3'): [b('a'), b('2 q')], b('b5'): [b('=%3D')], b('c@'): [b('')], b('c2'): [b('')]})
def test_names_and_values_are_percent_decoded(self): qs = 'b5=%3D%253D&a3=a&c%40=&a2=r%20b&c2&a3=2+q' q = parse_qs(qs) self.assertDictEqual(q, {b('a2'): [b('r b')], b('a3'): [b('a'), b('2 q')], b('b5'): [b('=%3D')], b('c@'): [b('')], b('c2'): [b('')]})
def _parse_credentials_response(cls, response, strict=True): """ Parses the entity-body of the OAuth server response to an OAuth credential request. :param response: An instance of :class:`pyoauth.http.ResponseAdapter`. :param strict: ``True`` (default) for string response parsing; ``False`` to be a bit lenient. Some non-compliant OAuth servers return credentials without setting the content-type. Setting this to ``False`` will not raise an error, but will still warn you that the response content-type is not valid. :returns: A tuple of the form:: (pyoauth.oauth1.Credentials instance, other parameters) """ if not response.status: raise InvalidHttpResponseError( "Invalid status code: `%r`" % response.status) if not response.reason: raise InvalidHttpResponseError( "Invalid status message: `%r`" % response.reason) if not response.body: raise InvalidHttpResponseError( "Body is invalid or empty: `%r`" % response.body) if not response.headers: raise InvalidHttpResponseError( "Headers are invalid or not specified: `%r`" % \ response.headers) if response.error: raise HttpError("Could not fetch credentials: HTTP %d - %s" \ % (response.status, response.reason,)) # The response body must be form URL-encoded. if not response.is_body_form_urlencoded(): if strict: raise InvalidContentTypeError( "OAuth credentials server response must " \ "have Content-Type: `%s`; got %r" % (CONTENT_TYPE_FORM_URLENCODED, response.content_type)) else: logging.warning( "Response parsing strict-mode disabled -- " \ "OAuth server credentials response specifies invalid " \ "Content-Type: expected %r; got %r", CONTENT_TYPE_FORM_URLENCODED, response.content_type) params = parse_qs(response.body) # Ensure the keys to this dictionary are unicode strings in Python 3.x. params = map_dict(lambda k, v: (utf8_decode_if_bytes(k), v), params) credentials = Credentials(identifier=params[OAUTH_PARAM_TOKEN][0], shared_secret=params[OAUTH_PARAM_TOKEN_SECRET][0]) return credentials, params
def _url_equals(url1, url2): """ Compares two URLs and determines whether they are the equal. :param url1: First URL. :param url2: Second URL. :returns: ``True`` if equal; ``False`` otherwise. Usage:: >>> _url_equals("http://www.google.com/a", "http://www.google.com/a") True >>> _url_equals("https://www.google.com/a", "http://www.google.com/a") False >>> _url_equals("http://www.google.com/", "http://www.example.com/") False >>> _url_equals("http://example.com:80/", "http://example.com:8000/") False >>> _url_equals("http://[email protected]/", "http://[email protected]/") False >>> _url_equals("http://[email protected]/request?a=b&b=c&b=d#fragment", "http://[email protected]/request?b=c&b=d&a=b#fragment") True >>> _url_equals("http://[email protected]/request?a=b&b=c&b=d#fragment", "http://[email protected]/request?b=c&b=d&a=b#fragment2") False >>> _url_equals("http://www.google.com/request?a=b", "http://www.google.com/request?b=c") False """ u1 = urlparse(url1) u2 = urlparse(url2) return u1.scheme == u2.scheme and \ u1.path == u2.path and \ u1.params == u2.params and \ u1.netloc == u2.netloc and \ u1.fragment == u2.fragment and \ parse_qs(u1.query, keep_blank_values=True) == \ parse_qs(u2.query, keep_blank_values=True)
def test_percent_decoding_treats_plus_as_space(self): self.assertDictEqual(parse_qs('a=2+q'), {b('a'): [b('2 q')]})
def test_are_multiple_values_obtained(self): self.assertDictEqual(parse_qs("a=1&a=2&a=3&b=c"), {b("a"): [b("1"), b("2"), b("3")], b("b"): [b("c")]})
def test_are_blank_values_preserved(self): self.assertDictEqual(parse_qs("a="), {b("a"): [b("")]}) self.assertDictEqual(parse_qs("a"), {b("a"): [b("")]})
def _generate_signature(cls, method, url, params, body, headers, oauth_consumer_secret, oauth_token_secret, oauth_params): """ Given the base string parameters, secrets, and protocol parameters, calculates a signature for the request. :param method: HTTP method. :param url: Request URL. :param params: Additional query/payload parameters. :param body: Payload if any. :param headers: HTTP headers as a dictionary. :param oauth_consumer_secret: OAuth client shared secret (consumer secret). :param oauth_token_secret: OAuth token/temporary shared secret if obtained from the OAuth server. :param oauth_params: OAuth parameters generated by :func:`OAuthClient._generate_oauth_params`. :returns: Request signature. """ # Take parameters from the body if the Content-Type is specified # as ``application/x-www-form-urlencoded``. # http://tools.ietf.org/html/rfc5849#section-3.4.1.3.1 if body: try: try: content_type = headers[HEADER_CONTENT_TYPE] except KeyError: content_type = headers[HEADER_CONTENT_TYPE_CAPS] if content_type == CONTENT_TYPE_FORM_URLENCODED: # These parameters must also be included in the signature. # Ignore OAuth-specific parameters. They must be specified # separately. body_params = query_remove_oauth(parse_qs(body)) params = query_add(params, body_params) else: logging.info( "Entity-body specified but `content-type` header " \ "value is not %r: entity-body parameters if " \ "present will not be signed: got body %r" % \ (CONTENT_TYPE_FORM_URLENCODED, body) ) except KeyError: logging.warning( "Entity-body specified but `content-type` is missing " ) # Make oauth params and sign the request. signature_url = url_add_query(url, query_remove_oauth(params)) # NOTE: We're not explicitly cleaning up because this method # expects oauth params generated by _generate_oauth_params. base_string = generate_base_string(method, signature_url, oauth_params) signature_method = oauth_params[OAUTH_PARAM_SIGNATURE_METHOD] cls.check_signature_method(signature_method) try: sign_func = SIGNATURE_METHOD_MAP[signature_method] return sign_func(base_string, oauth_consumer_secret, oauth_token_secret) except KeyError: raise InvalidSignatureMethodError( "unsupported signature method: %r" % signature_method )
def test_percent_decoding_treats_plus_as_space(self): assert_dict_equal(parse_qs('a=2+q'), {'a': ['2 q']})
def test_names_and_values_are_percent_decoded(self): qs = 'b5=%3D%253D&a3=a&c%40=&a2=r%20b' + '&' + 'c2&a3=2+q' q = parse_qs(qs) assert_dict_equal(q, {'a2': ['r b'], 'a3': ['a', '2 q'], 'b5': ['=%3D'], 'c@': [''], 'c2': ['']})
def test_single_value_lists_are_not_flattened(self): d = parse_qs("a=1&a=2&a=3&b=c") for n, v in d.items(): assert_true(isinstance(n, str), "Dictionary key is not a string.") assert_true(isinstance(v, list), "Dictionary value is not a list.")
def test_are_multiple_values_obtained(self): assert_dict_equal(parse_qs("a=1&a=2&a=3&b=c"), {"a": ["1", "2", "3"], "b": ["c"]})
def test_are_blank_values_preserved(self): assert_dict_equal(parse_qs("a="), {"a": [""]}) assert_dict_equal(parse_qs("a"), {"a": [""]})
def test_ignores_prefixed_question_mark_character_if_included(self): qs = '?b5=%3D%253D&a3=a&c%40=&a2=r%20b' + '&' + 'c2&a3=2+q' q = parse_qs(qs) assert_dict_equal(q, {'a2': ['r b'], 'a3': ['a', '2 q'], 'b5': ['=%3D'], 'c@': [''], 'c2': ['']})