Skip to content

Commit dec637a

Browse files
[3.12] gh-115197: Stop resolving host in urllib.request proxy bypass (GH-115210)
gh-115197: Stop resolving host in urllib.request proxy bypass (GH-115210) Use of a proxy is intended to defer DNS for the hosts to the proxy itself, rather than a potential for information leak of the host doing DNS resolution itself for any reason. Proxy bypass lists are strictly name based. Most implementations of proxy support agree. (cherry picked from commit c43b26d) Co-authored-by: Weii Wang <[email protected]>
1 parent 91e680b commit dec637a

File tree

3 files changed

+64
-44
lines changed

3 files changed

+64
-44
lines changed

Lib/test/test_urllib2.py

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,11 @@
1414
import subprocess
1515

1616
import urllib.request
17-
# The proxy bypass method imported below has logic specific to the OSX
18-
# proxy config data structure but is testable on all platforms.
17+
# The proxy bypass method imported below has logic specific to the
18+
# corresponding system but is testable on all platforms.
1919
from urllib.request import (Request, OpenerDirector, HTTPBasicAuthHandler,
2020
HTTPPasswordMgrWithPriorAuth, _parse_proxy,
21+
_proxy_bypass_winreg_override,
2122
_proxy_bypass_macosx_sysconf,
2223
AbstractDigestAuthHandler)
2324
from urllib.parse import urlparse
@@ -1483,6 +1484,30 @@ def test_proxy_https_proxy_authorization(self):
14831484
self.assertEqual(req.host, "proxy.example.com:3128")
14841485
self.assertEqual(req.get_header("Proxy-authorization"), "FooBar")
14851486

