8000 [3.9] gh-115197: Stop resolving host in urllib.request proxy bypass (… · python/cpython@fc2c98f · GitHub
[go: up one dir, main page]

Skip to content

Commit fc2c98f

Browse files
[3.9] gh-115197: Stop resolving host in urllib.request proxy bypass (GH-115210) (GH-116068)
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 <weii.wang@canonical.com>
1 parent 2007624 commit fc2c98f

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
@@ -12,10 +12,11 @@
1212
import subprocess
1313

1414
import urllib.request
15-
# The proxy bypass method imported below has logic specific to the OSX
16-
# proxy config data structure but is testable on all platforms.
15+
# The proxy bypass method imported below has logic specific to the
16+
# corresponding system but is testable on all platforms.
1717
from urllib.request import (Request, OpenerDirector, HTTPBasicAuthHandler,
1818
HTTPPasswordMgrWithPriorAuth, _parse_proxy,
19+
_proxy_bypass_winreg_override,
1920
_proxy_bypass_macosx_sysconf,
2021
AbstractDigestAuthHandler)
2122
from urllib.parse import urlparse
@@ -1441,6 +1442,30 @@ def test_proxy_https_proxy_authorization(self):
14411442
self.assertEqual(req.host, "proxy.example.com:3128")
14421443
self.assertEqual(req.get_header("Proxy-authorization"), "FooBar")
14431444

1445+
@unittest.skipUnless(os.name == "nt", "only relevant for Windows")
1446+
def test_winreg_proxy_bypass(self):
1447+
proxy_override = "www.example.com;*.example.net; 192.168.0.1"
1448+
proxy_bypass = _proxy_bypass_winreg_override
1449+
for host in ("www.example.com", "www.example.net", "192.168.0.1"):
1450+
self.assertTrue(proxy_bypass(host, proxy_override),
1451+
"expected bypass of %s to be true" % host)
1452+
1453+
for host in ("example.com", "www.example.org", "example.net",
1454+
"192.168.0.2"):
1455+
self.assertFalse(proxy_bypass(host, proxy_override),
1456+
"expected bypass of %s to be False" % host)
1457+
1458+
# check intranet address bypass
1459+
proxy_override = "example.com; <local>"
1460+
self.assertTrue(proxy_bypass("example.com", proxy_override),
1461+
"expected bypass of %s to be true" % host)
1462+
self.assertFalse(proxy_bypass("example.net", proxy_override),
1463+
"expected bypass of %s to be False" % host)
1464+
for host in ("test", "localhost"):
1465+
self.assertTrue(proxy_bypass(host, proxy_override),
1466+
"expect <local> to bypass intranet address '%s'"
1467+
% host)
1468+
14441469
@unittest.skipUnless(sys.platform == 'darwin', "only relevant for OSX")
14451470
def test_osx_proxy_bypass(self):
14461471
bypass = {

Lib/urllib/request.py

Lines changed: 35 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -2564,6 +2564,7 @@ def _proxy_bypass_macosx_sysconf(host, proxy_settings):
25642564
}
25652565
"""
25662566
from fnmatch import fnmatch
2567+
from ipaddress import AddressValueError, IPv4Address
25672568

25682569
hostonly, port = _splitport(host)
25692570

@@ -2580,20 +2581,17 @@ def ip2num(ipAddr):
25802581
return True
25812582

25822583
hostIP = None
2584+
try:
2585+
hostIP = int(IPv4Address(hostonly))
2586+
except AddressValueError:
2587+
pass
25832588

25842589
for value in proxy_settings.get('exceptions', ()):
25852590
# Items in the list are strings like these: *.local, 169.254/16
25862591
if not value: continue
25872592

25882593
m = re.match(r"(\d+(?:\.\d+)*)(/\d+)?", value)
2589-
if m is not None:
2590-
if hostIP is None:
2591-
try:
2592-
hostIP = socket.gethostbyname(hostonly)
2593-
hostIP = ip2num(hostIP)
2594-
except OSError:
2595-
continue
2596-
2594+
if m is not None and hostIP is not None:
25972595
base = ip2num(m.group(1))
25982596
mask = m.group(2)
25992597
if mask is None:
@@ -2616,6 +2614,31 @@ def ip2num(ipAddr):
26162614
return False
26172615

26182616

2617+
# Same as _proxy_bypass_macosx_sysconf, testable on all platforms
2618+
def _proxy_bypass_winreg_override(host, override):
2619+
"""Return True if the host should bypass the proxy server.
2620+
2621+
The proxy override list is obtained from the Windows
2622+
Internet settings proxy override registry value.
2623+
2624+
An example of a proxy override value is:
2625+
"www.example.com;*.example.net; 192.168.0.1"
2626+
"""
2627+
from fnmatch import fnmatch
2628+
2629+
host, _ = _splitport(host)
2630+
proxy_override = override.split(';')
2631+
for test in proxy_override:
2632+
test = test.strip()
2633+
# "<local>" should bypass the proxy server for all intranet addresses
2634+
if test == '<local>':
2635+
if '.' not in host:
2636+
return True
2637+
elif fnmatch(host, test):
2638+
return True
2639+
return False
2640+
2641+
26192642
if sys.platform == 'darwin':
26202643
from _scproxy import _get_proxy_settings, _get_proxies
26212644

@@ -2714,7 +2737,7 @@ def proxy_bypass_registry(host):
27142737
import winreg
27152738
except ImportError:
27162739
# Std modules, so should be around - but you never know!
2717-
return 0
2740+
return False
27182741
try:
27192742
internetSettings = winreg.OpenKey(winreg.HKEY_CURRENT_USER,
27202743
r'Software\Microsoft\Windows\CurrentVersion\Internet Settings')
@@ -2724,40 +2747,10 @@ def proxy_bypass_registry(host):
27242747
'ProxyOverride')[0])
27252748
# ^^^^ Returned as Unicode but problems if not converted to ASCII
27262749
except OSError:
2727-
return 0
2750+
return False
27282751
if not proxyEnable or not proxyOverride:
2729-
return 0
2730-
# try to make a host list from name and IP address.
2731-
rawHost, port = _splitport(host)
2732-
host = [rawHost]
2733-
try:
2734-
addr = socket.gethostbyname(rawHost)
2735-
if addr != rawHost:
2736-
host.append(addr)
2737-
except OSError:
2738-
pass
2739-
try:
2740-
fqdn = socket.getfqdn(rawHost)
2741-
if fqdn != rawHost:
2742-
host.append(fqdn)
2743-
except OSError:
2744-
pass
2745-
# make a check value list from the registry entry: replace the
2746-
# '<local>' string by the localhost entry and the corresponding
2747-
# canonical entry.
2748-
proxyOverride = proxyOverride.split(';')
2749-
# now check if we match one of the registry values.
2750-
for test in proxyOverride:
2751-
if test == '<local>':
2752-
if '.' not in rawHost:
2753-
return 1
2754-
test = test.replace(".", r"\.") # mask dots
2755-
test = test.replace("*", r".*") # change glob sequence
2756-
test = test.replace("?", r".") # change glob char
2757-
for val in host:
2758-
if re.match(test, val, re.I):
2759-
return 1
2760-
return 0
2752+
return False
2753+
return _proxy_bypass_winreg_override(host, proxyOverride)
27612754

27622755
def proxy_bypass(host):
27632756
"""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)
0