|
33 | 33 | import re
|
34 | 34 | import types
|
35 | 35 | import warnings
|
| 36 | +import ipaddress |
36 | 37 |
|
37 | 38 | __all__ = ["urlparse", "urlunparse", "urljoin", "urldefrag",
|
38 | 39 | "urlsplit", "urlunsplit", "urlencode", "parse_qs",
|
@@ -199,7 +200,7 @@ def _hostinfo(self):
|
199 | 200 | _, _, hostinfo = netloc.rpartition('@')
|
200 | 201 | _, have_open_br, bracketed = hostinfo.partition('[')
|
201 | 202 | if have_open_br:
|
202 |
| - hostname, _, port = bracketed.partition(']') |
| 203 | + hostname, _, port = bracketed.rpartition(']') |
203 | 204 | _, _, port = port.partition(':')
|
204 | 205 | else:
|
205 | 206 | hostname, _, port = hostinfo.partition(':')
|
@@ -229,7 +230,7 @@ def _hostinfo(self):
|
229 | 230 | _, _, hostinfo = netloc.rpartition(b'@')
|
230 | 231 | _, have_open_br, bracketed = hostinfo.partition(b'[')
|
231 | 232 | if have_open_br:
|
232 |
| - hostname, _, port = bracketed.partition(b']') |
| 233 | + hostname, _, port = bracketed.rpartition(b']') |
233 | 234 | _, _, port = port.partition(b':')
|
234 | 235 | else:
|
235 | 236 | hostname, _, port = hostinfo.partition(b':')
|
@@ -426,6 +427,15 @@ def _checknetloc(netloc):
|
426 | 427 | if c in netloc2:
|
427 | 428 | raise ValueError("netloc '" + netloc + "' contains invalid " +
|
428 | 429 | "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") |
429 | 439 |
|
430 | 440 | # typed=True avoids BytesWarnings being emitted during cache key
|
431 | 441 | # comparison since this API supports both bytes and str input.
|
@@ -466,12 +476,14 @@ def urlsplit(url, scheme='', allow_fragments=True):
|
466 | 476 | break
|
467 | 477 | else:
|
468 | 478 | scheme, url = url[:i].lower(), url[i+1:]
|
469 |
| - |
470 | 479 | if url[:2] == '//':
|
471 | 480 | netloc, url = _splitnetloc(url, 2)
|
472 | 481 | if (('[' in netloc and ']' not in netloc) or
|
473 | 482 | (']' in netloc and '[' not in netloc)):
|
474 | 483 | 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) |
475 | 487 | if allow_fragments and '#' in url:
|
476 | 488 | url, fragment = url.split('#', 1)
|
477 | 489 | if '?' in url:
|
|
0 commit comments