Compare commits

..

15 Commits

Author SHA1 Message Date
Cory Benfield
1285f576ae v2.12.4 2016-12-14 11:37:52 +00:00
Cory Benfield
9726f5314e Merge pull request #3766 from nateprewitt/1979_test_401
testing HTTPDigestAuth hooks
2016-12-14 09:41:11 +00:00
Nate Prewitt
1da1213561 add socket tests for 401 redirect and 401 failure 2016-12-13 14:43:16 -07:00
Cory Benfield
4683f16990 Merge pull request #3760 from sigmavirus24/deprecation-warning
Add deprecation warnings for 3.0
2016-12-09 14:48:42 +00:00
Ian Cordasco
fae6701478 Add deprecation warnings for 3.0
Add extra test parameter for basic auth encoding
2016-12-09 08:43:54 -06:00
Cory Benfield
449d74842d Merge pull request #3758 from nateprewitt/3753_allow_non_string_params_basic_auth
adding string casting for non-bytes values
2016-12-09 08:45:14 +00:00
Nate Prewitt
576b447a37 adding string casting for non-bytes values 2016-12-08 21:13:14 -07:00
Cory Benfield
11a86a5651 Merge pull request #3754 from nateprewitt/3066_auth_and_data
test auth doesn't generate incorrect http headers
2016-12-08 09:05:12 +00:00
Nate Prewitt
13716728e7 minor doc string cleanup 2016-12-04 12:11:09 -07:00
Casey Davidson
14d71fd41a Test chunked upload doesn't set content length 2016-12-04 12:01:07 -07:00
Casey Davidson
2d2c831d07 Add tests to verify that correct 'Content-Length' or 'Transfer-Encoding' headers are being set in PreparedRequest. 2016-12-04 12:00:57 -07:00
Cory Benfield
c398ab0e7d v2.12.3 2016-12-01 10:48:35 +00:00
Cory Benfield
7d2dfa8684 Merge pull request #3738 from Lukasa/revert-exclusion-patch
Revert "Restrict URL preparation to HTTP/HTTPS"
2016-12-01 10:45:25 +00:00
Cory Benfield
6f659a4179 Tests for our URL handling. 2016-11-30 21:45:09 +00:00
Cory Benfield
402a55b647 Revert "Restrict URL preparation to HTTP/HTTPS"
This reverts commit 34af72c87d.
2016-11-30 21:19:31 +00:00
7 changed files with 284 additions and 20 deletions

View File

@@ -173,3 +173,4 @@ Patches and Suggestions
- Om Prakash Kumar <omprakash070@gmail.com> (`@iamprakashom <https://github.com/iamprakashom>`_)
- Philipp Konrad <gardiac2002@gmail.com> (`@gardiac2002 <https://github.com/gardiac2002>`_)
- Hussain Tamboli <hussaintamboli18@gmail.com> (`@hussaintamboli <https://github.com/hussaintamboli>`_)
- Casey Davidson (`@davidsoncasey <https://github.com/davidsoncasey>`_)

View File

@@ -3,6 +3,27 @@
Release History
---------------
2.12.4 (2016-12-14)
+++++++++++++++++++
**Bugfixes**
- Fixed regression from 2.12.2 where non-string types were rejected in the
basic auth parameters. While support for this behaviour has been readded,
the behaviour is deprecated and will be removed in the future.
2.12.3 (2016-12-01)
+++++++++++++++++++
**Bugfixes**
- Fixed regression from v2.12.1 for URLs with schemes that begin with "http".
These URLs have historically been processed as though they were HTTP-schemed
URLs, and so have had parameters added. This was removed in v2.12.2 in an
overzealous attempt to resolve problems with IDNA-encoding those URLs. This
change was reverted: the other fixes for IDNA-encoding have been judged to
be sufficient to return to the behaviour Requests had before v2.12.0.
2.12.2 (2016-11-30)
+++++++++++++++++++

View File

