GeezerVison is showing github.com — Support CIDR no_proxy entries · python/cpython@a6e2fa6
File treeExpand file treeCollapse file tree Open diff view settings Expand file treeCollapse file tree Open diff view settings Collapse file Copy file name to clipboardExpand all lines: Doc/library/urllib.request.rst +3-2Lines changed: 3 additions & 2 deletions - Display the source diff
- Display the rich diff
| Original file line number | Diff line number | Diff line change |
|---|
@@ -360,8 +360,9 @@ The following classes are provided: | 360 | 360 |
| 361 | 361 | The :envvar:`no_proxy` environment variable can be used to specify hosts | 362 | 362 | which shouldn't be reached via proxy; if set, it should be a comma-separated | 363 | | - list of hostname suffixes, optionally with ``:port`` appended, for example | 364 | | - ``cern.ch,ncsa.uiuc.edu,some.host:8080``. | | 363 | + list of hostname suffixes, optionally with ``:port`` appended, and IP | | 364 | + address CIDR ranges, for example | | 365 | + ``cern.ch,ncsa.uiuc.edu,some.host:8080,192.168.0.0/16``. | 365 | 366 |
| 366 | 367 | .. note:: | 367 | 368 |
| |
Collapse file Copy file name to clipboardExpand all lines: Lib/test/test_urllib.py +22Lines changed: 22 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -245,6 +245,28 @@ def test_proxy_bypass_environment_host_match(self): | 245 | 245 | self.assertFalse(bypass('newdomain.com')) # no port | 246 | 246 | self.assertFalse(bypass('newdomain.com:1235')) # wrong port | 247 | 247 |
| | 248 | + def test_proxy_bypass_environment_cidr_match(self): | | 249 | + bypass = urllib.request.proxy_bypass_environment | | 250 | + self.env.set('NO_PROXY', | | 251 | + '192.168.0.0/16, 2001:db8::/32, 172.16.1.1/24') | | 252 | + self.assertTrue(bypass('192.168.1.1')) | | 253 | + self.assertTrue(bypass('192.168.1.1:1234')) | | 254 | + self.assertTrue(bypass('2001:db8::1')) | | 255 | + self.assertTrue(bypass('[2001:db8::1]:1234')) | | 256 | + self.assertTrue(bypass('172.16.1.255')) | | 257 | + self.assertFalse(bypass('192.169.1.1')) | | 258 | + self.assertFalse(bypass('2001:db9::1')) | | 259 | + self.assertFalse(bypass('172.16.2.1')) | | 260 | + self.assertFalse(bypass('python.org')) | | 261 | + | | 262 | + def test_proxy_bypass_environment_invalid_cidr(self): | | 263 | + bypass = urllib.request.proxy_bypass_environment | | 264 | + self.env.set('NO_PROXY', | | 265 | + '192.168.0.0/33, 2001:db8::/129, anotherdomain.com') | | 266 | + self.assertFalse(bypass('192.168.1.1')) | | 267 | + self.assertFalse(bypass('2001:db8::1')) | | 268 | + self.assertTrue(bypass('anotherdomain.com')) | | 269 | + | 248 | 270 | def test_proxy_bypass_environment_always_match(self): | 249 | 271 | bypass = urllib.request.proxy_bypass_environment | 250 | 272 | self.env.set('NO_PROXY', '*') | |
Collapse file Copy file name to clipboardExpand all lines: Lib/urllib/request.py +29-2Lines changed: 29 additions & 2 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -1912,7 +1912,8 @@ def proxy_bypass_environment(host, proxies=None): | 1912 | 1912 | """Test if proxies should not be used for a particular host. | 1913 | 1913 |
| 1914 | 1914 | Checks the proxy dict for the value of no_proxy, which should | 1915 | | - be a list of comma separated DNS suffixes, or '*' for all hosts. | | 1915 | + be a list of comma separated DNS suffixes, IP address CIDR ranges, | | 1916 | + or '*' for all hosts. | 1916 | 1917 |
| 1917 | 1918 | """ | 1918 | 1919 | if proxies is None: | @@ -1928,14 +1929,40 @@ def proxy_bypass_environment(host, proxies=None): | 1928 | 1929 | host = host.lower() | 1929 | 1930 | # strip port off host | 1930 | 1931 | hostonly, port = _splitport(host) | 1931 | | - # check if the host ends with any of the DNS suffixes | | 1932 | + host_ip = None | | 1933 | + checked_host_ip = False | | 1934 | + # for every entry in no_proxy... | 1932 | 1935 | for name in no_proxy.split(','): | 1933 | 1936 | name = name.strip() | 1934 | 1937 | if name: | 1935 | 1938 | name = name.lstrip('.') # ignore leading dots | 1936 | 1939 | name = name.lower() | | 1940 | + | | 1941 | + # check for exact match | 1937 | 1942 | if hostonly == name or host == name: | 1938 | 1943 | return True | | 1944 | + | | 1945 | + # check if the IP is within CIDR range | | 1946 | + if '/' in name: | | 1947 | + if not checked_host_ip: | | 1948 | + from ipaddress import ip_address | | 1949 | + for candidate in (hostonly, host): | | 1950 | + candidate = candidate.strip('[]') | | 1951 | + try: | | 1952 | + host_ip = ip_address(candidate) | | 1953 | + break | | 1954 | + except ValueError: | | 1955 | + pass | | 1956 | + checked_host_ip = True | | 1957 | + if host_ip is not None: | | 1958 | + from ipaddress import ip_network | | 1959 | + try: | | 1960 | + if host_ip in ip_network(name, strict=False): | | 1961 | + return True | | 1962 | + except ValueError: | | 1963 | + pass | | 1964 | + | | 1965 | + # check if the host ends with any of the DNS suffixes | 1939 | 1966 | name = '.' + name | 1940 | 1967 | if hostonly.endswith(name) or host.endswith(name): | 1941 | 1968 | return True | |
|