1487+
@unittest.skipUnless(os.name == "nt", "only relevant for Windows")
1488+
def test_winreg_proxy_bypass(self):
1489+
proxy_override = "www.example.com;*.example.net; 192.168.0.1"
1490+
proxy_bypass = _proxy_bypass_winreg_override
1491+
for host in ("www.example.com", "www.example.net", "192.168.0.1"):
1492+
self.assertTrue(proxy_bypass(host, proxy_override),
1493+
"expected bypass of %s to be true" % host)
1494+
1495+
for host in ("example.com", "www.example.org", "example.net",
1496+
"192.168.0.2"):
1497+
self.assertFalse(proxy_bypass(host, proxy_override),
1498+
"expected bypass of %s to be False" % host)
1499+
1500+
# check intranet address bypass
1501+
proxy_override = "example.com; <local>"
1502+
self.assertTrue(proxy_bypass("example.com", proxy_override),
1503+
"expected bypass of %s to be true" % host)
1504+
self.assertFalse(proxy_bypass("example.net", proxy_override),
1505+
"expected bypass of %s to be False" % host)
1506+
for host in ("test", "localhost"):
1507+
self.assertTrue(proxy_bypass(host, proxy_override),
1508+
"expect <local> to bypass intranet address '%s'"
1509+
% host)
1510+
14861511
@unittest.skipUnless(sys.platform == 'darwin', "only relevant for OSX")
14871512
def test_osx_proxy_bypass(self):
14881513
bypass = {

Lib/urllib/request.py

Lines changed: 35 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -2589,6 +2589,7 @@ def _proxy_bypass_macosx_sysconf(host, proxy_settings):
25892589
}
25902590
"""
25912591
from fnmatch import fnmatch
2592+
from ipaddress import AddressValueError, IPv4Address
25922593

25932594
hostonly, port = _splitport(host)
25942595

@@ -2605,20 +2606,17 @@ def ip2num(ipAddr):
26052606
return True
26062607

26072608
hostIP = None
2609+
try:
2610+
hostIP = int(IPv4Address(hostonly))
2611+
except AddressValueError:
2612+
pass
26082613

26092614
for value in proxy_settings.get('exceptions', ()):
26102615
# Items in the list are strings like these: *.local, 169.254/16
26112616
if not value: continue
26122617

26132618
m = re.match(r"(\d+(?:\.\d+)*)(/\d+)?", value)
2614-
if m is not None:
2615-
if hostIP is None:
2616-
try:
2617-
hostIP = socket.gethostbyname(hostonly)
2618-
hostIP = ip2num(hostIP)
2619-
except OSError:
2620-
continue
2621-
2619+
if m is not None and hostIP is not None:
26222620
base = ip2num(m.group(1))
26232621
mask = m.group(2)
26242622
if mask is None:
@@ -2641,6 +2639,31 @@ def ip2num(ipAddr):
26412639
return False
26422640

26432641

2642+
# Same as _proxy_bypass_macosx_sysconf, testable on all platforms
2643+
def _proxy_bypass_winreg_override(host, override):
2644+
"""Return True if the host should bypass the proxy server.
2645+
2646+
The proxy override list is obtained from the Windows
2647+
Internet settings proxy override registry value.
2648+
2649+
An example of a proxy override value is:
2650+
"www.example.com;*.example.net; 192.168.0.1"
2651+
"""
2652+
from fnmatch import fnmatch
2653+
2654+
host, _ = _splitport(host)
2655+
proxy_override = override.split(';')
2656+
for test in proxy_override:
2657+
test = test.strip()
2658+
# "<local>" should bypass the proxy server for all intranet addresses
2659+
if test == '<local>':
2660+
if '.' not in host:
2661+
return True
2662+
elif fnmatch(host, test):
2663+
return True
2664+
return False
2665+
2666+
26442667
if sys.platform == 'darwin':
26452668
from _scproxy import _get_proxy_settings, _get_proxies
26462669

@@ -2739,7 +2762,7 @@ def proxy_bypass_registry(host):
27392762
import winreg
27402763
except ImportError:
27412764
# Std modules, so should be around - but you never know!
2742-
return 0
2765+
return False
27432766
try:
27442767
internetSettings = winreg.OpenKey(winreg.HKEY_CURRENT_USER,
27452768
r'Software\Microsoft\Windows\CurrentVersion\Internet Settings')
@@ -2749,40 +2772,10 @@ def proxy_bypass_registry(host):
27492772
'ProxyOverride')[0])
27502773
# ^^^^ Returned as Unicode but problems if not converted to ASCII
27512774
except OSError:
2752-
return 0
2775+
return False
27532776
if not proxyEnable or not proxyOverride:
2754-
return 0
2755-
# try to make a host list from name and IP address.
2756-
rawHost, port = _splitport(host)
2757-
host = [rawHost]
2758-
try:
2759-
addr = socket.gethostbyname(rawHost)
2760-
if addr != rawHost:
2761-
host.append(addr)
2762-
except OSError:
2763-
pass
2764-
try:
2765-
fqdn = socket.getfqdn(rawHost)
2766-
if fqdn != rawHost:
2767-
host.append(fqdn)
2768-
except OSError:
2769-
pass
2770-
# make a check value list from the registry entry: replace the
2771-
# '<local>' string by the localhost entry and the corresponding
2772-
# canonical entry.
2773-
proxyOverride = proxyOverride.split(';')
2774-
# now check if we match one of the registry values.
2775-
for test in proxyOverride:
2776-
if test == '<local>':
2777-
if '.' not in rawHost:
2778-
return 1
2779-
test = test.replace(".", r"\.") # mask dots
2780-
test = test.replace("*", r".*") # change glob sequence
2781-
test = test.replace("?", r".") # change glob char
2782-
for val in host:
2783-
if re.match(test, val, re.I):
2784-
return 1
2785-
return 0
2777+
return False
2778+
return _proxy_bypass_winreg_override(host, proxyOverride)
27862779

27872780
def proxy_bypass(host):
27882781
"""Return True, if host should be bypassed.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
``urllib.request`` no longer resolves the hostname before checking it
2+
against the system's proxy bypass list on macOS and Windows.

0 commit comments

Comments
 (0)