Skip to content

Commit ab6f221

Browse files
JohnJamesUtleygpshead
authored andcommitted
pythongh-103848: Adds checks to ensure that bracketed hosts found by urlsplit are of IPv6 or IPvFuture format (python#103849)
* Adds checks to ensure that bracketed hosts found by urlsplit are of IPv6 or IPvFuture format --------- Co-authored-by: Gregory P. Smith <[email protected]> (cherry picked from commit 29f348e)
1 parent 5f24da9 commit ab6f221

File tree

3 files changed

+43
-1
lines changed

3 files changed

+43
-1
lines changed

Lib/test/test_urlparse.py

+26
Original file line numberDiff line numberDiff line change
@@ -1138,6 +1138,32 @@ def test_issue14072(self):
11381138
self.assertEqual(p2.scheme, 'tel')
11391139
self.assertEqual(p2.path, '+31641044153')
11401140

1141+
def test_invalid_bracketed_hosts(self):
1142+
self.assertRaises(ValueError, urllib.parse.urlsplit, 'Scheme://user@[192.0.2.146]/Path?Query')
1143+
self.assertRaises(ValueError, urllib.parse.urlsplit, 'Scheme://user@[important.com:8000]/Path?Query')
1144+
self.assertRaises(ValueError, urllib.parse.urlsplit, 'Scheme://user@[v123r.IP]/Path?Query')
1145+
self.assertRaises(ValueError, urllib.parse.urlsplit, 'Scheme://user@[v12ae]/Path?Query')
1146+
self.assertRaises(ValueError, urllib.parse.urlsplit, 'Scheme://user@[v.IP]/Path?Query')
1147+
self.assertRaises(ValueError, urllib.parse.urlsplit, 'Scheme://user@[v123.]/Path?Query')
1148+
self.assertRaises(ValueError, urllib.parse.urlsplit, 'Scheme://user@[v]/Path?Query')
1149+
self.assertRaises(ValueError, urllib.parse.urlsplit, 'Scheme://user@[0439:23af::2309::fae7:1234]/Path?Query')
1150+
self.assertRaises(ValueError, urllib.parse.urlsplit, 'Scheme://user@[0439:23af:2309::fae7:1234:2342:438e:192.0.2.146]/Path?Query')
1151+
self.assertRaises(ValueError, urllib.parse.urlsplit, 'Scheme://user@]v6a.ip[/Path')
1152+
1153+
def test_splitting_bracketed_hosts(self):
1154+
p1 = urllib.parse.urlsplit('scheme://user@[v6a.ip]/path?query')
1155+
self.assertEqual(p1.hostname, 'v6a.ip')
1156+
self.assertEqual(p1.username, 'user')
1157+
self.assertEqual(p1.path, '/path')
1158+
p2 = urllib.parse.urlsplit('scheme://user@[0439:23af:2309::fae7%test]/path?query')
1159+
self.assertEqual(p2.hostname, '0439:23af:2309::fae7%test')
1160+
self.assertEqual(p2.username, 'user')
1161+
self.assertEqual(p2.path, '/path')
1162+
p3 = urllib.parse.urlsplit('scheme://user@[0439:23af:2309::fae7:1234:192.0.2.146%test]/path?query')
1163+
self.assertEqual(p3.hostname, '0439:23af:2309::fae7:1234:192.0.2.146%test')
1164+
self.assertEqual(p3.username, 'user')
1165+
self.assertEqual(p3.path, '/path')
1166+
11411167
def test_port_casting_failure_message(self):
11421168
message = "Port could not be cast to integer value as 'oracle'"
11431169
p1 = urllib.parse.urlparse('http://Server=sde; Service=sde:oracle')

Lib/urllib/parse.py

+15-1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import types
3737
import collections
3838
import warnings
39+
import ipaddress
3940

4041
__all__ = ["urlparse", "urlunparse", "urljoin", "urldefrag",
4142
"urlsplit", "urlunsplit", "urlencode", "parse_qs",
@@ -441,6 +442,17 @@ def _checknetloc(netloc):
441442
raise ValueError("netloc '" + netloc + "' contains invalid " +
442443
"characters under NFKC normalization")
443444

445+
# Valid bracketed hosts are defined in
446+
# https://www.rfc-editor.org/rfc/rfc3986#page-49 and https://url.spec.whatwg.org/
447+
def _check_bracketed_host(hostname):
448+
if hostname.startswith('v'):
449+
if not re.match(r"\Av[a-fA-F0-9]+\..+\Z", hostname):
450+
raise ValueError(f"IPvFuture address is invalid")
451+
else:
452+
ip = ipaddress.ip_address(hostname) # Throws Value Error if not IPv6 or IPv4
453+
if isinstance(ip, ipaddress.IPv4Address):
454+
raise ValueError(f"An IPv4 address cannot be in brackets")
455+
444456
def urlsplit(url, scheme='', allow_fragments=True):
445457
"""Parse a URL into 5 components:
446458
<scheme>://<netloc>/<path>?<query>#<fragment>
@@ -487,12 +499,14 @@ def urlsplit(url, scheme='', allow_fragments=True):
487499
break
488500
else:
489501
scheme, url = url[:i].lower(), url[i+1:]
490-
491502
if url[:2] == '//':
492503
netloc, url = _splitnetloc(url, 2)
493504
if (('[' in netloc and ']' not in netloc) or
494505
(']' in netloc and '[' not in netloc)):
495506
raise ValueError("Invalid IPv6 URL")
507+
if '[' in netloc and ']' in netloc:
508+
bracketed_host = netloc.partition('[')[2].partition(']')[0]
509+
_check_bracketed_host(bracketed_host)
496510
if allow_fragments and '#' in url:
497511
url, fragment = url.split('#', 1)
498512
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)