Skip to content

Commit 7f22368

Browse files
miss-islingtonJohnJamesUtleygpshead
authored andcommitted
00444: pythongh-103848: Adds checks to ensure that bracketed hosts found by urlsplit are of IPv6 or IPvFuture format (pythonGH-103849) (python#104349)
pythongh-103848: Adds checks to ensure that bracketed hosts found by urlsplit are of IPv6 or IPvFuture format (pythonGH-103849) * Adds checks to ensure that bracketed hosts found by urlsplit are of IPv6 or IPvFuture format --------- (cherry picked from commit 29f348e) Co-authored-by: JohnJamesUtley <[email protected]> Co-authored-by: Gregory P. Smith <[email protected]>
1 parent f20031e commit 7f22368

File tree

3 files changed

+52
-0
lines changed

3 files changed

+52
-0
lines changed

Lib/test/test_urlparse.py

+26
Original file line numberDiff line numberDiff line change
@@ -1076,6 +1076,32 @@ def test_issue14072(self):
10761076
self.assertEqual(p2.scheme, 'tel')
10771077
self.assertEqual(p2.path, '+31641044153')
10781078

1079+
def test_invalid_bracketed_hosts(self):
1080+
self.assertRaises(ValueError, urllib.parse.urlsplit, 'Scheme://user@[192.0.2.146]/Path?Query')
1081+
self.assertRaises(ValueError, urllib.parse.urlsplit, 'Scheme://user@[important.com:8000]/Path?Query')
1082+
self.assertRaises(ValueError, urllib.parse.urlsplit, 'Scheme://user@[v123r.IP]/Path?Query')
1083+
self.assertRaises(ValueError, urllib.parse.urlsplit, 'Scheme://user@[v12ae]/Path?Query')
1084+
self.assertRaises(ValueError, urllib.parse.urlsplit, 'Scheme://user@[v.IP]/Path?Query')
1085+
self.assertRaises(ValueError, urllib.parse.urlsplit, 'Scheme://user@[v123.]/Path?Query')
1086+
self.assertRaises(ValueError, urllib.parse.urlsplit, 'Scheme://user@[v]/Path?Query')
1087+
self.assertRaises(ValueError, urllib.parse.urlsplit, 'Scheme://user@[0439:23af::2309::fae7:1234]/Path?Query')
1088+
self.assertRaises(ValueError, urllib.parse.urlsplit, 'Scheme://user@[0439:23af:2309::fae7:1234:2342:438e:192.0.2.146]/Path?Query')
1089+
self.assertRaises(ValueError, urllib.parse.urlsplit, 'Scheme://user@]v6a.ip[/Path')
1090+
1091+
def test_splitting_bracketed_hosts(self):
1092+
p1 = urllib.parse.urlsplit('scheme://user@[v6a.ip]/path?query')
1093+
self.assertEqual(p1.hostname, 'v6a.ip')
1094+
self.assertEqual(p1.username, 'user')
1095+
self.assertEqual(p1.path, '/path')
1096+
p2 = urllib.parse.urlsplit('scheme://user@[0439:23af:2309::fae7%test]/path?query')
1097+
self.assertEqual(p2.hostname, '0439:23af:2309::fae7%test')
1098+
self.assertEqual(p2.username, 'user')
1099+
self.assertEqual(p2.path, '/path')
1100+
p3 = urllib.parse.urlsplit('scheme://user@[0439:23af:2309::fae7:1234:192.0.2.146%test]/path?query')
1101+
self.assertEqual(p3.hostname, '0439:23af:2309::fae7:1234:192.0.2.146%test')
1102+
self.assertEqual(p3.username, 'user')
1103+
self.assertEqual(p3.path, '/path')
1104+
10791105
def test_telurl_params(self):
10801106
p1 = urllib.parse.urlparse('tel:123-4;phone-context=+1-650-516')
10811107
self.assertEqual(p1.scheme, 'tel')

Lib/urllib/parse.py

+24
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,13 @@
3333

3434
import re
3535
import sys
36+
<<<<<<< HEAD
3637
import collections
38+
=======
39+
import types
40+
import warnings
41+
import ipaddress
42+
>>>>>>> b2171a2fd41 ([3.11] gh-103848: Adds checks to ensure that bracketed hosts found by urlsplit are of IPv6 or IPvFuture format (GH-103849) (#104349))
3743

3844
__all__ = ["urlparse", "urlunparse", "urljoin", "urldefrag",
3945
"urlsplit", "urlunsplit", "urlencode", "parse_qs",
@@ -425,6 +431,17 @@ def _remove_unsafe_bytes_from_url(url):
425431
url = url.replace(b, "")
426432
return url
427433

434+
# Valid bracketed hosts are defined in
435+
# https://www.rfc-editor.org/rfc/rfc3986#page-49 and https://url.spec.whatwg.org/
436+
def _check_bracketed_host(hostname):
437+
if hostname.startswith('v'):
438+
if not re.match(r"\Av[a-fA-F0-9]+\..+\Z", hostname):
439+
raise ValueError(f"IPvFuture address is invalid")
440+
else:
441+
ip = ipaddress.ip_address(hostname) # Throws Value Error if not IPv6 or IPv4
442+
if isinstance(ip, ipaddress.IPv4Address):
443+
raise ValueError(f"An IPv4 address cannot be in brackets")
444+
428445
def urlsplit(url, scheme='', allow_fragments=True):
429446
"""Parse a URL into 5 components:
430447
<scheme>://<netloc>/<path>?<query>#<fragment>
@@ -468,18 +485,25 @@ def urlsplit(url, scheme='', allow_fragments=True):
468485
if c not in scheme_chars:
469486
break
470487
else:
488+
<<<<<<< HEAD
471489
# make sure "url" is not actually a port number (in which case
472490
# "scheme" is really part of the path)
473491
rest = url[i+1:]
474492
if not rest or any(c not in '0123456789' for c in rest):
475493
# not a port number
476494
scheme, url = url[:i].lower(), rest
477495

496+
=======
497+
scheme, url = url[:i].lower(), url[i+1:]
498+
>>>>>>> b2171a2fd41 ([3.11] gh-103848: Adds checks to ensure that bracketed hosts found by urlsplit are of IPv6 or IPvFuture format (GH-103849) (#104349))
478499
if url[:2] == '//':
479500
netloc, url = _splitnetloc(url, 2)
480501
if (('[' in netloc and ']' not in netloc) or
481502
(']' in netloc and '[' not in netloc)):
482503
raise ValueError("Invalid IPv6 URL")
504+
if '[' in netloc and ']' in netloc:
505+
bracketed_host = netloc.partition('[')[2].partition(']')[0]
506+
_check_bracketed_host(bracketed_host)
483507
if allow_fragments and '#' in url:
484508
url, fragment = url.split('#', 1)
485509
if '?' in url:
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Add checks to ensure that ``[`` bracketed ``]`` hosts found by
2+
:func:`urllib.parse.urlsplit` are of IPv6 or IPvFuture format.

0 commit comments

Comments
 (0)