From 9a8a826f226e6973d72914b4a4fc806e0b5036f4 Mon Sep 17 00:00:00 2001 From: Nehal J Wani Date: Thu, 26 Oct 2017 10:33:05 -0400 Subject: [PATCH] Check if host is invalid for proxy According to RFC3986, the authority section can be empty for a given URL, however, for a proxy URL, it shouldn't be. This patch adds a check to verify that the parsed URL will have a valid host before creating the proxy manager. Fixes #4353 --- AUTHORS.rst | 1 + HISTORY.rst | 1 + requests/adapters.py | 7 ++++++- requests/exceptions.py | 4 ++++ tests/test_requests.py | 15 ++++++++++++++- 5 files changed, 26 insertions(+), 2 deletions(-) diff --git a/AUTHORS.rst b/AUTHORS.rst index 1bec7846..8379f65c 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -180,3 +180,4 @@ Patches and Suggestions - Matt Liu (`@mlcrazy `_) - Taylor Hoff (`@PrimordialHelios `_) - Arthur Vigil (`@ahvigil `_) +- Nehal J Wani (`@nehaljwani `_) diff --git a/HISTORY.rst b/HISTORY.rst index e6281c1e..79202df6 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -9,6 +9,7 @@ dev **Improvements** - Warn user about possible slowdown when using cryptography version < 1.3.4 +- Check for invalid host in proxy URL, before forwarding request to adapter. **Bugfixes** diff --git a/requests/adapters.py b/requests/adapters.py index cdaabdbe..bc01e336 100644 --- a/requests/adapters.py +++ b/requests/adapters.py @@ -13,6 +13,7 @@ import socket from urllib3.poolmanager import PoolManager, proxy_from_url from urllib3.response import HTTPResponse +from urllib3.util import parse_url from urllib3.util import Timeout as TimeoutSauce from urllib3.util.retry import Retry from urllib3.exceptions import ClosedPoolError @@ -34,7 +35,7 @@ from .utils import (DEFAULT_CA_BUNDLE_PATH, extract_zipped_paths, from .structures import CaseInsensitiveDict from .cookies import extract_cookies_to_jar from .exceptions import (ConnectionError, ConnectTimeout, ReadTimeout, SSLError, - ProxyError, RetryError, InvalidSchema) + ProxyError, RetryError, InvalidSchema, InvalidProxyURL) from .auth import _basic_auth_str try: @@ -300,6 +301,10 @@ class HTTPAdapter(BaseAdapter): if proxy: proxy = prepend_scheme_if_needed(proxy, 'http') + proxy_url = parse_url(proxy) + if not proxy_url.host: + raise InvalidProxyURL("Please check proxy URL. It is malformed" + " and could be missing the host.") proxy_manager = self.proxy_manager_for(proxy) conn = proxy_manager.connection_from_url(url) else: diff --git a/requests/exceptions.py b/requests/exceptions.py index be7eaed6..a80cad80 100644 --- a/requests/exceptions.py +++ b/requests/exceptions.py @@ -85,6 +85,10 @@ class InvalidHeader(RequestException, ValueError): """The header value provided was somehow invalid.""" +class InvalidProxyURL(InvalidURL): + """The proxy URL provided is invalid.""" + + class ChunkedEncodingError(RequestException): """The server declared chunked encoding but sent an invalid chunk.""" diff --git a/tests/test_requests.py b/tests/test_requests.py index 05fe63e3..e6a026f2 100644 --- a/tests/test_requests.py +++ b/tests/test_requests.py @@ -23,7 +23,7 @@ from requests.cookies import ( from requests.exceptions import ( ConnectionError, ConnectTimeout, InvalidSchema, InvalidURL, MissingSchema, ReadTimeout, Timeout, RetryError, TooManyRedirects, - ProxyError, InvalidHeader, UnrewindableBodyError, SSLError) + ProxyError, InvalidHeader, UnrewindableBodyError, SSLError, InvalidProxyURL) from requests.models import PreparedRequest from requests.structures import CaseInsensitiveDict from requests.sessions import SessionRedirectMixin @@ -526,6 +526,19 @@ class TestRequests: with pytest.raises(ProxyError): requests.get('http://localhost:1', proxies={'http': 'non-resolvable-address'}) + def test_proxy_error_on_bad_url(self, httpbin, httpbin_secure): + with pytest.raises(InvalidProxyURL): + requests.get(httpbin_secure(), proxies={'https': 'http:/badproxyurl:3128'}) + + with pytest.raises(InvalidProxyURL): + requests.get(httpbin(), proxies={'http': 'http://:8080'}) + + with pytest.raises(InvalidProxyURL): + requests.get(httpbin_secure(), proxies={'https': 'https://'}) + + with pytest.raises(InvalidProxyURL): + requests.get(httpbin(), proxies={'http': 'http:///example.com:8080'}) + def test_basicauth_with_netrc(self, httpbin): auth = ('user', 'pass') wrong_auth = ('wronguser', 'wrongpass')