From bfb202527d5e63dfa08dc1aab2280c20b5151b4a Mon Sep 17 00:00:00 2001 From: Casey Davidson Date: Tue, 10 May 2016 19:36:32 -0700 Subject: [PATCH 1/8] Alternate fix for 3066 to refactor prepare_body to always call prepare_content_length. This allows for the 'Content-Length' header to only be set in prepare_content_length. --- requests/exceptions.py | 4 ++++ requests/models.py | 50 ++++++++++++++++++++++-------------------- requests/utils.py | 9 ++++++++ tests/test_requests.py | 25 ++++++++++++++++++++- 4 files changed, 63 insertions(+), 25 deletions(-) diff --git a/requests/exceptions.py b/requests/exceptions.py index 82797664..cddffd95 100644 --- a/requests/exceptions.py +++ b/requests/exceptions.py @@ -107,6 +107,10 @@ class RetryError(RequestException): class UnrewindableBodyError(RequestException): """Requests encountered an error when trying to rewind a body""" +class ConflictingHeaderError(RequestException): + """Mutually exclusive request headers set""" + + # Warnings diff --git a/requests/models.py b/requests/models.py index df94f026..365c0ed8 100644 --- a/requests/models.py +++ b/requests/models.py @@ -32,12 +32,14 @@ from .packages.urllib3.exceptions import ( LocationParseError, ConnectionError) from .exceptions import ( HTTPError, MissingScheme, InvalidURL, ChunkedEncodingError, - ContentDecodingError, ConnectionError, StreamConsumedError) + ContentDecodingError, ConnectionError, StreamConsumedError, + ConflictingHeaderError) from ._internal_utils import to_native_string, unicode_is_ascii from .utils import ( guess_filename, get_auth_from_url, requote_uri, stream_decode_response_unicode, to_key_val_list, parse_header_links, - iter_slices, guess_json_utf, super_len, check_header_validity) + iter_slices, guess_json_utf, super_len, check_header_validity, + determine_if_stream) from .compat import ( cookielib, urlunparse, urlsplit, urlencode, str, bytes, StringIO, is_py2, chardet, builtin_str, basestring) @@ -466,15 +468,7 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin): if not isinstance(body, bytes): body = body.encode('utf-8') - is_stream = all([ - hasattr(data, '__iter__'), - not isinstance(data, (basestring, list, tuple, collections.Mapping)) - ]) - - try: - length = super_len(data) - except (TypeError, AttributeError, UnsupportedOperation): - length = None + is_stream = determine_if_stream(data) if is_stream: body = data @@ -493,10 +487,6 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin): if files: raise NotImplementedError('Streamed bodies and files are mutually exclusive.') - if length: - self.headers['Content-Length'] = builtin_str(length) - else: - self.headers['Transfer-Encoding'] = 'chunked' else: # Multi-part file uploads. if files: @@ -509,27 +499,39 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin): else: content_type = 'application/x-www-form-urlencoded' - self.prepare_content_length(body) - # Add content-type if it wasn't explicitly provided. if content_type and ('content-type' not in self.headers): self.headers['Content-Type'] = content_type + self.prepare_content_length(body) self.body = body def prepare_content_length(self, body): - """Prepare Content-Length header based on request method and body""" + """Prepares Content-Length header. + + If the length of the body of the request can be computed, Content-Length is set using + super_len. If user has manually set either a Transfer-Encoding or Content-Length header + when it should not be set (they should be mutually exclusive) an ConflictingHeaderError + error will be raised. + """ if body is not None: - length = super_len(body) + is_stream = determine_if_stream(body) + + try: + length = super_len(body) + except (TypeError, AttributeError, UnsupportedOperation): + length = None + if length: - # If length exists, set it. Otherwise, we fallback - # to Transfer-Encoding: chunked. self.headers['Content-Length'] = builtin_str(length) - elif self.method not in ('GET', 'HEAD') and self.headers.get('Content-Length') is None: - # Set Content-Length to 0 for methods that can have a body - # but don't provide one. (i.e. not GET or HEAD) + elif is_stream and not length: + self.headers['Transfer-Encoding'] = 'chunked' + elif (self.method not in ('GET', 'HEAD')) and (self.headers.get('Content-Length') is None): self.headers['Content-Length'] = '0' + if 'Transfer-Encoding' in self.headers and 'Content-Length' in self.headers: + raise ConflictingHeaderError('Transfer-Encoding and Content-Length headers both set') + def prepare_auth(self, auth, url=''): """Prepares the given HTTP auth data.""" diff --git a/requests/utils.py b/requests/utils.py index e9460be4..e36d62af 100644 --- a/requests/utils.py +++ b/requests/utils.py @@ -865,6 +865,7 @@ def urldefragauth(url): return urlunparse((scheme, netloc, path, params, query, '')) + def rewind_body(prepared_request): """Move file pointer back to its recorded starting position so it can be read again on redirect. @@ -878,3 +879,11 @@ def rewind_body(prepared_request): "body for redirect.") else: raise UnrewindableBodyError("Unable to rewind request body for redirect.") + + +def determine_if_stream(data): + """Given data, determines if it should be sent as a stream. + """ + is_iterable = hasattr(data, '__iter__') + is_io_type = not isinstance(data, (basestring, list, tuple, dict)) + return is_iterable and is_io_type diff --git a/tests/test_requests.py b/tests/test_requests.py index 696cb2bd..a9de9c17 100755 --- a/tests/test_requests.py +++ b/tests/test_requests.py @@ -25,7 +25,7 @@ from requests.cookies import ( from requests.exceptions import ( ConnectionError, ConnectTimeout, InvalidScheme, InvalidURL, MissingScheme, ReadTimeout, Timeout, RetryError, TooManyRedirects, - ProxyError, InvalidHeader, UnrewindableBodyError) + ProxyError, InvalidHeader, UnrewindableBodyError, ConflictingHeaderError) from requests.models import PreparedRequest from requests.structures import CaseInsensitiveDict from requests.sessions import SessionRedirectMixin @@ -1924,6 +1924,29 @@ class TestRequests: assert 'Transfer-Encoding' in prepared_request.headers assert 'Content-Length' not in prepared_request.headers + def test_chunked_upload_with_manually_set_content_length_header_raises_error(self, httpbin): + """Ensure that if a user manually sets a content length header when the data + is chunked that an ConflictingHeaderError is raised""" + data = (i for i in [b'a', b'b', b'c']) + url = httpbin('post') + with pytest.raises(ConflictingHeaderError): + r = requests.post(url, data=data, headers={'Content-Length': 'foo'}) + + def test_content_length_with_manually_set_transfer_encoding_raises_error(self, httpbin): + """Ensure that if a user manually sets a Transfer-Encoding header when data is not chunked + that an ConflictingHeaderError is raised""" + data = 'test data' + url = httpbin('post') + with pytest.raises(ConflictingHeaderError): + r = requests.post(url, data=data, headers={'Transfer-Encoding': 'chunked'}) + + def test_null_body_does_not_raise_error(self, httpbin): + url = httpbin('post') + try: + requests.post(url, data=None) + except ConflictingHeaderError: + pytest.fail('ConflictingHeaderError raised') + def test_custom_redirect_mixin(self, httpbin): """Tests a custom mixin to overwrite ``get_redirect_target``. From 033dfc165d0c0946e95f5b3ac290c6c8fe3a813a Mon Sep 17 00:00:00 2001 From: Casey Davidson Date: Wed, 8 Jun 2016 21:47:34 -0700 Subject: [PATCH 2/8] Raise an error if body is not null, has not length and is not streamable. --- requests/models.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/requests/models.py b/requests/models.py index 365c0ed8..eb68289b 100644 --- a/requests/models.py +++ b/requests/models.py @@ -526,6 +526,8 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin): self.headers['Content-Length'] = builtin_str(length) elif is_stream and not length: self.headers['Transfer-Encoding'] = 'chunked' + else: + assert False, "If body is not null, it must either have a length or be streamable" elif (self.method not in ('GET', 'HEAD')) and (self.headers.get('Content-Length') is None): self.headers['Content-Length'] = '0' From 1003fdf0f2731bc6398678bec9ff0414ac1e00bc Mon Sep 17 00:00:00 2001 From: Casey Davidson Date: Mon, 13 Jun 2016 21:02:19 -0700 Subject: [PATCH 3/8] Small fixes based on feedback in pull request. --- requests/exceptions.py | 2 ++ requests/models.py | 2 +- requests/utils.py | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/requests/exceptions.py b/requests/exceptions.py index cddffd95..f2ac3a16 100644 --- a/requests/exceptions.py +++ b/requests/exceptions.py @@ -110,6 +110,8 @@ class UnrewindableBodyError(RequestException): class ConflictingHeaderError(RequestException): """Mutually exclusive request headers set""" +class UnreachableCodeError(RequestException, RuntimeError): + """Unreachable code block reached""" # Warnings diff --git a/requests/models.py b/requests/models.py index eb68289b..60e6ca94 100644 --- a/requests/models.py +++ b/requests/models.py @@ -527,7 +527,7 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin): elif is_stream and not length: self.headers['Transfer-Encoding'] = 'chunked' else: - assert False, "If body is not null, it must either have a length or be streamable" + raise UnreachableCodeError("Non-null body must have length or be streamable") elif (self.method not in ('GET', 'HEAD')) and (self.headers.get('Content-Length') is None): self.headers['Content-Length'] = '0' diff --git a/requests/utils.py b/requests/utils.py index e36d62af..e541a626 100644 --- a/requests/utils.py +++ b/requests/utils.py @@ -884,6 +884,6 @@ def rewind_body(prepared_request): def determine_if_stream(data): """Given data, determines if it should be sent as a stream. """ - is_iterable = hasattr(data, '__iter__') + is_iterable = getattr(data, '__iter__', False) is_io_type = not isinstance(data, (basestring, list, tuple, dict)) return is_iterable and is_io_type From f239fe754d92b5c229e740987ab8176e8de64af0 Mon Sep 17 00:00:00 2001 From: Casey Davidson Date: Tue, 14 Jun 2016 09:23:20 -0700 Subject: [PATCH 4/8] Change UnreachableCodeError to InvalidBodyError. --- requests/exceptions.py | 4 ++-- requests/models.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/requests/exceptions.py b/requests/exceptions.py index f2ac3a16..f45d9c76 100644 --- a/requests/exceptions.py +++ b/requests/exceptions.py @@ -110,8 +110,8 @@ class UnrewindableBodyError(RequestException): class ConflictingHeaderError(RequestException): """Mutually exclusive request headers set""" -class UnreachableCodeError(RequestException, RuntimeError): - """Unreachable code block reached""" +class InvalidBodyError(RequestException, ValueError): + """An invalid request body was specified""" # Warnings diff --git a/requests/models.py b/requests/models.py index 60e6ca94..c1b8ba0c 100644 --- a/requests/models.py +++ b/requests/models.py @@ -527,7 +527,7 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin): elif is_stream and not length: self.headers['Transfer-Encoding'] = 'chunked' else: - raise UnreachableCodeError("Non-null body must have length or be streamable") + raise InvalidBodyError("Non-null body must have length or be streamable") elif (self.method not in ('GET', 'HEAD')) and (self.headers.get('Content-Length') is None): self.headers['Content-Length'] = '0' From a52fe6586c0bcadb1610b93d39c454e0ff83cd0f Mon Sep 17 00:00:00 2001 From: Nate Prewitt Date: Sun, 12 Feb 2017 23:02:47 -0700 Subject: [PATCH 5/8] consolidate super_len code and cleanup docstrings --- requests/exceptions.py | 3 +++ requests/models.py | 28 +++++++++++----------------- requests/utils.py | 7 +++---- tests/test_requests.py | 14 ++++++++------ 4 files changed, 25 insertions(+), 27 deletions(-) diff --git a/requests/exceptions.py b/requests/exceptions.py index f45d9c76..2f093baf 100644 --- a/requests/exceptions.py +++ b/requests/exceptions.py @@ -104,12 +104,15 @@ class StreamConsumedError(RequestException, TypeError): class RetryError(RequestException): """Custom retries logic failed""" + class UnrewindableBodyError(RequestException): """Requests encountered an error when trying to rewind a body""" + class ConflictingHeaderError(RequestException): """Mutually exclusive request headers set""" + class InvalidBodyError(RequestException, ValueError): """An invalid request body was specified""" diff --git a/requests/models.py b/requests/models.py index c1b8ba0c..2b3e1fe3 100644 --- a/requests/models.py +++ b/requests/models.py @@ -39,7 +39,7 @@ from .utils import ( guess_filename, get_auth_from_url, requote_uri, stream_decode_response_unicode, to_key_val_list, parse_header_links, iter_slices, guess_json_utf, super_len, check_header_validity, - determine_if_stream) + is_stream) from .compat import ( cookielib, urlunparse, urlsplit, urlencode, str, bytes, StringIO, is_py2, chardet, builtin_str, basestring) @@ -468,9 +468,7 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin): if not isinstance(body, bytes): body = body.encode('utf-8') - is_stream = determine_if_stream(data) - - if is_stream: + if is_stream(data): body = data if getattr(body, 'tell', None) is not None: @@ -509,30 +507,26 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin): def prepare_content_length(self, body): """Prepares Content-Length header. - If the length of the body of the request can be computed, Content-Length is set using - super_len. If user has manually set either a Transfer-Encoding or Content-Length header - when it should not be set (they should be mutually exclusive) an ConflictingHeaderError + If the length of the body of the request can be computed, Content-Length + is set using ``super_len``. If user has manually set either a + Transfer-Encoding or Content-Length header when it should not be set + (they should be mutually exclusive) a ConflictingHeaderError error will be raised. """ if body is not None: - is_stream = determine_if_stream(body) - - try: - length = super_len(body) - except (TypeError, AttributeError, UnsupportedOperation): - length = None + length = super_len(body) if length: self.headers['Content-Length'] = builtin_str(length) - elif is_stream and not length: + elif is_stream(body): self.headers['Transfer-Encoding'] = 'chunked' else: - raise InvalidBodyError("Non-null body must have length or be streamable") - elif (self.method not in ('GET', 'HEAD')) and (self.headers.get('Content-Length') is None): + raise InvalidBodyError('Non-null body must have length or be streamable.') + elif self.method not in ('GET', 'HEAD') and self.headers.get('Content-Length') is None: self.headers['Content-Length'] = '0' if 'Transfer-Encoding' in self.headers and 'Content-Length' in self.headers: - raise ConflictingHeaderError('Transfer-Encoding and Content-Length headers both set') + raise ConflictingHeaderError('Transfer-Encoding and Content-Length headers both set.') def prepare_auth(self, auth, url=''): """Prepares the given HTTP auth data.""" diff --git a/requests/utils.py b/requests/utils.py index e541a626..fff14db7 100644 --- a/requests/utils.py +++ b/requests/utils.py @@ -881,9 +881,8 @@ def rewind_body(prepared_request): raise UnrewindableBodyError("Unable to rewind request body for redirect.") -def determine_if_stream(data): - """Given data, determines if it should be sent as a stream. - """ +def is_stream(data): + """Given data, determines if it should be sent as a stream.""" is_iterable = getattr(data, '__iter__', False) - is_io_type = not isinstance(data, (basestring, list, tuple, dict)) + is_io_type = not isinstance(data, (basestring, list, tuple, collections.Mapping)) return is_iterable and is_io_type diff --git a/tests/test_requests.py b/tests/test_requests.py index a9de9c17..3beb861d 100755 --- a/tests/test_requests.py +++ b/tests/test_requests.py @@ -1925,16 +1925,18 @@ class TestRequests: assert 'Content-Length' not in prepared_request.headers def test_chunked_upload_with_manually_set_content_length_header_raises_error(self, httpbin): - """Ensure that if a user manually sets a content length header when the data - is chunked that an ConflictingHeaderError is raised""" - data = (i for i in [b'a', b'b', b'c']) + """Ensure that if a user manually sets a content length header, when + the data is chunked, that a ConflictingHeaderError is raised. + """ + data = (i for i in [b'a', b'b', b'c']) url = httpbin('post') with pytest.raises(ConflictingHeaderError): r = requests.post(url, data=data, headers={'Content-Length': 'foo'}) def test_content_length_with_manually_set_transfer_encoding_raises_error(self, httpbin): - """Ensure that if a user manually sets a Transfer-Encoding header when data is not chunked - that an ConflictingHeaderError is raised""" + """Ensure that if a user manually sets a Transfer-Encoding header when + data is not chunked that an ConflictingHeaderError is raised. + """ data = 'test data' url = httpbin('post') with pytest.raises(ConflictingHeaderError): @@ -1945,7 +1947,7 @@ class TestRequests: try: requests.post(url, data=None) except ConflictingHeaderError: - pytest.fail('ConflictingHeaderError raised') + pytest.fail('ConflictingHeaderError raised.') def test_custom_redirect_mixin(self, httpbin): """Tests a custom mixin to overwrite ``get_redirect_target``. From 5a65a0dab1310fe5410c74bfca2f0c1d9262084d Mon Sep 17 00:00:00 2001 From: Nate Prewitt Date: Wed, 1 Mar 2017 07:23:38 -0700 Subject: [PATCH 6/8] use InvalidHeader instead of ConflictingHeaderError --- requests/exceptions.py | 4 ---- requests/models.py | 9 ++++++--- tests/test_requests.py | 14 +++++++------- 3 files changed, 13 insertions(+), 14 deletions(-) diff --git a/requests/exceptions.py b/requests/exceptions.py index 2f093baf..314783b7 100644 --- a/requests/exceptions.py +++ b/requests/exceptions.py @@ -109,10 +109,6 @@ class UnrewindableBodyError(RequestException): """Requests encountered an error when trying to rewind a body""" -class ConflictingHeaderError(RequestException): - """Mutually exclusive request headers set""" - - class InvalidBodyError(RequestException, ValueError): """An invalid request body was specified""" diff --git a/requests/models.py b/requests/models.py index 2b3e1fe3..ac348e9d 100644 --- a/requests/models.py +++ b/requests/models.py @@ -33,7 +33,7 @@ from .packages.urllib3.exceptions import ( from .exceptions import ( HTTPError, MissingScheme, InvalidURL, ChunkedEncodingError, ContentDecodingError, ConnectionError, StreamConsumedError, - ConflictingHeaderError) + InvalidHeader, InvalidBodyError) from ._internal_utils import to_native_string, unicode_is_ascii from .utils import ( guess_filename, get_auth_from_url, requote_uri, @@ -510,7 +510,7 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin): If the length of the body of the request can be computed, Content-Length is set using ``super_len``. If user has manually set either a Transfer-Encoding or Content-Length header when it should not be set - (they should be mutually exclusive) a ConflictingHeaderError + (they should be mutually exclusive) an InvalidHeader error will be raised. """ if body is not None: @@ -523,10 +523,13 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin): else: raise InvalidBodyError('Non-null body must have length or be streamable.') elif self.method not in ('GET', 'HEAD') and self.headers.get('Content-Length') is None: + # Set Content-Length to 0 for methods that can have a body + # but don't provide one. (i.e. not GET or HEAD) self.headers['Content-Length'] = '0' if 'Transfer-Encoding' in self.headers and 'Content-Length' in self.headers: - raise ConflictingHeaderError('Transfer-Encoding and Content-Length headers both set.') + raise InvalidHeader('Conflicting Headers: Both Transfer-Encoding and ' + 'Content-Length are set.') def prepare_auth(self, auth, url=''): """Prepares the given HTTP auth data.""" diff --git a/tests/test_requests.py b/tests/test_requests.py index 3beb861d..780ebbbc 100755 --- a/tests/test_requests.py +++ b/tests/test_requests.py @@ -25,7 +25,7 @@ from requests.cookies import ( from requests.exceptions import ( ConnectionError, ConnectTimeout, InvalidScheme, InvalidURL, MissingScheme, ReadTimeout, Timeout, RetryError, TooManyRedirects, - ProxyError, InvalidHeader, UnrewindableBodyError, ConflictingHeaderError) + ProxyError, InvalidHeader, UnrewindableBodyError) from requests.models import PreparedRequest from requests.structures import CaseInsensitiveDict from requests.sessions import SessionRedirectMixin @@ -1926,28 +1926,28 @@ class TestRequests: def test_chunked_upload_with_manually_set_content_length_header_raises_error(self, httpbin): """Ensure that if a user manually sets a content length header, when - the data is chunked, that a ConflictingHeaderError is raised. + the data is chunked, that an InvalidHeader error is raised. """ data = (i for i in [b'a', b'b', b'c']) url = httpbin('post') - with pytest.raises(ConflictingHeaderError): + with pytest.raises(InvalidHeader): r = requests.post(url, data=data, headers={'Content-Length': 'foo'}) def test_content_length_with_manually_set_transfer_encoding_raises_error(self, httpbin): """Ensure that if a user manually sets a Transfer-Encoding header when - data is not chunked that an ConflictingHeaderError is raised. + data is not chunked that an InvalidHeader error is raised. """ data = 'test data' url = httpbin('post') - with pytest.raises(ConflictingHeaderError): + with pytest.raises(InvalidHeader): r = requests.post(url, data=data, headers={'Transfer-Encoding': 'chunked'}) def test_null_body_does_not_raise_error(self, httpbin): url = httpbin('post') try: requests.post(url, data=None) - except ConflictingHeaderError: - pytest.fail('ConflictingHeaderError raised.') + except InvalidHeader: + pytest.fail('InvalidHeader error raised unexpectedly.') def test_custom_redirect_mixin(self, httpbin): """Tests a custom mixin to overwrite ``get_redirect_target``. From 64b66b6409928605236cf989678643f65daa0014 Mon Sep 17 00:00:00 2001 From: Nate Prewitt Date: Wed, 1 Mar 2017 09:39:53 -0700 Subject: [PATCH 7/8] test prepare_content_length sets expected headers --- tests/test_requests.py | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/tests/test_requests.py b/tests/test_requests.py index 780ebbbc..4d91dd20 100755 --- a/tests/test_requests.py +++ b/tests/test_requests.py @@ -25,7 +25,7 @@ from requests.cookies import ( from requests.exceptions import ( ConnectionError, ConnectTimeout, InvalidScheme, InvalidURL, MissingScheme, ReadTimeout, Timeout, RetryError, TooManyRedirects, - ProxyError, InvalidHeader, UnrewindableBodyError) + ProxyError, InvalidHeader, UnrewindableBodyError, InvalidBodyError) from requests.models import PreparedRequest from requests.structures import CaseInsensitiveDict from requests.sessions import SessionRedirectMixin @@ -1949,6 +1949,36 @@ class TestRequests: except InvalidHeader: pytest.fail('InvalidHeader error raised unexpectedly.') + @pytest.mark.parametrize( + 'body, expected', ( + (None, ('Content-Length', '0')), + ('test_data', ('Content-Length', '9')), + (io.BytesIO(b'test_data'), ('Content-Length', '9')), + (StringIO.StringIO(''), ('Transfer-Encoding', 'chunked')) + )) + def test_prepare_content_length(self, httpbin, body, expected): + """Test prepare_content_length creates expected header.""" + prep = requests.PreparedRequest() + prep.headers = {} + prep.method = 'POST' + + # Ensure Content-Length is set appropriately. + key, value = expected + prep.prepare_content_length(body) + assert prep.headers[key] == value + + def test_prepare_content_length_with_bad_body(self, httpbin): + """Test prepare_content_length raises exception with unsendable body.""" + # Initialize minimum required PreparedRequest. + prep = requests.PreparedRequest() + prep.headers = {} + prep.method = 'POST' + + with pytest.raises(InvalidBodyError) as e: + # Send object that isn't iterable and has no accessible content. + prep.prepare_content_length(object()) + assert "Non-null body must have length or be streamable." in str(e) + def test_custom_redirect_mixin(self, httpbin): """Tests a custom mixin to overwrite ``get_redirect_target``. From be2f92b9e300d0b5c7af0fed7d1f421b06676d3a Mon Sep 17 00:00:00 2001 From: Nate Prewitt Date: Wed, 1 Mar 2017 10:32:05 -0700 Subject: [PATCH 8/8] updating HISTORY --- 3.0-HISTORY.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/3.0-HISTORY.rst b/3.0-HISTORY.rst index 4701e529..4674579a 100644 --- a/3.0-HISTORY.rst +++ b/3.0-HISTORY.rst @@ -1,6 +1,10 @@ 3.0.0 (2017-xx-xx) ++++++++++++++++++ +- Simplified logic for determining Content-Length and Transfer-Encoding. + Requests will now avoid setting both headers on the same request, and + raise an exception if this is done manually by a user. + - Remove the HTTPProxyAuth class in favor of supporting proxy auth via the proxies parameter.