diff --git a/requests/sessions.py b/requests/sessions.py index 72ef179f..bc67334a 100644 --- a/requests/sessions.py +++ b/requests/sessions.py @@ -236,13 +236,16 @@ class SessionRedirectMixin(object): :rtype: dict """ + proxies = proxies if proxies is not None else {} headers = prepared_request.headers url = prepared_request.url scheme = urlparse(url).scheme - new_proxies = proxies.copy() if proxies is not None else {} + new_proxies = proxies.copy() + no_proxy = proxies.get('no_proxy') - if self.trust_env and not should_bypass_proxies(url): - environ_proxies = get_environ_proxies(url) + bypass_proxy = should_bypass_proxies(url, no_proxy=no_proxy) + if self.trust_env and not bypass_proxy: + environ_proxies = get_environ_proxies(url, no_proxy=no_proxy) proxy = environ_proxies.get(scheme, environ_proxies.get('all')) @@ -656,7 +659,8 @@ class Session(SessionRedirectMixin): # Gather clues from the surrounding environment. if self.trust_env: # Set environment's proxies. - env_proxies = get_environ_proxies(url) or {} + no_proxy = proxies.get('no_proxy') if proxies is not None else None + env_proxies = get_environ_proxies(url, no_proxy=no_proxy) for (k, v) in env_proxies.items(): proxies.setdefault(k, v) diff --git a/requests/utils.py b/requests/utils.py index 47325090..e5ecd350 100644 --- a/requests/utils.py +++ b/requests/utils.py @@ -11,6 +11,7 @@ that are also useful for external consumption. import cgi import codecs import collections +import contextlib import io import os import re @@ -554,7 +555,29 @@ def is_valid_cidr(string_network): return True -def should_bypass_proxies(url): +@contextlib.contextmanager +def set_environ(env_name, value): + """Set the environment variable 'env_name' to 'value' + + Save previous value, yield, and then restore the previous value stored in + the environment variable 'env_name'. + + If 'value' is None, do nothing""" + if value is not None: + old_value = os.environ.get(env_name) + os.environ[env_name] = value + try: + yield + finally: + if value is None: + return + if old_value is None: + del os.environ[env_name] + else: + os.environ[env_name] = old_value + + +def should_bypass_proxies(url, no_proxy): """ Returns whether we should bypass proxies or not. @@ -564,7 +587,9 @@ def should_bypass_proxies(url): # First check whether no_proxy is defined. If it is, check that the URL # we're getting isn't in the no_proxy list. - no_proxy = get_proxy('no_proxy') + no_proxy_arg = no_proxy + if no_proxy is None: + no_proxy = get_proxy('no_proxy') netloc = urlparse(url).netloc if no_proxy: @@ -597,10 +622,11 @@ def should_bypass_proxies(url): # of Python 2.6, so allow this call to fail. Only catch the specific # exceptions we've seen, though: this call failing in other ways can reveal # legitimate problems. - try: - bypass = proxy_bypass(netloc) - except (TypeError, socket.gaierror): - bypass = False + with set_environ('no_proxy', no_proxy_arg): + try: + bypass = proxy_bypass(netloc) + except (TypeError, socket.gaierror): + bypass = False if bypass: return True @@ -608,13 +634,13 @@ def should_bypass_proxies(url): return False -def get_environ_proxies(url): +def get_environ_proxies(url, no_proxy): """ Return a dict of environment proxies. :rtype: dict """ - if should_bypass_proxies(url): + if should_bypass_proxies(url, no_proxy=no_proxy): return {} else: return getproxies() diff --git a/tests/test_utils.py b/tests/test_utils.py index 1edf6218..11ebf617 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -161,7 +161,7 @@ class TestGetEnvironProxies: 'http://localhost.localdomain:5000/v1.0/', )) def test_bypass(self, url): - assert get_environ_proxies(url) == {} + assert get_environ_proxies(url, no_proxy=None) == {} @pytest.mark.parametrize( 'url', ( @@ -170,7 +170,32 @@ class TestGetEnvironProxies: 'http://www.requests.com/', )) def test_not_bypass(self, url): - assert get_environ_proxies(url) != {} + assert get_environ_proxies(url, no_proxy=None) != {} + + @pytest.mark.parametrize( + 'url', ( + 'http://192.168.1.1:5000/', + 'http://192.168.1.1/', + 'http://www.requests.com/', + )) + def test_bypass_no_proxy_keyword(self, url): + no_proxy = '192.168.1.1,requests.com' + assert get_environ_proxies(url, no_proxy=no_proxy) == {} + + @pytest.mark.parametrize( + 'url', ( + 'http://192.168.0.1:5000/', + 'http://192.168.0.1/', + 'http://172.16.1.1/', + 'http://172.16.1.1:5000/', + 'http://localhost.localdomain:5000/v1.0/', + )) + def test_not_bypass_no_proxy_keyword(self, url, monkeypatch): + # This is testing that the 'no_proxy' argument overrides the + # environment variable 'no_proxy' + monkeypatch.setenv('http_proxy', 'http://proxy.example.com:3128/') + no_proxy = '192.168.1.1,requests.com' + assert get_environ_proxies(url, no_proxy=no_proxy) != {} class TestIsIPv4Address: @@ -525,7 +550,7 @@ def test_should_bypass_proxies(url, expected, monkeypatch): """ monkeypatch.setenv('no_proxy', '192.168.0.0/24,127.0.0.1,localhost.localdomain,172.16.1.1') monkeypatch.setenv('NO_PROXY', '192.168.0.0/24,127.0.0.1,localhost.localdomain,172.16.1.1') - assert should_bypass_proxies(url) == expected + assert should_bypass_proxies(url, no_proxy=None) == expected @pytest.mark.parametrize( @@ -553,3 +578,24 @@ def test_add_dict_to_cookiejar(cookiejar): ) def test_unicode_is_ascii(value, expected): assert unicode_is_ascii(value) is expected + + +@pytest.mark.parametrize( + 'url, expected', ( + ('http://192.168.0.1:5000/', True), + ('http://192.168.0.1/', True), + ('http://172.16.1.1/', True), + ('http://172.16.1.1:5000/', True), + ('http://localhost.localdomain:5000/v1.0/', True), + ('http://172.16.1.12/', False), + ('http://172.16.1.12:5000/', False), + ('http://google.com:5000/v1.0/', False), + )) +def test_should_bypass_proxies_no_proxy( + url, expected, monkeypatch): + """Tests for function should_bypass_proxies to check if proxy + can be bypassed or not using the 'no_proxy' argument + """ + no_proxy = '192.168.0.0/24,127.0.0.1,localhost.localdomain,172.16.1.1' + # Test 'no_proxy' argument + assert should_bypass_proxies(url, no_proxy=no_proxy) == expected