Compare commits
	
		
			No commits in common. "imports/c9-beta/python3.11-3.11.4-3.el9" and "c8-beta" have entirely different histories.
		
	
	
		
			imports/c9
			...
			c8-beta
		
	
		
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @ -1 +1 @@ | |||||||
| SOURCES/Python-3.11.4.tar.xz | SOURCES/Python-3.11.7.tar.xz | ||||||
|  | |||||||
| @ -1 +1 @@ | |||||||
| 413b3715d919a7b473281529ab91eeea5c82e632 SOURCES/Python-3.11.4.tar.xz | f2534d591121f3845388fbdd6a121b96dfe305a6 SOURCES/Python-3.11.7.tar.xz | ||||||
|  | |||||||
| @ -1,4 +1,4 @@ | |||||||
| From c96f1bea2ffc5c0ca849d5406236c07ea229a64f Mon Sep 17 00:00:00 2001 | From ecc5137120f471c22ff6dcb1bd128561c31e023c Mon Sep 17 00:00:00 2001 | ||||||
| From: Charalampos Stratakis <cstratak@redhat.com> | From: Charalampos Stratakis <cstratak@redhat.com> | ||||||
| Date: Thu, 12 Dec 2019 16:58:31 +0100 | Date: Thu, 12 Dec 2019 16:58:31 +0100 | ||||||
| Subject: [PATCH 1/7] Expose blake2b and blake2s hashes from OpenSSL | Subject: [PATCH 1/7] Expose blake2b and blake2s hashes from OpenSSL | ||||||
| @ -29,10 +29,10 @@ index 67becdd..6607ef7 100644 | |||||||
|              computed = m.hexdigest() if not shake else m.hexdigest(length) |              computed = m.hexdigest() if not shake else m.hexdigest(length) | ||||||
|              self.assertEqual( |              self.assertEqual( | ||||||
| diff --git a/Modules/_hashopenssl.c b/Modules/_hashopenssl.c
 | diff --git a/Modules/_hashopenssl.c b/Modules/_hashopenssl.c
 | ||||||
| index 3c40f09..e819d02 100644
 | index 57d64bd..d0c3b9e 100644
 | ||||||
| --- a/Modules/_hashopenssl.c
 | --- a/Modules/_hashopenssl.c
 | ||||||
| +++ b/Modules/_hashopenssl.c
 | +++ b/Modules/_hashopenssl.c
 | ||||||
| @@ -1077,6 +1077,41 @@ _hashlib_openssl_sha512_impl(PyObject *module, PyObject *data_obj,
 | @@ -1078,6 +1078,41 @@ _hashlib_openssl_sha512_impl(PyObject *module, PyObject *data_obj,
 | ||||||
|  } |  } | ||||||
|   |   | ||||||
|   |   | ||||||
| @ -74,7 +74,7 @@ index 3c40f09..e819d02 100644 | |||||||
|  #ifdef PY_OPENSSL_HAS_SHA3 |  #ifdef PY_OPENSSL_HAS_SHA3 | ||||||
|   |   | ||||||
|  /*[clinic input] |  /*[clinic input] | ||||||
| @@ -2065,6 +2100,8 @@ static struct PyMethodDef EVP_functions[] = {
 | @@ -2066,6 +2101,8 @@ static struct PyMethodDef EVP_functions[] = {
 | ||||||
|      _HASHLIB_OPENSSL_SHA256_METHODDEF |      _HASHLIB_OPENSSL_SHA256_METHODDEF | ||||||
|      _HASHLIB_OPENSSL_SHA384_METHODDEF |      _HASHLIB_OPENSSL_SHA384_METHODDEF | ||||||
|      _HASHLIB_OPENSSL_SHA512_METHODDEF |      _HASHLIB_OPENSSL_SHA512_METHODDEF | ||||||
| @ -205,10 +205,10 @@ index 5d84f4a..011026a 100644 | |||||||
| -/*[clinic end generated code: output=69f2374071bff707 input=a9049054013a1b77]*/
 | -/*[clinic end generated code: output=69f2374071bff707 input=a9049054013a1b77]*/
 | ||||||
| +/*[clinic end generated code: output=c6a9af5563972eda input=a9049054013a1b77]*/
 | +/*[clinic end generated code: output=c6a9af5563972eda input=a9049054013a1b77]*/
 | ||||||
| -- 
 | -- 
 | ||||||
| 2.39.1 | 2.43.0 | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| From 9a7e164840aa35602e1c6dddadd461fafc666a63 Mon Sep 17 00:00:00 2001 | From 0198d467525e79cb4be4418708719af3eaee7a40 Mon Sep 17 00:00:00 2001 | ||||||
| From: Petr Viktorin <pviktori@redhat.com> | From: Petr Viktorin <pviktori@redhat.com> | ||||||
| Date: Thu, 1 Aug 2019 17:57:05 +0200 | Date: Thu, 1 Aug 2019 17:57:05 +0200 | ||||||
| Subject: [PATCH 2/7] Use a stronger hash in multiprocessing handshake | Subject: [PATCH 2/7] Use a stronger hash in multiprocessing handshake | ||||||
| @ -220,10 +220,10 @@ https://bugs.python.org/issue17258 | |||||||
|  1 file changed, 6 insertions(+), 2 deletions(-) |  1 file changed, 6 insertions(+), 2 deletions(-) | ||||||
| 
 | 
 | ||||||
| diff --git a/Lib/multiprocessing/connection.py b/Lib/multiprocessing/connection.py
 | diff --git a/Lib/multiprocessing/connection.py b/Lib/multiprocessing/connection.py
 | ||||||
| index b08144f..0497557 100644
 | index 8b81f99..69c0b7e 100644
 | ||||||
| --- a/Lib/multiprocessing/connection.py
 | --- a/Lib/multiprocessing/connection.py
 | ||||||
| +++ b/Lib/multiprocessing/connection.py
 | +++ b/Lib/multiprocessing/connection.py
 | ||||||
| @@ -42,6 +42,10 @@ BUFSIZE = 8192
 | @@ -43,6 +43,10 @@ BUFSIZE = 8192
 | ||||||
|  # A very generous timeout when it comes to local connections... |  # A very generous timeout when it comes to local connections... | ||||||
|  CONNECTION_TIMEOUT = 20. |  CONNECTION_TIMEOUT = 20. | ||||||
|   |   | ||||||
| @ -234,7 +234,7 @@ index b08144f..0497557 100644 | |||||||
|  _mmap_counter = itertools.count() |  _mmap_counter = itertools.count() | ||||||
|   |   | ||||||
|  default_family = 'AF_INET' |  default_family = 'AF_INET' | ||||||
| @@ -735,7 +739,7 @@ def deliver_challenge(connection, authkey):
 | @@ -752,7 +756,7 @@ def deliver_challenge(connection, authkey):
 | ||||||
|              "Authkey must be bytes, not {0!s}".format(type(authkey))) |              "Authkey must be bytes, not {0!s}".format(type(authkey))) | ||||||
|      message = os.urandom(MESSAGE_LENGTH) |      message = os.urandom(MESSAGE_LENGTH) | ||||||
|      connection.send_bytes(CHALLENGE + message) |      connection.send_bytes(CHALLENGE + message) | ||||||
| @ -243,7 +243,7 @@ index b08144f..0497557 100644 | |||||||
|      response = connection.recv_bytes(256)        # reject large message |      response = connection.recv_bytes(256)        # reject large message | ||||||
|      if response == digest: |      if response == digest: | ||||||
|          connection.send_bytes(WELCOME) |          connection.send_bytes(WELCOME) | ||||||
| @@ -751,7 +755,7 @@ def answer_challenge(connection, authkey):
 | @@ -768,7 +772,7 @@ def answer_challenge(connection, authkey):
 | ||||||
|      message = connection.recv_bytes(256)         # reject large message |      message = connection.recv_bytes(256)         # reject large message | ||||||
|      assert message[:len(CHALLENGE)] == CHALLENGE, 'message = %r' % message |      assert message[:len(CHALLENGE)] == CHALLENGE, 'message = %r' % message | ||||||
|      message = message[len(CHALLENGE):] |      message = message[len(CHALLENGE):] | ||||||
| @ -253,10 +253,10 @@ index b08144f..0497557 100644 | |||||||
|      response = connection.recv_bytes(256)        # reject large message |      response = connection.recv_bytes(256)        # reject large message | ||||||
|      if response != WELCOME: |      if response != WELCOME: | ||||||
| -- 
 | -- 
 | ||||||
| 2.39.1 | 2.43.0 | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| From 10b91783a2f22153738c5658a98daf7475ad9a8c Mon Sep 17 00:00:00 2001 | From a7822e2e1f21529e9730885bd8c9c6ab7c704d5b Mon Sep 17 00:00:00 2001 | ||||||
| From: Petr Viktorin <pviktori@redhat.com> | From: Petr Viktorin <pviktori@redhat.com> | ||||||
| Date: Thu, 25 Jul 2019 17:19:06 +0200 | Date: Thu, 25 Jul 2019 17:19:06 +0200 | ||||||
| Subject: [PATCH 3/7] Disable Python's hash implementations in FIPS mode, | Subject: [PATCH 3/7] Disable Python's hash implementations in FIPS mode, | ||||||
| @ -359,7 +359,7 @@ index c2cac98..55b1677 100644 | |||||||
|   |   | ||||||
|      if (self->lock == NULL && buf.len >= HASHLIB_GIL_MINSIZE) |      if (self->lock == NULL && buf.len >= HASHLIB_GIL_MINSIZE) | ||||||
| diff --git a/Modules/_blake2/blake2module.c b/Modules/_blake2/blake2module.c
 | diff --git a/Modules/_blake2/blake2module.c b/Modules/_blake2/blake2module.c
 | ||||||
| index 44d783b..d247e44 100644
 | index 93478f5..e3a024d 100644
 | ||||||
| --- a/Modules/_blake2/blake2module.c
 | --- a/Modules/_blake2/blake2module.c
 | ||||||
| +++ b/Modules/_blake2/blake2module.c
 | +++ b/Modules/_blake2/blake2module.c
 | ||||||
| @@ -13,6 +13,7 @@
 | @@ -13,6 +13,7 @@
 | ||||||
| @ -370,7 +370,7 @@ index 44d783b..d247e44 100644 | |||||||
|  #include "blake2module.h" |  #include "blake2module.h" | ||||||
|   |   | ||||||
|  extern PyType_Spec blake2b_type_spec; |  extern PyType_Spec blake2b_type_spec; | ||||||
| @@ -77,6 +78,7 @@ _blake2_free(void *module)
 | @@ -83,6 +84,7 @@ _blake2_free(void *module)
 | ||||||
