Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Wrap urllib3's SSLError as requests' SSLError #6057

Merged
merged 1 commit into from Feb 5, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 5 additions & 0 deletions HISTORY.md
Expand Up @@ -6,6 +6,11 @@ dev

- \[Short description of non-trivial change.\]

**Bugfixes**

- Fixed urllib3 exception leak, wrapping `urllib3.exceptions.SSLError` with
`requests.exceptions.SSLError` for `content` and `iter_content`.

2.27.1 (2022-01-05)
-------------------

Expand Down
10 changes: 9 additions & 1 deletion requests/models.py
Expand Up @@ -19,7 +19,12 @@
from urllib3.filepost import encode_multipart_formdata
from urllib3.util import parse_url
from urllib3.exceptions import (
DecodeError, ReadTimeoutError, ProtocolError, LocationParseError)
DecodeError,
LocationParseError,
ProtocolError,
ReadTimeoutError,
SSLError,
)

from io import UnsupportedOperation
from .hooks import default_hooks
Expand All @@ -32,6 +37,7 @@
ContentDecodingError, ConnectionError, StreamConsumedError,
InvalidJSONError)
from .exceptions import JSONDecodeError as RequestsJSONDecodeError
from .exceptions import SSLError as RequestsSSLError
from ._internal_utils import to_native_string, unicode_is_ascii
from .utils import (
guess_filename, get_auth_from_url, requote_uri,
Expand Down Expand Up @@ -765,6 +771,8 @@ def generate():
raise ContentDecodingError(e)
except ReadTimeoutError as e:
raise ConnectionError(e)
except SSLError as e:
raise RequestsSSLError(e)
else:
# Standard file-like object.
while True:
Expand Down
45 changes: 41 additions & 4 deletions tests/test_requests.py
Expand Up @@ -14,6 +14,7 @@
import io
import requests
import pytest
import urllib3
from requests.adapters import HTTPAdapter
from requests.auth import HTTPDigestAuth, _basic_auth_str
from requests.compat import (
Expand All @@ -22,9 +23,25 @@
from requests.cookies import (
cookiejar_from_dict, morsel_to_cookie)
from requests.exceptions import (
ConnectionError, ConnectTimeout, InvalidSchema, InvalidURL,
MissingSchema, ReadTimeout, Timeout, RetryError, RequestException, TooManyRedirects,
ProxyError, InvalidHeader, UnrewindableBodyError, SSLError, InvalidProxyURL, InvalidJSONError)
ChunkedEncodingError,
ConnectionError,
ConnectTimeout,
ContentDecodingError,
InvalidHeader,
InvalidJSONError,
InvalidProxyURL,
InvalidSchema,
InvalidURL,
MissingSchema,
ProxyError,
ReadTimeout,
RequestException,
RetryError,
Timeout,
TooManyRedirects,
UnrewindableBodyError,
)
from requests.exceptions import SSLError as RequestsSSLError
from requests.models import PreparedRequest
from requests.structures import CaseInsensitiveDict
from requests.sessions import SessionRedirectMixin
Expand Down Expand Up @@ -910,7 +927,7 @@ def test_certificate_failure(self, httpbin_secure):
"""
When underlying SSL problems occur, an SSLError is raised.
"""
with pytest.raises(SSLError):
with pytest.raises(RequestsSSLError):
# Our local httpbin does not have a trusted CA, so this call will
# fail if we use our default trust bundle.
requests.get(httpbin_secure('status', '200'))
Expand Down Expand Up @@ -1320,6 +1337,26 @@ def test_response_chunk_size_type(self):
with pytest.raises(TypeError):
chunks = r.iter_content("1024")

@pytest.mark.parametrize(
'exception, args, expected', (
(urllib3.exceptions.ProtocolError, tuple(), ChunkedEncodingError),
(urllib3.exceptions.DecodeError, tuple(), ContentDecodingError),
(urllib3.exceptions.ReadTimeoutError, (None, '', ''), ConnectionError),
(urllib3.exceptions.SSLError, tuple(), RequestsSSLError),
)
)
def test_iter_content_wraps_exceptions(
self, httpbin, mocker, exception, args, expected
):
r = requests.Response()
r.raw = mocker.Mock()
# ReadTimeoutError can't be initialized by mock
# so we'll manually create the instance with args
r.raw.stream.side_effect = exception(*args)

with pytest.raises(expected):
next(r.iter_content(1024))

def test_request_and_response_are_pickleable(self, httpbin):
r = requests.get(httpbin('get'))

Expand Down