parent
cfce98bdaa
commit
3a8211ae5f
@ -0,0 +1,281 @@
|
|||||||
|
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||||
|
From: Victor Stinner <vstinner@python.org>
|
||||||
|
Date: Fri, 1 Nov 2024 14:11:47 +0100
|
||||||
|
Subject: [PATCH] 00443: gh-124651: Quote template strings in `venv` activation
|
||||||
|
scripts
|
||||||
|
|
||||||
|
(cherry picked from 3.9)
|
||||||
|
---
|
||||||
|
Lib/test/test_venv.py | 82 +++++++++++++++++++
|
||||||
|
Lib/venv/__init__.py | 42 ++++++++--
|
||||||
|
Lib/venv/scripts/common/activate | 8 +-
|
||||||
|
Lib/venv/scripts/posix/activate.csh | 8 +-
|
||||||
|
Lib/venv/scripts/posix/activate.fish | 8 +-
|
||||||
|
...-09-28-02-03-04.gh-issue-124651.bLBGtH.rst | 1 +
|
||||||
|
6 files changed, 132 insertions(+), 17 deletions(-)
|
||||||
|
create mode 100644 Misc/NEWS.d/next/Library/2024-09-28-02-03-04.gh-issue-124651.bLBGtH.rst
|
||||||
|
|
||||||
|
diff --git a/Lib/test/test_venv.py b/Lib/test/test_venv.py
|
||||||
|
index 842470fef0..67fdcd86bb 100644
|
||||||
|
--- a/Lib/test/test_venv.py
|
||||||
|
+++ b/Lib/test/test_venv.py
|
||||||
|
@@ -13,6 +13,8 @@ import struct
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
+import shlex
|
||||||
|
+import shutil
|
||||||
|
from test.support import (captured_stdout, captured_stderr, requires_zlib,
|
||||||
|
can_symlink, EnvironmentVarGuard, rmtree)
|
||||||
|
import unittest
|
||||||
|
@@ -80,6 +82,10 @@ class BaseTest(unittest.TestCase):
|
||||||
|
result = f.read()
|
||||||
|
return result
|
||||||
|
|
||||||
|
+ def assertEndsWith(self, string, tail):
|
||||||
|
+ if not string.endswith(tail):
|
||||||
|
+ self.fail(f"String {string!r} does not end with {tail!r}")
|
||||||
|
+
|
||||||
|
class BasicTest(BaseTest):
|
||||||
|
"""Test venv module functionality."""
|
||||||
|
|
||||||
|
@@ -293,6 +299,82 @@ class BasicTest(BaseTest):
|
||||||
|
'import sys; print(sys.executable)'])
|
||||||
|
self.assertEqual(out.strip(), envpy.encode())
|
||||||
|
|
||||||
|
+ # gh-124651: test quoted strings
|
||||||
|
+ @unittest.skipIf(os.name == 'nt', 'contains invalid characters on Windows')
|
||||||
|
+ def test_special_chars_bash(self):
|
||||||
|
+ """
|
||||||
|
+ Test that the template strings are quoted properly (bash)
|
||||||
|
+ """
|
||||||
|
+ rmtree(self.env_dir)
|
||||||
|
+ bash = shutil.which('bash')
|
||||||
|
+ if bash is None:
|
||||||
|
+ self.skipTest('bash required for this test')
|
||||||
|
+ env_name = '"\';&&$e|\'"'
|
||||||
|
+ env_dir = os.path.join(os.path.realpath(self.env_dir), env_name)
|
||||||
|
+ builder = venv.EnvBuilder(clear=True)
|
||||||
|
+ builder.create(env_dir)
|
||||||
|
+ activate = os.path.join(env_dir, self.bindir, 'activate')
|
||||||
|
+ test_script = os.path.join(self.env_dir, 'test_special_chars.sh')
|
||||||
|
+ with open(test_script, "w") as f:
|
||||||
|
+ f.write(f'source {shlex.quote(activate)}\n'
|
||||||
|
+ 'python -c \'import sys; print(sys.executable)\'\n'
|
||||||
|
+ 'python -c \'import os; print(os.environ["VIRTUAL_ENV"])\'\n'
|
||||||
|
+ 'deactivate\n')
|
||||||
|
+ out, err = check_output([bash, test_script])
|
||||||
|
+ lines = out.splitlines()
|
||||||
|
+ self.assertTrue(env_name.encode() in lines[0])
|
||||||
|
+ self.assertEndsWith(lines[1], env_name.encode())
|
||||||
|
+
|
||||||
|
+ # gh-124651: test quoted strings
|
||||||
|
+ @unittest.skipIf(os.name == 'nt', 'contains invalid characters on Windows')
|
||||||
|
+ def test_special_chars_csh(self):
|
||||||
|
+ """
|
||||||
|
+ Test that the template strings are quoted properly (csh)
|
||||||
|
+ """
|
||||||
|
+ rmtree(self.env_dir)
|
||||||
|
+ csh = shutil.which('tcsh') or shutil.which('csh')
|
||||||
|
+ if csh is None:
|
||||||
|
+ self.skipTest('csh required for this test')
|
||||||
|
+ env_name = '"\';&&$e|\'"'
|
||||||
|
+ env_dir = os.path.join(os.path.realpath(self.env_dir), env_name)
|
||||||
|
+ builder = venv.EnvBuilder(clear=True)
|
||||||
|
+ builder.create(env_dir)
|
||||||
|
+ activate = os.path.join(env_dir, self.bindir, 'activate.csh')
|
||||||
|
+ test_script = os.path.join(self.env_dir, 'test_special_chars.csh')
|
||||||
|
+ with open(test_script, "w") as f:
|
||||||
|
+ f.write(f'source {shlex.quote(activate)}\n'
|
||||||
|
+ 'python -c \'import sys; print(sys.executable)\'\n'
|
||||||
|
+ 'python -c \'import os; print(os.environ["VIRTUAL_ENV"])\'\n'
|
||||||
|
+ 'deactivate\n')
|
||||||
|
+ out, err = check_output([csh, test_script])
|
||||||
|
+ lines = out.splitlines()
|
||||||
|
+ self.assertTrue(env_name.encode() in lines[0])
|
||||||
|
+ self.assertEndsWith(lines[1], env_name.encode())
|
||||||
|
+
|
||||||
|
+ # gh-124651: test quoted strings on Windows
|
||||||
|
+ @unittest.skipUnless(os.name == 'nt', 'only relevant on Windows')
|
||||||
|
+ def test_special_chars_windows(self):
|
||||||
|
+ """
|
||||||
|
+ Test that the template strings are quoted properly on Windows
|
||||||
|
+ """
|
||||||
|
+ rmtree(self.env_dir)
|
||||||
|
+ env_name = "'&&^$e"
|
||||||
|
+ env_dir = os.path.join(os.path.realpath(self.env_dir), env_name)
|
||||||
|
+ builder = venv.EnvBuilder(clear=True)
|
||||||
|
+ builder.create(env_dir)
|
||||||
|
+ activate = os.path.join(env_dir, self.bindir, 'activate.bat')
|
||||||
|
+ test_batch = os.path.join(self.env_dir, 'test_special_chars.bat')
|
||||||
|
+ with open(test_batch, "w") as f:
|
||||||
|
+ f.write('@echo off\n'
|
||||||
|
+ f'"{activate}" & '
|
||||||
|
+ f'{self.exe} -c "import sys; print(sys.executable)" & '
|
||||||
|
+ f'{self.exe} -c "import os; print(os.environ[\'VIRTUAL_ENV\'])" & '
|
||||||
|
+ 'deactivate')
|
||||||
|
+ out, err = check_output([test_batch])
|
||||||
|
+ lines = out.splitlines()
|
||||||
|
+ self.assertTrue(env_name.encode() in lines[0])
|
||||||
|
+ self.assertEndsWith(lines[1], env_name.encode())
|
||||||
|
+
|
||||||
|
@unittest.skipUnless(os.name == 'nt', 'only relevant on Windows')
|
||||||
|
def test_unicode_in_batch_file(self):
|
||||||
|
"""
|
||||||
|
diff --git a/Lib/venv/__init__.py b/Lib/venv/__init__.py
|
||||||
|
index 716129d139..0c44dfd07d 100644
|
||||||
|
--- a/Lib/venv/__init__.py
|
||||||
|
+++ b/Lib/venv/__init__.py
|
||||||
|
@@ -10,6 +10,7 @@ import shutil
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import types
|
||||||
|
+import shlex
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@@ -280,11 +281,41 @@ class EnvBuilder:
|
||||||
|
:param context: The information for the environment creation request
|
||||||
|
being processed.
|
||||||
|
"""
|
||||||
|
- text = text.replace('__VENV_DIR__', context.env_dir)
|
||||||
|
- text = text.replace('__VENV_NAME__', context.env_name)
|
||||||
|
- text = text.replace('__VENV_PROMPT__', context.prompt)
|
||||||
|
- text = text.replace('__VENV_BIN_NAME__', context.bin_name)
|
||||||
|
- text = text.replace('__VENV_PYTHON__', context.env_exe)
|
||||||
|
+ replacements = {
|
||||||
|
+ '__VENV_DIR__': context.env_dir,
|
||||||
|
+ '__VENV_NAME__': context.env_name,
|
||||||
|
+ '__VENV_PROMPT__': context.prompt,
|
||||||
|
+ '__VENV_BIN_NAME__': context.bin_name,
|
||||||
|
+ '__VENV_PYTHON__': context.env_exe,
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ def quote_ps1(s):
|
||||||
|
+ """
|
||||||
|
+ This should satisfy PowerShell quoting rules [1], unless the quoted
|
||||||
|
+ string is passed directly to Windows native commands [2].
|
||||||
|
+ [1]: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_quoting_rules
|
||||||
|
+ [2]: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_parsing#passing-arguments-that-contain-quote-characters
|
||||||
|
+ """
|
||||||
|
+ s = s.replace("'", "''")
|
||||||
|
+ return f"'{s}'"
|
||||||
|
+
|
||||||
|
+ def quote_bat(s):
|
||||||
|
+ return s
|
||||||
|
+
|
||||||
|
+ # gh-124651: need to quote the template strings properly
|
||||||
|
+ quote = shlex.quote
|
||||||
|
+ script_path = context.script_path
|
||||||
|
+ if script_path.endswith('.ps1'):
|
||||||
|
+ quote = quote_ps1
|
||||||
|
+ elif script_path.endswith('.bat'):
|
||||||
|
+ quote = quote_bat
|
||||||
|
+ else:
|
||||||
|
+ # fallbacks to POSIX shell compliant quote
|
||||||
|
+ quote = shlex.quote
|
||||||
|
+
|
||||||
|
+ replacements = {key: quote(s) for key, s in replacements.items()}
|
||||||
|
+ for key, quoted in replacements.items():
|
||||||
|
+ text = text.replace(key, quoted)
|
||||||
|
return text
|
||||||
|
|
||||||
|
def install_scripts(self, context, path):
|
||||||
|
@@ -321,6 +352,7 @@ class EnvBuilder:
|
||||||
|
with open(srcfile, 'rb') as f:
|
||||||
|
data = f.read()
|
||||||
|
if not srcfile.endswith('.exe'):
|
||||||
|
+ context.script_path = srcfile
|
||||||
|
try:
|
||||||
|
data = data.decode('utf-8')
|
||||||
|
data = self.replace_variables(data, context)
|
||||||
|
diff --git a/Lib/venv/scripts/common/activate b/Lib/venv/scripts/common/activate
|
||||||
|
index fff0765af5..c2e2f968fa 100644
|
||||||
|
--- a/Lib/venv/scripts/common/activate
|
||||||
|
+++ b/Lib/venv/scripts/common/activate
|
||||||
|
@@ -37,11 +37,11 @@ deactivate () {
|
||||||
|
# unset irrelevant variables
|
||||||
|
deactivate nondestructive
|
||||||
|
|
||||||
|
-VIRTUAL_ENV="__VENV_DIR__"
|
||||||
|
+VIRTUAL_ENV=__VENV_DIR__
|
||||||
|
export VIRTUAL_ENV
|
||||||
|
|
||||||
|
_OLD_VIRTUAL_PATH="$PATH"
|
||||||
|
-PATH="$VIRTUAL_ENV/__VENV_BIN_NAME__:$PATH"
|
||||||
|
+PATH="$VIRTUAL_ENV/"__VENV_BIN_NAME__":$PATH"
|
||||||
|
export PATH
|
||||||
|
|
||||||
|
# unset PYTHONHOME if set
|
||||||
|
@@ -54,8 +54,8 @@ fi
|
||||||
|
|
||||||
|
if [ -z "${VIRTUAL_ENV_DISABLE_PROMPT:-}" ] ; then
|
||||||
|
_OLD_VIRTUAL_PS1="${PS1:-}"
|
||||||
|
- if [ "x__VENV_PROMPT__" != x ] ; then
|
||||||
|
- PS1="__VENV_PROMPT__${PS1:-}"
|
||||||
|
+ if [ "x"__VENV_PROMPT__ != x ] ; then
|
||||||
|
+ PS1=__VENV_PROMPT__"${PS1:-}"
|
||||||
|
else
|
||||||
|
if [ "`basename \"$VIRTUAL_ENV\"`" = "__" ] ; then
|
||||||
|
# special case for Aspen magic directories
|
||||||
|
diff --git a/Lib/venv/scripts/posix/activate.csh b/Lib/venv/scripts/posix/activate.csh
|
||||||
|
index b0c7028a92..0e90d54008 100644
|
||||||
|
--- a/Lib/venv/scripts/posix/activate.csh
|
||||||
|
+++ b/Lib/venv/scripts/posix/activate.csh
|
||||||
|
@@ -8,17 +8,17 @@ alias deactivate 'test $?_OLD_VIRTUAL_PATH != 0 && setenv PATH "$_OLD_VIRTUAL_PA
|
||||||
|
# Unset irrelevant variables.
|
||||||
|
deactivate nondestructive
|
||||||
|
|
||||||
|
-setenv VIRTUAL_ENV "__VENV_DIR__"
|
||||||
|
+setenv VIRTUAL_ENV __VENV_DIR__
|
||||||
|
|
||||||
|
set _OLD_VIRTUAL_PATH="$PATH"
|
||||||
|
-setenv PATH "$VIRTUAL_ENV/__VENV_BIN_NAME__:$PATH"
|
||||||
|
+setenv PATH "$VIRTUAL_ENV/"__VENV_BIN_NAME__":$PATH"
|
||||||
|
|
||||||
|
|
||||||
|
set _OLD_VIRTUAL_PROMPT="$prompt"
|
||||||
|
|
||||||
|
if (! "$?VIRTUAL_ENV_DISABLE_PROMPT") then
|
||||||
|
- if ("__VENV_NAME__" != "") then
|
||||||
|
- set env_name = "__VENV_NAME__"
|
||||||
|
+ if (__VENV_NAME__ != "") then
|
||||||
|
+ set env_name = __VENV_NAME__
|
||||||
|
else
|
||||||
|
if (`basename "VIRTUAL_ENV"` == "__") then
|
||||||
|
# special case for Aspen magic directories
|
||||||
|
diff --git a/Lib/venv/scripts/posix/activate.fish b/Lib/venv/scripts/posix/activate.fish
|
||||||
|
index 4d4f0bd7a4..0407f9c7be 100644
|
||||||
|
--- a/Lib/venv/scripts/posix/activate.fish
|
||||||
|
+++ b/Lib/venv/scripts/posix/activate.fish
|
||||||
|
@@ -29,10 +29,10 @@ end
|
||||||
|
# unset irrelevant variables
|
||||||
|
deactivate nondestructive
|
||||||
|
|
||||||
|
-set -gx VIRTUAL_ENV "__VENV_DIR__"
|
||||||
|
+set -gx VIRTUAL_ENV __VENV_DIR__
|
||||||
|
|
||||||
|
set -gx _OLD_VIRTUAL_PATH $PATH
|
||||||
|
-set -gx PATH "$VIRTUAL_ENV/__VENV_BIN_NAME__" $PATH
|
||||||
|
+set -gx PATH "$VIRTUAL_ENV/"__VENV_BIN_NAME__ $PATH
|
||||||
|
|
||||||
|
# unset PYTHONHOME if set
|
||||||
|
if set -q PYTHONHOME
|
||||||
|
@@ -52,8 +52,8 @@ if test -z "$VIRTUAL_ENV_DISABLE_PROMPT"
|
||||||
|
set -l old_status $status
|
||||||
|
|
||||||
|
# Prompt override?
|
||||||
|
- if test -n "__VENV_PROMPT__"
|
||||||
|
- printf "%s%s" "__VENV_PROMPT__" (set_color normal)
|
||||||
|
+ if test -n __VENV_PROMPT__
|
||||||
|
+ printf "%s%s" __VENV_PROMPT__ (set_color normal)
|
||||||
|
else
|
||||||
|
# ...Otherwise, prepend env
|
||||||
|
set -l _checkbase (basename "$VIRTUAL_ENV")
|
||||||
|
diff --git a/Misc/NEWS.d/next/Library/2024-09-28-02-03-04.gh-issue-124651.bLBGtH.rst b/Misc/NEWS.d/next/Library/2024-09-28-02-03-04.gh-issue-124651.bLBGtH.rst
|
||||||
|
new file mode 100644
|
||||||
|
index 0000000000..17fc917139
|
||||||
|
--- /dev/null
|
||||||
|
+++ b/Misc/NEWS.d/next/Library/2024-09-28-02-03-04.gh-issue-124651.bLBGtH.rst
|
||||||
|
@@ -0,0 +1 @@
|
||||||
|
+Properly quote template strings in :mod:`venv` activation scripts.
|
@ -0,0 +1,110 @@
|
|||||||
|
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||||
|
From: "Miss Islington (bot)"
|
||||||
|
<31488909+miss-islington@users.noreply.github.com>
|
||||||
|
Date: Tue, 9 May 2023 23:35:24 -0700
|
||||||
|
Subject: [PATCH] 00444: Security fix for CVE-2024-11168
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Content-Type: text/plain; charset=UTF-8
|
||||||
|
Content-Transfer-Encoding: 8bit
|
||||||
|
|
||||||
|
gh-103848: Adds checks to ensure that bracketed hosts found by urlsplit are of IPv6 or IPvFuture format (GH-103849)
|
||||||
|
|
||||||
|
Tests are adjusted because Python <3.9 don't support scoped IPv6 addresses.
|
||||||
|
|
||||||
|
(cherry picked from commit 29f348e232e82938ba2165843c448c2b291504c5)
|
||||||
|
|
||||||
|
Co-authored-by: JohnJamesUtley <81572567+JohnJamesUtley@users.noreply.github.com>
|
||||||
|
Co-authored-by: Gregory P. Smith <greg@krypto.org>
|
||||||
|
Co-authored-by: Lumír Balhar <lbalhar@redhat.com>
|
||||||
|
---
|
||||||
|
Lib/test/test_urlparse.py | 26 +++++++++++++++++++
|
||||||
|
Lib/urllib/parse.py | 15 +++++++++++
|
||||||
|
...-04-26-09-54-25.gh-issue-103848.aDSnpR.rst | 2 ++
|
||||||
|
3 files changed, 43 insertions(+)
|
||||||
|
create mode 100644 Misc/NEWS.d/next/Library/2023-04-26-09-54-25.gh-issue-103848.aDSnpR.rst
|
||||||
|
|
||||||
|
diff --git a/Lib/test/test_urlparse.py b/Lib/test/test_urlparse.py
|
||||||
|
index 7fd61ffea9..090d2f17bf 100644
|
||||||
|
--- a/Lib/test/test_urlparse.py
|
||||||
|
+++ b/Lib/test/test_urlparse.py
|
||||||
|
@@ -1076,6 +1076,32 @@ class UrlParseTestCase(unittest.TestCase):
|
||||||
|
self.assertEqual(p2.scheme, 'tel')
|
||||||
|
self.assertEqual(p2.path, '+31641044153')
|
||||||
|
|
||||||
|
+ def test_invalid_bracketed_hosts(self):
|
||||||
|
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'Scheme://user@[192.0.2.146]/Path?Query')
|
||||||
|
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'Scheme://user@[important.com:8000]/Path?Query')
|
||||||
|
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'Scheme://user@[v123r.IP]/Path?Query')
|
||||||
|
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'Scheme://user@[v12ae]/Path?Query')
|
||||||
|
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'Scheme://user@[v.IP]/Path?Query')
|
||||||
|
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'Scheme://user@[v123.]/Path?Query')
|
||||||
|
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'Scheme://user@[v]/Path?Query')
|
||||||
|
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'Scheme://user@[0439:23af::2309::fae7:1234]/Path?Query')
|
||||||
|
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'Scheme://user@[0439:23af:2309::fae7:1234:2342:438e:192.0.2.146]/Path?Query')
|
||||||
|
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'Scheme://user@]v6a.ip[/Path')
|
||||||
|
+
|
||||||
|
+ def test_splitting_bracketed_hosts(self):
|
||||||
|
+ p1 = urllib.parse.urlsplit('scheme://user@[v6a.ip]/path?query')
|
||||||
|
+ self.assertEqual(p1.hostname, 'v6a.ip')
|
||||||
|
+ self.assertEqual(p1.username, 'user')
|
||||||
|
+ self.assertEqual(p1.path, '/path')
|
||||||
|
+ p2 = urllib.parse.urlsplit('scheme://user@[0439:23af:2309::fae7]/path?query')
|
||||||
|
+ self.assertEqual(p2.hostname, '0439:23af:2309::fae7')
|
||||||
|
+ self.assertEqual(p2.username, 'user')
|
||||||
|
+ self.assertEqual(p2.path, '/path')
|
||||||
|
+ p3 = urllib.parse.urlsplit('scheme://user@[0439:23af:2309::fae7:1234:192.0.2.146]/path?query')
|
||||||
|
+ self.assertEqual(p3.hostname, '0439:23af:2309::fae7:1234:192.0.2.146')
|
||||||
|
+ self.assertEqual(p3.username, 'user')
|
||||||
|
+ self.assertEqual(p3.path, '/path')
|
||||||
|
+
|
||||||
|
def test_telurl_params(self):
|
||||||
|
p1 = urllib.parse.urlparse('tel:123-4;phone-context=+1-650-516')
|
||||||
|
self.assertEqual(p1.scheme, 'tel')
|
||||||
|
diff --git a/Lib/urllib/parse.py b/Lib/urllib/parse.py
|
||||||
|
index 717e990997..bf186b7984 100644
|
||||||
|
--- a/Lib/urllib/parse.py
|
||||||
|
+++ b/Lib/urllib/parse.py
|
||||||
|
@@ -34,6 +34,7 @@ It serves as a useful guide when making changes.
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import collections
|
||||||
|
+import ipaddress
|
||||||
|
|
||||||
|
__all__ = ["urlparse", "urlunparse", "urljoin", "urldefrag",
|
||||||
|
"urlsplit", "urlunsplit", "urlencode", "parse_qs",
|
||||||
|
@@ -425,6 +426,17 @@ def _remove_unsafe_bytes_from_url(url):
|
||||||
|
url = url.replace(b, "")
|
||||||
|
return url
|
||||||
|
|
||||||
|
+# Valid bracketed hosts are defined in
|
||||||
|
+# https://www.rfc-editor.org/rfc/rfc3986#page-49 and https://url.spec.whatwg.org/
|
||||||
|
+def _check_bracketed_host(hostname):
|
||||||
|
+ if hostname.startswith('v'):
|
||||||
|
+ if not re.match(r"\Av[a-fA-F0-9]+\..+\Z", hostname):
|
||||||
|
+ raise ValueError(f"IPvFuture address is invalid")
|
||||||
|
+ else:
|
||||||
|
+ ip = ipaddress.ip_address(hostname) # Throws Value Error if not IPv6 or IPv4
|
||||||
|
+ if isinstance(ip, ipaddress.IPv4Address):
|
||||||
|
+ raise ValueError(f"An IPv4 address cannot be in brackets")
|
||||||
|
+
|
||||||
|
def urlsplit(url, scheme='', allow_fragments=True):
|
||||||
|
"""Parse a URL into 5 components:
|
||||||
|
<scheme>://<netloc>/<path>?<query>#<fragment>
|
||||||
|
@@ -480,6 +492,9 @@ def urlsplit(url, scheme='', allow_fragments=True):
|
||||||
|
if (('[' in netloc and ']' not in netloc) or
|
||||||
|
(']' in netloc and '[' not in netloc)):
|
||||||
|
raise ValueError("Invalid IPv6 URL")
|
||||||
|
+ if '[' in netloc and ']' in netloc:
|
||||||
|
+ bracketed_host = netloc.partition('[')[2].partition(']')[0]
|
||||||
|
+ _check_bracketed_host(bracketed_host)
|
||||||
|
if allow_fragments and '#' in url:
|
||||||
|
url, fragment = url.split('#', 1)
|
||||||
|
if '?' in url:
|
||||||
|
diff --git a/Misc/NEWS.d/next/Library/2023-04-26-09-54-25.gh-issue-103848.aDSnpR.rst b/Misc/NEWS.d/next/Library/2023-04-26-09-54-25.gh-issue-103848.aDSnpR.rst
|
||||||
|
new file mode 100644
|
||||||
|
index 0000000000..81e5904aa6
|
||||||
|
--- /dev/null
|
||||||
|
+++ b/Misc/NEWS.d/next/Library/2023-04-26-09-54-25.gh-issue-103848.aDSnpR.rst
|
||||||
|
@@ -0,0 +1,2 @@
|
||||||
|
+Add checks to ensure that ``[`` bracketed ``]`` hosts found by
|
||||||
|
+:func:`urllib.parse.urlsplit` are of IPv6 or IPvFuture format.
|
Loading…
Reference in new issue