|  static int |  static int | ||||||
|  blake2_exec(PyObject *m) |  blake2_exec(PyObject *m) | ||||||
|  { |  { | ||||||
| @ -378,7 +378,7 @@ index 44d783b..d247e44 100644 | |||||||
|      Blake2State* st = blake2_get_state(m); |      Blake2State* st = blake2_get_state(m); | ||||||
|   |   | ||||||
|      st->blake2b_type = (PyTypeObject *)PyType_FromModuleAndSpec( |      st->blake2b_type = (PyTypeObject *)PyType_FromModuleAndSpec( | ||||||
| @@ -145,5 +147,6 @@ static struct PyModuleDef blake2_module = {
 | @@ -154,5 +156,6 @@ static struct PyModuleDef blake2_module = {
 | ||||||
|  PyMODINIT_FUNC |  PyMODINIT_FUNC | ||||||
|  PyInit__blake2(void) |  PyInit__blake2(void) | ||||||
|  { |  { | ||||||
| @ -446,10 +446,10 @@ index 56ae7a5..45fb403 100644 | |||||||
| +    if (_Py_hashlib_fips_error(exc, name)) return NULL; \
 | +    if (_Py_hashlib_fips_error(exc, name)) return NULL; \
 | ||||||
| +} while (0)
 | +} while (0)
 | ||||||
| diff --git a/configure.ac b/configure.ac
 | diff --git a/configure.ac b/configure.ac
 | ||||||
| index c62a565..861f7a0 100644
 | index 52d5c1f..56aff78 100644
 | ||||||
| --- a/configure.ac
 | --- a/configure.ac
 | ||||||
| +++ b/configure.ac
 | +++ b/configure.ac
 | ||||||
| @@ -7044,7 +7044,8 @@ PY_STDLIB_MOD([_sha512], [test "$with_builtin_sha512" = yes])
 | @@ -7069,7 +7069,8 @@ PY_STDLIB_MOD([_sha512], [test "$with_builtin_sha512" = yes])
 | ||||||
|  PY_STDLIB_MOD([_sha3], [test "$with_builtin_sha3" = yes]) |  PY_STDLIB_MOD([_sha3], [test "$with_builtin_sha3" = yes]) | ||||||
|  PY_STDLIB_MOD([_blake2], |  PY_STDLIB_MOD([_blake2], | ||||||
|    [test "$with_builtin_blake2" = yes], [], |    [test "$with_builtin_blake2" = yes], [], | ||||||
| @ -460,10 +460,10 @@ index c62a565..861f7a0 100644 | |||||||
|  PY_STDLIB_MOD([_crypt], |  PY_STDLIB_MOD([_crypt], | ||||||
|    [], [test "$ac_cv_crypt_crypt" = yes], |    [], [test "$ac_cv_crypt_crypt" = yes], | ||||||
| -- 
 | -- 
 | ||||||
| 2.39.1 | 2.43.0 | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| From e26066b1c05c9768e38cb6f45d6a01058de55b3f Mon Sep 17 00:00:00 2001 | From e9ce6d33544559172dbebbe0c0dfba2757c62331 Mon Sep 17 00:00:00 2001 | ||||||
| From: Charalampos Stratakis <cstratak@redhat.com> | From: Charalampos Stratakis <cstratak@redhat.com> | ||||||
| Date: Fri, 29 Jan 2021 14:16:21 +0100 | Date: Fri, 29 Jan 2021 14:16:21 +0100 | ||||||
| Subject: [PATCH 4/7] Use python's fall back crypto implementations only if we | Subject: [PATCH 4/7] Use python's fall back crypto implementations only if we | ||||||
| @ -623,10 +623,10 @@ index 01d12f5..a7cdb07 100644 | |||||||
|      def test_pbkdf2_hmac_py(self): |      def test_pbkdf2_hmac_py(self): | ||||||
|          with warnings_helper.check_warnings(): |          with warnings_helper.check_warnings(): | ||||||
| -- 
 | -- 
 | ||||||
| 2.39.1 | 2.43.0 | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| From 9ccbd22b8538fee379717c8b2916dc1ff8b96f07 Mon Sep 17 00:00:00 2001 | From 641c617775b6973ed84711a2602ba190fe064474 Mon Sep 17 00:00:00 2001 | ||||||
| From: Charalampos Stratakis <cstratak@redhat.com> | From: Charalampos Stratakis <cstratak@redhat.com> | ||||||
| Date: Wed, 31 Jul 2019 15:43:43 +0200 | Date: Wed, 31 Jul 2019 15:43:43 +0200 | ||||||
| Subject: [PATCH 5/7] Test equivalence of hashes for the various digests with | Subject: [PATCH 5/7] Test equivalence of hashes for the various digests with | ||||||
| @ -783,10 +783,10 @@ index a7cdb07..c071f28 100644 | |||||||
|  class KDFTests(unittest.TestCase): |  class KDFTests(unittest.TestCase): | ||||||
|   |   | ||||||
| -- 
 | -- 
 | ||||||
| 2.39.1 | 2.43.0 | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| From c3b8d6ecc76c87e8b05fd2cb212d5dece50ce0b1 Mon Sep 17 00:00:00 2001 | From a706c8342f0f9307d44c43c203702e1476fe73b4 Mon Sep 17 00:00:00 2001 | ||||||
| From: Petr Viktorin <pviktori@redhat.com> | From: Petr Viktorin <pviktori@redhat.com> | ||||||
| Date: Mon, 26 Aug 2019 19:39:48 +0200 | Date: Mon, 26 Aug 2019 19:39:48 +0200 | ||||||
| Subject: [PATCH 6/7] Guard against Python HMAC in FIPS mode | Subject: [PATCH 6/7] Guard against Python HMAC in FIPS mode | ||||||
| @ -844,7 +844,7 @@ index 8b4f920..20ef96c 100644 | |||||||
|              digest_cons = digestmod |              digest_cons = digestmod | ||||||
|          elif isinstance(digestmod, str): |          elif isinstance(digestmod, str): | ||||||
| diff --git a/Lib/test/test_hmac.py b/Lib/test/test_hmac.py
 | diff --git a/Lib/test/test_hmac.py b/Lib/test/test_hmac.py
 | ||||||
| index 7cf9973..a9e4e39 100644
 | index a39a2c4..0742a1c 100644
 | ||||||
| --- a/Lib/test/test_hmac.py
 | --- a/Lib/test/test_hmac.py
 | ||||||
| +++ b/Lib/test/test_hmac.py
 | +++ b/Lib/test/test_hmac.py
 | ||||||
| @@ -5,6 +5,7 @@ import hashlib
 | @@ -5,6 +5,7 @@ import hashlib
 | ||||||
| @ -875,7 +875,7 @@ index 7cf9973..a9e4e39 100644 | |||||||
|          with warnings.catch_warnings(): |          with warnings.catch_warnings(): | ||||||
|              warnings.simplefilter('error', RuntimeWarning) |              warnings.simplefilter('error', RuntimeWarning) | ||||||
|              with self.assertRaises(RuntimeWarning): |              with self.assertRaises(RuntimeWarning): | ||||||
| @@ -443,6 +450,7 @@ class ConstructorTestCase(unittest.TestCase):
 | @@ -453,6 +460,7 @@ class ConstructorTestCase(unittest.TestCase):
 | ||||||
|          with self.assertRaisesRegex(TypeError, "immutable type"): |          with self.assertRaisesRegex(TypeError, "immutable type"): | ||||||
|              C_HMAC.value = None |              C_HMAC.value = None | ||||||
|   |   | ||||||
| @ -883,7 +883,7 @@ index 7cf9973..a9e4e39 100644 | |||||||
|      @unittest.skipUnless(sha256_module is not None, 'need _sha256') |      @unittest.skipUnless(sha256_module is not None, 'need _sha256') | ||||||
|      def test_with_sha256_module(self): |      def test_with_sha256_module(self): | ||||||
|          h = hmac.HMAC(b"key", b"hash this!", digestmod=sha256_module.sha256) |          h = hmac.HMAC(b"key", b"hash this!", digestmod=sha256_module.sha256) | ||||||
| @@ -471,6 +479,7 @@ class SanityTestCase(unittest.TestCase):
 | @@ -481,6 +489,7 @@ class SanityTestCase(unittest.TestCase):
 | ||||||
|   |   | ||||||
|  class CopyTestCase(unittest.TestCase): |  class CopyTestCase(unittest.TestCase): | ||||||
|   |   | ||||||
| @ -891,7 +891,7 @@ index 7cf9973..a9e4e39 100644 | |||||||
|      @hashlib_helper.requires_hashdigest('sha256') |      @hashlib_helper.requires_hashdigest('sha256') | ||||||
|      def test_attributes_old(self): |      def test_attributes_old(self): | ||||||
|          # Testing if attributes are of same type. |          # Testing if attributes are of same type. | ||||||
| @@ -482,6 +491,7 @@ class CopyTestCase(unittest.TestCase):
 | @@ -492,6 +501,7 @@ class CopyTestCase(unittest.TestCase):
 | ||||||
|          self.assertEqual(type(h1._outer), type(h2._outer), |          self.assertEqual(type(h1._outer), type(h2._outer), | ||||||
|              "Types of outer don't match.") |              "Types of outer don't match.") | ||||||
|   |   | ||||||
| @ -900,10 +900,10 @@ index 7cf9973..a9e4e39 100644 | |||||||
|      def test_realcopy_old(self): |      def test_realcopy_old(self): | ||||||
|          # Testing if the copy method created a real copy. |          # Testing if the copy method created a real copy. | ||||||
| -- 
 | -- 
 | ||||||
| 2.39.1 | 2.43.0 | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| From 2b06ee89344e8735cdc8435aadbdf83fe289e934 Mon Sep 17 00:00:00 2001 | From 03f1dedfe5d29af20fb3686d76b045384d41d8dd Mon Sep 17 00:00:00 2001 | ||||||
| From: Petr Viktorin <encukou@gmail.com> | From: Petr Viktorin <encukou@gmail.com> | ||||||
| Date: Wed, 25 Aug 2021 16:44:43 +0200 | Date: Wed, 25 Aug 2021 16:44:43 +0200 | ||||||
| Subject: [PATCH 7/7] Disable hash-based PYCs in FIPS mode | Subject: [PATCH 7/7] Disable hash-based PYCs in FIPS mode | ||||||
| @ -946,11 +946,11 @@ index db52725..5fca65e 100644 | |||||||
|          return PycInvalidationMode.CHECKED_HASH |          return PycInvalidationMode.CHECKED_HASH | ||||||
|      else: |      else: | ||||||
| diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py
 | diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py
 | ||||||
| index c33f90d..7d40540 100644
 | index dc7a6e6..646b328 100644
 | ||||||
| --- a/Lib/test/support/__init__.py
 | --- a/Lib/test/support/__init__.py
 | ||||||
| +++ b/Lib/test/support/__init__.py
 | +++ b/Lib/test/support/__init__.py
 | ||||||
| @@ -2225,6 +2225,20 @@ def requires_venv_with_pip():
 | @@ -2203,6 +2203,20 @@ def sleeping_retry(timeout, err_msg=None, /,
 | ||||||
|      return unittest.skipUnless(ctypes, 'venv: pip requires ctypes') |          delay = min(delay * 2, max_delay) | ||||||
|   |   | ||||||
|   |   | ||||||
| +def fails_in_fips_mode(expected_error):
 | +def fails_in_fips_mode(expected_error):
 | ||||||
| @ -971,7 +971,7 @@ index c33f90d..7d40540 100644 | |||||||
|  def adjust_int_max_str_digits(max_digits): |  def adjust_int_max_str_digits(max_digits): | ||||||
|      """Temporarily change the integer string conversion length limit.""" |      """Temporarily change the integer string conversion length limit.""" | ||||||
| diff --git a/Lib/test/test_cmd_line_script.py b/Lib/test/test_cmd_line_script.py
 | diff --git a/Lib/test/test_cmd_line_script.py b/Lib/test/test_cmd_line_script.py
 | ||||||
| index 4dadbc0..7dc7e51 100644
 | index 7fcd563..476b557 100644
 | ||||||
| --- a/Lib/test/test_cmd_line_script.py
 | --- a/Lib/test/test_cmd_line_script.py
 | ||||||
| +++ b/Lib/test/test_cmd_line_script.py
 | +++ b/Lib/test/test_cmd_line_script.py
 | ||||||
| @@ -286,6 +286,7 @@ class CmdLineTest(unittest.TestCase):
 | @@ -286,6 +286,7 @@ class CmdLineTest(unittest.TestCase):
 | ||||||
| @ -991,10 +991,10 @@ index 4dadbc0..7dc7e51 100644 | |||||||
|          with os_helper.temp_dir() as script_dir: |          with os_helper.temp_dir() as script_dir: | ||||||
|              script_name = _make_test_script(script_dir, '__main__') |              script_name = _make_test_script(script_dir, '__main__') | ||||||
| diff --git a/Lib/test/test_compileall.py b/Lib/test/test_compileall.py
 | diff --git a/Lib/test/test_compileall.py b/Lib/test/test_compileall.py
 | ||||||
| index 05154c8..c678d4a 100644
 | index 9cd92ad..4ec29a1 100644
 | ||||||
| --- a/Lib/test/test_compileall.py
 | --- a/Lib/test/test_compileall.py
 | ||||||
| +++ b/Lib/test/test_compileall.py
 | +++ b/Lib/test/test_compileall.py
 | ||||||
| @@ -800,14 +800,23 @@ class CommandLineTestsBase:
 | @@ -806,14 +806,23 @@ class CommandLineTestsBase:
 | ||||||
|          out = self.assertRunOK('badfilename') |          out = self.assertRunOK('badfilename') | ||||||
|          self.assertRegex(out, b"Can't list 'badfilename'") |          self.assertRegex(out, b"Can't list 'badfilename'") | ||||||
|   |   | ||||||
| @ -1020,10 +1020,10 @@ index 05154c8..c678d4a 100644 | |||||||
|          with open(pyc, 'rb') as fp: |          with open(pyc, 'rb') as fp: | ||||||
|              data = fp.read() |              data = fp.read() | ||||||
| diff --git a/Lib/test/test_imp.py b/Lib/test/test_imp.py
 | diff --git a/Lib/test/test_imp.py b/Lib/test/test_imp.py
 | ||||||
| index 4bb0390..ff62483 100644
 | index 4062afd..6bc276d 100644
 | ||||||
| --- a/Lib/test/test_imp.py
 | --- a/Lib/test/test_imp.py
 | ||||||
| +++ b/Lib/test/test_imp.py
 | +++ b/Lib/test/test_imp.py
 | ||||||
| @@ -350,6 +350,7 @@ class ImportTests(unittest.TestCase):
 | @@ -352,6 +352,7 @@ class ImportTests(unittest.TestCase):
 | ||||||
|          import _frozen_importlib |          import _frozen_importlib | ||||||
|          self.assertEqual(_frozen_importlib.__spec__.origin, "frozen") |          self.assertEqual(_frozen_importlib.__spec__.origin, "frozen") | ||||||
|   |   | ||||||
| @ -1031,7 +1031,7 @@ index 4bb0390..ff62483 100644 | |||||||
|      def test_source_hash(self): |      def test_source_hash(self): | ||||||
|          self.assertEqual(_imp.source_hash(42, b'hi'), b'\xfb\xd9G\x05\xaf$\x9b~') |          self.assertEqual(_imp.source_hash(42, b'hi'), b'\xfb\xd9G\x05\xaf$\x9b~') | ||||||
|          self.assertEqual(_imp.source_hash(43, b'hi'), b'\xd0/\x87C\xccC\xff\xe2') |          self.assertEqual(_imp.source_hash(43, b'hi'), b'\xd0/\x87C\xccC\xff\xe2') | ||||||
| @@ -369,6 +370,7 @@ class ImportTests(unittest.TestCase):
 | @@ -371,6 +372,7 @@ class ImportTests(unittest.TestCase):
 | ||||||
|              res = script_helper.assert_python_ok(*args) |              res = script_helper.assert_python_ok(*args) | ||||||
|              self.assertEqual(res.out.strip().decode('utf-8'), expected) |              self.assertEqual(res.out.strip().decode('utf-8'), expected) | ||||||
|   |   | ||||||
| @ -1092,10 +1092,10 @@ index 378dcbe..7b223a1 100644 | |||||||
|          with util.create_modules('_temp') as mapping: |          with util.create_modules('_temp') as mapping: | ||||||
|              bc_path = self.manipulate_bytecode( |              bc_path = self.manipulate_bytecode( | ||||||
| diff --git a/Lib/test/test_py_compile.py b/Lib/test/test_py_compile.py
 | diff --git a/Lib/test/test_py_compile.py b/Lib/test/test_py_compile.py
 | ||||||
| index e53f5d9..7266212 100644
 | index 9b420d2..dd6460a 100644
 | ||||||
| --- a/Lib/test/test_py_compile.py
 | --- a/Lib/test/test_py_compile.py
 | ||||||
| +++ b/Lib/test/test_py_compile.py
 | +++ b/Lib/test/test_py_compile.py
 | ||||||
| @@ -141,13 +141,16 @@ class PyCompileTestsBase:
 | @@ -143,13 +143,16 @@ class PyCompileTestsBase:
 | ||||||
|              importlib.util.cache_from_source(bad_coding))) |              importlib.util.cache_from_source(bad_coding))) | ||||||
|   |   | ||||||
|      def test_source_date_epoch(self): |      def test_source_date_epoch(self): | ||||||
| @ -1113,7 +1113,7 @@ index e53f5d9..7266212 100644 | |||||||
|              expected_flags = 0b11 |              expected_flags = 0b11 | ||||||
|          else: |          else: | ||||||
|              expected_flags = 0b00 |              expected_flags = 0b00 | ||||||
| @@ -178,7 +181,8 @@ class PyCompileTestsBase:
 | @@ -180,7 +183,8 @@ class PyCompileTestsBase:
 | ||||||
|          # Specifying optimized bytecode should lead to a path reflecting that. |          # Specifying optimized bytecode should lead to a path reflecting that. | ||||||
|          self.assertIn('opt-2', py_compile.compile(self.source_path, optimize=2)) |          self.assertIn('opt-2', py_compile.compile(self.source_path, optimize=2)) | ||||||
|   |   | ||||||
| @ -1123,7 +1123,7 @@ index e53f5d9..7266212 100644 | |||||||
|          py_compile.compile( |          py_compile.compile( | ||||||
|              self.source_path, |              self.source_path, | ||||||
|              invalidation_mode=py_compile.PycInvalidationMode.CHECKED_HASH, |              invalidation_mode=py_compile.PycInvalidationMode.CHECKED_HASH, | ||||||
| @@ -187,6 +191,9 @@ class PyCompileTestsBase:
 | @@ -189,6 +193,9 @@ class PyCompileTestsBase:
 | ||||||
|              flags = importlib._bootstrap_external._classify_pyc( |              flags = importlib._bootstrap_external._classify_pyc( | ||||||
|                  fp.read(), 'test', {}) |                  fp.read(), 'test', {}) | ||||||
|          self.assertEqual(flags, 0b11) |          self.assertEqual(flags, 0b11) | ||||||
| @ -1154,10 +1154,10 @@ index 59a5200..81fadb3 100644 | |||||||
|      def test_checked_hash_based_change_pyc(self): |      def test_checked_hash_based_change_pyc(self): | ||||||
|          source = b"state = 'old'" |          source = b"state = 'old'" | ||||||
| diff --git a/Python/import.c b/Python/import.c
 | diff --git a/Python/import.c b/Python/import.c
 | ||||||
| index 07a8b90..e97b47b 100644
 | index 39144d3..b439059 100644
 | ||||||
| --- a/Python/import.c
 | --- a/Python/import.c
 | ||||||
| +++ b/Python/import.c
 | +++ b/Python/import.c
 | ||||||
| @@ -2437,6 +2437,26 @@ static PyObject *
 | @@ -2449,6 +2449,26 @@ static PyObject *
 | ||||||
|  _imp_source_hash_impl(PyObject *module, long key, Py_buffer *source) |  _imp_source_hash_impl(PyObject *module, long key, Py_buffer *source) | ||||||
|  /*[clinic end generated code: output=edb292448cf399ea input=9aaad1e590089789]*/ |  /*[clinic end generated code: output=edb292448cf399ea input=9aaad1e590089789]*/ | ||||||
|  { |  { | ||||||
| @ -1185,5 +1185,5 @@ index 07a8b90..e97b47b 100644 | |||||||
|          uint64_t x; |          uint64_t x; | ||||||
|          char data[sizeof(uint64_t)]; |          char data[sizeof(uint64_t)]; | ||||||
| -- 
 | -- 
 | ||||||
| 2.39.1 | 2.43.0 | ||||||
| 
 | 
 | ||||||
|  | |||||||
							
								
								
									
										47
									
								
								SOURCES/00378-support-expat-2-4-5.patch
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								SOURCES/00378-support-expat-2-4-5.patch
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,47 @@ | |||||||
|  | From db083095e3bdb93e4f8170d814664c482b1e94da Mon Sep 17 00:00:00 2001 | ||||||
|  | From: rpm-build <rpm-build> | ||||||
|  | Date: Tue, 14 Jun 2022 06:38:43 +0200 | ||||||
|  | Subject: [PATCH] Fix test suite for Expat >= 2.4.5 | ||||||
|  | 
 | ||||||
|  | ---
 | ||||||
|  |  Lib/test/test_minidom.py | 17 +++++------------ | ||||||
|  |  1 file changed, 5 insertions(+), 12 deletions(-) | ||||||
|  | 
 | ||||||
|  | diff --git a/Lib/test/test_minidom.py b/Lib/test/test_minidom.py
 | ||||||
|  | index 9762025..5f52ed1 100644
 | ||||||
|  | --- a/Lib/test/test_minidom.py
 | ||||||
|  | +++ b/Lib/test/test_minidom.py
 | ||||||
|  | @@ -1149,14 +1149,10 @@ class MinidomTest(unittest.TestCase):
 | ||||||
|  |   | ||||||
|  |          # Verify that character decoding errors raise exceptions instead | ||||||
|  |          # of crashing | ||||||
|  | -        if pyexpat.version_info >= (2, 4, 5):
 | ||||||
|  | -            self.assertRaises(ExpatError, parseString,
 | ||||||
|  | -                    b'<fran\xe7ais></fran\xe7ais>')
 | ||||||
|  | -            self.assertRaises(ExpatError, parseString,
 | ||||||
|  | -                    b'<franais>Comment \xe7a va ? Tr\xe8s bien ?</franais>')
 | ||||||
|  | -        else:
 | ||||||
|  | -            self.assertRaises(UnicodeDecodeError, parseString,
 | ||||||
|  | -                b'<fran\xe7ais>Comment \xe7a va ? Tr\xe8s bien ?</fran\xe7ais>')
 | ||||||
|  | +        self.assertRaises(ExpatError, parseString,
 | ||||||
|  | +                b'<fran\xe7ais></fran\xe7ais>')
 | ||||||
|  | +        self.assertRaises(ExpatError, parseString,
 | ||||||
|  | +                b'<franais>Comment \xe7a va ? Tr\xe8s bien ?</franais>')
 | ||||||
|  |   | ||||||
|  |          doc.unlink() | ||||||
|  |   | ||||||
|  | @@ -1617,10 +1613,7 @@ class MinidomTest(unittest.TestCase):
 | ||||||
|  |          self.confirm(doc2.namespaceURI == xml.dom.EMPTY_NAMESPACE) | ||||||
|  |   | ||||||
|  |      def testExceptionOnSpacesInXMLNSValue(self): | ||||||
|  | -        if pyexpat.version_info >= (2, 4, 5):
 | ||||||
|  | -            context = self.assertRaisesRegex(ExpatError, 'syntax error')
 | ||||||
|  | -        else:
 | ||||||
|  | -            context = self.assertRaisesRegex(ValueError, 'Unsupported syntax')
 | ||||||
|  | +        context = self.assertRaisesRegex(ExpatError, 'syntax error')
 | ||||||
|  |   | ||||||
|  |          with context: | ||||||
|  |              parseString('<element xmlns:abc="http:abc.com/de f g/hi/j k"><abc:foo /></element>') | ||||||
|  | -- 
 | ||||||
|  | 2.35.3 | ||||||
|  | 
 | ||||||
| @ -1,113 +1,3 @@ | |||||||
| From f36519078bde3cce4328c03fffccb846121fb5bc Mon Sep 17 00:00:00 2001 |  | ||||||
| From: Petr Viktorin <encukou@gmail.com> |  | ||||||
| Date: Wed, 9 Aug 2023 20:23:03 +0200 |  | ||||||
| Subject: [PATCH] Fix symlink handling for tarfile.data_filter |  | ||||||
| 
 |  | ||||||
| ---
 |  | ||||||
|  Doc/library/tarfile.rst  |  5 +++++ |  | ||||||
|  Lib/tarfile.py           |  9 ++++++++- |  | ||||||
|  Lib/test/test_tarfile.py | 26 ++++++++++++++++++++++++-- |  | ||||||
|  3 files changed, 37 insertions(+), 3 deletions(-) |  | ||||||
| 
 |  | ||||||
| diff --git a/Doc/library/tarfile.rst b/Doc/library/tarfile.rst
 |  | ||||||
| index 00f3070324e..e0511bfeb64 100644
 |  | ||||||
| --- a/Doc/library/tarfile.rst
 |  | ||||||
| +++ b/Doc/library/tarfile.rst
 |  | ||||||
| @@ -740,6 +740,11 @@ A ``TarInfo`` object has the following public data attributes:
 |  | ||||||
|     Name of the target file name, which is only present in :class:`TarInfo` objects |  | ||||||
|     of type :const:`LNKTYPE` and :const:`SYMTYPE`. |  | ||||||
|   |  | ||||||
| +   For symbolic links (``SYMTYPE``), the linkname is relative to the directory
 |  | ||||||
| +   that contains the link.
 |  | ||||||
| +   For hard links (``LNKTYPE``), the linkname is relative to the root of
 |  | ||||||
| +   the archive.
 |  | ||||||
| +
 |  | ||||||
|   |  | ||||||
|  .. attribute:: TarInfo.uid |  | ||||||
|     :type: int |  | ||||||
| diff --git a/Lib/tarfile.py b/Lib/tarfile.py
 |  | ||||||
| index df4e41f7a0d..d62323715b4 100755
 |  | ||||||
| --- a/Lib/tarfile.py
 |  | ||||||
| +++ b/Lib/tarfile.py
 |  | ||||||
| @@ -802,7 +802,14 @@ def _get_filtered_attrs(member, dest_path, for_data=True):
 |  | ||||||
|          if member.islnk() or member.issym(): |  | ||||||
|              if os.path.isabs(member.linkname): |  | ||||||
|                  raise AbsoluteLinkError(member) |  | ||||||
| -            target_path = os.path.realpath(os.path.join(dest_path, member.linkname))
 |  | ||||||
| +            if member.issym():
 |  | ||||||
| +                target_path = os.path.join(dest_path,
 |  | ||||||
| +                                           os.path.dirname(name),
 |  | ||||||
| +                                           member.linkname)
 |  | ||||||
| +            else:
 |  | ||||||
| +                target_path = os.path.join(dest_path,
 |  | ||||||
| +                                           member.linkname)
 |  | ||||||
| +            target_path = os.path.realpath(target_path)
 |  | ||||||
|              if os.path.commonpath([target_path, dest_path]) != dest_path: |  | ||||||
|                  raise LinkOutsideDestinationError(member, target_path) |  | ||||||
|      return new_attrs |  | ||||||
| diff --git a/Lib/test/test_tarfile.py b/Lib/test/test_tarfile.py
 |  | ||||||
| index 2eda7fc4cea..79fc35c2895 100644
 |  | ||||||
| --- a/Lib/test/test_tarfile.py
 |  | ||||||
| +++ b/Lib/test/test_tarfile.py
 |  | ||||||
| @@ -3337,10 +3337,12 @@ def __exit__(self, *exc):
 |  | ||||||
|          self.bio = None |  | ||||||
|   |  | ||||||
|      def add(self, name, *, type=None, symlink_to=None, hardlink_to=None, |  | ||||||
| -            mode=None, **kwargs):
 |  | ||||||
| +            mode=None, size=None, **kwargs):
 |  | ||||||
|          """Add a member to the test archive. Call within `with`.""" |  | ||||||
|          name = str(name) |  | ||||||
|          tarinfo = tarfile.TarInfo(name).replace(**kwargs) |  | ||||||
| +        if size is not None:
 |  | ||||||
| +            tarinfo.size = size
 |  | ||||||
|          if mode: |  | ||||||
|              tarinfo.mode = _filemode_to_int(mode) |  | ||||||
|          if symlink_to is not None: |  | ||||||
| @@ -3416,7 +3418,8 @@ def check_context(self, tar, filter):
 |  | ||||||
|                  raise self.raised_exception |  | ||||||
|              self.assertEqual(self.expected_paths, set()) |  | ||||||
|   |  | ||||||
| -    def expect_file(self, name, type=None, symlink_to=None, mode=None):
 |  | ||||||
| +    def expect_file(self, name, type=None, symlink_to=None, mode=None,
 |  | ||||||
| +                    size=None):
 |  | ||||||
|          """Check a single file. See check_context.""" |  | ||||||
|          if self.raised_exception: |  | ||||||
|              raise self.raised_exception |  | ||||||
| @@ -3445,6 +3448,8 @@ def expect_file(self, name, type=None, symlink_to=None, mode=None):
 |  | ||||||
|              self.assertTrue(path.is_fifo()) |  | ||||||
|          else: |  | ||||||
|              raise NotImplementedError(type) |  | ||||||
| +        if size is not None:
 |  | ||||||
| +            self.assertEqual(path.stat().st_size, size)
 |  | ||||||
|          for parent in path.parents: |  | ||||||
|              self.expected_paths.discard(parent) |  | ||||||
|   |  | ||||||
| @@ -3649,6 +3654,22 @@ def test_sly_relative2(self):
 |  | ||||||
|                      + """['"].*moo['"], which is outside the """ |  | ||||||
|                      + "destination") |  | ||||||
|   |  | ||||||
| +    def test_deep_symlink(self):
 |  | ||||||
| +        with ArchiveMaker() as arc:
 |  | ||||||
| +            arc.add('targetdir/target', size=3)
 |  | ||||||
| +            arc.add('linkdir/hardlink', hardlink_to='targetdir/target')
 |  | ||||||
| +            arc.add('linkdir/symlink', symlink_to='../targetdir/target')
 |  | ||||||
| +
 |  | ||||||
| +        for filter in 'tar', 'data', 'fully_trusted':
 |  | ||||||
| +            with self.check_context(arc.open(), filter):
 |  | ||||||
| +                self.expect_file('targetdir/target', size=3)
 |  | ||||||
| +                self.expect_file('linkdir/hardlink', size=3)
 |  | ||||||
| +                if os_helper.can_symlink():
 |  | ||||||
| +                    self.expect_file('linkdir/symlink', size=3,
 |  | ||||||
| +                                     symlink_to='../targetdir/target')
 |  | ||||||
| +                else:
 |  | ||||||
| +                    self.expect_file('linkdir/symlink', size=3)
 |  | ||||||
| +
 |  | ||||||
|      def test_modes(self): |  | ||||||
|          # Test how file modes are extracted |  | ||||||
|          # (Note that the modes are ignored on platforms without working chmod) |  | ||||||
| -- 
 |  | ||||||
| 2.41.0 |  | ||||||
| 
 |  | ||||||
| From 8b70605b594b3831331a9340ba764ff751871612 Mon Sep 17 00:00:00 2001 | From 8b70605b594b3831331a9340ba764ff751871612 Mon Sep 17 00:00:00 2001 | ||||||
| From: Petr Viktorin <encukou@gmail.com> | From: Petr Viktorin <encukou@gmail.com> | ||||||
| Date: Mon, 6 Mar 2023 17:24:24 +0100 | Date: Mon, 6 Mar 2023 17:24:24 +0100 | ||||||
|  | |||||||
| @ -0,0 +1,755 @@ | |||||||
|  | From d8b0fafb202bf884135a3f7f0ce0b086217a2da2 Mon Sep 17 00:00:00 2001 | ||||||
|  | From: Victor Stinner <vstinner@python.org> | ||||||
|  | Date: Fri, 15 Dec 2023 16:10:40 +0100 | ||||||
|  | Subject: [PATCH 1/2] 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 0e266b6..6723dc4 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 cfdfeb3..9522341 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 _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)
 | ||||||
|  |   | ||||||
|  | -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
 | ||||||
|  | +
 | ||||||
|  | +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): | ||||||
|  | @@ -205,16 +321,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 677f209..20b6779 100644
 | ||||||
|  | --- a/Lib/test/test_email/test_email.py
 | ||||||
|  | +++ b/Lib/test/test_email/test_email.py
 | ||||||
|  | @@ -17,6 +17,7 @@ from unittest.mock import patch
 | ||||||
|  |   | ||||||
|  |  import email | ||||||
|  |  import email.policy | ||||||
|  | +import email.utils
 | ||||||
|  |   | ||||||
|  |  from email.charset import Charset | ||||||
|  |  from email.generator import Generator, DecodedGenerator, BytesGenerator | ||||||
|  | @@ -3321,15 +3322,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""" | ||||||
|  | @@ -3520,6 +3660,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 0000000..3d0e9e4
 | ||||||
|  | --- /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.
 | ||||||
|  | -- 
 | ||||||
|  | 2.43.0 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | From 6c34f5b95da90bd494e29776c0e807af44689fae Mon Sep 17 00:00:00 2001 | ||||||
|  | From: Lumir Balhar <lbalhar@redhat.com> | ||||||
|  | Date: Wed, 10 Jan 2024 08:53:53 +0100 | ||||||
|  | Subject: [PATCH 2/2] 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 | 74 ++++++++++++++++++++++++++++++- | ||||||
|  |  3 files changed, 150 insertions(+), 4 deletions(-) | ||||||
|  | 
 | ||||||
|  | diff --git a/Doc/library/email.utils.rst b/Doc/library/email.utils.rst
 | ||||||
|  | index 6723dc4..c89602d 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 9522341..2e30e09 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 [('', | ||||||
|  | @@ -321,7 +366,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. | ||||||
|  |   | ||||||
|  | @@ -330,6 +375,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 20b6779..d7d99f0 100644
 | ||||||
|  | --- a/Lib/test/test_email/test_email.py
 | ||||||
|  | +++ b/Lib/test/test_email/test_email.py
 | ||||||
|  | @@ -8,6 +8,9 @@ import base64
 | ||||||
|  |  import unittest | ||||||
|  |  import textwrap | ||||||
|  |  import warnings | ||||||
|  | +import contextlib
 | ||||||
|  | +import tempfile
 | ||||||
|  | +import os
 | ||||||
|  |   | ||||||
|  |  from io import StringIO, BytesIO | ||||||
|  |  from itertools import chain | ||||||
|  | @@ -41,8 +44,8 @@ from email import quoprimime
 | ||||||
|  |  from email import utils | ||||||
|  |   | ||||||
|  |  from test import support | ||||||
|  | -from test.support import threading_helper
 | ||||||
|  | -from test.support.os_helper import unlink
 | ||||||
|  | +from test.support import threading_helper, swap_attr
 | ||||||
|  | +from test.support.os_helper import unlink, EnvironmentVarGuard
 | ||||||
|  |  from test.test_email import openfile, TestEmailBase | ||||||
|  |   | ||||||
|  |  # These imports are documented to work, but we are testing them using a | ||||||
|  | @@ -3427,6 +3430,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 | ||||||
|  | 
 | ||||||
| @ -1,16 +0,0 @@ | |||||||
| -----BEGIN PGP SIGNATURE----- |  | ||||||
| 
 |  | ||||||
| iQIzBAABCAAdFiEEz9yiRbEEPPKl+Xhl/+h0BBaL2EcFAmR/sHIACgkQ/+h0BBaL |  | ||||||
| 2EfQDQ//eFWvcQ5ijhVd3r5lp7NTNUPK6xKR2iqzpNWlN2Z4QkGJ2+IworBaZoGA |  | ||||||
| tzmbT0j0LB9ZQ+ba3xnqXGXD8Ky+fHLg8GV5yshPlH/bD7tPuHtfDRxNcWplEVSS |  | ||||||
| MbMuLjAYavTIHhYEz/Rpx4jvZTI5lwplVqj9WxNI/8tNrL5M2bsCtv+IB6brohiw |  | ||||||
| rUOUlT/KDkZbrGfB1Fe033Ep8hay5MkKjhgr7O1dU7zMuDRG+HRsCYGs7a5x6KhH |  | ||||||
| 3QNTEp+GEIAKEsip5nR7vl5KqL02lHa5sf36SV2wjRTwO+IhgV7lvtJEwOD12oE5 |  | ||||||
| c+TCQMFbmBXg2vVmNBN/Lwftw1SwT/+orFX6V4U93jq6QNUo4GvPqum6YzuayGYc |  | ||||||
| /JM4MNziqmfdNW2YjEHPPfzti3f40eTapys97YufOrmYjM2NY0Fs+kAErvyxiWqi |  | ||||||
| guVQtaZIYeLl/9KWqQ0F/Apy1N+fVDuWBkZlizwHrUsGips4Rp7Bh/iCrDdOj+1D |  | ||||||
| gRCio7+KvdtzHavZPZnU5dcpUiXZgsDzOTI138IyYaEtVUS59ELkA2qxI1yCb5mk |  | ||||||
| eLVG1L7r/J2tIaTcguQppp5Z+62UDTArlUbnRxda0buzA2r1aFiQCTMwp+kTRegw |  | ||||||
| T9Ht/CT/D4vpMdmSQTun9MkKifcK+2uGfSsS7Lz4fSWjQLqg36k= |  | ||||||
| =zSfJ |  | ||||||
| -----END PGP SIGNATURE----- |  | ||||||
							
								
								
									
										16
									
								
								SOURCES/Python-3.11.7.tar.xz.asc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								SOURCES/Python-3.11.7.tar.xz.asc
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,16 @@ | |||||||
|  | -----BEGIN PGP SIGNATURE----- | ||||||
|  | 
 | ||||||
|  | iQIzBAABCAAdFiEEz9yiRbEEPPKl+Xhl/+h0BBaL2EcFAmVuFigACgkQ/+h0BBaL | ||||||
|  | 2EeHPg/+LU5xs2ZDrQogDcH+A1v8RyursiggypdM5hXTrsFsTCIk4iekcI9xkhG1 | ||||||
|  | ltNX4UuCe5PUEbTgtaWP0ncXARrUnPCoQaQ1sHVDTYoHegancsk+sXZc1JM7qr0p | ||||||
|  | Y4Ig6mKjuHFMXCInQSI2GaH4t5r4Z1jGk/PGrecIHOPJgqfA/6Z3TBF5N+y3jEvS | ||||||
|  | 2QazMB298q4RDhh9m3REe8LwFPHDlfw9eRohv0MB8xygg9KtxhLZrN7gLBQZvKGD | ||||||
|  | ihNw6EgJj5OZ0dvwKCCXnlZuwknuJW7vAOPHhYeenPdVdYCGoRSyN7JdD07L+5AG | ||||||
|  | O14l2rqZrz5Eu28by+kAUrcPYAfAXekw1PmtT3HSd9U/nqnUiTkkJcjyGG/e3cjJ | ||||||
|  | sUDKMNCSBq0G7j5DB3bB6VHkZjVuz+T+iR5QdfJ4kI2pYSuE/rUj1rhkUXApYsHl | ||||||
|  | 7Wff0QbOW6QT1wCtQcMpJSzkTDVJVYxiqrko/ihlOhphDHYLdOIGOrxWAUwc06x/ | ||||||
|  | BhJD6tM1kEVZvifoJp1OsNwDzZ/Ku6CUs05E1vWxdeNVeANyKAgCZ5hOVmhnv866 | ||||||
|  | 11zfgo/znRsMzMIyJuy0bhO0C6omVLzzfhipAbZM2jDorn37xxV0v/I0pceNtLrp | ||||||
|  | YR7Tjs7+Ihe6/oItjW53j9T7ANdgQ1RVDg98lKlPFNL+hxfctwY= | ||||||
|  | =0Pkd | ||||||
|  | -----END PGP SIGNATURE----- | ||||||
| @ -16,9 +16,10 @@ LEVELS = (None, 1, 2) | |||||||
| # list of globs of test and other files that we expect not to have bytecode | # list of globs of test and other files that we expect not to have bytecode | ||||||
| not_compiled = [ | not_compiled = [ | ||||||
|     '/usr/bin/*', |     '/usr/bin/*', | ||||||
|     '*/test/bad_coding.py', |     '/usr/lib/rpm/redhat/*', | ||||||
|     '*/test/bad_coding2.py', |     '*/test/*/bad_coding.py', | ||||||
|     '*/test/badsyntax_*.py', |     '*/test/*/bad_coding2.py', | ||||||
|  |     '*/test/*/badsyntax_*.py', | ||||||
|     '*/lib2to3/tests/data/bom.py', |     '*/lib2to3/tests/data/bom.py', | ||||||
|     '*/lib2to3/tests/data/crlf.py', |     '*/lib2to3/tests/data/crlf.py', | ||||||
|     '*/lib2to3/tests/data/different_encoding.py', |     '*/lib2to3/tests/data/different_encoding.py', | ||||||
|  | |||||||
							
								
								
									
										171
									
								
								SOURCES/import_all_modules_py3_11.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										171
									
								
								SOURCES/import_all_modules_py3_11.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,171 @@ | |||||||
|  | '''Script to perform import of each module given to %%py_check_import | ||||||
|  | ''' | ||||||
|  | import argparse | ||||||
|  | import importlib | ||||||
|  | import fnmatch | ||||||
|  | import os | ||||||
|  | import re | ||||||
|  | import site | ||||||
|  | import sys | ||||||
|  | 
 | ||||||
|  | from contextlib import contextmanager | ||||||
|  | from pathlib import Path | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def read_modules_files(file_paths): | ||||||
|  |     '''Read module names from the files (modules must be newline separated). | ||||||
|  | 
 | ||||||
|  |     Return the module names list or, if no files were provided, an empty list. | ||||||
|  |     ''' | ||||||
|  | 
 | ||||||
|  |     if not file_paths: | ||||||
|  |         return [] | ||||||
|  | 
 | ||||||
|  |     modules = [] | ||||||
|  |     for file in file_paths: | ||||||
|  |         file_contents = file.read_text() | ||||||
|  |         modules.extend(file_contents.split()) | ||||||
|  |     return modules | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def read_modules_from_cli(argv): | ||||||
|  |     '''Read module names from command-line arguments (space or comma separated). | ||||||
|  | 
 | ||||||
|  |     Return the module names list. | ||||||
|  |     ''' | ||||||
|  | 
 | ||||||
|  |     if not argv: | ||||||
|  |         return [] | ||||||
|  | 
 | ||||||
|  |     # %%py3_check_import allows to separate module list with comma or whitespace, | ||||||
|  |     # we need to unify the output to a list of particular elements | ||||||
|  |     modules_as_str = ' '.join(argv) | ||||||
|  |     modules = re.split(r'[\s,]+', modules_as_str) | ||||||
|  |     # Because of shell expansion in some less typical cases it may happen | ||||||
|  |     # that a trailing space will occur at the end of the list. | ||||||
|  |     # Remove the empty items from the list before passing it further | ||||||
|  |     modules = [m for m in modules if m] | ||||||
|  |     return modules | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def filter_top_level_modules_only(modules): | ||||||
|  |     '''Filter out entries with nested modules (containing dot) ie. 'foo.bar'. | ||||||
|  | 
 | ||||||
|  |     Return the list of top-level modules. | ||||||
|  |     ''' | ||||||
|  | 
 | ||||||
|  |     return [module for module in modules if '.' not in module] | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def any_match(text, globs): | ||||||
|  |     '''Return True if any of given globs fnmatchcase's the given text.''' | ||||||
|  | 
 | ||||||
|  |     return any(fnmatch.fnmatchcase(text, g) for g in globs) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def exclude_unwanted_module_globs(globs, modules): | ||||||
|  |     '''Filter out entries which match the either of the globs given as argv. | ||||||
|  | 
 | ||||||
|  |     Return the list of filtered modules. | ||||||
|  |     ''' | ||||||
|  | 
 | ||||||
|  |     return [m for m in modules if not any_match(m, globs)] | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def read_modules_from_all_args(args): | ||||||
|  |     '''Return a joined list of modules from all given command-line arguments. | ||||||
|  |     ''' | ||||||
|  | 
 | ||||||
|  |     modules = read_modules_files(args.filename) | ||||||
|  |     modules.extend(read_modules_from_cli(args.modules)) | ||||||
|  |     if args.exclude: | ||||||
|  |         modules = exclude_unwanted_module_globs(args.exclude, modules) | ||||||
|  | 
 | ||||||
|  |     if args.top_level: | ||||||
|  |         modules = filter_top_level_modules_only(modules) | ||||||
|  | 
 | ||||||
|  |     # Error when someone accidentally managed to filter out everything | ||||||
|  |     if len(modules) == 0: | ||||||
|  |         raise ValueError('No modules to check were left') | ||||||
|  | 
 | ||||||
|  |     return modules | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def import_modules(modules): | ||||||
|  |     '''Procedure to perform import check for each module name from the given list of modules. | ||||||
|  |     ''' | ||||||
|  | 
 | ||||||
|  |     for module in modules: | ||||||
|  |         print('Check import:', module, file=sys.stderr) | ||||||
|  |         importlib.import_module(module) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def argparser(): | ||||||
|  |     parser = argparse.ArgumentParser( | ||||||
|  |         description='Generate list of all importable modules for import check.' | ||||||
|  |     ) | ||||||
|  |     parser.add_argument( | ||||||
|  |         'modules', nargs='*', | ||||||
|  |         help=('Add modules to check the import (space or comma separated).'), | ||||||
|  |     ) | ||||||
|  |     parser.add_argument( | ||||||
|  |         '-f', '--filename', action='append', type=Path, | ||||||
|  |         help='Add importable module names list from file.', | ||||||
|  |     ) | ||||||
|  |     parser.add_argument( | ||||||
|  |         '-t', '--top-level', action='store_true', | ||||||
|  |         help='Check only top-level modules.', | ||||||
|  |     ) | ||||||
|  |     parser.add_argument( | ||||||
|  |         '-e', '--exclude', action='append', | ||||||
|  |         help='Provide modules globs to be excluded from the check.', | ||||||
|  |     ) | ||||||
|  |     return parser | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @contextmanager | ||||||
|  | def remove_unwanteds_from_sys_path(): | ||||||
|  |     '''Remove cwd and this script's parent from sys.path for the import test. | ||||||
|  |     Bring the original contents back after import is done (or failed) | ||||||
|  |     ''' | ||||||
|  | 
 | ||||||
|  |     cwd_absolute = Path.cwd().absolute() | ||||||
|  |     this_file_parent = Path(__file__).parent.absolute() | ||||||
|  |     old_sys_path = list(sys.path) | ||||||
|  |     for path in old_sys_path: | ||||||
|  |         if Path(path).absolute() in (cwd_absolute, this_file_parent): | ||||||
|  |             sys.path.remove(path) | ||||||
|  |     try: | ||||||
|  |         yield | ||||||
|  |     finally: | ||||||
|  |         sys.path = old_sys_path | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def addsitedirs_from_environ(): | ||||||
|  |     '''Load directories from the _PYTHONSITE environment variable (separated by :) | ||||||
|  |     and load the ones already present in sys.path via site.addsitedir() | ||||||
|  |     to handle .pth files in them. | ||||||
|  | 
 | ||||||
|  |     This is needed to properly import old-style namespace packages with nspkg.pth files. | ||||||
|  |     See https://bugzilla.redhat.com/2018551 for a more detailed rationale.''' | ||||||
|  |     for path in os.getenv('_PYTHONSITE', '').split(':'): | ||||||
|  |         if path in sys.path: | ||||||
|  |             site.addsitedir(path) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def main(argv=None): | ||||||
|  | 
 | ||||||
|  |     cli_args = argparser().parse_args(argv) | ||||||
|  | 
 | ||||||
|  |     if not cli_args.modules and not cli_args.filename: | ||||||
|  |         raise ValueError('No modules to check were provided') | ||||||
|  | 
 | ||||||
|  |     modules = read_modules_from_all_args(cli_args) | ||||||
|  | 
 | ||||||
|  |     with remove_unwanteds_from_sys_path(): | ||||||
|  |         addsitedirs_from_environ() | ||||||
|  |         import_modules(modules) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | if __name__ == '__main__': | ||||||
|  |     main() | ||||||
							
								
								
									
										77
									
								
								SOURCES/macros.python3.11
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								SOURCES/macros.python3.11
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,77 @@ | |||||||
|  | %__python3 /usr/bin/python3.11 | ||||||
|  | %python3_pkgversion 3.11 | ||||||
|  | 
 | ||||||
|  | # The following are macros from macros.python3 in Fedora that are newer/different than those in the python3-rpm-macros package in RHEL. | ||||||
|  | # These macros overwrite/supercede some of the macros in the python3-rpm-macros package in RHEL. | ||||||
|  | 
 | ||||||
|  | # nb: $RPM_BUILD_ROOT is not set when the macros are expanded (at spec parse time) | ||||||
|  | #     so we set it manually (to empty string), making our Python prefer the correct install scheme location | ||||||
|  | # platbase/base is explicitly set to %%{_prefix} to support custom values, such as /app for flatpaks | ||||||
|  | %python3_sitelib %(RPM_BUILD_ROOT= %{__python3} -Ic "import sysconfig; print(sysconfig.get_path('purelib', vars={'platbase': '%{_prefix}', 'base': '%{_prefix}'}))") | ||||||
|  | %python3_sitearch %(RPM_BUILD_ROOT= %{__python3} -Ic "import sysconfig; print(sysconfig.get_path('platlib', vars={'platbase': '%{_prefix}', 'base': '%{_prefix}'}))") | ||||||
|  | %python3_version %(RPM_BUILD_ROOT= %{__python3} -Ic "import sys; sys.stdout.write('{0.major}.{0.minor}'.format(sys.version_info))") | ||||||
|  | %python3_version_nodots %(RPM_BUILD_ROOT= %{__python3} -Ic "import sys; sys.stdout.write('{0.major}{0.minor}'.format(sys.version_info))") | ||||||
|  | %python3_platform %(RPM_BUILD_ROOT= %{__python3} -Ic "import sysconfig; print(sysconfig.get_platform())") | ||||||
|  | %python3_platform_triplet %(RPM_BUILD_ROOT= %{__python3} -Ic "import sysconfig; print(sysconfig.get_config_var('MULTIARCH'))") | ||||||
|  | %python3_ext_suffix %(RPM_BUILD_ROOT= %{__python3} -Ic "import sysconfig; print(sysconfig.get_config_var('EXT_SUFFIX'))") | ||||||
|  | %python3_cache_tag %(RPM_BUILD_ROOT= %{__python3} -Ic "import sys; print(sys.implementation.cache_tag)") | ||||||
|  | 
 | ||||||
|  | %_py3_shebang_s s | ||||||
|  | %_py3_shebang_P %(RPM_BUILD_ROOT= %{__python3} -Ic "import sys; print('P' if hasattr(sys.flags, 'safe_path') else '')") | ||||||
|  | %py3_shbang_opts -%{?_py3_shebang_s}%{?_py3_shebang_P} | ||||||
|  | 
 | ||||||
|  | %py3_install() %{expand:\\\ | ||||||
|  |   CFLAGS="${CFLAGS:-${RPM_OPT_FLAGS}}" LDFLAGS="${LDFLAGS:-${RPM_LD_FLAGS}}"\\\ | ||||||
|  |   %{__python3} %{py_setup} %{?py_setup_args} install -O1 --skip-build --root %{buildroot} --prefix %{_prefix} %{?*} | ||||||
|  |   rm -rfv %{buildroot}%{_bindir}/__pycache__ | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | %py3_install_egg() %{expand:\\\ | ||||||
|  |   mkdir -p %{buildroot}%{python3_sitelib} | ||||||
|  |   %{__python3} -m easy_install -m --prefix %{buildroot}%{_prefix} -Z dist/*-py%{python3_version}.egg %{?*} | ||||||
|  |   rm -rfv %{buildroot}%{_bindir}/__pycache__ | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | %py3_install_wheel() %{expand:\\\ | ||||||
|  |   %{__python3} -m pip install -I dist/%{1} --root %{buildroot} --prefix %{_prefix} --no-deps --no-index --no-warn-script-location | ||||||
|  |   rm -rfv %{buildroot}%{_bindir}/__pycache__ | ||||||
|  |   for distinfo in %{buildroot}%{python3_sitelib}/*.dist-info %{buildroot}%{python3_sitearch}/*.dist-info; do | ||||||
|  |     if [ -f ${distinfo}/direct_url.json ]; then | ||||||
|  |       rm -fv ${distinfo}/direct_url.json | ||||||
|  |       sed -i '/direct_url.json/d' ${distinfo}/RECORD | ||||||
|  |     fi | ||||||
|  |   done | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | # With $PATH and $PYTHONPATH set to the %%buildroot, | ||||||
|  | # try to import the Python 3 module(s) given as command-line args or read from file (-f). | ||||||
|  | # Respect the custom values of %%py3_shebang_flags or set nothing if it's undefined. | ||||||
|  | # Filter and check import on only top-level modules using -t flag. | ||||||
|  | # Exclude unwanted modules by passing their globs to -e option. | ||||||
|  | # Useful as a smoke test in %%check when running tests is not feasible. | ||||||
|  | # Use spaces or commas as separators if providing list directly. | ||||||
|  | # Use newlines as separators if providing list in a file. | ||||||
|  | %py3_check_import(e:tf:) %{expand:\\\ | ||||||
|  |   PATH="%{buildroot}%{_bindir}:$PATH"\\\ | ||||||
|  |   PYTHONPATH="${PYTHONPATH:-%{buildroot}%{python3_sitearch}:%{buildroot}%{python3_sitelib}}"\\\ | ||||||
|  |   _PYTHONSITE="%{buildroot}%{python3_sitearch}:%{buildroot}%{python3_sitelib}"\\\ | ||||||
|  |   PYTHONDONTWRITEBYTECODE=1\\\ | ||||||
|  |   %{lua: | ||||||
|  |   local command = "%{__python3} " | ||||||
|  |   if rpm.expand("%{?py3_shebang_flags}") ~= "" then | ||||||
|  |     command = command .. "-%{py3_shebang_flags}" | ||||||
|  |   end | ||||||
|  |   command = command .. " %{_rpmconfigdir}/redhat/import_all_modules_py3_11.py " | ||||||
|  |   -- handle multiline arguments correctly, see https://bugzilla.redhat.com/2018809 | ||||||
|  |   local args=rpm.expand('%{?**}'):gsub("[%s\\\\]*%s+", " ") | ||||||
|  |   print(command .. args) | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | %pytest %{expand:\\\ | ||||||
|  |   CFLAGS="${CFLAGS:-${RPM_OPT_FLAGS}}" LDFLAGS="${LDFLAGS:-${RPM_LD_FLAGS}}"\\\ | ||||||
|  |   PATH="%{buildroot}%{_bindir}:$PATH"\\\ | ||||||
|  |   PYTHONPATH="${PYTHONPATH:-%{buildroot}%{python3_sitearch}:%{buildroot}%{python3_sitelib}}"\\\ | ||||||
|  |   PYTHONDONTWRITEBYTECODE=1\\\ | ||||||
|  |   %{?__pytest_addopts:PYTEST_ADDOPTS="${PYTEST_ADDOPTS:-} %{__pytest_addopts}"}\\\ | ||||||
|  |   %__pytest} | ||||||
| @ -16,11 +16,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}.4 | %global general_version %{pybasever}.7 | ||||||
| #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: 3%{?dist} | Release: 1%{?dist} | ||||||
| License: Python | License: Python | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @ -63,7 +63,7 @@ License: Python | |||||||
| # If the rpmwheels condition is disabled, we use the bundled wheel packages | # If the rpmwheels condition is disabled, we use the bundled wheel packages | ||||||
| # from Python with the versions below. | # from Python with the versions below. | ||||||
| # This needs to be manually updated when we update Python. | # This needs to be manually updated when we update Python. | ||||||
| %global pip_version 23.1.2 | %global pip_version 23.2.1 | ||||||
| %global setuptools_version 65.5.0 | %global setuptools_version 65.5.0 | ||||||
| 
 | 
 | ||||||
| # Expensive optimizations (mainly, profile-guided optimizations) | # Expensive optimizations (mainly, profile-guided optimizations) | ||||||
| @ -253,6 +253,10 @@ Source1: %{url}ftp/python/%{general_version}/Python-%{upstream_version}.tar.xz.a | |||||||
| # The release manager for Python 3.11 is pablogsal | # The release manager for Python 3.11 is pablogsal | ||||||
| Source2: https://keybase.io/pablogsal/pgp_keys.asc | Source2: https://keybase.io/pablogsal/pgp_keys.asc | ||||||
| 
 | 
 | ||||||
|  | # Sources for the python3.11-rpm-macros | ||||||
|  | Source3: macros.python3.11 | ||||||
|  | Source4: import_all_modules_py3_11.py | ||||||
|  | 
 | ||||||
| # A simple script to check timestamps of bytecode files | # A simple script to check timestamps of bytecode files | ||||||
| # Run in check section with Python that is currently being built | # Run in check section with Python that is currently being built | ||||||
| # Originally written by bkabrda | # Originally written by bkabrda | ||||||
| @ -332,6 +336,31 @@ Patch329: 00329-fips.patch | |||||||
| # https://github.com/GrahamDumpleton/mod_wsgi/issues/730 | # https://github.com/GrahamDumpleton/mod_wsgi/issues/730 | ||||||
| Patch371: 00371-revert-bpo-1596321-fix-threading-_shutdown-for-the-main-thread-gh-28549-gh-28589.patch | Patch371: 00371-revert-bpo-1596321-fix-threading-_shutdown-for-the-main-thread-gh-28549-gh-28589.patch | ||||||
| 
 | 
 | ||||||
|  | # 00378 # | ||||||
|  | # Support expat 2.4.5 | ||||||
|  | # | ||||||
|  | # Curly brackets were never allowed in namespace URIs | ||||||
|  | # according to RFC 3986, and so-called namespace-validating | ||||||
|  | # XML parsers have the right to reject them a invalid URIs. | ||||||
|  | # | ||||||
|  | # libexpat >=2.4.5 has become strcter in that regard due to | ||||||
|  | # related security issues; with ET.XML instantiating a | ||||||
|  | # namespace-aware parser under the hood, this test has no | ||||||
|  | # future in CPython. | ||||||
|  | # | ||||||
|  | # References: | ||||||
|  | # - https://datatracker.ietf.org/doc/html/rfc3968 | ||||||
|  | # - https://www.w3.org/TR/xml-names/ | ||||||
|  | # | ||||||
|  | # Also, test_minidom.py: Support Expat >=2.4.5 | ||||||
|  | # | ||||||
|  | # The patch has diverged from upstream as the python test | ||||||
|  | # suite was relying on checking the expat version, whereas | ||||||
|  | # in RHEL fixes get backported instead of rebasing packages. | ||||||
|  | # | ||||||
|  | # Upstream: https://bugs.python.org/issue46811 | ||||||
|  | Patch378: 00378-support-expat-2-4-5.patch | ||||||
|  | 
 | ||||||
| # 00397 # | # 00397 # | ||||||
| # Filters for tarfile extraction (CVE-2007-4559, PEP-706) | # Filters for tarfile extraction (CVE-2007-4559, PEP-706) | ||||||
| # First patch fixes determination of symlink targets, which were treated | # First patch fixes determination of symlink targets, which were treated | ||||||
| @ -342,6 +371,20 @@ Patch371: 00371-revert-bpo-1596321-fix-threading-_shutdown-for-the-main-thread-g | |||||||
| # - https://access.redhat.com/articles/7004769 | # - https://access.redhat.com/articles/7004769 | ||||||
| Patch397: 00397-tarfile-filter.patch | Patch397: 00397-tarfile-filter.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., | ||||||
| @ -360,6 +403,14 @@ Patch397: 00397-tarfile-filter.patch | |||||||
| # Descriptions, and metadata for subpackages | # Descriptions, and metadata for subpackages | ||||||
| # ========================================== | # ========================================== | ||||||
| 
 | 
 | ||||||
|  | # Require alternatives version that implements the --keep-foreign flag and fixes rhbz#2203820 | ||||||
|  | Requires:         alternatives >= 1.19.2-1 | ||||||
|  | Requires(post):   alternatives >= 1.19.2-1 | ||||||
|  | Requires(postun): alternatives >= 1.19.2-1 | ||||||
|  | 
 | ||||||
|  | # When the user tries to `yum install python`, yum will list this package among | ||||||
|  | # the possible alternatives | ||||||
|  | Provides: alternative-for(python) | ||||||
| 
 | 
 | ||||||
| %if %{with main_python} | %if %{with main_python} | ||||||
| # Description for the python3X SRPM only: | # Description for the python3X SRPM only: | ||||||
| @ -434,6 +485,7 @@ Documentation for Python is provided in the %{pkgname}-docs package. | |||||||
| Packages containing additional libraries for Python are generally named with | Packages containing additional libraries for Python are generally named with | ||||||
| the "%{pkgname}-" prefix. | the "%{pkgname}-" prefix. | ||||||
| 
 | 
 | ||||||
|  | For the unversioned "python" executable, see manual page "unversioned-python". | ||||||
| 
 | 
 | ||||||
| %if %{with main_python} | %if %{with main_python} | ||||||
| # https://fedoraproject.org/wiki/Changes/Move_usr_bin_python_into_separate_package | # https://fedoraproject.org/wiki/Changes/Move_usr_bin_python_into_separate_package | ||||||
| @ -502,9 +554,12 @@ Requires: %{pkgname}-libs%{?_isa} = %{version}-%{release} | |||||||
| Requires: (python-rpm-macros if rpm-build) | Requires: (python-rpm-macros if rpm-build) | ||||||
| Requires: (python3-rpm-macros if rpm-build) | Requires: (python3-rpm-macros if rpm-build) | ||||||
| 
 | 
 | ||||||
| # We provide the python3.11-rpm-macros here to make it possible to | # Require alternatives version that implements the --keep-foreign flag and fixes rhbz#2203820 | ||||||
| # BuildRequire them in the same manner as RHEL8. | Requires(postun): alternatives >= 1.19.2-1 | ||||||
| Provides: %{pkgname}-rpm-macros = %{version}-%{release} | 
 | ||||||
|  | # python3.11 installs the alternatives master symlink to which we attach a slave | ||||||
|  | Requires(post): %{pkgname} | ||||||
|  | Requires(postun): %{pkgname} | ||||||
| 
 | 
 | ||||||
| %unversioned_obsoletes_of_python3_X_if_main devel | %unversioned_obsoletes_of_python3_X_if_main devel | ||||||
| 
 | 
 | ||||||
| @ -553,6 +608,13 @@ Provides: idle = %{version}-%{release} | |||||||
| Provides: %{pkgname}-tools = %{version}-%{release} | Provides: %{pkgname}-tools = %{version}-%{release} | ||||||
| Provides: %{pkgname}-tools%{?_isa} = %{version}-%{release} | Provides: %{pkgname}-tools%{?_isa} = %{version}-%{release} | ||||||
| 
 | 
 | ||||||
|  | # Require alternatives version that implements the --keep-foreign flag and fixes rhbz#2203820 | ||||||
|  | Requires(postun): alternatives >= 1.19.2-1 | ||||||
|  | 
 | ||||||
|  | # python3.11 installs the alternatives master symlink to which we attach a slave | ||||||
|  | Requires(post): %{pkgname} | ||||||
|  | Requires(postun): %{pkgname} | ||||||
|  | 
 | ||||||
| %description -n %{pkgname}-idle | %description -n %{pkgname}-idle | ||||||
| IDLE is Python’s Integrated Development and Learning Environment. | IDLE is Python’s Integrated Development and Learning Environment. | ||||||
| 
 | 
 | ||||||
| @ -612,6 +674,13 @@ Requires: %{pkgname}-idle%{?_isa} = %{version}-%{release} | |||||||
| 
 | 
 | ||||||
| %unversioned_obsoletes_of_python3_X_if_main debug | %unversioned_obsoletes_of_python3_X_if_main debug | ||||||
| 
 | 
 | ||||||
|  | # Require alternatives version that implements the --keep-foreign flag and fixes rhbz#2203820 | ||||||
|  | Requires(postun): alternatives >= 1.19.2-1 | ||||||
|  | 
 | ||||||
|  | # python3.11 installs the alternatives master symlink to which we attach a slave | ||||||
|  | Requires(post): %{pkgname} | ||||||
|  | Requires(postun): %{pkgname} | ||||||
|  | 
 | ||||||
| %description -n %{pkgname}-debug | %description -n %{pkgname}-debug | ||||||
| python3-debug provides a version of the Python runtime with numerous debugging | python3-debug provides a version of the Python runtime with numerous debugging | ||||||
| features enabled, aimed at advanced Python users such as developers of Python | features enabled, aimed at advanced Python users such as developers of Python | ||||||
| @ -629,6 +698,23 @@ The debug runtime additionally supports debug builds of C-API extensions | |||||||
| (with the "d" ABI flag) for debugging issues in those extensions. | (with the "d" ABI flag) for debugging issues in those extensions. | ||||||
| %endif # with debug_build | %endif # with debug_build | ||||||
| 
 | 
 | ||||||
|  | # We package the python3.11-rpm-macros in RHEL8 as to properly set the | ||||||
|  | # %%__python3 and %%python3_pkgversion macros as well as provide modern | ||||||
|  | # versions the current base macros. | ||||||
|  | %package -n %{pkgname}-rpm-macros | ||||||
|  | Summary:    RPM macros for building RPMs with Python %{pybasever} | ||||||
|  | License:    MIT | ||||||
|  | Provides:   python-modular-rpm-macros == %{pybasever} | ||||||
|  | Conflicts:  python-modular-rpm-macros > %{pybasever} | ||||||
|  | Requires:   python3-rpm-macros | ||||||
|  | BuildArch:  noarch | ||||||
|  | 
 | ||||||
|  | %description -n %{pkgname}-rpm-macros | ||||||
|  | RPM macros for building RPMs with Python %{pybasever} from the python%{pyshortver} module. | ||||||
|  | If you want to build an RPM against the python%{pyshortver} module, you need to add: | ||||||
|  | 
 | ||||||
|  |     BuildRequire: %{pkgname}-rpm-macros. | ||||||
|  | 
 | ||||||
| # ====================================================== | # ====================================================== | ||||||
| # The prep phase of the build: | # The prep phase of the build: | ||||||
| # ====================================================== | # ====================================================== | ||||||
| @ -937,6 +1023,10 @@ for tool in pygettext msgfmt; do | |||||||
|   ln -s ${tool}%{pybasever}.py %{buildroot}%{_bindir}/${tool}3.py |   ln -s ${tool}%{pybasever}.py %{buildroot}%{_bindir}/${tool}3.py | ||||||
| done | done | ||||||
| 
 | 
 | ||||||
|  | # Install missing test data | ||||||
|  | # Fixed upstream: https://github.com/python/cpython/pull/112784 | ||||||
|  | cp -rp Lib/test/regrtestdata/ %{buildroot}%{pylibdir}/test/ | ||||||
|  | 
 | ||||||
| # Switch all shebangs to refer to the specific Python version. | # Switch all shebangs to refer to the specific Python version. | ||||||
| # This currently only covers files matching ^[a-zA-Z0-9_]+\.py$, | # This currently only covers files matching ^[a-zA-Z0-9_]+\.py$, | ||||||
| # so handle files named using other naming scheme separately. | # so handle files named using other naming scheme separately. | ||||||
| @ -1034,6 +1124,28 @@ for file in %{buildroot}%{pylibdir}/pydoc_data/topics.py $(grep --include='*.py' | |||||||
|     rm ${directory}/{__pycache__/${module}.cpython-%{pyshortver}.opt-?.pyc,${module}.py} |     rm ${directory}/{__pycache__/${module}.cpython-%{pyshortver}.opt-?.pyc,${module}.py} | ||||||
| done | done | ||||||
| 
 | 
 | ||||||
|  | # Python RPM macros | ||||||
|  | mkdir -p %{buildroot}%{rpmmacrodir}/ | ||||||
|  | install -m 644 %{SOURCE3} \ | ||||||
|  |     %{buildroot}/%{rpmmacrodir}/ | ||||||
|  | 
 | ||||||
|  | # Add a script that is being used by python3.11-rpm-macros | ||||||
|  | mkdir -p %{buildroot}%{_rpmconfigdir}/redhat | ||||||
|  | install -m 644 %{SOURCE4} %{buildroot}%{_rpmconfigdir}/redhat/ | ||||||
|  | 
 | ||||||
|  | # All ghost files controlled by alternatives need to exist for the files | ||||||
|  | # section check to succeed | ||||||
|  | # - Don't list /usr/bin/python as a ghost file so `yum install /usr/bin/python` | ||||||
|  | #   doesn't install this package | ||||||
|  | touch %{buildroot}%{_bindir}/unversioned-python | ||||||
|  | touch %{buildroot}%{_mandir}/man1/python.1.gz | ||||||
|  | touch %{buildroot}%{_bindir}/python3 | ||||||
|  | touch %{buildroot}%{_mandir}/man1/python3.1.gz | ||||||
|  | touch %{buildroot}%{_bindir}/pydoc3 | ||||||
|  | touch %{buildroot}%{_bindir}/pydoc-3 | ||||||
|  | touch %{buildroot}%{_bindir}/idle3 | ||||||
|  | touch %{buildroot}%{_bindir}/python3-config | ||||||
|  | 
 | ||||||
| # ====================================================== | # ====================================================== | ||||||
| # Checks for packaging issues | # Checks for packaging issues | ||||||
| # ====================================================== | # ====================================================== | ||||||
| @ -1095,10 +1207,14 @@ CheckPython() { | |||||||
|   # test_freeze_simple_script is skipped, because it fails when bundled wheels |   # test_freeze_simple_script is skipped, because it fails when bundled wheels | ||||||
|   #  are removed in Fedora. |   #  are removed in Fedora. | ||||||
|   #  upstream report: https://bugs.python.org/issue45783 |   #  upstream report: https://bugs.python.org/issue45783 | ||||||
|  |   # test_check_probes is failing since it was introduced in 3.11.5, | ||||||
|  |   # the test is skipped until it is fixed in upstream. | ||||||
|  |   # see: https://github.com/python/cpython/issues/104280#issuecomment-1669249980 | ||||||
| 
 | 
 | ||||||
|   LD_LIBRARY_PATH=$ConfDir $ConfDir/python -m test.regrtest \ |   LD_LIBRARY_PATH=$ConfDir $ConfDir/python -m test.regrtest \ | ||||||
|     -wW --slowest -j0 --timeout=1800 \ |     -wW --slowest -j0 --timeout=1800 \ | ||||||
|     -i test_freeze_simple_script \ |     -i test_freeze_simple_script \ | ||||||
|  |     -i test_check_probes \ | ||||||
|     %if %{with bootstrap} |     %if %{with bootstrap} | ||||||
|     -x test_distutils \ |     -x test_distutils \ | ||||||
|     %endif |     %endif | ||||||
| @ -1120,9 +1236,118 @@ CheckPython optimized | |||||||
| 
 | 
 | ||||||
| %endif # with tests | %endif # with tests | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
|  | # ====================================================== | ||||||
|  | # Scriptlets for alternatives on rhel8 | ||||||
|  | # ====================================================== | ||||||
|  | %post | ||||||
|  | # Alternative for /usr/bin/python -> /usr/bin/python3 + man page | ||||||
|  | alternatives --install %{_bindir}/unversioned-python \ | ||||||
|  |                         python \ | ||||||
|  |                         %{_bindir}/python3 \ | ||||||
|  |                         300 \ | ||||||
|  |               --slave   %{_bindir}/python \ | ||||||
|  |                         unversioned-python \ | ||||||
|  |                         %{_bindir}/python3 \ | ||||||
|  |               --slave   %{_mandir}/man1/python.1.gz \ | ||||||
|  |                         unversioned-python-man \ | ||||||
|  |                         %{_mandir}/man1/python3.1.gz | ||||||
|  | 
 | ||||||
|  | # Alternative for /usr/bin/python -> /usr/bin/python3.11 + man page | ||||||
|  | alternatives --install %{_bindir}/unversioned-python \ | ||||||
|  |                         python \ | ||||||
|  |                         %{_bindir}/python3.11 \ | ||||||
|  |                         211 \ | ||||||
|  |               --slave   %{_bindir}/python \ | ||||||
|  |                         unversioned-python \ | ||||||
|  |                         %{_bindir}/python3.11 \ | ||||||
|  |               --slave   %{_mandir}/man1/python.1.gz \ | ||||||
|  |                         unversioned-python-man \ | ||||||
|  |                         %{_mandir}/man1/python3.11.1.gz | ||||||
|  | 
 | ||||||
|  | # Alternative for /usr/bin/python3 -> /usr/bin/python3.11 + related files | ||||||
|  | # Create only if it doesn't exist already | ||||||
|  | EXISTS=`alternatives --display python3 | \ | ||||||
|  |          grep -c "^/usr/bin/python3.11 - priority [0-9]*"` | ||||||
|  | 
 | ||||||
|  | if [ $EXISTS -eq 0 ]; then | ||||||
|  |      alternatives --install %{_bindir}/python3 \ | ||||||
|  |                             python3 \ | ||||||
|  |                             %{_bindir}/python3.11 \ | ||||||
|  |                             31100 \ | ||||||
|  |                   --slave   %{_mandir}/man1/python3.1.gz \ | ||||||
|  |                             python3-man \ | ||||||
|  |                             %{_mandir}/man1/python3.11.1.gz \ | ||||||
|  |                   --slave   %{_bindir}/pydoc3 \ | ||||||
|  |                             pydoc3 \ | ||||||
|  |                             %{_bindir}/pydoc3.11 \ | ||||||
|  |                   --slave   %{_bindir}/pydoc-3 \ | ||||||
|  |                             pydoc-3 \ | ||||||
|  |                             %{_bindir}/pydoc3.11 | ||||||
|  | fi | ||||||
|  | 
 | ||||||
|  | %postun | ||||||
|  | # Do this only during uninstall process (not during update) | ||||||
|  | if [ $1 -eq 0 ]; then | ||||||
|  |      alternatives --keep-foreign --remove python \ | ||||||
|  |                          %{_bindir}/python3.11 | ||||||
|  | 
 | ||||||
|  |      alternatives --keep-foreign --remove python3 \ | ||||||
|  |                          %{_bindir}/python3.11 | ||||||
|  | 
 | ||||||
|  |      # Remove link python → python3 if no other python3.* exists | ||||||
|  |      if ! alternatives --display python3 > /dev/null; then | ||||||
|  |          alternatives --keep-foreign --remove python \ | ||||||
|  |                              %{_bindir}/python3 | ||||||
|  |      fi | ||||||
|  | fi | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | %post devel | ||||||
|  | alternatives --add-slave python3 %{_bindir}/python3.11 \ | ||||||
|  |      %{_bindir}/python3-config \ | ||||||
|  |      python3-config \ | ||||||
|  |      %{_bindir}/python3.11-config | ||||||
|  | 
 | ||||||
|  | %postun devel | ||||||
|  | # Do this only during uninstall process (not during update) | ||||||
|  | if [ $1 -eq 0 ]; then | ||||||
|  |      alternatives --keep-foreign --remove-slave python3 %{_bindir}/python3.11 \ | ||||||
|  |          python3-config | ||||||
|  | fi | ||||||
|  | 
 | ||||||
|  | %post idle | ||||||
|  | alternatives --add-slave python3 %{_bindir}/python3.11 \ | ||||||
|  |      %{_bindir}/idle3 \ | ||||||
|  |      idle3 \ | ||||||
|  |      %{_bindir}/idle3.11 | ||||||
|  | 
 | ||||||
|  | %postun idle | ||||||
|  | # Do this only during uninstall process (not during update) | ||||||
|  | if [ $1 -eq 0 ]; then | ||||||
|  |      alternatives --keep-foreign --remove-slave python3 %{_bindir}/python3.11 \ | ||||||
|  |         idle3 | ||||||
|  | fi | ||||||
|  | 
 | ||||||
|  | # ====================================================== | ||||||
|  | # Files for each RPM (sub)package | ||||||
|  | # ====================================================== | ||||||
|  | 
 | ||||||
|  | %files -n %{pkgname}-rpm-macros | ||||||
|  | %{rpmmacrodir}/macros.python%{pybasever} | ||||||
|  | %{_rpmconfigdir}/redhat/import_all_modules_py3_11.py | ||||||
|  | 
 | ||||||
| %files -n %{pkgname} | %files -n %{pkgname} | ||||||
| %doc README.rst | %doc README.rst | ||||||
| 
 | 
 | ||||||
|  | # Alternatives | ||||||
|  | %ghost %{_bindir}/unversioned-python | ||||||
|  | %ghost %{_mandir}/man1/python.1.gz | ||||||
|  | %ghost %{_bindir}/python3 | ||||||
|  | %ghost %{_mandir}/man1/python3.1.gz | ||||||
|  | %ghost %{_bindir}/pydoc3 | ||||||
|  | %ghost %{_bindir}/pydoc-3 | ||||||
|  | 
 | ||||||
| %if %{with main_python} | %if %{with main_python} | ||||||
| %{_bindir}/pydoc* | %{_bindir}/pydoc* | ||||||
| %{_bindir}/python3 | %{_bindir}/python3 | ||||||
| @ -1412,6 +1637,9 @@ CheckPython optimized | |||||||
| %{_bindir}/python%{pybasever}-config | %{_bindir}/python%{pybasever}-config | ||||||
| %{_bindir}/python%{LDVERSION_optimized}-config | %{_bindir}/python%{LDVERSION_optimized}-config | ||||||
| %{_bindir}/python%{LDVERSION_optimized}-*-config | %{_bindir}/python%{LDVERSION_optimized}-*-config | ||||||
|  | # Alternatives | ||||||
|  | %ghost %{_bindir}/python3-config | ||||||
|  | 
 | ||||||
| %{_libdir}/libpython%{LDVERSION_optimized}.so | %{_libdir}/libpython%{LDVERSION_optimized}.so | ||||||
| %{_libdir}/pkgconfig/python-%{LDVERSION_optimized}.pc | %{_libdir}/pkgconfig/python-%{LDVERSION_optimized}.pc | ||||||
| %{_libdir}/pkgconfig/python-%{LDVERSION_optimized}-embed.pc | %{_libdir}/pkgconfig/python-%{LDVERSION_optimized}-embed.pc | ||||||
| @ -1424,6 +1652,8 @@ CheckPython optimized | |||||||
| %{_bindir}/idle* | %{_bindir}/idle* | ||||||
| %else | %else | ||||||
| %{_bindir}/idle%{pybasever} | %{_bindir}/idle%{pybasever} | ||||||
|  | # Alternatives | ||||||
|  | %ghost %{_bindir}/idle3 | ||||||
| %endif | %endif | ||||||
| 
 | 
 | ||||||
| %{pylibdir}/idlelib | %{pylibdir}/idlelib | ||||||
| @ -1609,6 +1839,23 @@ CheckPython optimized | |||||||
| # ====================================================== | # ====================================================== | ||||||
| 
 | 
 | ||||||
| %changelog | %changelog | ||||||
|  | * Mon Jan 22 2024 Charalampos Stratakis <cstratak@redhat.com> - 3.11.7-1 | ||||||
|  | - Rebase to 3.11.7 | ||||||
|  | Resolves: RHEL-21915 | ||||||
|  | 
 | ||||||
|  | * Tue Jan 09 2024 Lumír Balhar <lbalhar@redhat.com> - 3.11.5-2 | ||||||
|  | - Security fix for CVE-2023-27043 | ||||||
|  | Resolves: RHEL-7842 | ||||||
|  | 
 | ||||||
|  | * Thu Sep 07 2023 Charalampos Stratakis <cstratak@redhat.com> - 3.11.5-1 | ||||||
|  | - Rebase to 3.11.5 | ||||||
|  | - Security fixes for CVE-2023-40217 and CVE-2023-41105 | ||||||
|  | Resolves: RHEL-3047, RHEL-3267 | ||||||
|  | 
 | ||||||
|  | * Thu Aug 10 2023 Tomas Orsava <torsava@redhat.com> - 3.11.4-4 | ||||||
|  | - Add the import_all_modules_py3_11.py file for the python3.11-rpm-macros subpackage | ||||||
|  | Resolves: rhbz#2207631 | ||||||
|  | 
 | ||||||
| * Wed Aug 09 2023 Petr Viktorin <pviktori@redhat.com> - 3.11.4-3 | * Wed Aug 09 2023 Petr Viktorin <pviktori@redhat.com> - 3.11.4-3 | ||||||
| - Fix symlink handling in the fix for CVE-2023-24329 | - Fix symlink handling in the fix for CVE-2023-24329 | ||||||
| Resolves: rhbz#263261 | Resolves: rhbz#263261 | ||||||
| @ -1633,7 +1880,10 @@ Resolves: rhbz#2173917 | |||||||
| 
 | 
 | ||||||
| * Mon Jan 30 2023 Charalampos Stratakis <cstratak@redhat.com> - 3.11.1-3 | * Mon Jan 30 2023 Charalampos Stratakis <cstratak@redhat.com> - 3.11.1-3 | ||||||
| - Disable bootstrap | - Disable bootstrap | ||||||
| - Bump release to sync with RHEL8 | - Revert python3.11-rpm-macros requirement | ||||||
|  | 
 | ||||||
|  | * Mon Jan 30 2023 Charalampos Stratakis <cstratak@redhat.com> - 3.11.1-2 | ||||||
|  | - Fix macros requirements | ||||||
| 
 | 
 | ||||||
| * Tue Dec 13 2022 Charalampos Stratakis <cstratak@redhat.com> - 3.11.1-1 | * Tue Dec 13 2022 Charalampos Stratakis <cstratak@redhat.com> - 3.11.1-1 | ||||||
| - Initial package | - Initial package | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user