@@ -41,8 +41,8 @@ is at <http://python-requests.org>.
"""
__title__ = 'requests'
__version__ = '2.12.2'
__build__ = 0x021202
__version__ = '2.12.4'
__build__ = 0x021204
__author__ = 'Kenneth Reitz'
__license__ = 'Apache 2.0'
__copyright__ = 'Copyright 2016 Kenneth Reitz'

View File

@@ -12,10 +12,11 @@ import re
import time
import hashlib
import threading
import warnings
from base64 import b64encode
from .compat import urlparse, str
from .compat import urlparse, str, basestring
from .cookies import extract_cookies_to_jar
from ._internal_utils import to_native_string
from .utils import parse_dict_header
@@ -27,7 +28,35 @@ CONTENT_TYPE_MULTI_PART = 'multipart/form-data'
def _basic_auth_str(username, password):
"""Returns a Basic Auth string."""
# "I want us to put a big-ol' comment on top of it that
# says that this behaviour is dumb but we need to preserve
# it because people are relying on it."
# - Lukasa
#
# These are here solely to maintain backwards compatibility
# for things like ints. This will be removed in 3.0.0.
if not isinstance(username, basestring):
warnings.warn(
"Non-string usernames will no longer be supported in Requests "
"3.0.0. Please convert the object you've passed in ({!r}) to "
"a string or bytes object in the near future to avoid "
"problems.".format(username),
category=DeprecationWarning,
)
username = str(username)
if not isinstance(password, basestring):
warnings.warn(
"Non-string passwords will no longer be supported in Requests "
"3.0.0. Please convert the object you've passed in ({!r}) to "
"a string or bytes object in the near future to avoid "
"problems.".format(password),
category=DeprecationWarning,
)
password = str(password)
# -- End Removal --
if isinstance(username, str):
username = username.encode('latin1')

View File

@@ -347,9 +347,9 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
url = url.lstrip()
# Don't do any URL preparation for non-HTTP schemes like `mailto`,
# `data`, `http+unix` etc to work around exceptions from `url_parse`,
# which handles RFC 3986 only.
if ':' in url and not url.lower().startswith(('http://', 'https://')):
# `data` etc to work around exceptions from `url_parse`, which
# handles RFC 3986 only.
if ':' in url and not url.lower().startswith('http'):
self.url = url
return

View File

@@ -5,7 +5,7 @@ import pytest
import threading
import requests
from tests.testserver.server import Server
from tests.testserver.server import Server, consume_socket_content
from .utils import override_environ
@@ -25,6 +25,117 @@ def test_chunked_upload():
assert r.request.headers['Transfer-Encoding'] == 'chunked'
def test_digestauth_401_count_reset_on_redirect():
"""Ensure we correctly reset num_401_calls after a successful digest auth,
followed by a 302 redirect to another digest auth prompt.
See https://github.com/kennethreitz/requests/issues/1979.
"""
text_401 = (b'HTTP/1.1 401 UNAUTHORIZED\r\n'
b'Content-Length: 0\r\n'
b'WWW-Authenticate: Digest nonce="6bf5d6e4da1ce66918800195d6b9130d"'
b', opaque="372825293d1c26955496c80ed6426e9e", '
b'realm="me@kennethreitz.com", qop=auth\r\n\r\n')
text_302 = (b'HTTP/1.1 302 FOUND\r\n'
b'Content-Length: 0\r\n'
b'Location: /\r\n\r\n')
text_200 = (b'HTTP/1.1 200 OK\r\n'
b'Content-Length: 0\r\n\r\n')
expected_digest = (b'Authorization: Digest username="user", '
b'realm="me@kennethreitz.com", '
b'nonce="6bf5d6e4da1ce66918800195d6b9130d", uri="/"')
auth = requests.auth.HTTPDigestAuth('user', 'pass')
def digest_response_handler(sock):
# Respond to initial GET with a challenge.
request_content = consume_socket_content(sock, timeout=0.5)
assert request_content.startswith(b"GET / HTTP/1.1")
sock.send(text_401)
# Verify we receive an Authorization header in response, then redirect.
request_content = consume_socket_content(sock, timeout=0.5)
assert expected_digest in request_content
sock.send(text_302)
# Verify Authorization isn't sent to the redirected host,
# then send another challenge.
request_content = consume_socket_content(sock, timeout=0.5)
assert b'Authorization:' not in request_content
sock.send(text_401)
# Verify Authorization is sent correctly again, and return 200 OK.
request_content = consume_socket_content(sock, timeout=0.5)
assert expected_digest in request_content
sock.send(text_200)
return request_content
close_server = threading.Event()
server = Server(digest_response_handler, wait_to_close_event=close_server)
with server as (host, port):
url = 'http://{0}:{1}/'.format(host, port)
r = requests.get(url, auth=auth)
# Verify server succeeded in authenticating.
assert r.status_code == 200
# Verify Authorization was sent in final request.
assert 'Authorization' in r.request.headers
assert r.request.headers['Authorization'].startswith('Digest ')
# Verify redirect happened as we expected.
assert r.history[0].status_code == 302
close_server.set()
def test_digestauth_401_only_sent_once():
"""Ensure we correctly respond to a 401 challenge once, and then
stop responding if challenged again.
"""
text_401 = (b'HTTP/1.1 401 UNAUTHORIZED\r\n'
b'Content-Length: 0\r\n'
b'WWW-Authenticate: Digest nonce="6bf5d6e4da1ce66918800195d6b9130d"'
b', opaque="372825293d1c26955496c80ed6426e9e", '
b'realm="me@kennethreitz.com", qop=auth\r\n\r\n')
expected_digest = (b'Authorization: Digest username="user", '
b'realm="me@kennethreitz.com", '
b'nonce="6bf5d6e4da1ce66918800195d6b9130d", uri="/"')
auth = requests.auth.HTTPDigestAuth('user', 'pass')
def digest_failed_response_handler(sock):
# Respond to initial GET with a challenge.
request_content = consume_socket_content(sock, timeout=0.5)
assert request_content.startswith(b"GET / HTTP/1.1")
sock.send(text_401)
# Verify we receive an Authorization header in response, then
# challenge again.
request_content = consume_socket_content(sock, timeout=0.5)
assert expected_digest in request_content
sock.send(text_401)
# Verify the client didn't respond to second challenge.
request_content = consume_socket_content(sock, timeout=0.5)
assert request_content == b''
return request_content
close_server = threading.Event()
server = Server(digest_failed_response_handler, wait_to_close_event=close_server)
with server as (host, port):
url = 'http://{0}:{1}/'.format(host, port)
r = requests.get(url, auth=auth)
# Verify server didn't authenticate us.
assert r.status_code == 401
assert r.history[0].status_code == 401
close_server.set()
_schemes_by_var_prefix = [
('http', ['http']),
('https', ['https']),

View File

@@ -484,6 +484,8 @@ class TestRequests:
'username, password', (
('user', 'pass'),
(u'имя'.encode('utf-8'), u'пароль'.encode('utf-8')),
(42, 42),
(None, None),
))
def test_set_basicauth(self, httpbin, username, password):
auth = (username, password)
@@ -494,6 +496,16 @@ class TestRequests:
assert p.headers['Authorization'] == _basic_auth_str(username, password)
def test_basicauth_encodes_byte_strings(self):
"""Ensure b'test' formats as the byte string "test" rather
than the unicode string "b'test'" in Python 3.
"""
auth = (b'\xc5\xafsername', b'test\xc6\xb6')
r = requests.Request('GET', 'http://localhost', auth=auth)
p = r.prepare()
assert p.headers['Authorization'] == 'Basic xa9zZXJuYW1lOnRlc3TGtg=='
@pytest.mark.parametrize(
'url, exception', (
# Connecting to an unknown domain should raise a ConnectionError
@@ -1693,6 +1705,42 @@ class TestRequests:
resp.close()
assert resp.raw.closed
def test_empty_stream_with_auth_does_not_set_content_length_header(self, httpbin):
"""Ensure that a byte stream with size 0 will not set both a Content-Length
and Transfer-Encoding header.
"""
auth = ('user', 'pass')
url = httpbin('post')
file_obj = io.BytesIO(b'')
r = requests.Request('POST', url, auth=auth, data=file_obj)
prepared_request = r.prepare()
assert 'Transfer-Encoding' in prepared_request.headers
assert 'Content-Length' not in prepared_request.headers
def test_stream_with_auth_does_not_set_transfer_encoding_header(self, httpbin):
"""Ensure that a byte stream with size > 0 will not set both a Content-Length
and Transfer-Encoding header.
"""
auth = ('user', 'pass')
url = httpbin('post')
file_obj = io.BytesIO(b'test data')
r = requests.Request('POST', url, auth=auth, data=file_obj)
prepared_request = r.prepare()
assert 'Transfer-Encoding' not in prepared_request.headers
assert 'Content-Length' in prepared_request.headers
def test_chunked_upload_does_not_set_content_length_header(self, httpbin):
"""Ensure that requests with a generator body stream using
Transfer-Encoding: chunked, not a Content-Length header.
"""
data = (i for i in [b'a', b'b', b'c'])
url = httpbin('post')
r = requests.Request('POST', url, data=data)
prepared_request = r.prepare()
assert 'Transfer-Encoding' in prepared_request.headers
assert 'Content-Length' not in prepared_request.headers
class TestCaseInsensitiveDict:
@pytest.mark.parametrize(
@@ -2177,18 +2225,72 @@ class TestPreparingURLs(object):
r.prepare()
@pytest.mark.parametrize(
'protocol, url',
'input, expected',
(
("http+unix://", b"http+unix://%2Fvar%2Frun%2Fsocket/path"),
("http+unix://", u"http+unix://%2Fvar%2Frun%2Fsocket/path"),
("mailto", b"mailto:user@example.org"),
("mailto", u"mailto:user@example.org"),
("data", b"data:SSDimaUgUHl0aG9uIQ=="),
(
b"http+unix://%2Fvar%2Frun%2Fsocket/path",
u"http+unix://%2fvar%2frun%2fsocket/path",
),
(
u"http+unix://%2Fvar%2Frun%2Fsocket/path",
u"http+unix://%2fvar%2frun%2fsocket/path",
),
(
b"mailto:user@example.org",
u"mailto:user@example.org",
),
(
u"mailto:user@example.org",
u"mailto:user@example.org",
),
(
b"data:SSDimaUgUHl0aG9uIQ==",
u"data:SSDimaUgUHl0aG9uIQ==",
)
)
)
def test_url_passthrough(self, protocol, url):
session = requests.Session()
session.mount(protocol, HTTPAdapter())
p = requests.Request('GET', url=url)
p.prepare()
assert p.url == url
def test_url_mutation(self, input, expected):
"""
This test validates that we correctly exclude some URLs from
preparation, and that we handle others. Specifically, it tests that
any URL whose scheme doesn't begin with "http" is left alone, and
those whose scheme *does* begin with "http" are mutated.
"""
r = requests.Request('GET', url=input)
p = r.prepare()
assert p.url == expected
@pytest.mark.parametrize(
'input, params, expected',
(
(
b"http+unix://%2Fvar%2Frun%2Fsocket/path",
{"key": "value"},
u"http+unix://%2fvar%2frun%2fsocket/path?key=value",
),
(
u"http+unix://%2Fvar%2Frun%2Fsocket/path",
{"key": "value"},
u"http+unix://%2fvar%2frun%2fsocket/path?key=value",
),
(
b"mailto:user@example.org",
{"key": "value"},
u"mailto:user@example.org",
),
(
u"mailto:user@example.org",
{"key": "value"},
u"mailto:user@example.org",
),
)
)
def test_parameters_for_nonstandard_schemes(self, input, params, expected):
"""
Setting paramters for nonstandard schemes is allowed if those schemes
begin with "http", and is forbidden otherwise.
"""
r = requests.Request('GET', url=input, params=params)
p = r.prepare()
assert p.url == expected