Skip to content

Commit 1c38e1f

Browse files
committed
proxy bypass on Windows without DNS lookups
1 parent 4f34446 commit 1c38e1f

File tree

3 files changed

+71
-14
lines changed

3 files changed

+71
-14
lines changed

requests/compat.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,9 @@
3737
# ---------
3838

3939
if is_py2:
40-
from urllib import quote, unquote, quote_plus, unquote_plus, urlencode, getproxies, proxy_bypass
40+
from urllib import (
41+
quote, unquote, quote_plus, unquote_plus, urlencode, getproxies,
42+
proxy_bypass, proxy_bypass_environment, getproxies_environment)
4143
from urlparse import urlparse, urlunparse, urljoin, urlsplit, urldefrag
4244
from urllib2 import parse_http_list
4345
import cookielib
@@ -54,7 +56,7 @@
5456

5557
elif is_py3:
5658
from urllib.parse import urlparse, urlunparse, urljoin, urlsplit, urlencode, quote, unquote, quote_plus, unquote_plus, urldefrag
57-
from urllib.request import parse_http_list, getproxies, proxy_bypass
59+
from urllib.request import parse_http_list, getproxies, proxy_bypass, proxy_bypass_environment, getproxies_environment
5860
from http import cookiejar as cookielib
5961
from http.cookies import Morsel
6062
from io import StringIO

requests/utils.py

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import contextlib
1515
import io
1616
import os
17+
import platform
1718
import re
1819
import socket
1920
import struct
@@ -26,7 +27,8 @@
2627
from .compat import parse_http_list as _parse_list_header
2728
from .compat import (
2829
quote, urlparse, bytes, str, OrderedDict, unquote, getproxies,
29-
proxy_bypass, urlunparse, basestring, integer_types)
30+
proxy_bypass, urlunparse, basestring, integer_types, is_py3,
31+
proxy_bypass_environment, getproxies_environment)
3032
from .cookies import cookiejar_from_dict
3133
from .structures import CaseInsensitiveDict
3234
from .exceptions import (
@@ -37,6 +39,54 @@
3739
DEFAULT_CA_BUNDLE_PATH = certs.where()
3840

3941

42+
if platform.system() == 'Windows':
43+
# provide a proxy_bypass version on Windows without DNS lookups
44+
45+
def proxy_bypass_registry(host):
46+
if is_py3:
47+
import winreg
48+
else:
49+
import _winreg as winreg
50+
try:
51+
internetSettings = winreg.OpenKey(winreg.HKEY_CURRENT_USER,
52+
r'Software\Microsoft\Windows\CurrentVersion\Internet Settings')
53+
proxyEnable = winreg.QueryValueEx(internetSettings,
54+
'ProxyEnable')[0]
55+
proxyOverride = winreg.QueryValueEx(internetSettings,
56+
'ProxyOverride')[0]
57+
except OSError:
58+
return False
59+
if not proxyEnable or not proxyOverride:
60+
return False
61+
62+
# make a check value list from the registry entry: replace the
63+
# '<local>' string by the localhost entry and the corresponding
64+
# canonical entry.
65+
proxyOverride = proxyOverride.split(';')
66+
# now check if we match one of the registry values.
67+
for test in proxyOverride:
68+
if test == '<local>':
69+
if '.' not in host:
70+
return True
71+
test = test.replace(".", r"\.") # mask dots
72+
test = test.replace("*", r".*") # change glob sequence
73+
test = test.replace("?", r".") # change glob char
74+
if re.match(test, host, re.I):
75+
return True
76+
return False
77+
78+
def proxy_bypass(host): # noqa
79+
"""Return True, if the host should be bypassed.
80+
81+
Checks proxy settings gathered from the environment, if specified,
82+
or the registry.
83+
"""
84+
if getproxies_environment():
85+
return proxy_bypass_environment(host)
86+
else:
87+
return proxy_bypass_registry(host)
88+
89+
4090
def dict_to_sequence(d):
4191
"""Returns an internal sequence dictionary update."""
4292

tests/test_utils.py

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -604,20 +604,25 @@ def test_should_bypass_proxies_no_proxy(
604604

605605
@pytest.mark.skipif(os.name != 'nt', reason='Test only on Windows')
606606
@pytest.mark.parametrize(
607-
'url, expected', (
608-
('http://192.168.0.1:5000/', True),
609-
('http://192.168.0.1/', True),
610-
('http://172.16.1.1/', True),
611-
('http://172.16.1.1:5000/', True),
612-
('http://localhost.localdomain:5000/v1.0/', True),
613-
('http://172.16.1.22/', False),
614-
('http://172.16.1.22:5000/', False),
615-
('http://google.com:5000/v1.0/', False),
607+
'url, expected, override', (
608+
('http://192.168.0.1:5000/', True, None),
609+
('http://192.168.0.1/', True, None),
610+
('http://172.16.1.1/', True, None),
611+
('http://172.16.1.1:5000/', True, None),
612+
('http://localhost.localdomain:5000/v1.0/', True, None),
613+
('http://172.16.1.22/', False, None),
614+
('http://172.16.1.22:5000/', False, None),
615+
('http://google.com:5000/v1.0/', False, None),
616+
('http://mylocalhostname:5000/v1.0/', True, '<local>'),
617+
('http://192.168.0.1/', False, ''),
616618
))
617-
def test_should_bypass_proxies_win_registry(url, expected, monkeypatch):
619+
def test_should_bypass_proxies_win_registry(url, expected, override,
620+
monkeypatch):
618621
"""Tests for function should_bypass_proxies to check if proxy
619622
can be bypassed or not with Windows registry settings
620623
"""
624+
if override is None:
625+
override = '192.168.*;127.0.0.1;localhost.localdomain;172.16.1.1'
621626
if compat.is_py3:
622627
import winreg
623628
else:
@@ -637,7 +642,7 @@ def QueryValueEx(key, value_name):
637642
if value_name == 'ProxyEnable':
638643
return [1]
639644
elif value_name == 'ProxyOverride':
640-
return ['192.168.*;127.0.0.1;localhost.localdomain;172.16.1.1']
645+
return [override]
641646

642647
monkeypatch.setenv('http_proxy', '')
643648
monkeypatch.setenv('https_proxy', '')

0 commit comments

Comments
 (0)