Compare commits
5 Commits
imports/c9
...
c9-beta
Author | SHA1 | Date | |
---|---|---|---|
2d9c1663a4 | |||
|
818af0d5cf | ||
|
2d0264df93 | ||
|
194e06e23d | ||
|
65fe2eba09 |
2
.gitignore
vendored
2
.gitignore
vendored
@ -1 +1 @@
|
|||||||
SOURCES/Python-3.9.9.tar.xz
|
SOURCES/Python-3.9.18.tar.xz
|
||||||
|
@ -1 +1 @@
|
|||||||
6274e5631c520d75bf1f0a046640fd3996fe99f0 SOURCES/Python-3.9.9.tar.xz
|
abe4a20dcc11798495b17611ef9f8f33d6975722 SOURCES/Python-3.9.18.tar.xz
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
From 12b919396f3fd24521b5ded51e18beb55973f0ff Mon Sep 17 00:00:00 2001
|
||||||
From: =?UTF-8?q?Miro=20Hron=C4=8Dok?= <miro@hroncok.cz>
|
From: =?UTF-8?q?Miro=20Hron=C4=8Dok?= <miro@hroncok.cz>
|
||||||
Date: Wed, 15 Aug 2018 15:36:29 +0200
|
Date: Wed, 15 Aug 2018 15:36:29 +0200
|
||||||
Subject: [PATCH] 00189: Instead of bundled wheels, use our RPM packaged wheels
|
Subject: [PATCH] 00189: Instead of bundled wheels, use our RPM packaged wheels
|
||||||
@ -12,7 +12,7 @@ We might eventually pursuit upstream support, but it's low prio
|
|||||||
1 file changed, 26 insertions(+), 11 deletions(-)
|
1 file changed, 26 insertions(+), 11 deletions(-)
|
||||||
|
|
||||||
diff --git a/Lib/ensurepip/__init__.py b/Lib/ensurepip/__init__.py
|
diff --git a/Lib/ensurepip/__init__.py b/Lib/ensurepip/__init__.py
|
||||||
index 2a140a2624..5bd16a6c59 100644
|
index e510cc7..5bd16a6 100644
|
||||||
--- a/Lib/ensurepip/__init__.py
|
--- a/Lib/ensurepip/__init__.py
|
||||||
+++ b/Lib/ensurepip/__init__.py
|
+++ b/Lib/ensurepip/__init__.py
|
||||||
@@ -1,3 +1,5 @@
|
@@ -1,3 +1,5 @@
|
||||||
@ -31,7 +31,7 @@ index 2a140a2624..5bd16a6c59 100644
|
|||||||
|
|
||||||
__all__ = ["version", "bootstrap"]
|
__all__ = ["version", "bootstrap"]
|
||||||
-_SETUPTOOLS_VERSION = "58.1.0"
|
-_SETUPTOOLS_VERSION = "58.1.0"
|
||||||
-_PIP_VERSION = "21.2.4"
|
-_PIP_VERSION = "23.0.1"
|
||||||
+
|
+
|
||||||
+_WHEEL_DIR = "/usr/share/python-wheels/"
|
+_WHEEL_DIR = "/usr/share/python-wheels/"
|
||||||
+
|
+
|
||||||
@ -73,3 +73,6 @@ index 2a140a2624..5bd16a6c59 100644
|
|||||||
|
|
||||||
additional_paths.append(os.path.join(tmpdir, wheel_name))
|
additional_paths.append(os.path.join(tmpdir, wheel_name))
|
||||||
|
|
||||||
|
--
|
||||||
|
2.35.3
|
||||||
|
|
||||||
|
@ -2,41 +2,61 @@ From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
|||||||
From: Michal Cyprian <m.cyprian@gmail.com>
|
From: Michal Cyprian <m.cyprian@gmail.com>
|
||||||
Date: Mon, 26 Jun 2017 16:32:56 +0200
|
Date: Mon, 26 Jun 2017 16:32:56 +0200
|
||||||
Subject: [PATCH] 00251: Change user install location
|
Subject: [PATCH] 00251: Change user install location
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Content-Type: text/plain; charset=UTF-8
|
||||||
|
Content-Transfer-Encoding: 8bit
|
||||||
|
|
||||||
Set values of prefix and exec_prefix in distutils install command
|
Set values of prefix and exec_prefix in distutils install command
|
||||||
to /usr/local if executable is /usr/bin/python* and RPM build
|
to /usr/local if executable is /usr/bin/python* and RPM build
|
||||||
is not detected to make pip and distutils install into separate location.
|
is not detected to make pip and distutils install into separate location.
|
||||||
|
|
||||||
Fedora Change: https://fedoraproject.org/wiki/Changes/Making_sudo_pip_safe
|
Fedora Change: https://fedoraproject.org/wiki/Changes/Making_sudo_pip_safe
|
||||||
Downstream only: Awaiting resources to work on upstream PEP
|
Downstream only: Reworked in Fedora 36+/Python 3.10+ to follow https://bugs.python.org/issue43976
|
||||||
|
|
||||||
|
pypa/distutils integration: https://github.com/pypa/distutils/pull/70
|
||||||
|
|
||||||
|
Also set sysconfig._PIP_USE_SYSCONFIG = False, to force pip-upgraded-pip
|
||||||
|
to respect this patched distutils install command.
|
||||||
|
See https://bugzilla.redhat.com/show_bug.cgi?id=2014513
|
||||||
|
|
||||||
|
Co-authored-by: Miro Hrončok <miro@hroncok.cz>
|
||||||
---
|
---
|
||||||
Lib/distutils/command/install.py | 15 +++++++++++++--
|
Lib/distutils/command/install.py | 9 +++++++--
|
||||||
Lib/site.py | 9 ++++++++-
|
Lib/site.py | 9 ++++++++-
|
||||||
2 files changed, 21 insertions(+), 3 deletions(-)
|
Lib/sysconfig.py | 17 +++++++++++++++++
|
||||||
|
3 files changed, 32 insertions(+), 3 deletions(-)
|
||||||
|
|
||||||
diff --git a/Lib/distutils/command/install.py b/Lib/distutils/command/install.py
|
diff --git a/Lib/distutils/command/install.py b/Lib/distutils/command/install.py
|
||||||
index aaa300efa9..f8d453912a 100644
|
index aaa300efa9..18f01f10d4 100644
|
||||||
--- a/Lib/distutils/command/install.py
|
--- a/Lib/distutils/command/install.py
|
||||||
+++ b/Lib/distutils/command/install.py
|
+++ b/Lib/distutils/command/install.py
|
||||||
@@ -419,8 +419,19 @@ class install(Command):
|
@@ -3,6 +3,7 @@
|
||||||
|
Implements the Distutils 'install' command."""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
+import sysconfig
|
||||||
|
import os
|
||||||
|
|
||||||
|
from distutils import log
|
||||||
|
@@ -142,6 +143,8 @@ class install(Command):
|
||||||
|
|
||||||
|
negative_opt = {'no-compile' : 'compile'}
|
||||||
|
|
||||||
|
+ # Allow Fedora to add components to the prefix
|
||||||
|
+ _prefix_addition = getattr(sysconfig, '_prefix_addition', '')
|
||||||
|
|
||||||
|
def initialize_options(self):
|
||||||
|
"""Initializes options."""
|
||||||
|
@@ -419,8 +422,10 @@ class install(Command):
|
||||||
raise DistutilsOptionError(
|
raise DistutilsOptionError(
|
||||||
"must not supply exec-prefix without prefix")
|
"must not supply exec-prefix without prefix")
|
||||||
|
|
||||||
- self.prefix = os.path.normpath(sys.prefix)
|
- self.prefix = os.path.normpath(sys.prefix)
|
||||||
- self.exec_prefix = os.path.normpath(sys.exec_prefix)
|
- self.exec_prefix = os.path.normpath(sys.exec_prefix)
|
||||||
+ # self.prefix is set to sys.prefix + /local/
|
+ self.prefix = (
|
||||||
+ # if neither RPM build nor virtual environment is
|
+ os.path.normpath(sys.prefix) + self._prefix_addition)
|
||||||
+ # detected to make pip and distutils install packages
|
+ self.exec_prefix = (
|
||||||
+ # into the separate location.
|
+ os.path.normpath(sys.exec_prefix) + self._prefix_addition)
|
||||||
+ if (not (hasattr(sys, 'real_prefix') or
|
|
||||||
+ sys.prefix != sys.base_prefix) and
|
|
||||||
+ 'RPM_BUILD_ROOT' not in os.environ):
|
|
||||||
+ addition = "/local"
|
|
||||||
+ else:
|
|
||||||
+ addition = ""
|
|
||||||
+
|
|
||||||
+ self.prefix = os.path.normpath(sys.prefix) + addition
|
|
||||||
+ self.exec_prefix = os.path.normpath(sys.exec_prefix) + addition
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
if self.exec_prefix is None:
|
if self.exec_prefix is None:
|
||||||
@ -60,3 +80,31 @@ index 9e617afb00..db14f715f9 100644
|
|||||||
for sitedir in getsitepackages(prefixes):
|
for sitedir in getsitepackages(prefixes):
|
||||||
if os.path.isdir(sitedir):
|
if os.path.isdir(sitedir):
|
||||||
addsitedir(sitedir, known_paths)
|
addsitedir(sitedir, known_paths)
|
||||||
|
diff --git a/Lib/sysconfig.py b/Lib/sysconfig.py
|
||||||
|
index e3f79bfde5..e124104876 100644
|
||||||
|
--- a/Lib/sysconfig.py
|
||||||
|
+++ b/Lib/sysconfig.py
|
||||||
|
@@ -86,6 +86,23 @@ _INSTALL_SCHEMES = {
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
+# Force pip to use distutils paths instead of sysconfig
|
||||||
|
+# https://github.com/pypa/pip/issues/10647
|
||||||
|
+_PIP_USE_SYSCONFIG = False
|
||||||
|
+
|
||||||
|
+# This is used by distutils.command.install in the stdlib
|
||||||
|
+# as well as pypa/distutils (e.g. bundled in setuptools).
|
||||||
|
+# The self.prefix value is set to sys.prefix + /local/
|
||||||
|
+# if neither RPM build nor virtual environment is
|
||||||
|
+# detected to make distutils install packages
|
||||||
|
+# into the separate location.
|
||||||
|
+# https://fedoraproject.org/wiki/Changes/Making_sudo_pip_safe
|
||||||
|
+if (not (hasattr(sys, 'real_prefix') or
|
||||||
|
+ sys.prefix != sys.base_prefix) and
|
||||||
|
+ 'RPM_BUILD_ROOT' not in os.environ):
|
||||||
|
+ _prefix_addition = "/local"
|
||||||
|
+
|
||||||
|
+
|
||||||
|
_SCHEME_KEYS = ('stdlib', 'platstdlib', 'purelib', 'platlib', 'include',
|
||||||
|
'scripts', 'data')
|
||||||
|
|
||||||
|
File diff suppressed because it is too large
Load Diff
251
SOURCES/00397-tarfile-filter.patch
Normal file
251
SOURCES/00397-tarfile-filter.patch
Normal file
@ -0,0 +1,251 @@
|
|||||||
|
From 8b70605b594b3831331a9340ba764ff751871612 Mon Sep 17 00:00:00 2001
|
||||||
|
From: Petr Viktorin <encukou@gmail.com>
|
||||||
|
Date: Mon, 6 Mar 2023 17:24:24 +0100
|
||||||
|
Subject: [PATCH 2/2] CVE-2007-4559, PEP-706: Add filters for tarfile
|
||||||
|
extraction (downstream)
|
||||||
|
|
||||||
|
Add and test RHEL-specific ways of configuring the default behavior: environment
|
||||||
|
variable and config file.
|
||||||
|
---
|
||||||
|
Lib/tarfile.py | 42 +++++++++++++
|
||||||
|
Lib/test/test_shutil.py | 3 +-
|
||||||
|
Lib/test/test_tarfile.py | 128 ++++++++++++++++++++++++++++++++++++++-
|
||||||
|
3 files changed, 169 insertions(+), 4 deletions(-)
|
||||||
|
|
||||||
|
diff --git a/Lib/tarfile.py b/Lib/tarfile.py
|
||||||
|
index b6ad7dbe2a4..dc7050b2c63 100755
|
||||||
|
--- a/Lib/tarfile.py
|
||||||
|
+++ b/Lib/tarfile.py
|
||||||
|
@@ -72,6 +72,13 @@ __all__ = ["TarFile", "TarInfo", "is_tarfile", "TarError", "ReadError",
|
||||||
|
"ENCODING", "USTAR_FORMAT", "GNU_FORMAT", "PAX_FORMAT",
|
||||||
|
"DEFAULT_FORMAT", "open"]
|
||||||
|
|
||||||
|
+# If true, use the safer (but backwards-incompatible) 'tar' extraction filter,
|
||||||
|
+# rather than 'fully_trusted', by default.
|
||||||
|
+# The emitted warning is changed to match.
|
||||||
|
+_RH_SAFER_DEFAULT = True
|
||||||
|
+
|
||||||
|
+# System-wide configuration file
|
||||||
|
+_CONFIG_FILENAME = '/etc/python/tarfile.cfg'
|
||||||
|
|
||||||
|
#---------------------------------------------------------
|
||||||
|
# tar constants
|
||||||
|
@@ -2197,6 +2204,41 @@ class TarFile(object):
|
||||||
|
if filter is None:
|
||||||
|
filter = self.extraction_filter
|
||||||
|
if filter is None:
|
||||||
|
+ name = os.environ.get('PYTHON_TARFILE_EXTRACTION_FILTER')
|
||||||
|
+ if name is None:
|
||||||
|
+ try:
|
||||||
|
+ file = bltn_open(_CONFIG_FILENAME)
|
||||||
|
+ except FileNotFoundError:
|
||||||
|
+ pass
|
||||||
|
+ else:
|
||||||
|
+ import configparser
|
||||||
|
+ conf = configparser.ConfigParser(
|
||||||
|
+ interpolation=None,
|
||||||
|
+ comment_prefixes=('#', ),
|
||||||
|
+ )
|
||||||
|
+ with file:
|
||||||
|
+ conf.read_file(file)
|
||||||
|
+ name = conf.get('tarfile',
|
||||||
|
+ 'PYTHON_TARFILE_EXTRACTION_FILTER',
|
||||||
|
+ fallback='')
|
||||||
|
+ if name:
|
||||||
|
+ try:
|
||||||
|
+ filter = _NAMED_FILTERS[name]
|
||||||
|
+ except KeyError:
|
||||||
|
+ raise ValueError(f"filter {filter!r} not found") from None
|
||||||
|
+ self.extraction_filter = filter
|
||||||
|
+ return filter
|
||||||
|
+ if _RH_SAFER_DEFAULT:
|
||||||
|
+ warnings.warn(
|
||||||
|
+ 'The default behavior of tarfile extraction has been '
|
||||||
|
+ + 'changed to disallow common exploits '
|
||||||
|
+ + '(including CVE-2007-4559). '
|
||||||
|
+ + 'By default, absolute/parent paths are disallowed '
|
||||||
|
+ + 'and some mode bits are cleared. '
|
||||||
|
+ + 'See https://access.redhat.com/articles/7004769 '
|
||||||
|
+ + 'for more details.',
|
||||||
|
+ RuntimeWarning)
|
||||||
|
+ return tar_filter
|
||||||
|
return fully_trusted_filter
|
||||||
|
if isinstance(filter, str):
|
||||||
|
raise TypeError(
|
||||||
|
diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py
|
||||||
|
index 9041e7aa368..1eb1116cc10 100644
|
||||||
|
--- a/Lib/test/test_shutil.py
|
||||||
|
+++ b/Lib/test/test_shutil.py
|
||||||
|
@@ -1613,7 +1613,8 @@ class TestArchives(BaseTest, unittest.TestCase):
|
||||||
|
def check_unpack_tarball(self, format):
|
||||||
|
self.check_unpack_archive(format, filter='fully_trusted')
|
||||||
|
self.check_unpack_archive(format, filter='data')
|
||||||
|
- with warnings_helper.check_no_warnings(self):
|
||||||
|
+ with warnings_helper.check_warnings(
|
||||||
|
+ ('.*CVE-2007-4559', RuntimeWarning)):
|
||||||
|
self.check_unpack_archive(format)
|
||||||
|
|
||||||
|
def test_unpack_archive_tar(self):
|
||||||
|
diff --git a/Lib/test/test_tarfile.py b/Lib/test/test_tarfile.py
|
||||||
|
index a66f7efd2d6..6fd3c384b5c 100644
|
||||||
|
--- a/Lib/test/test_tarfile.py
|
||||||
|
+++ b/Lib/test/test_tarfile.py
|
||||||
|
@@ -2,7 +2,7 @@ import sys
|
||||||
|
import os
|
||||||
|
import io
|
||||||
|
from hashlib import sha256
|
||||||
|
-from contextlib import contextmanager
|
||||||
|
+from contextlib import contextmanager, ExitStack
|
||||||
|
from random import Random
|
||||||
|
import pathlib
|
||||||
|
import shutil
|
||||||
|
@@ -2929,7 +2929,11 @@ class NoneInfoExtractTests(ReadTest):
|
||||||
|
tar = tarfile.open(tarname, mode='r', encoding="iso8859-1")
|
||||||
|
cls.control_dir = pathlib.Path(TEMPDIR) / "extractall_ctrl"
|
||||||
|
tar.errorlevel = 0
|
||||||
|
- tar.extractall(cls.control_dir, filter=cls.extraction_filter)
|
||||||
|
+ with ExitStack() as cm:
|
||||||
|
+ if cls.extraction_filter is None:
|
||||||
|
+ cm.enter_context(warnings.catch_warnings())
|
||||||
|
+ warnings.simplefilter(action="ignore", category=RuntimeWarning)
|
||||||
|
+ tar.extractall(cls.control_dir, filter=cls.extraction_filter)
|
||||||
|
tar.close()
|
||||||
|
cls.control_paths = set(
|
||||||
|
p.relative_to(cls.control_dir)
|
||||||
|
@@ -3592,7 +3596,8 @@ class TestExtractionFilters(unittest.TestCase):
|
||||||
|
"""Ensure the default filter does not warn (like in 3.12)"""
|
||||||
|
with ArchiveMaker() as arc:
|
||||||
|
arc.add('foo')
|
||||||
|
- with warnings_helper.check_no_warnings(self):
|
||||||
|
+ with warnings_helper.check_warnings(
|
||||||
|
+ ('.*CVE-2007-4559', RuntimeWarning)):
|
||||||
|
with self.check_context(arc.open(), None):
|
||||||
|
self.expect_file('foo')
|
||||||
|
|
||||||
|
@@ -3762,6 +3767,123 @@ class TestExtractionFilters(unittest.TestCase):
|
||||||
|
self.expect_exception(TypeError) # errorlevel is not int
|
||||||
|
|
||||||
|
|
||||||
|
+ @contextmanager
|
||||||
|
+ def rh_config_context(self, config_lines=None):
|
||||||
|
+ """Set up for testing various ways of overriding the default filter
|
||||||
|
+
|
||||||
|
+ return a triple with:
|
||||||
|
+ - temporary directory
|
||||||
|
+ - EnvironmentVarGuard()
|
||||||
|
+ - a test archive for use with check_* methods below
|
||||||
|
+
|
||||||
|
+ If config_lines is given, write them to the config file. Otherwise
|
||||||
|
+ the config file is missing.
|
||||||
|
+ """
|
||||||
|
+ tempdir = pathlib.Path(TEMPDIR) / 'tmp'
|
||||||
|
+ configfile = tempdir / 'tarfile.cfg'
|
||||||
|
+ with ArchiveMaker() as arc:
|
||||||
|
+ arc.add('good')
|
||||||
|
+ arc.add('ugly', symlink_to='/etc/passwd')
|
||||||
|
+ arc.add('../bad')
|
||||||
|
+ with (
|
||||||
|
+ support.temp_dir(tempdir),
|
||||||
|
+ support.swap_attr(tarfile, '_CONFIG_FILENAME', str(configfile)),
|
||||||
|
+ support.EnvironmentVarGuard() as env,
|
||||||
|
+ arc.open() as tar,
|
||||||
|
+ ):
|
||||||
|
+ if config_lines is not None:
|
||||||
|
+ with configfile.open('w') as f:
|
||||||
|
+ for line in config_lines:
|
||||||
|
+ print(line, file=f)
|
||||||
|
+ yield tempdir, env, tar
|
||||||
|
+
|
||||||
|
+ def check_rh_default_behavior(self, tar, tempdir):
|
||||||
|
+ """Check RH default: warn and refuse to extract dangerous files."""
|
||||||
|
+ with (
|
||||||
|
+ warnings_helper.check_warnings(
|
||||||
|
+ ('.*CVE-2007-4559', RuntimeWarning)),
|
||||||
|
+ self.assertRaises(tarfile.OutsideDestinationError),
|
||||||
|
+ ):
|
||||||
|
+ tar.extractall(tempdir / 'outdir')
|
||||||
|
+
|
||||||
|
+ def check_trusted_default(self, tar, tempdir):
|
||||||
|
+ """Check 'fully_trusted' is configured as the default filter."""
|
||||||
|
+ with (
|
||||||
|
+ warnings_helper.check_no_warnings(self),
|
||||||
|
+ ):
|
||||||
|
+ tar.extractall(tempdir / 'outdir')
|
||||||
|
+ self.assertTrue((tempdir / 'outdir/good').exists())
|
||||||
|
+ self.assertEqual((tempdir / 'outdir/ugly').readlink(),
|
||||||
|
+ pathlib.Path('/etc/passwd'))
|
||||||
|
+ self.assertTrue((tempdir / 'bad').exists())
|
||||||
|
+
|
||||||
|
+ def test_rh_default_no_conf(self):
|
||||||
|
+ with self.rh_config_context() as (tempdir, env, tar):
|
||||||
|
+ self.check_rh_default_behavior(tar, tempdir)
|
||||||
|
+
|
||||||
|
+ def test_rh_default_from_file(self):
|
||||||
|
+ lines = ['[tarfile]', 'PYTHON_TARFILE_EXTRACTION_FILTER=fully_trusted']
|
||||||
|
+ with self.rh_config_context(lines) as (tempdir, env, tar):
|
||||||
|
+ self.check_trusted_default(tar, tempdir)
|
||||||
|
+
|
||||||
|
+ def test_rh_empty_config_file(self):
|
||||||
|
+ """Empty config file -> default behavior"""
|
||||||
|
+ lines = []
|
||||||
|
+ with self.rh_config_context(lines) as (tempdir, env, tar):
|
||||||
|
+ self.check_rh_default_behavior(tar, tempdir)
|
||||||
|
+
|
||||||
|
+ def test_empty_config_section(self):
|
||||||
|
+ """Empty section in config file -> default behavior"""
|
||||||
|
+ lines = ['[tarfile]']
|
||||||
|
+ with self.rh_config_context(lines) as (tempdir, env, tar):
|
||||||
|
+ self.check_rh_default_behavior(tar, tempdir)
|
||||||
|
+
|
||||||
|
+ def test_rh_default_empty_config_option(self):
|
||||||
|
+ """Empty option value in config file -> default behavior"""
|
||||||
|
+ lines = ['[tarfile]', 'PYTHON_TARFILE_EXTRACTION_FILTER=']
|
||||||
|
+ with self.rh_config_context(lines) as (tempdir, env, tar):
|
||||||
|
+ self.check_rh_default_behavior(tar, tempdir)
|
||||||
|
+
|
||||||
|
+ def test_bad_config_option(self):
|
||||||
|
+ """Bad option value in config file -> ValueError"""
|
||||||
|
+ lines = ['[tarfile]', 'PYTHON_TARFILE_EXTRACTION_FILTER=unknown!']
|
||||||
|
+ with self.rh_config_context(lines) as (tempdir, env, tar):
|
||||||
|
+ with self.assertRaises(ValueError):
|
||||||
|
+ tar.extractall(tempdir / 'outdir')
|
||||||
|
+
|
||||||
|
+ def test_default_from_envvar(self):
|
||||||
|
+ with self.rh_config_context() as (tempdir, env, tar):
|
||||||
|
+ env['PYTHON_TARFILE_EXTRACTION_FILTER'] = 'fully_trusted'
|
||||||
|
+ self.check_trusted_default(tar, tempdir)
|
||||||
|
+
|
||||||
|
+ def test_empty_envvar(self):
|
||||||
|
+ """Empty env variable -> default behavior"""
|
||||||
|
+ with self.rh_config_context() as (tempdir, env, tar):
|
||||||
|
+ env['PYTHON_TARFILE_EXTRACTION_FILTER'] = ''
|
||||||
|
+ self.check_rh_default_behavior(tar, tempdir)
|
||||||
|
+
|
||||||
|
+ def test_bad_envvar(self):
|
||||||
|
+ with self.rh_config_context() as (tempdir, env, tar):
|
||||||
|
+ env['PYTHON_TARFILE_EXTRACTION_FILTER'] = 'unknown!'
|
||||||
|
+ with self.assertRaises(ValueError):
|
||||||
|
+ tar.extractall(tempdir / 'outdir')
|
||||||
|
+
|
||||||
|
+ def test_envvar_overrides_file(self):
|
||||||
|
+ lines = ['[tarfile]', 'PYTHON_TARFILE_EXTRACTION_FILTER=data']
|
||||||
|
+ with self.rh_config_context(lines) as (tempdir, env, tar):
|
||||||
|
+ env['PYTHON_TARFILE_EXTRACTION_FILTER'] = 'fully_trusted'
|
||||||
|
+ self.check_trusted_default(tar, tempdir)
|
||||||
|
+
|
||||||
|
+ def test_monkeypatch_overrides_envvar(self):
|
||||||
|
+ with self.rh_config_context(None) as (tempdir, env, tar):
|
||||||
|
+ env['PYTHON_TARFILE_EXTRACTION_FILTER'] = 'data'
|
||||||
|
+ with support.swap_attr(
|
||||||
|
+ tarfile.TarFile, 'extraction_filter',
|
||||||
|
+ staticmethod(tarfile.fully_trusted_filter)
|
||||||
|
+ ):
|
||||||
|
+ self.check_trusted_default(tar, tempdir)
|
||||||
|
+
|
||||||
|
+
|
||||||
|
def setUpModule():
|
||||||
|
support.unlink(TEMPDIR)
|
||||||
|
os.makedirs(TEMPDIR)
|
||||||
|
--
|
||||||
|
2.40.1
|
||||||
|
|
88
SOURCES/00414-skip_test_zlib_s390x.patch
Normal file
88
SOURCES/00414-skip_test_zlib_s390x.patch
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
From e5be32d6eb880c9563fde2f23cc31b7e449719ec Mon Sep 17 00:00:00 2001
|
||||||
|
From: Victor Stinner <vstinner@python.org>
|
||||||
|
Date: Wed, 24 Jan 2024 18:14:14 +0100
|
||||||
|
Subject: [PATCH] bpo-46623: Skip two test_zlib tests on s390x (GH-31096)
|
||||||
|
|
||||||
|
Skip test_pair() and test_speech128() of test_zlib on s390x since
|
||||||
|
they fail if zlib uses the s390x hardware accelerator.
|
||||||
|
---
|
||||||
|
Lib/test/test_zlib.py | 32 +++++++++++++++++++
|
||||||
|
.../2022-02-03-09-45-26.bpo-46623.vxzuhV.rst | 2 ++
|
||||||
|
2 files changed, 34 insertions(+)
|
||||||
|
create mode 100644 Misc/NEWS.d/next/Tests/2022-02-03-09-45-26.bpo-46623.vxzuhV.rst
|
||||||
|
|
||||||
|
diff --git a/Lib/test/test_zlib.py b/Lib/test/test_zlib.py
|
||||||
|
index 02509cd..f3654c9 100644
|
||||||
|
--- a/Lib/test/test_zlib.py
|
||||||
|
+++ b/Lib/test/test_zlib.py
|
||||||
|
@@ -2,6 +2,7 @@ import unittest
|
||||||
|
from test import support
|
||||||
|
import binascii
|
||||||
|
import copy
|
||||||
|
+import os
|
||||||
|
import pickle
|
||||||
|
import random
|
||||||
|
import sys
|
||||||
|
@@ -16,6 +17,35 @@ requires_Decompress_copy = unittest.skipUnless(
|
||||||
|
hasattr(zlib.decompressobj(), "copy"),
|
||||||
|
'requires Decompress.copy()')
|
||||||
|
|
||||||
|
+# bpo-46623: On s390x, when a hardware accelerator is used, using different
|
||||||
|
+# ways to compress data with zlib can produce different compressed data.
|
||||||
|
+# Simplified test_pair() code:
|
||||||
|
+#
|
||||||
|
+# def func1(data):
|
||||||
|
+# return zlib.compress(data)
|
||||||
|
+#
|
||||||
|
+# def func2(data)
|
||||||
|
+# co = zlib.compressobj()
|
||||||
|
+# x1 = co.compress(data)
|
||||||
|
+# x2 = co.flush()
|
||||||
|
+# return x1 + x2
|
||||||
|
+#
|
||||||
|
+# On s390x if zlib uses a hardware accelerator, func1() creates a single
|
||||||
|
+# "final" compressed block whereas func2() produces 3 compressed blocks (the
|
||||||
|
+# last one is a final block). On other platforms with no accelerator, func1()
|
||||||
|
+# and func2() produce the same compressed data made of a single (final)
|
||||||
|
+# compressed block.
|
||||||
|
+#
|
||||||
|
+# Only the compressed data is different, the decompression returns the original
|
||||||
|
+# data:
|
||||||
|
+#
|
||||||
|
+# zlib.decompress(func1(data)) == zlib.decompress(func2(data)) == data
|
||||||
|
+#
|
||||||
|
+# Make the assumption that s390x always has an accelerator to simplify the skip
|
||||||
|
+# condition. Windows doesn't have os.uname() but it doesn't support s390x.
|
||||||
|
+skip_on_s390x = unittest.skipIf(hasattr(os, 'uname') and os.uname().machine == 's390x',
|
||||||
|
+ 'skipped on s390x')
|
||||||
|
+
|
||||||
|
|
||||||
|
class VersionTestCase(unittest.TestCase):
|
||||||
|
|
||||||
|
@@ -174,6 +204,7 @@ class CompressTestCase(BaseCompressTestCase, unittest.TestCase):
|
||||||
|
bufsize=zlib.DEF_BUF_SIZE),
|
||||||
|
HAMLET_SCENE)
|
||||||
|
|
||||||
|
+ @skip_on_s390x
|
||||||
|
def test_speech128(self):
|
||||||
|
# compress more data
|
||||||
|
data = HAMLET_SCENE * 128
|
||||||
|
@@ -225,6 +256,7 @@ class CompressTestCase(BaseCompressTestCase, unittest.TestCase):
|
||||||
|
|
||||||
|
class CompressObjectTestCase(BaseCompressTestCase, unittest.TestCase):
|
||||||
|
# Test compression object
|
||||||
|
+ @skip_on_s390x
|
||||||
|
def test_pair(self):
|
||||||
|
# straightforward compress/decompress objects
|
||||||
|
datasrc = HAMLET_SCENE * 128
|
||||||
|
diff --git a/Misc/NEWS.d/next/Tests/2022-02-03-09-45-26.bpo-46623.vxzuhV.rst b/Misc/NEWS.d/next/Tests/2022-02-03-09-45-26.bpo-46623.vxzuhV.rst
|
||||||
|
new file mode 100644
|
||||||
|
index 0000000..be085c0
|
||||||
|
--- /dev/null
|
||||||
|
+++ b/Misc/NEWS.d/next/Tests/2022-02-03-09-45-26.bpo-46623.vxzuhV.rst
|
||||||
|
@@ -0,0 +1,2 @@
|
||||||
|
+Skip test_pair() and test_speech128() of test_zlib on s390x since they fail
|
||||||
|
+if zlib uses the s390x hardware accelerator. Patch by Victor Stinner.
|
||||||
|
--
|
||||||
|
2.43.0
|
||||||
|
|
@ -0,0 +1,750 @@
|
|||||||
|
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||||
|
From: Victor Stinner <vstinner@python.org>
|
||||||
|
Date: Fri, 15 Dec 2023 16:10:40 +0100
|
||||||
|
Subject: [PATCH] 00415: [CVE-2023-27043] gh-102988: Reject malformed addresses
|
||||||
|
in email.parseaddr() (#111116)
|
||||||
|
|
||||||
|
Detect email address parsing errors and return empty tuple to
|
||||||
|
indicate the parsing error (old API). Add an optional 'strict'
|
||||||
|
parameter to getaddresses() and parseaddr() functions. Patch by
|
||||||
|
Thomas Dwyer.
|
||||||
|
|
||||||
|
Co-Authored-By: Thomas Dwyer <github@tomd.tel>
|
||||||
|
---
|
||||||
|
Doc/library/email.utils.rst | 19 +-
|
||||||
|
Lib/email/utils.py | 151 ++++++++++++-
|
||||||
|
Lib/test/test_email/test_email.py | 204 +++++++++++++++++-
|
||||||
|
...-10-20-15-28-08.gh-issue-102988.dStNO7.rst | 8 +
|
||||||
|
4 files changed, 361 insertions(+), 21 deletions(-)
|
||||||
|
create mode 100644 Misc/NEWS.d/next/Library/2023-10-20-15-28-08.gh-issue-102988.dStNO7.rst
|
||||||
|
|
||||||
|
diff --git a/Doc/library/email.utils.rst b/Doc/library/email.utils.rst
|
||||||
|
index 4d0e920eb0..104229e9e5 100644
|
||||||
|
--- a/Doc/library/email.utils.rst
|
||||||
|
+++ b/Doc/library/email.utils.rst
|
||||||
|
@@ -60,13 +60,18 @@ of the new API.
|
||||||
|
begins with angle brackets, they are stripped off.
|
||||||
|
|
||||||
|
|
||||||
|
-.. function:: parseaddr(address)
|
||||||
|
+.. function:: parseaddr(address, *, strict=True)
|
||||||
|
|
||||||
|
Parse address -- which should be the value of some address-containing field such
|
||||||
|
as :mailheader:`To` or :mailheader:`Cc` -- into its constituent *realname* and
|
||||||
|
*email address* parts. Returns a tuple of that information, unless the parse
|
||||||
|
fails, in which case a 2-tuple of ``('', '')`` is returned.
|
||||||
|
|
||||||
|
+ If *strict* is true, use a strict parser which rejects malformed inputs.
|
||||||
|
+
|
||||||
|
+ .. versionchanged:: 3.13
|
||||||
|
+ Add *strict* optional parameter and reject malformed inputs by default.
|
||||||
|
+
|
||||||
|
|
||||||
|
.. function:: formataddr(pair, charset='utf-8')
|
||||||
|
|
||||||
|
@@ -84,12 +89,15 @@ of the new API.
|
||||||
|
Added the *charset* option.
|
||||||
|
|
||||||
|
|
||||||
|
-.. function:: getaddresses(fieldvalues)
|
||||||
|
+.. function:: getaddresses(fieldvalues, *, strict=True)
|
||||||
|
|
||||||
|
This method returns a list of 2-tuples of the form returned by ``parseaddr()``.
|
||||||
|
*fieldvalues* is a sequence of header field values as might be returned by
|
||||||
|
- :meth:`Message.get_all <email.message.Message.get_all>`. Here's a simple
|
||||||
|
- example that gets all the recipients of a message::
|
||||||
|
+ :meth:`Message.get_all <email.message.Message.get_all>`.
|
||||||
|
+
|
||||||
|
+ If *strict* is true, use a strict parser which rejects malformed inputs.
|
||||||
|
+
|
||||||
|
+ Here's a simple example that gets all the recipients of a message::
|
||||||
|
|
||||||
|
from email.utils import getaddresses
|
||||||
|
|
||||||
|
@@ -99,6 +107,9 @@ of the new API.
|
||||||
|
resent_ccs = msg.get_all('resent-cc', [])
|
||||||
|
all_recipients = getaddresses(tos + ccs + resent_tos + resent_ccs)
|
||||||
|
|
||||||
|
+ .. versionchanged:: 3.13
|
||||||
|
+ Add *strict* optional parameter and reject malformed inputs by default.
|
||||||
|
+
|
||||||
|
|
||||||
|
.. function:: parsedate(date)
|
||||||
|
|
||||||
|
diff --git a/Lib/email/utils.py b/Lib/email/utils.py
|
||||||
|
index 48d30160aa..7ca7a7c886 100644
|
||||||
|
--- a/Lib/email/utils.py
|
||||||
|
+++ b/Lib/email/utils.py
|
||||||
|
@@ -48,6 +48,7 @@ TICK = "'"
|
||||||
|
specialsre = re.compile(r'[][\\()<>@,:;".]')
|
||||||
|
escapesre = re.compile(r'[\\"]')
|
||||||
|
|
||||||
|
+
|
||||||
|
def _has_surrogates(s):
|
||||||
|
"""Return True if s contains surrogate-escaped binary data."""
|
||||||
|
# This check is based on the fact that unless there are surrogates, utf8
|
||||||
|
@@ -106,12 +107,127 @@ def formataddr(pair, charset='utf-8'):
|
||||||
|
return address
|
||||||
|
|
||||||
|
|
||||||
|
+def _iter_escaped_chars(addr):
|
||||||
|
+ pos = 0
|
||||||
|
+ escape = False
|
||||||
|
+ for pos, ch in enumerate(addr):
|
||||||
|
+ if escape:
|
||||||
|
+ yield (pos, '\\' + ch)
|
||||||
|
+ escape = False
|
||||||
|
+ elif ch == '\\':
|
||||||
|
+ escape = True
|
||||||
|
+ else:
|
||||||
|
+ yield (pos, ch)
|
||||||
|
+ if escape:
|
||||||
|
+ yield (pos, '\\')
|
||||||
|
|
||||||
|
-def getaddresses(fieldvalues):
|
||||||
|
- """Return a list of (REALNAME, EMAIL) for each fieldvalue."""
|
||||||
|
- all = COMMASPACE.join(str(v) for v in fieldvalues)
|
||||||
|
- a = _AddressList(all)
|
||||||
|
- return a.addresslist
|
||||||
|
+
|
||||||
|
+def _strip_quoted_realnames(addr):
|
||||||
|
+ """Strip real names between quotes."""
|
||||||
|
+ if '"' not in addr:
|
||||||
|
+ # Fast path
|
||||||
|
+ return addr
|
||||||
|
+
|
||||||
|
+ start = 0
|
||||||
|
+ open_pos = None
|
||||||
|
+ result = []
|
||||||
|
+ for pos, ch in _iter_escaped_chars(addr):
|
||||||
|
+ if ch == '"':
|
||||||
|
+ if open_pos is None:
|
||||||
|
+ open_pos = pos
|
||||||
|
+ else:
|
||||||
|
+ if start != open_pos:
|
||||||
|
+ result.append(addr[start:open_pos])
|
||||||
|
+ start = pos + 1
|
||||||
|
+ open_pos = None
|
||||||
|
+
|
||||||
|
+ if start < len(addr):
|
||||||
|
+ result.append(addr[start:])
|
||||||
|
+
|
||||||
|
+ return ''.join(result)
|
||||||
|
+
|
||||||
|
+
|
||||||
|
+supports_strict_parsing = True
|
||||||
|
+
|
||||||
|
+def getaddresses(fieldvalues, *, strict=True):
|
||||||
|
+ """Return a list of (REALNAME, EMAIL) or ('','') for each fieldvalue.
|
||||||
|
+
|
||||||
|
+ When parsing fails for a fieldvalue, a 2-tuple of ('', '') is returned in
|
||||||
|
+ its place.
|
||||||
|
+
|
||||||
|
+ If strict is true, use a strict parser which rejects malformed inputs.
|
||||||
|
+ """
|
||||||
|
+
|
||||||
|
+ # If strict is true, if the resulting list of parsed addresses is greater
|
||||||
|
+ # than the number of fieldvalues in the input list, a parsing error has
|
||||||
|
+ # occurred and consequently a list containing a single empty 2-tuple [('',
|
||||||
|
+ # '')] is returned in its place. This is done to avoid invalid output.
|
||||||
|
+ #
|
||||||
|
+ # Malformed input: getaddresses(['alice@example.com <bob@example.com>'])
|
||||||
|
+ # Invalid output: [('', 'alice@example.com'), ('', 'bob@example.com')]
|
||||||
|
+ # Safe output: [('', '')]
|
||||||
|
+
|
||||||
|
+ if not strict:
|
||||||
|
+ all = COMMASPACE.join(str(v) for v in fieldvalues)
|
||||||
|
+ a = _AddressList(all)
|
||||||
|
+ return a.addresslist
|
||||||
|
+
|
||||||
|
+ fieldvalues = [str(v) for v in fieldvalues]
|
||||||
|
+ fieldvalues = _pre_parse_validation(fieldvalues)
|
||||||
|
+ addr = COMMASPACE.join(fieldvalues)
|
||||||
|
+ a = _AddressList(addr)
|
||||||
|
+ result = _post_parse_validation(a.addresslist)
|
||||||
|
+
|
||||||
|
+ # Treat output as invalid if the number of addresses is not equal to the
|
||||||
|
+ # expected number of addresses.
|
||||||
|
+ n = 0
|
||||||
|
+ for v in fieldvalues:
|
||||||
|
+ # When a comma is used in the Real Name part it is not a deliminator.
|
||||||
|
+ # So strip those out before counting the commas.
|
||||||
|
+ v = _strip_quoted_realnames(v)
|
||||||
|
+ # Expected number of addresses: 1 + number of commas
|
||||||
|
+ n += 1 + v.count(',')
|
||||||
|
+ if len(result) != n:
|
||||||
|
+ return [('', '')]
|
||||||
|
+
|
||||||
|
+ return result
|
||||||
|
+
|
||||||
|
+
|
||||||
|
+def _check_parenthesis(addr):
|
||||||
|
+ # Ignore parenthesis in quoted real names.
|
||||||
|
+ addr = _strip_quoted_realnames(addr)
|
||||||
|
+
|
||||||
|
+ opens = 0
|
||||||
|
+ for pos, ch in _iter_escaped_chars(addr):
|
||||||
|
+ if ch == '(':
|
||||||
|
+ opens += 1
|
||||||
|
+ elif ch == ')':
|
||||||
|
+ opens -= 1
|
||||||
|
+ if opens < 0:
|
||||||
|
+ return False
|
||||||
|
+ return (opens == 0)
|
||||||
|
+
|
||||||
|
+
|
||||||
|
+def _pre_parse_validation(email_header_fields):
|
||||||
|
+ accepted_values = []
|
||||||
|
+ for v in email_header_fields:
|
||||||
|
+ if not _check_parenthesis(v):
|
||||||
|
+ v = "('', '')"
|
||||||
|
+ accepted_values.append(v)
|
||||||
|
+
|
||||||
|
+ return accepted_values
|
||||||
|
+
|
||||||
|
+
|
||||||
|
+def _post_parse_validation(parsed_email_header_tuples):
|
||||||
|
+ accepted_values = []
|
||||||
|
+ # The parser would have parsed a correctly formatted domain-literal
|
||||||
|
+ # The existence of an [ after parsing indicates a parsing failure
|
||||||
|
+ for v in parsed_email_header_tuples:
|
||||||
|
+ if '[' in v[1]:
|
||||||
|
+ v = ('', '')
|
||||||
|
+ accepted_values.append(v)
|
||||||
|
+
|
||||||
|
+ return accepted_values
|
||||||
|
|
||||||
|
|
||||||
|
def _format_timetuple_and_zone(timetuple, zone):
|
||||||
|
@@ -202,16 +318,33 @@ def parsedate_to_datetime(data):
|
||||||
|
tzinfo=datetime.timezone(datetime.timedelta(seconds=tz)))
|
||||||
|
|
||||||
|
|
||||||
|
-def parseaddr(addr):
|
||||||
|
+def parseaddr(addr, *, strict=True):
|
||||||
|
"""
|
||||||
|
Parse addr into its constituent realname and email address parts.
|
||||||
|
|
||||||
|
Return a tuple of realname and email address, unless the parse fails, in
|
||||||
|
which case return a 2-tuple of ('', '').
|
||||||
|
+
|
||||||
|
+ If strict is True, use a strict parser which rejects malformed inputs.
|
||||||
|
"""
|
||||||
|
- addrs = _AddressList(addr).addresslist
|
||||||
|
- if not addrs:
|
||||||
|
- return '', ''
|
||||||
|
+ if not strict:
|
||||||
|
+ addrs = _AddressList(addr).addresslist
|
||||||
|
+ if not addrs:
|
||||||
|
+ return ('', '')
|
||||||
|
+ return addrs[0]
|
||||||
|
+
|
||||||
|
+ if isinstance(addr, list):
|
||||||
|
+ addr = addr[0]
|
||||||
|
+
|
||||||
|
+ if not isinstance(addr, str):
|
||||||
|
+ return ('', '')
|
||||||
|
+
|
||||||
|
+ addr = _pre_parse_validation([addr])[0]
|
||||||
|
+ addrs = _post_parse_validation(_AddressList(addr).addresslist)
|
||||||
|
+
|
||||||
|
+ if not addrs or len(addrs) > 1:
|
||||||
|
+ return ('', '')
|
||||||
|
+
|
||||||
|
return addrs[0]
|
||||||
|
|
||||||
|
|
||||||
|
diff --git a/Lib/test/test_email/test_email.py b/Lib/test/test_email/test_email.py
|
||||||
|
index 761ea90b78..0c689643de 100644
|
||||||
|
--- a/Lib/test/test_email/test_email.py
|
||||||
|
+++ b/Lib/test/test_email/test_email.py
|
||||||
|
@@ -16,6 +16,7 @@ from unittest.mock import patch
|
||||||
|
|
||||||
|
import email
|
||||||
|
import email.policy
|
||||||
|
+import email.utils
|
||||||
|
|
||||||
|
from email.charset import Charset
|
||||||
|
from email.header import Header, decode_header, make_header
|
||||||
|
@@ -3263,15 +3264,154 @@ Foo
|
||||||
|
[('Al Person', 'aperson@dom.ain'),
|
||||||
|
('Bud Person', 'bperson@dom.ain')])
|
||||||
|
|
||||||
|
+ def test_getaddresses_comma_in_name(self):
|
||||||
|
+ """GH-106669 regression test."""
|
||||||
|
+ self.assertEqual(
|
||||||
|
+ utils.getaddresses(
|
||||||
|
+ [
|
||||||
|
+ '"Bud, Person" <bperson@dom.ain>',
|
||||||
|
+ 'aperson@dom.ain (Al Person)',
|
||||||
|
+ '"Mariusz Felisiak" <to@example.com>',
|
||||||
|
+ ]
|
||||||
|
+ ),
|
||||||
|
+ [
|
||||||
|
+ ('Bud, Person', 'bperson@dom.ain'),
|
||||||
|
+ ('Al Person', 'aperson@dom.ain'),
|
||||||
|
+ ('Mariusz Felisiak', 'to@example.com'),
|
||||||
|
+ ],
|
||||||
|
+ )
|
||||||
|
+
|
||||||
|
+ def test_parsing_errors(self):
|
||||||
|
+ """Test for parsing errors from CVE-2023-27043 and CVE-2019-16056"""
|
||||||
|
+ alice = 'alice@example.org'
|
||||||
|
+ bob = 'bob@example.com'
|
||||||
|
+ empty = ('', '')
|
||||||
|
+
|
||||||
|
+ # Test utils.getaddresses() and utils.parseaddr() on malformed email
|
||||||
|
+ # addresses: default behavior (strict=True) rejects malformed address,
|
||||||
|
+ # and strict=False which tolerates malformed address.
|
||||||
|
+ for invalid_separator, expected_non_strict in (
|
||||||
|
+ ('(', [(f'<{bob}>', alice)]),
|
||||||
|
+ (')', [('', alice), empty, ('', bob)]),
|
||||||
|
+ ('<', [('', alice), empty, ('', bob), empty]),
|
||||||
|
+ ('>', [('', alice), empty, ('', bob)]),
|
||||||
|
+ ('[', [('', f'{alice}[<{bob}>]')]),
|
||||||
|
+ (']', [('', alice), empty, ('', bob)]),
|
||||||
|
+ ('@', [empty, empty, ('', bob)]),
|
||||||
|
+ (';', [('', alice), empty, ('', bob)]),
|
||||||
|
+ (':', [('', alice), ('', bob)]),
|
||||||
|
+ ('.', [('', alice + '.'), ('', bob)]),
|
||||||
|
+ ('"', [('', alice), ('', f'<{bob}>')]),
|
||||||
|
+ ):
|
||||||
|
+ address = f'{alice}{invalid_separator}<{bob}>'
|
||||||
|
+ with self.subTest(address=address):
|
||||||
|
+ self.assertEqual(utils.getaddresses([address]),
|
||||||
|
+ [empty])
|
||||||
|
+ self.assertEqual(utils.getaddresses([address], strict=False),
|
||||||
|
+ expected_non_strict)
|
||||||
|
+
|
||||||
|
+ self.assertEqual(utils.parseaddr([address]),
|
||||||
|
+ empty)
|
||||||
|
+ self.assertEqual(utils.parseaddr([address], strict=False),
|
||||||
|
+ ('', address))
|
||||||
|
+
|
||||||
|
+ # Comma (',') is treated differently depending on strict parameter.
|
||||||
|
+ # Comma without quotes.
|
||||||
|
+ address = f'{alice},<{bob}>'
|
||||||
|
+ self.assertEqual(utils.getaddresses([address]),
|
||||||
|
+ [('', alice), ('', bob)])
|
||||||
|
+ self.assertEqual(utils.getaddresses([address], strict=False),
|
||||||
|
+ [('', alice), ('', bob)])
|
||||||
|
+ self.assertEqual(utils.parseaddr([address]),
|
||||||
|
+ empty)
|
||||||
|
+ self.assertEqual(utils.parseaddr([address], strict=False),
|
||||||
|
+ ('', address))
|
||||||
|
+
|
||||||
|
+ # Real name between quotes containing comma.
|
||||||
|
+ address = '"Alice, alice@example.org" <bob@example.com>'
|
||||||
|
+ expected_strict = ('Alice, alice@example.org', 'bob@example.com')
|
||||||
|
+ self.assertEqual(utils.getaddresses([address]), [expected_strict])
|
||||||
|
+ self.assertEqual(utils.getaddresses([address], strict=False), [expected_strict])
|
||||||
|
+ self.assertEqual(utils.parseaddr([address]), expected_strict)
|
||||||
|
+ self.assertEqual(utils.parseaddr([address], strict=False),
|
||||||
|
+ ('', address))
|
||||||
|
+
|
||||||
|
+ # Valid parenthesis in comments.
|
||||||
|
+ address = 'alice@example.org (Alice)'
|
||||||
|
+ expected_strict = ('Alice', 'alice@example.org')
|
||||||
|
+ self.assertEqual(utils.getaddresses([address]), [expected_strict])
|
||||||
|
+ self.assertEqual(utils.getaddresses([address], strict=False), [expected_strict])
|
||||||
|
+ self.assertEqual(utils.parseaddr([address]), expected_strict)
|
||||||
|
+ self.assertEqual(utils.parseaddr([address], strict=False),
|
||||||
|
+ ('', address))
|
||||||
|
+
|
||||||
|
+ # Invalid parenthesis in comments.
|
||||||
|
+ address = 'alice@example.org )Alice('
|
||||||
|
+ self.assertEqual(utils.getaddresses([address]), [empty])
|
||||||
|
+ self.assertEqual(utils.getaddresses([address], strict=False),
|
||||||
|
+ [('', 'alice@example.org'), ('', ''), ('', 'Alice')])
|
||||||
|
+ self.assertEqual(utils.parseaddr([address]), empty)
|
||||||
|
+ self.assertEqual(utils.parseaddr([address], strict=False),
|
||||||
|
+ ('', address))
|
||||||
|
+
|
||||||
|
+ # Two addresses with quotes separated by comma.
|
||||||
|
+ address = '"Jane Doe" <jane@example.net>, "John Doe" <john@example.net>'
|
||||||
|
+ self.assertEqual(utils.getaddresses([address]),
|
||||||
|
+ [('Jane Doe', 'jane@example.net'),
|
||||||
|
+ ('John Doe', 'john@example.net')])
|
||||||
|
+ self.assertEqual(utils.getaddresses([address], strict=False),
|
||||||
|
+ [('Jane Doe', 'jane@example.net'),
|
||||||
|
+ ('John Doe', 'john@example.net')])
|
||||||
|
+ self.assertEqual(utils.parseaddr([address]), empty)
|
||||||
|
+ self.assertEqual(utils.parseaddr([address], strict=False),
|
||||||
|
+ ('', address))
|
||||||
|
+
|
||||||
|
+ # Test email.utils.supports_strict_parsing attribute
|
||||||
|
+ self.assertEqual(email.utils.supports_strict_parsing, True)
|
||||||
|
+
|
||||||
|
def test_getaddresses_nasty(self):
|
||||||
|
- eq = self.assertEqual
|
||||||
|
- eq(utils.getaddresses(['foo: ;']), [('', '')])
|
||||||
|
- eq(utils.getaddresses(
|
||||||
|
- ['[]*-- =~$']),
|
||||||
|
- [('', ''), ('', ''), ('', '*--')])
|
||||||
|
- eq(utils.getaddresses(
|
||||||
|
- ['foo: ;', '"Jason R. Mastaler" <jason@dom.ain>']),
|
||||||
|
- [('', ''), ('Jason R. Mastaler', 'jason@dom.ain')])
|
||||||
|
+ for addresses, expected in (
|
||||||
|
+ (['"Sürname, Firstname" <to@example.com>'],
|
||||||
|
+ [('Sürname, Firstname', 'to@example.com')]),
|
||||||
|
+
|
||||||
|
+ (['foo: ;'],
|
||||||
|
+ [('', '')]),
|
||||||
|
+
|
||||||
|
+ (['foo: ;', '"Jason R. Mastaler" <jason@dom.ain>'],
|
||||||
|
+ [('', ''), ('Jason R. Mastaler', 'jason@dom.ain')]),
|
||||||
|
+
|
||||||
|
+ ([r'Pete(A nice \) chap) <pete(his account)@silly.test(his host)>'],
|
||||||
|
+ [('Pete (A nice ) chap his account his host)', 'pete@silly.test')]),
|
||||||
|
+
|
||||||
|
+ (['(Empty list)(start)Undisclosed recipients :(nobody(I know))'],
|
||||||
|
+ [('', '')]),
|
||||||
|
+
|
||||||
|
+ (['Mary <@machine.tld:mary@example.net>, , jdoe@test . example'],
|
||||||
|
+ [('Mary', 'mary@example.net'), ('', ''), ('', 'jdoe@test.example')]),
|
||||||
|
+
|
||||||
|
+ (['John Doe <jdoe@machine(comment). example>'],
|
||||||
|
+ [('John Doe (comment)', 'jdoe@machine.example')]),
|
||||||
|
+
|
||||||
|
+ (['"Mary Smith: Personal Account" <smith@home.example>'],
|
||||||
|
+ [('Mary Smith: Personal Account', 'smith@home.example')]),
|
||||||
|
+
|
||||||
|
+ (['Undisclosed recipients:;'],
|
||||||
|
+ [('', '')]),
|
||||||
|
+
|
||||||
|
+ ([r'<boss@nil.test>, "Giant; \"Big\" Box" <bob@example.net>'],
|
||||||
|
+ [('', 'boss@nil.test'), ('Giant; "Big" Box', 'bob@example.net')]),
|
||||||
|
+ ):
|
||||||
|
+ with self.subTest(addresses=addresses):
|
||||||
|
+ self.assertEqual(utils.getaddresses(addresses),
|
||||||
|
+ expected)
|
||||||
|
+ self.assertEqual(utils.getaddresses(addresses, strict=False),
|
||||||
|
+ expected)
|
||||||
|
+
|
||||||
|
+ addresses = ['[]*-- =~$']
|
||||||
|
+ self.assertEqual(utils.getaddresses(addresses),
|
||||||
|
+ [('', '')])
|
||||||
|
+ self.assertEqual(utils.getaddresses(addresses, strict=False),
|
||||||
|
+ [('', ''), ('', ''), ('', '*--')])
|
||||||
|
|
||||||
|
def test_getaddresses_embedded_comment(self):
|
||||||
|
"""Test proper handling of a nested comment"""
|
||||||
|
@@ -3460,6 +3600,54 @@ multipart/report
|
||||||
|
m = cls(*constructor, policy=email.policy.default)
|
||||||
|
self.assertIs(m.policy, email.policy.default)
|
||||||
|
|
||||||
|
+ def test_iter_escaped_chars(self):
|
||||||
|
+ self.assertEqual(list(utils._iter_escaped_chars(r'a\\b\"c\\"d')),
|
||||||
|
+ [(0, 'a'),
|
||||||
|
+ (2, '\\\\'),
|
||||||
|
+ (3, 'b'),
|
||||||
|
+ (5, '\\"'),
|
||||||
|
+ (6, 'c'),
|
||||||
|
+ (8, '\\\\'),
|
||||||
|
+ (9, '"'),
|
||||||
|
+ (10, 'd')])
|
||||||
|
+ self.assertEqual(list(utils._iter_escaped_chars('a\\')),
|
||||||
|
+ [(0, 'a'), (1, '\\')])
|
||||||
|
+
|
||||||
|
+ def test_strip_quoted_realnames(self):
|
||||||
|
+ def check(addr, expected):
|
||||||
|
+ self.assertEqual(utils._strip_quoted_realnames(addr), expected)
|
||||||
|
+
|
||||||
|
+ check('"Jane Doe" <jane@example.net>, "John Doe" <john@example.net>',
|
||||||
|
+ ' <jane@example.net>, <john@example.net>')
|
||||||
|
+ check(r'"Jane \"Doe\"." <jane@example.net>',
|
||||||
|
+ ' <jane@example.net>')
|
||||||
|
+
|
||||||
|
+ # special cases
|
||||||
|
+ check(r'before"name"after', 'beforeafter')
|
||||||
|
+ check(r'before"name"', 'before')
|
||||||
|
+ check(r'b"name"', 'b') # single char
|
||||||
|
+ check(r'"name"after', 'after')
|
||||||
|
+ check(r'"name"a', 'a') # single char
|
||||||
|
+ check(r'"name"', '')
|
||||||
|
+
|
||||||
|
+ # no change
|
||||||
|
+ for addr in (
|
||||||
|
+ 'Jane Doe <jane@example.net>, John Doe <john@example.net>',
|
||||||
|
+ 'lone " quote',
|
||||||
|
+ ):
|
||||||
|
+ self.assertEqual(utils._strip_quoted_realnames(addr), addr)
|
||||||
|
+
|
||||||
|
+
|
||||||
|
+ def test_check_parenthesis(self):
|
||||||
|
+ addr = 'alice@example.net'
|
||||||
|
+ self.assertTrue(utils._check_parenthesis(f'{addr} (Alice)'))
|
||||||
|
+ self.assertFalse(utils._check_parenthesis(f'{addr} )Alice('))
|
||||||
|
+ self.assertFalse(utils._check_parenthesis(f'{addr} (Alice))'))
|
||||||
|
+ self.assertFalse(utils._check_parenthesis(f'{addr} ((Alice)'))
|
||||||
|
+
|
||||||
|
+ # Ignore real name between quotes
|
||||||
|
+ self.assertTrue(utils._check_parenthesis(f'")Alice((" {addr}'))
|
||||||
|
+
|
||||||
|
|
||||||
|
# Test the iterator/generators
|
||||||
|
class TestIterators(TestEmailBase):
|
||||||
|
diff --git a/Misc/NEWS.d/next/Library/2023-10-20-15-28-08.gh-issue-102988.dStNO7.rst b/Misc/NEWS.d/next/Library/2023-10-20-15-28-08.gh-issue-102988.dStNO7.rst
|
||||||
|
new file mode 100644
|
||||||
|
index 0000000000..3d0e9e4078
|
||||||
|
--- /dev/null
|
||||||
|
+++ b/Misc/NEWS.d/next/Library/2023-10-20-15-28-08.gh-issue-102988.dStNO7.rst
|
||||||
|
@@ -0,0 +1,8 @@
|
||||||
|
+:func:`email.utils.getaddresses` and :func:`email.utils.parseaddr` now
|
||||||
|
+return ``('', '')`` 2-tuples in more situations where invalid email
|
||||||
|
+addresses are encountered instead of potentially inaccurate values. Add
|
||||||
|
+optional *strict* parameter to these two functions: use ``strict=False`` to
|
||||||
|
+get the old behavior, accept malformed inputs.
|
||||||
|
+``getattr(email.utils, 'supports_strict_parsing', False)`` can be use to check
|
||||||
|
+if the *strict* paramater is available. Patch by Thomas Dwyer and Victor
|
||||||
|
+Stinner to improve the CVE-2023-27043 fix.
|
||||||
|
|
||||||
|
|
||||||
|
From 4df4fad359c280f2328b98ea9b4414f244624a58 Mon Sep 17 00:00:00 2001
|
||||||
|
From: Lumir Balhar <lbalhar@redhat.com>
|
||||||
|
Date: Mon, 18 Dec 2023 20:15:33 +0100
|
||||||
|
Subject: [PATCH] Make it possible to disable strict parsing in email module
|
||||||
|
|
||||||
|
---
|
||||||
|
Doc/library/email.utils.rst | 26 +++++++++++
|
||||||
|
Lib/email/utils.py | 54 ++++++++++++++++++++++-
|
||||||
|
Lib/test/test_email/test_email.py | 72 ++++++++++++++++++++++++++++++-
|
||||||
|
3 files changed, 149 insertions(+), 3 deletions(-)
|
||||||
|
|
||||||
|
diff --git a/Doc/library/email.utils.rst b/Doc/library/email.utils.rst
|
||||||
|
index d1e1898591..7aef773b5f 100644
|
||||||
|
--- a/Doc/library/email.utils.rst
|
||||||
|
+++ b/Doc/library/email.utils.rst
|
||||||
|
@@ -69,6 +69,19 @@ of the new API.
|
||||||
|
|
||||||
|
If *strict* is true, use a strict parser which rejects malformed inputs.
|
||||||
|
|
||||||
|
+ The default setting for *strict* is set to ``True``, but you can override
|
||||||
|
+ it by setting the environment variable ``PYTHON_EMAIL_DISABLE_STRICT_ADDR_PARSING``
|
||||||
|
+ to non-empty string.
|
||||||
|
+
|
||||||
|
+ Additionally, you can permanently set the default value for *strict* to
|
||||||
|
+ ``False`` by creating the configuration file ``/etc/python/email.cfg``
|
||||||
|
+ with the following content:
|
||||||
|
+
|
||||||
|
+ .. code-block:: ini
|
||||||
|
+
|
||||||
|
+ [email_addr_parsing]
|
||||||
|
+ PYTHON_EMAIL_DISABLE_STRICT_ADDR_PARSING = true
|
||||||
|
+
|
||||||
|
.. versionchanged:: 3.13
|
||||||
|
Add *strict* optional parameter and reject malformed inputs by default.
|
||||||
|
|
||||||
|
@@ -97,6 +110,19 @@ of the new API.
|
||||||
|
|
||||||
|
If *strict* is true, use a strict parser which rejects malformed inputs.
|
||||||
|
|
||||||
|
+ The default setting for *strict* is set to ``True``, but you can override
|
||||||
|
+ it by setting the environment variable ``PYTHON_EMAIL_DISABLE_STRICT_ADDR_PARSING``
|
||||||
|
+ to non-empty string.
|
||||||
|
+
|
||||||
|
+ Additionally, you can permanently set the default value for *strict* to
|
||||||
|
+ ``False`` by creating the configuration file ``/etc/python/email.cfg``
|
||||||
|
+ with the following content:
|
||||||
|
+
|
||||||
|
+ .. code-block:: ini
|
||||||
|
+
|
||||||
|
+ [email_addr_parsing]
|
||||||
|
+ PYTHON_EMAIL_DISABLE_STRICT_ADDR_PARSING = true
|
||||||
|
+
|
||||||
|
Here's a simple example that gets all the recipients of a message::
|
||||||
|
|
||||||
|
from email.utils import getaddresses
|
||||||
|
diff --git a/Lib/email/utils.py b/Lib/email/utils.py
|
||||||
|
index f83b7e5d7e..b8e90ceb8e 100644
|
||||||
|
--- a/Lib/email/utils.py
|
||||||
|
+++ b/Lib/email/utils.py
|
||||||
|
@@ -48,6 +48,46 @@ TICK = "'"
|
||||||
|
specialsre = re.compile(r'[][\\()<>@,:;".]')
|
||||||
|
escapesre = re.compile(r'[\\"]')
|
||||||
|
|
||||||
|
+_EMAIL_CONFIG_FILE = "/etc/python/email.cfg"
|
||||||
|
+_cached_strict_addr_parsing = None
|
||||||
|
+
|
||||||
|
+
|
||||||
|
+def _use_strict_email_parsing():
|
||||||
|
+ """"Cache implementation for _cached_strict_addr_parsing"""
|
||||||
|
+ global _cached_strict_addr_parsing
|
||||||
|
+ if _cached_strict_addr_parsing is None:
|
||||||
|
+ _cached_strict_addr_parsing = _use_strict_email_parsing_impl()
|
||||||
|
+ return _cached_strict_addr_parsing
|
||||||
|
+
|
||||||
|
+
|
||||||
|
+def _use_strict_email_parsing_impl():
|
||||||
|
+ """Returns True if strict email parsing is not disabled by
|
||||||
|
+ config file or env variable.
|
||||||
|
+ """
|
||||||
|
+ disabled = bool(os.environ.get("PYTHON_EMAIL_DISABLE_STRICT_ADDR_PARSING"))
|
||||||
|
+ if disabled:
|
||||||
|
+ return False
|
||||||
|
+
|
||||||
|
+ try:
|
||||||
|
+ file = open(_EMAIL_CONFIG_FILE)
|
||||||
|
+ except FileNotFoundError:
|
||||||
|
+ pass
|
||||||
|
+ else:
|
||||||
|
+ with file:
|
||||||
|
+ import configparser
|
||||||
|
+ config = configparser.ConfigParser(
|
||||||
|
+ interpolation=None,
|
||||||
|
+ comment_prefixes=('#', ),
|
||||||
|
+
|
||||||
|
+ )
|
||||||
|
+ config.read_file(file)
|
||||||
|
+ disabled = config.getboolean('email_addr_parsing', "PYTHON_EMAIL_DISABLE_STRICT_ADDR_PARSING", fallback=None)
|
||||||
|
+
|
||||||
|
+ if disabled:
|
||||||
|
+ return False
|
||||||
|
+
|
||||||
|
+ return True
|
||||||
|
+
|
||||||
|
|
||||||
|
def _has_surrogates(s):
|
||||||
|
"""Return True if s contains surrogate-escaped binary data."""
|
||||||
|
@@ -149,7 +189,7 @@ def _strip_quoted_realnames(addr):
|
||||||
|
|
||||||
|
supports_strict_parsing = True
|
||||||
|
|
||||||
|
-def getaddresses(fieldvalues, *, strict=True):
|
||||||
|
+def getaddresses(fieldvalues, *, strict=None):
|
||||||
|
"""Return a list of (REALNAME, EMAIL) or ('','') for each fieldvalue.
|
||||||
|
|
||||||
|
When parsing fails for a fieldvalue, a 2-tuple of ('', '') is returned in
|
||||||
|
@@ -158,6 +198,11 @@ def getaddresses(fieldvalues, *, strict=True):
|
||||||
|
If strict is true, use a strict parser which rejects malformed inputs.
|
||||||
|
"""
|
||||||
|
|
||||||
|
+ # If default is used, it's True unless disabled
|
||||||
|
+ # by env variable or config file.
|
||||||
|
+ if strict == None:
|
||||||
|
+ strict = _use_strict_email_parsing()
|
||||||
|
+
|
||||||
|
# If strict is true, if the resulting list of parsed addresses is greater
|
||||||
|
# than the number of fieldvalues in the input list, a parsing error has
|
||||||
|
# occurred and consequently a list containing a single empty 2-tuple [('',
|
||||||
|
@@ -330,7 +375,7 @@ def parsedate_to_datetime(data):
|
||||||
|
tzinfo=datetime.timezone(datetime.timedelta(seconds=tz)))
|
||||||
|
|
||||||
|
|
||||||
|
-def parseaddr(addr, *, strict=True):
|
||||||
|
+def parseaddr(addr, *, strict=None):
|
||||||
|
"""
|
||||||
|
Parse addr into its constituent realname and email address parts.
|
||||||
|
|
||||||
|
@@ -339,6 +384,11 @@ def parseaddr(addr, *, strict=True):
|
||||||
|
|
||||||
|
If strict is True, use a strict parser which rejects malformed inputs.
|
||||||
|
"""
|
||||||
|
+ # If default is used, it's True unless disabled
|
||||||
|
+ # by env variable or config file.
|
||||||
|
+ if strict == None:
|
||||||
|
+ strict = _use_strict_email_parsing()
|
||||||
|
+
|
||||||
|
if not strict:
|
||||||
|
addrs = _AddressList(addr).addresslist
|
||||||
|
if not addrs:
|
||||||
|
diff --git a/Lib/test/test_email/test_email.py b/Lib/test/test_email/test_email.py
|
||||||
|
index ce36efc1b1..05ea201b68 100644
|
||||||
|
--- a/Lib/test/test_email/test_email.py
|
||||||
|
+++ b/Lib/test/test_email/test_email.py
|
||||||
|
@@ -7,6 +7,9 @@ import time
|
||||||
|
import base64
|
||||||
|
import unittest
|
||||||
|
import textwrap
|
||||||
|
+import contextlib
|
||||||
|
+import tempfile
|
||||||
|
+import os
|
||||||
|
|
||||||
|
from io import StringIO, BytesIO
|
||||||
|
from itertools import chain
|
||||||
|
@@ -41,7 +44,7 @@ from email import iterators
|
||||||
|
from email import base64mime
|
||||||
|
from email import quoprimime
|
||||||
|
|
||||||
|
-from test.support import unlink, start_threads
|
||||||
|
+from test.support import unlink, start_threads, EnvironmentVarGuard, swap_attr
|
||||||
|
from test.test_email import openfile, TestEmailBase
|
||||||
|
|
||||||
|
# These imports are documented to work, but we are testing them using a
|
||||||
|
@@ -3313,6 +3316,73 @@ Foo
|
||||||
|
# Test email.utils.supports_strict_parsing attribute
|
||||||
|
self.assertEqual(email.utils.supports_strict_parsing, True)
|
||||||
|
|
||||||
|
+ def test_parsing_errors_strict_set_via_env_var(self):
|
||||||
|
+ address = 'alice@example.org )Alice('
|
||||||
|
+ empty = ('', '')
|
||||||
|
+
|
||||||
|
+ # Reset cached default value to make the function
|
||||||
|
+ # reload the config file provided below.
|
||||||
|
+ utils._cached_strict_addr_parsing = None
|
||||||
|
+
|
||||||
|
+ # Strict disabled via env variable, old behavior expected
|
||||||
|
+ with EnvironmentVarGuard() as environ:
|
||||||
|
+ environ["PYTHON_EMAIL_DISABLE_STRICT_ADDR_PARSING"] = "1"
|
||||||
|
+
|
||||||
|
+ self.assertEqual(utils.getaddresses([address]),
|
||||||
|
+ [('', 'alice@example.org'), ('', ''), ('', 'Alice')])
|
||||||
|
+ self.assertEqual(utils.parseaddr([address]), ('', address))
|
||||||
|
+
|
||||||
|
+ # Clear cache again
|
||||||
|
+ utils._cached_strict_addr_parsing = None
|
||||||
|
+
|
||||||
|
+ # Default strict=True, empty result expected
|
||||||
|
+ self.assertEqual(utils.getaddresses([address]), [empty])
|
||||||
|
+ self.assertEqual(utils.parseaddr([address]), empty)
|
||||||
|
+
|
||||||
|
+ # Clear cache again
|
||||||
|
+ utils._cached_strict_addr_parsing = None
|
||||||
|
+
|
||||||
|
+ # Empty string in env variable = strict parsing enabled (default)
|
||||||
|
+ with EnvironmentVarGuard() as environ:
|
||||||
|
+ environ["PYTHON_EMAIL_DISABLE_STRICT_ADDR_PARSING"] = ""
|
||||||
|
+
|
||||||
|
+ # Default strict=True, empty result expected
|
||||||
|
+ self.assertEqual(utils.getaddresses([address]), [empty])
|
||||||
|
+ self.assertEqual(utils.parseaddr([address]), empty)
|
||||||
|
+
|
||||||
|
+ @contextlib.contextmanager
|
||||||
|
+ def _email_strict_parsing_conf(self):
|
||||||
|
+ """Context for the given email strict parsing configured in config file"""
|
||||||
|
+ with tempfile.TemporaryDirectory() as tmpdirname:
|
||||||
|
+ filename = os.path.join(tmpdirname, 'conf.cfg')
|
||||||
|
+ with swap_attr(utils, "_EMAIL_CONFIG_FILE", filename):
|
||||||
|
+ with open(filename, 'w') as file:
|
||||||
|
+ file.write('[email_addr_parsing]\n')
|
||||||
|
+ file.write('PYTHON_EMAIL_DISABLE_STRICT_ADDR_PARSING = true')
|
||||||
|
+ utils._EMAIL_CONFIG_FILE = filename
|
||||||
|
+ yield
|
||||||
|
+
|
||||||
|
+ def test_parsing_errors_strict_disabled_via_config_file(self):
|
||||||
|
+ address = 'alice@example.org )Alice('
|
||||||
|
+ empty = ('', '')
|
||||||
|
+
|
||||||
|
+ # Reset cached default value to make the function
|
||||||
|
+ # reload the config file provided below.
|
||||||
|
+ utils._cached_strict_addr_parsing = None
|
||||||
|
+
|
||||||
|
+ # Strict disabled via config file, old results expected
|
||||||
|
+ with self._email_strict_parsing_conf():
|
||||||
|
+ self.assertEqual(utils.getaddresses([address]),
|
||||||
|
+ [('', 'alice@example.org'), ('', ''), ('', 'Alice')])
|
||||||
|
+ self.assertEqual(utils.parseaddr([address]), ('', address))
|
||||||
|
+
|
||||||
|
+ # Clear cache again
|
||||||
|
+ utils._cached_strict_addr_parsing = None
|
||||||
|
+
|
||||||
|
+ # Default strict=True, empty result expected
|
||||||
|
+ self.assertEqual(utils.getaddresses([address]), [empty])
|
||||||
|
+ self.assertEqual(utils.parseaddr([address]), empty)
|
||||||
|
+
|
||||||
|
def test_getaddresses_nasty(self):
|
||||||
|
for addresses, expected in (
|
||||||
|
(['"Sürname, Firstname" <to@example.com>'],
|
||||||
|
--
|
||||||
|
2.43.0
|
||||||
|
|
16
SOURCES/Python-3.9.18.tar.xz.asc
Normal file
16
SOURCES/Python-3.9.18.tar.xz.asc
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
-----BEGIN PGP SIGNATURE-----
|
||||||
|
|
||||||
|
iQIzBAABCgAdFiEE4/8oOcBIslwITevpsmmV4xAlBWgFAmTnntEACgkQsmmV4xAl
|
||||||
|
BWgmQw/9EFWMXtSfWBV93AQF37r0nbUnOBvrOcubkO7ygt+GfHKzN8EPuNeO2It7
|
||||||
|
yNZDuCmwepnNGaIkO7UkgbwYyNw3YaoHQqxG8izAfJAVqK6BSk8UAET/YKWFXbLv
|
||||||
|
cZBfgxSa0tTEkwq3BAY4vDewRXnLkUq7k6JRRCKFGLNSi/ygC56SijxyAV2g4Vio
|
||||||
|
Qcwr9VhsTvz6ujoWuPrfVpUY4I81LBJxKK7n9zBreYzh5uUXRu5k4lN2W8HrE4q0
|
||||||
|
7tTdsccB9j1CJAiUacYLxTFsvwd/hBs9+g9Eu5kqGeChqEU56Gd8wR96TEu8cVIZ
|
||||||
|
Bv5UEo9MgT1KsJwk0FMfV8qVScqZrGG3QaoMtNAeAm/tUrhhZO9ANYsC9dey03ut
|
||||||
|
tU6s5GAeh6i17bqW5WfvzCdhY9ayCInndzkq7SPi9F7fYx79PgdsofqPdyCSBXUo
|
||||||
|
Ozfn1VQkYQJTmYtrwqLfdAivubaEPIf1+fLqMOXbrI85Ujuy5xzlgVrrqO2K9rbE
|
||||||
|
DYyPgGZjPtss/yZGRCUdJX6rbW8Tq0HKt/8HpbW5fCt9o0wCSawR71GhzPA1fpNs
|
||||||
|
0mkAGvvoNGdiSizTLLPvNCaecw4kSzeBNViyP6oRCv69ifNqHPErItsMZ0YIMU14
|
||||||
|
w4/d9yI9kUa2bvE3cmx6G+9OS8PYip9MsJbQgP7kJsZ8wgt9rQU=
|
||||||
|
=aw+P
|
||||||
|
-----END PGP SIGNATURE-----
|
@ -1,16 +0,0 @@
|
|||||||
-----BEGIN PGP SIGNATURE-----
|
|
||||||
|
|
||||||
iQIzBAABCgAdFiEE4/8oOcBIslwITevpsmmV4xAlBWgFAmGSq04ACgkQsmmV4xAl
|
|
||||||
BWjtXQ/8CggiE692fd0jq3sVMNdAarUwWPWTVblm3H0zkwrpkrLAOTZfb+6UMini
|
|
||||||
XbXgFBIAERLffO5XHumJgXKR9pkJmYxQ5gjz+9YA5hBswQzj6x6nFqANYj3Jes7N
|
|
||||||
GUTohgVgmhfTW8EsJwE+bfRuJfpQaWDS1YHErjvfH+iywpJdagNGoHlxjoFFY4LQ
|
|
||||||
pOxO9fe6WqCCqGcGoniyRrGOpoyo59eMezVc9bCOeuvsEhOxcxAJq8bEMrphL0AJ
|
|
||||||
EOAsb5AZYsQFZhxbdN//f5CPMJ1eskqYN0ho1LGhL/z0JcRUDpAb4h5YQHcCgQN+
|
|
||||||
rNM6keTfFtNEa19ByW74f1fJJw/VgqBHNm6AVnvw9WOJvhlwSei0xeZHZtSL5VtH
|
|
||||||
WsJtZuhYqRWCrokjt2HOVD4b8vvAegaEqEPR+SFPBqJJbiHJ/LQTOv+fweQnyo+F
|
|
||||||
EXKof0ZXbax7Q/ADIVhd2y3C0hYrpzRKVEk1u643ns0V+wZ2VuilL/k5aB/LaOoy
|
|
||||||
Z9O2WoxSw0SMm0etegVyefT5+gj5l/8euXwKWqxKFPhue7pWngwzMPzjkMDcvfYb
|
|
||||||
4o21d9vbTFvGAHJ5SCqHPLy5iIf40pfLLZreEXtSMLM1mGMlfGnt270UasUIIxWE
|
|
||||||
TxHYHthQX0M2hMW77dc5hcVcRrUNKmf8P7XbnDjc61eSlRgTJaE=
|
|
||||||
=2zA6
|
|
||||||
-----END PGP SIGNATURE-----
|
|
@ -13,11 +13,11 @@ URL: https://www.python.org/
|
|||||||
|
|
||||||
# WARNING When rebasing to a new Python version,
|
# WARNING When rebasing to a new Python version,
|
||||||
# remember to update the python3-docs package as well
|
# remember to update the python3-docs package as well
|
||||||
%global general_version %{pybasever}.9
|
%global general_version %{pybasever}.18
|
||||||
#global prerel ...
|
#global prerel ...
|
||||||
%global upstream_version %{general_version}%{?prerel}
|
%global upstream_version %{general_version}%{?prerel}
|
||||||
Version: %{general_version}%{?prerel:~%{prerel}}
|
Version: %{general_version}%{?prerel:~%{prerel}}
|
||||||
Release: 2%{?dist}
|
Release: 3%{?dist}
|
||||||
License: Python
|
License: Python
|
||||||
|
|
||||||
|
|
||||||
@ -325,10 +325,10 @@ Patch189: 00189-use-rpm-wheels.patch
|
|||||||
# The versions are written in Lib/ensurepip/__init__.py, this patch removes them.
|
# The versions are written in Lib/ensurepip/__init__.py, this patch removes them.
|
||||||
# When the bundled setuptools/pip wheel is updated, the patch no longer applies cleanly.
|
# When the bundled setuptools/pip wheel is updated, the patch no longer applies cleanly.
|
||||||
# In such cases, the patch needs to be amended and the versions updated here:
|
# In such cases, the patch needs to be amended and the versions updated here:
|
||||||
%global pip_version 21.2.4
|
%global pip_version 23.0.1
|
||||||
%global setuptools_version 58.1.0
|
%global setuptools_version 58.1.0
|
||||||
|
|
||||||
# 00251 # 2eabd04356402d488060bc8fe316ad13fc8a3356
|
# 00251 # 1b1047c14ff98eae6d355b4aac4df3e388813f62
|
||||||
# Change user install location
|
# Change user install location
|
||||||
#
|
#
|
||||||
# Set values of prefix and exec_prefix in distutils install command
|
# Set values of prefix and exec_prefix in distutils install command
|
||||||
@ -336,7 +336,13 @@ Patch189: 00189-use-rpm-wheels.patch
|
|||||||
# is not detected to make pip and distutils install into separate location.
|
# is not detected to make pip and distutils install into separate location.
|
||||||
#
|
#
|
||||||
# Fedora Change: https://fedoraproject.org/wiki/Changes/Making_sudo_pip_safe
|
# Fedora Change: https://fedoraproject.org/wiki/Changes/Making_sudo_pip_safe
|
||||||
# Downstream only: Awaiting resources to work on upstream PEP
|
# Downstream only: Reworked in Fedora 36+/Python 3.10+ to follow https://bugs.python.org/issue43976
|
||||||
|
#
|
||||||
|
# pypa/distutils integration: https://github.com/pypa/distutils/pull/70
|
||||||
|
#
|
||||||
|
# Also set sysconfig._PIP_USE_SYSCONFIG = False, to force pip-upgraded-pip
|
||||||
|
# to respect this patched distutils install command.
|
||||||
|
# See https://bugzilla.redhat.com/show_bug.cgi?id=2014513
|
||||||
Patch251: 00251-change-user-install-location.patch
|
Patch251: 00251-change-user-install-location.patch
|
||||||
|
|
||||||
# 00328 # 367fdcb5a075f083aea83ac174999272a8faf75c
|
# 00328 # 367fdcb5a075f083aea83ac174999272a8faf75c
|
||||||
@ -363,8 +369,9 @@ Patch328: 00328-pyc-timestamp-invalidation-mode.patch
|
|||||||
# - In FIPS mode, the blake2 hashes use OpenSSL wrappers
|
# - In FIPS mode, the blake2 hashes use OpenSSL wrappers
|
||||||
# and do not offer extended functionality (keys, tree hashing, custom digest size)
|
# and do not offer extended functionality (keys, tree hashing, custom digest size)
|
||||||
#
|
#
|
||||||
# - The patch, in its current state, is in a preliminary form to set the groundwork
|
# - In FIPS mode, hmac.HMAC can only be instantiated with an OpenSSL wrapper
|
||||||
# for refining Python's FIPS compatibility until OpenSSL is FIPS ready.
|
# or a string with OpenSSL hash name as the "digestmod" argument.
|
||||||
|
# The argument must be specified (instead of defaulting to ‘md5’).
|
||||||
Patch329: 00329-fips.patch
|
Patch329: 00329-fips.patch
|
||||||
|
|
||||||
# 00353 # ab4cc97b643cfe99f567e3a03e5617b507183771
|
# 00353 # ab4cc97b643cfe99f567e3a03e5617b507183771
|
||||||
@ -392,6 +399,36 @@ Patch329: 00329-fips.patch
|
|||||||
# a nightmare because it's basically a binary file.
|
# a nightmare because it's basically a binary file.
|
||||||
Patch353: 00353-architecture-names-upstream-downstream.patch
|
Patch353: 00353-architecture-names-upstream-downstream.patch
|
||||||
|
|
||||||
|
# 00397 #
|
||||||
|
# Add filters for tarfile extraction (CVE-2007-4559, PEP-706)
|
||||||
|
# First patch fixes determination of symlink targets, which were treated
|
||||||
|
# as relative to the root of the archive,
|
||||||
|
# rather than the directory containing the symlink.
|
||||||
|
# Not yet upstream as of this writing.
|
||||||
|
# The second patch is Red Hat configuration, see KB for documentation:
|
||||||
|
# - https://access.redhat.com/articles/7004769
|
||||||
|
Patch397: 00397-tarfile-filter.patch
|
||||||
|
|
||||||
|
# 00414 #
|
||||||
|
#
|
||||||
|
# Skip test_pair() and test_speech128() of test_zlib on s390x since
|
||||||
|
# they fail if zlib uses the s390x hardware accelerator.
|
||||||
|
Patch414: 00414-skip_test_zlib_s390x.patch
|
||||||
|
|
||||||
|
# 00415 #
|
||||||
|
# [CVE-2023-27043] gh-102988: Reject malformed addresses in email.parseaddr() (#111116)
|
||||||
|
#
|
||||||
|
# Detect email address parsing errors and return empty tuple to
|
||||||
|
# indicate the parsing error (old API). Add an optional 'strict'
|
||||||
|
# parameter to getaddresses() and parseaddr() functions. Patch by
|
||||||
|
# Thomas Dwyer.
|
||||||
|
#
|
||||||
|
# Upstream PR: https://github.com/python/cpython/pull/111116
|
||||||
|
#
|
||||||
|
# Second patch implmenets the possibility to restore the old behavior via
|
||||||
|
# config file or environment variable.
|
||||||
|
Patch415: 00415-cve-2023-27043-gh-102988-reject-malformed-addresses-in-email-parseaddr-111116.patch
|
||||||
|
|
||||||
# (New patches go here ^^^)
|
# (New patches go here ^^^)
|
||||||
#
|
#
|
||||||
# When adding new patches to "python" and "python3" in Fedora, EL, etc.,
|
# When adding new patches to "python" and "python3" in Fedora, EL, etc.,
|
||||||
@ -768,6 +805,9 @@ version once Python %{pybasever} is stable.
|
|||||||
%autopatch -M 188
|
%autopatch -M 188
|
||||||
|
|
||||||
%if %{with rpmwheels}
|
%if %{with rpmwheels}
|
||||||
|
# Temporary workaround for https://bugzilla.redhat.com/show_bug.cgi?id=1954999
|
||||||
|
%{?!apply_patch:%define apply_patch(qp:m:) {%__apply_patch %**}}
|
||||||
|
|
||||||
%apply_patch -q %{PATCH189}
|
%apply_patch -q %{PATCH189}
|
||||||
rm Lib/ensurepip/_bundled/*.whl
|
rm Lib/ensurepip/_bundled/*.whl
|
||||||
%endif
|
%endif
|
||||||
@ -1790,6 +1830,67 @@ CheckPython optimized
|
|||||||
# ======================================================
|
# ======================================================
|
||||||
|
|
||||||
%changelog
|
%changelog
|
||||||
|
* Wed Jan 24 2024 Lumír Balhar <lbalhar@redhat.com> - 3.9.18-3
|
||||||
|
- Fix tests on s390x with hw acceleration
|
||||||
|
Resolves: RHEL-13043
|
||||||
|
|
||||||
|
* Thu Jan 04 2024 Lumír Balhar <lbalhar@redhat.com> - 3.9.18-2
|
||||||
|
- Security fix for CVE-2023-27043
|
||||||
|
Resolves: RHEL-20613
|
||||||
|
|
||||||
|
* Thu Sep 07 2023 Charalampos Stratakis <cstratak@redhat.com> - 3.9.18-1
|
||||||
|
- Update to 3.9.18
|
||||||
|
- Security fix for CVE-2023-40217
|
||||||
|
Resolves: RHEL-3043
|
||||||
|
|
||||||
|
* Wed Aug 09 2023 Petr Viktorin <pviktori@redhat.com> - 3.9.17-2
|
||||||
|
- Fix symlink handling in the fix for CVE-2023-24329
|
||||||
|
Resolves: rhbz#263261
|
||||||
|
|
||||||
|
* Mon Jun 26 2023 Charalampos Stratakis <cstratak@redhat.com> - 3.9.17-1
|
||||||
|
- Update to 3.9.17
|
||||||
|
- Security fix for CVE-2023-24329
|
||||||
|
Resolves: rhbz#2173917
|
||||||
|
|
||||||
|
* Tue Mar 07 2023 Petr Viktorin <pviktori@redhat.com> - 3.9.16-2
|
||||||
|
- Add filters for tarfile extraction (CVE-2007-4559, PEP-706)
|
||||||
|
Resolves: rhbz#263261
|
||||||
|
|
||||||
|
* Thu Dec 08 2022 Charalampos Stratakis <cstratak@redhat.com> - 3.9.16-1
|
||||||
|
- Update to 3.9.16
|
||||||
|
- Security fixes for CVE-2022-42919 and CVE-2022-45061
|
||||||
|
Resolves: rhbz#2138705, rhbz#2144072
|
||||||
|
|
||||||
|
* Wed Sep 21 2022 Charalampos Stratakis <cstratak@redhat.com> - 3.9.14-1
|
||||||
|
- Update to 3.9.14
|
||||||
|
- Security fixes for CVE-2020-10735 and CVE-2021-28861
|
||||||
|
Resolves: rhbz#2120642, rhbz#1834423, rhbz#2128249
|
||||||
|
|
||||||
|
* Mon Jul 25 2022 Lumír Balhar <lbalhar@redhat.com> - 3.9.13-3
|
||||||
|
- Fix test_get_ciphers in test_ssl.py for FIPS mode
|
||||||
|
Resolves: rhbz#2058233
|
||||||
|
|
||||||
|
* Thu Jun 09 2022 Charalampos Stratakis <cstratak@redhat.com> - 3.9.13-2
|
||||||
|
- Security fix for CVE-2015-20107
|
||||||
|
Resolves: rhbz#2075390
|
||||||
|
|
||||||
|
* Wed Jun 01 2022 Charalampos Stratakis <cstratak@redhat.com> - 3.9.13-1
|
||||||
|
- Update to 3.9.13
|
||||||
|
Resolves: rhbz#2054702, rhbz#2059951
|
||||||
|
|
||||||
|
* Wed Feb 09 2022 Charalampos Stratakis <cstratak@redhat.com> - 3.9.10-2
|
||||||
|
- Fix undefined behavior in Modules/_hashopenssl.c
|
||||||
|
Resolves: rhbz#1942527
|
||||||
|
|
||||||
|
* Mon Jan 17 2022 Charalampos Stratakis <cstratak@redhat.com> - 3.9.10-1
|
||||||
|
- Update to 3.9.10
|
||||||
|
- Support OpenSSL FIPS mode
|
||||||
|
Resolves: rhbz#1942527, rhbz#1835169
|
||||||
|
|
||||||
|
* Sat Jan 08 2022 Miro Hrončok <mhroncok@redhat.com> - 3.9.9-4
|
||||||
|
- Instruct pip to use distutils
|
||||||
|
- Instruct pypa/distutils to add /local/ addition to prefix
|
||||||
|
|
||||||
* Mon Nov 22 2021 Tomas Orsava <torsava@redhat.com> - 3.9.9-2
|
* Mon Nov 22 2021 Tomas Orsava <torsava@redhat.com> - 3.9.9-2
|
||||||
- Read pre-packaged Python wheels from a newly versioned directory
|
- Read pre-packaged Python wheels from a newly versioned directory
|
||||||
/usr/share/python3-wheels
|
/usr/share/python3-wheels
|
||||||
|
Loading…
Reference in New Issue
Block a user