Skip to content

Commit df34308

Browse files
Adds checks to ensure that bracketed hosts found by urlsplit are of IPv6 or IPvFuture format
1 parent a44568b commit df34308

File tree

2 files changed

+38
-3
lines changed

2 files changed

+38
-3
lines changed

Diff for: Lib/test/test_urlparse.py

+23
Original file line numberDiff line numberDiff line change
@@ -1037,6 +1037,29 @@ def test_issue14072(self):
10371037
self.assertEqual(p2.scheme, 'tel')
10381038
self.assertEqual(p2.path, '+31641044153')
10391039

1040+
def test_splitting_bracketed_hosts(self):
1041+
self.assertRaises(ValueError, urllib.parse.urlsplit, 'Scheme://user@[192.0.2.146]/Path?Query')
1042+
self.assertRaises(ValueError, urllib.parse.urlsplit, 'Scheme://user@[important.com:8000]/Path?Query')
1043+
self.assertRaises(ValueError, urllib.parse.urlsplit, 'Scheme://user@[v123r.IP]/Path?Query')
1044+
self.assertRaises(ValueError, urllib.parse.urlsplit, 'Scheme://user@[v12ae]/Path?Query')
1045+
self.assertRaises(ValueError, urllib.parse.urlsplit, 'Scheme://user@[v.IP]/Path?Query')
1046+
self.assertRaises(ValueError, urllib.parse.urlsplit, 'Scheme://user@[v123.]/Path?Query')
1047+
self.assertRaises(ValueError, urllib.parse.urlsplit, 'Scheme://user@[v]/Path?Query')
1048+
self.assertRaises(ValueError, urllib.parse.urlsplit, 'Scheme://user@[0439:23af::2309::fae7:1234]/Path?Query')
1049+
self.assertRaises(ValueError, urllib.parse.urlsplit, 'Scheme://user@[0439:23af:2309::fae7:1234:2342:438e:192.0.2.146]/Path?Query')
1050+
p1 = urllib.parse.urlsplit('scheme://user@[v6a.ip]/path?query')
1051+
self.assertEqual(p1.hostname, 'v6a.ip')
1052+
self.assertEqual(p1.username, 'user')
1053+
self.assertEqual(p1.path, '/path')
1054+
p2 = urllib.parse.urlsplit('scheme://user@[0439:23af:2309::fae7%test]/path?query')
1055+
self.assertEqual(p2.hostname, '0439:23af:2309::fae7%test')
1056+
self.assertEqual(p2.username, 'user')
1057+
self.assertEqual(p2.path, '/path')
1058+
p3 = urllib.parse.urlsplit('scheme://user@[0439:23af:2309::fae7:1234:192.0.2.146%test]/path?query')
1059+
self.assertEqual(p3.hostname, '0439:23af:2309::fae7:1234:192.0.2.146%test')
1060+
self.assertEqual(p3.username, 'user')
1061+
self.assertEqual(p3.path, '/path')
1062+
10401063
def test_port_casting_failure_message(self):
10411064
message = "Port could not be cast to integer value as 'oracle'"
10421065
p1 = urllib.parse.urlparse('http://Server=sde; Service=sde:oracle')

Diff for: Lib/urllib/parse.py

+15-3
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import re
3434
import types
3535
import warnings
36+
import ipaddress
3637

3738
__all__ = ["urlparse", "urlunparse", "urljoin", "urldefrag",
3839
"urlsplit", "urlunsplit", "urlencode", "parse_qs",
@@ -199,7 +200,7 @@ def _hostinfo(self):
199200
_, _, hostinfo = netloc.rpartition('@')
200201
_, have_open_br, bracketed = hostinfo.partition('[')
201202
if have_open_br:
202-
hostname, _, port = bracketed.partition(']')
203+
hostname, _, port = bracketed.rpartition(']')
203204
_, _, port = port.partition(':')
204205
else:
205206
hostname, _, port = hostinfo.partition(':')
@@ -229,7 +230,7 @@ def _hostinfo(self):
229230
_, _, hostinfo = netloc.rpartition(b'@')
230231
_, have_open_br, bracketed = hostinfo.partition(b'[')
231232
if have_open_br:
232-
hostname, _, port = bracketed.partition(b']')
233+
hostname, _, port = bracketed.rpartition(b']')
233234
_, _, port = port.partition(b':')
234235
else:
235236
hostname, _, port = hostinfo.partition(b':')
@@ -426,6 +427,15 @@ def _checknetloc(netloc):
426427
if c in netloc2:
427428
raise ValueError("netloc '" + netloc + "' contains invalid " +
428429
"characters under NFKC normalization")
430+
431+
def _check_bracketed_host(hostname):
432+
if hostname.startswith('v'):
433+
if not re.match(r"\Av[a-fA-F0-9]+\..+\Z", hostname):
434+
raise ValueError(f"IPvFuture address is invalid")
435+
else:
436+
ip = ipaddress.ip_address(hostname) # Throws Value Error if not IPv6 or IPv4
437+
if isinstance(ip, ipaddress.IPv4Address):
438+
raise ValueError(f"An IPv4 address cannot be in brackets")
429439

430440
# typed=True avoids BytesWarnings being emitted during cache key
431441
# comparison since this API supports both bytes and str input.
@@ -466,12 +476,14 @@ def urlsplit(url, scheme='', allow_fragments=True):
466476
break
467477
else:
468478
scheme, url = url[:i].lower(), url[i+1:]
469-
470479
if url[:2] == '//':
471480
netloc, url = _splitnetloc(url, 2)
472481
if (('[' in netloc and ']' not in netloc) or
473482
(']' in netloc and '[' not in netloc)):
474483
raise ValueError("Invalid IPv6 URL")
484+
if '[' in netloc and ']' in netloc:
485+
bracketed_host = netloc.partition('[')[2].rpartition(']')[0]
486+
_check_bracketed_host(bracketed_host)
475487
if allow_fragments and '#' in url:
476488
url, fragment = url.split('#', 1)
477489
if '?' in url:

0 commit comments

Comments
 (0)