Sync from SUSE:ALP:Source:Standard:1.0 saltbundlepy revision 8a2cf4f00e4b2b371f89744cb2d05ac8

This commit is contained in:
Adrian Schröter 2024-11-15 12:52:25 +01:00
parent b0c198dfbe
commit 4ce5a3fa6a
8 changed files with 1134 additions and 22 deletions

View File

@ -0,0 +1,366 @@
From b47c766d6085d7918edd7715750d135868fdafd6 Mon Sep 17 00:00:00 2001
From: Petr Viktorin <encukou@gmail.com>
Date: Wed, 24 Apr 2024 14:29:30 +0200
Subject: [PATCH] gh-113171: gh-65056: Fix "private" (non-global) IP address
ranges (GH-113179) (GH-113186) (GH-118177)
* GH-113171: Fix "private" (non-global) IP address ranges (GH-113179)
The _private_networks variables, used by various is_private
implementations, were missing some ranges and at the same time had
overly strict ranges (where there are more specific ranges considered
globally reachable by the IANA registries).
This patch updates the ranges with what was missing or otherwise
incorrect.
100.64.0.0/10 is left alone, for now, as it's been made special in [1].
The _address_exclude_many() call returns 8 networks for IPv4, 121
networks for IPv6.
[1] https://github.com/python/cpython/issues/61602
* GH-65056: Improve the IP address' is_global/is_private documentation (GH-113186)
It wasn't clear what the semantics of is_global/is_private are and, when
one gets to the bottom of it, it's not quite so simple (hence the
exceptions listed).
(cherry picked from commit 2a4cbf17af19a01d942f9579342f77c39fbd23c4)
(cherry picked from commit 40d75c2b7f5c67e254d0a025e0f2e2c7ada7f69f)
---------
(cherry picked from commit f86b17ac511e68192ba71f27e752321a3252cee3)
Co-authored-by: Jakub Stasiak <jakub@stasiak.at>
---
Doc/library/ipaddress.rst | 43 +++-
Doc/whatsnew/3.11.rst | 9
Lib/ipaddress.py | 105 +++++++---
Lib/test/test_ipaddress.py | 21 +-
Misc/NEWS.d/next/Library/2024-03-14-01-38-44.gh-issue-113171.VFnObz.rst | 9
5 files changed, 160 insertions(+), 27 deletions(-)
create mode 100644 Misc/NEWS.d/next/Library/2024-03-14-01-38-44.gh-issue-113171.VFnObz.rst
--- a/Doc/library/ipaddress.rst
+++ b/Doc/library/ipaddress.rst
@@ -178,18 +178,53 @@ write code that handles both IP versions
.. attribute:: is_private
- ``True`` if the address is allocated for private networks. See
+ ``True`` if the address is defined as not globally reachable by
iana-ipv4-special-registry_ (for IPv4) or iana-ipv6-special-registry_
- (for IPv6).
+ (for IPv6) with the following exceptions:
+
+ * ``is_private`` is ``False`` for the shared address space (``100.64.0.0/10``)
+ * For IPv4-mapped IPv6-addresses the ``is_private`` value is determined by the
+ semantics of the underlying IPv4 addresses and the following condition holds
+ (see :attr:`IPv6Address.ipv4_mapped`)::
+
+ address.is_private == address.ipv4_mapped.is_private
+
+ ``is_private`` has value opposite to :attr:`is_global`, except for the shared address space
+ (``100.64.0.0/10`` range) where they are both ``False``.
+
+ .. versionchanged:: 3.11.10
+
+ Fixed some false positives and false negatives.
+
+ * ``192.0.0.0/24`` is considered private with the exception of ``192.0.0.9/32`` and
+ ``192.0.0.10/32`` (previously: only the ``192.0.0.0/29`` sub-range was considered private).
+ * ``64:ff9b:1::/48`` is considered private.
+ * ``2002::/16`` is considered private.
+ * There are exceptions within ``2001::/23`` (otherwise considered private): ``2001:1::1/128``,
+ ``2001:1::2/128``, ``2001:3::/32``, ``2001:4:112::/48``, ``2001:20::/28``, ``2001:30::/28``.
+ The exceptions are not considered private.
.. attribute:: is_global
- ``True`` if the address is allocated for public networks. See
+ ``True`` if the address is defined as globally reachable by
iana-ipv4-special-registry_ (for IPv4) or iana-ipv6-special-registry_
- (for IPv6).
+ (for IPv6) with the following exception:
+
+ For IPv4-mapped IPv6-addresses the ``is_private`` value is determined by the
+ semantics of the underlying IPv4 addresses and the following condition holds
+ (see :attr:`IPv6Address.ipv4_mapped`)::
+
+ address.is_global == address.ipv4_mapped.is_global
+
+ ``is_global`` has value opposite to :attr:`is_private`, except for the shared address space
+ (``100.64.0.0/10`` range) where they are both ``False``.
.. versionadded:: 3.4
+ .. versionchanged:: 3.11.10
+
+ Fixed some false positives and false negatives, see :attr:`is_private` for details.
+
.. attribute:: is_unspecified
``True`` if the address is unspecified. See :RFC:`5735` (for IPv4)
--- a/Doc/whatsnew/3.11.rst
+++ b/Doc/whatsnew/3.11.rst
@@ -2727,3 +2727,12 @@ OpenSSL
* Windows builds and macOS installers from python.org now use OpenSSL 3.0.
.. _libb2: https://www.blake2.net/
+
+Notable changes in 3.11.10
+==========================
+
+ipaddress
+---------
+
+* Fixed ``is_global`` and ``is_private`` behavior in ``IPv4Address``,
+ ``IPv6Address``, ``IPv4Network`` and ``IPv6Network``.
--- a/Lib/ipaddress.py
+++ b/Lib/ipaddress.py
@@ -1086,7 +1086,11 @@ class _BaseNetwork(_IPAddressBase):
"""
return any(self.network_address in priv_network and
self.broadcast_address in priv_network
- for priv_network in self._constants._private_networks)
+ for priv_network in self._constants._private_networks) and all(
+ self.network_address not in network and
+ self.broadcast_address not in network
+ for network in self._constants._private_networks_exceptions
+ )
@property
def is_global(self):
@@ -1333,18 +1337,41 @@ class IPv4Address(_BaseV4, _BaseAddress)
@property
@functools.lru_cache()
def is_private(self):
- """Test if this address is allocated for private networks.
-
- Returns:
- A boolean, True if the address is reserved per
- iana-ipv4-special-registry.
-
- """
- return any(self in net for net in self._constants._private_networks)
+ """``True`` if the address is defined as not globally reachable by
+ iana-ipv4-special-registry_ (for IPv4) or iana-ipv6-special-registry_
+ (for IPv6) with the following exceptions:
+
+ * ``is_private`` is ``False`` for ``100.64.0.0/10``
+ * For IPv4-mapped IPv6-addresses the ``is_private`` value is determined by the
+ semantics of the underlying IPv4 addresses and the following condition holds
+ (see :attr:`IPv6Address.ipv4_mapped`)::
+
+ address.is_private == address.ipv4_mapped.is_private
+
+ ``is_private`` has value opposite to :attr:`is_global`, except for the ``100.64.0.0/10``
+ IPv4 range where they are both ``False``.
+ """
+ return (
+ any(self in net for net in self._constants._private_networks)
+ and all(self not in net for net in self._constants._private_networks_exceptions)
+ )
@property
@functools.lru_cache()
def is_global(self):
+ """``True`` if the address is defined as globally reachable by
+ iana-ipv4-special-registry_ (for IPv4) or iana-ipv6-special-registry_
+ (for IPv6) with the following exception:
+
+ For IPv4-mapped IPv6-addresses the ``is_private`` value is determined by the
+ semantics of the underlying IPv4 addresses and the following condition holds
+ (see :attr:`IPv6Address.ipv4_mapped`)::
+
+ address.is_global == address.ipv4_mapped.is_global
+
+ ``is_global`` has value opposite to :attr:`is_private`, except for the ``100.64.0.0/10``
+ IPv4 range where they are both ``False``.
+ """
return self not in self._constants._public_network and not self.is_private
@property
@@ -1548,13 +1575,15 @@ class _IPv4Constants:
_public_network = IPv4Network('100.64.0.0/10')
+ # Not globally reachable address blocks listed on
+ # https://www.iana.org/assignments/iana-ipv4-special-registry/iana-ipv4-special-registry.xhtml
_private_networks = [
IPv4Network('0.0.0.0/8'),
IPv4Network('10.0.0.0/8'),
IPv4Network('127.0.0.0/8'),
IPv4Network('169.254.0.0/16'),
IPv4Network('172.16.0.0/12'),
- IPv4Network('192.0.0.0/29'),
+ IPv4Network('192.0.0.0/24'),
IPv4Network('192.0.0.170/31'),
IPv4Network('192.0.2.0/24'),
IPv4Network('192.168.0.0/16'),
@@ -1565,6 +1594,11 @@ class _IPv4Constants:
IPv4Network('255.255.255.255/32'),
]
+ _private_networks_exceptions = [
+ IPv4Network('192.0.0.9/32'),
+ IPv4Network('192.0.0.10/32'),
+ ]
+
_reserved_network = IPv4Network('240.0.0.0/4')
_unspecified_address = IPv4Address('0.0.0.0')
@@ -2010,27 +2044,42 @@ class IPv6Address(_BaseV6, _BaseAddress)
@property
@functools.lru_cache()
def is_private(self):
- """Test if this address is allocated for private networks.
+ """``True`` if the address is defined as not globally reachable by
+ iana-ipv4-special-registry_ (for IPv4) or iana-ipv6-special-registry_
+ (for IPv6) with the following exceptions:
+
+ * ``is_private`` is ``False`` for ``100.64.0.0/10``
+ * For IPv4-mapped IPv6-addresses the ``is_private`` value is determined by the
+ semantics of the underlying IPv4 addresses and the following condition holds
+ (see :attr:`IPv6Address.ipv4_mapped`)::
- Returns:
- A boolean, True if the address is reserved per
- iana-ipv6-special-registry, or is ipv4_mapped and is
- reserved in the iana-ipv4-special-registry.
+ address.is_private == address.ipv4_mapped.is_private
+ ``is_private`` has value opposite to :attr:`is_global`, except for the ``100.64.0.0/10``
+ IPv4 range where they are both ``False``.
"""
ipv4_mapped = self.ipv4_mapped
if ipv4_mapped is not None:
return ipv4_mapped.is_private
- return any(self in net for net in self._constants._private_networks)
+ return (
+ any(self in net for net in self._constants._private_networks)
+ and all(self not in net for net in self._constants._private_networks_exceptions)
+ )
@property
def is_global(self):
- """Test if this address is allocated for public networks.
+ """``True`` if the address is defined as globally reachable by
+ iana-ipv4-special-registry_ (for IPv4) or iana-ipv6-special-registry_
+ (for IPv6) with the following exception:
+
+ For IPv4-mapped IPv6-addresses the ``is_private`` value is determined by the
+ semantics of the underlying IPv4 addresses and the following condition holds
+ (see :attr:`IPv6Address.ipv4_mapped`)::
- Returns:
- A boolean, true if the address is not reserved per
- iana-ipv6-special-registry.
+ address.is_global == address.ipv4_mapped.is_global
+ ``is_global`` has value opposite to :attr:`is_private`, except for the ``100.64.0.0/10``
+ IPv4 range where they are both ``False``.
"""
return not self.is_private
@@ -2271,19 +2320,31 @@ class _IPv6Constants:
_multicast_network = IPv6Network('ff00::/8')
+ # Not globally reachable address blocks listed on
+ # https://www.iana.org/assignments/iana-ipv6-special-registry/iana-ipv6-special-registry.xhtml
_private_networks = [
IPv6Network('::1/128'),
IPv6Network('::/128'),
IPv6Network('::ffff:0:0/96'),
+ IPv6Network('64:ff9b:1::/48'),
IPv6Network('100::/64'),
IPv6Network('2001::/23'),
- IPv6Network('2001:2::/48'),
IPv6Network('2001:db8::/32'),
- IPv6Network('2001:10::/28'),
+ # IANA says N/A, let's consider it not globally reachable to be safe
+ IPv6Network('2002::/16'),
IPv6Network('fc00::/7'),
IPv6Network('fe80::/10'),
]
+ _private_networks_exceptions = [
+ IPv6Network('2001:1::1/128'),
+ IPv6Network('2001:1::2/128'),
+ IPv6Network('2001:3::/32'),
+ IPv6Network('2001:4:112::/48'),
+ IPv6Network('2001:20::/28'),
+ IPv6Network('2001:30::/28'),
+ ]
+
_reserved_networks = [
IPv6Network('::/8'), IPv6Network('100::/8'),
IPv6Network('200::/7'), IPv6Network('400::/6'),
--- a/Lib/test/test_ipaddress.py
+++ b/Lib/test/test_ipaddress.py
@@ -2269,6 +2269,10 @@ class IpaddrUnitTest(unittest.TestCase):
self.assertEqual(True, ipaddress.ip_address(
'172.31.255.255').is_private)
self.assertEqual(False, ipaddress.ip_address('172.32.0.0').is_private)
+ self.assertFalse(ipaddress.ip_address('192.0.0.0').is_global)
+ self.assertTrue(ipaddress.ip_address('192.0.0.9').is_global)
+ self.assertTrue(ipaddress.ip_address('192.0.0.10').is_global)
+ self.assertFalse(ipaddress.ip_address('192.0.0.255').is_global)
self.assertEqual(True,
ipaddress.ip_address('169.254.100.200').is_link_local)
@@ -2294,6 +2298,7 @@ class IpaddrUnitTest(unittest.TestCase):
self.assertEqual(True, ipaddress.ip_network("169.254.0.0/16").is_private)
self.assertEqual(True, ipaddress.ip_network("172.16.0.0/12").is_private)
self.assertEqual(True, ipaddress.ip_network("192.0.0.0/29").is_private)
+ self.assertEqual(False, ipaddress.ip_network("192.0.0.9/32").is_private)
self.assertEqual(True, ipaddress.ip_network("192.0.0.170/31").is_private)
self.assertEqual(True, ipaddress.ip_network("192.0.2.0/24").is_private)
self.assertEqual(True, ipaddress.ip_network("192.168.0.0/16").is_private)
@@ -2310,8 +2315,8 @@ class IpaddrUnitTest(unittest.TestCase):
self.assertEqual(True, ipaddress.ip_network("::/128").is_private)
self.assertEqual(True, ipaddress.ip_network("::ffff:0:0/96").is_private)
self.assertEqual(True, ipaddress.ip_network("100::/64").is_private)
- self.assertEqual(True, ipaddress.ip_network("2001::/23").is_private)
self.assertEqual(True, ipaddress.ip_network("2001:2::/48").is_private)
+ self.assertEqual(False, ipaddress.ip_network("2001:3::/48").is_private)
self.assertEqual(True, ipaddress.ip_network("2001:db8::/32").is_private)
self.assertEqual(True, ipaddress.ip_network("2001:10::/28").is_private)
self.assertEqual(True, ipaddress.ip_network("fc00::/7").is_private)
@@ -2390,6 +2395,20 @@ class IpaddrUnitTest(unittest.TestCase):
self.assertEqual(True, ipaddress.ip_address('0::0').is_unspecified)
self.assertEqual(False, ipaddress.ip_address('::1').is_unspecified)
+ self.assertFalse(ipaddress.ip_address('64:ff9b:1::').is_global)
+ self.assertFalse(ipaddress.ip_address('2001::').is_global)
+ self.assertTrue(ipaddress.ip_address('2001:1::1').is_global)
+ self.assertTrue(ipaddress.ip_address('2001:1::2').is_global)
+ self.assertFalse(ipaddress.ip_address('2001:2::').is_global)
+ self.assertTrue(ipaddress.ip_address('2001:3::').is_global)
+ self.assertFalse(ipaddress.ip_address('2001:4::').is_global)
+ self.assertTrue(ipaddress.ip_address('2001:4:112::').is_global)
+ self.assertFalse(ipaddress.ip_address('2001:10::').is_global)
+ self.assertTrue(ipaddress.ip_address('2001:20::').is_global)
+ self.assertTrue(ipaddress.ip_address('2001:30::').is_global)
+ self.assertFalse(ipaddress.ip_address('2001:40::').is_global)
+ self.assertFalse(ipaddress.ip_address('2002::').is_global)
+
# some generic IETF reserved addresses
self.assertEqual(True, ipaddress.ip_address('100::').is_reserved)
self.assertEqual(True, ipaddress.ip_network('4000::1/128').is_reserved)
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2024-03-14-01-38-44.gh-issue-113171.VFnObz.rst
@@ -0,0 +1,9 @@
+Fixed various false positives and false negatives in
+
+* :attr:`ipaddress.IPv4Address.is_private` (see these docs for details)
+* :attr:`ipaddress.IPv4Address.is_global`
+* :attr:`ipaddress.IPv6Address.is_private`
+* :attr:`ipaddress.IPv6Address.is_global`
+
+Also in the corresponding :class:`ipaddress.IPv4Network` and :class:`ipaddress.IPv6Network`
+attributes.

View File

@ -0,0 +1,368 @@
From f9ddc53ea850fb02d640a9b3263756d43fb6d868 Mon Sep 17 00:00:00 2001
From: Petr Viktorin <encukou@gmail.com>
Date: Wed, 31 Jul 2024 00:19:48 +0200
Subject: [PATCH] [3.11] gh-121650: Encode newlines in headers, and verify
headers are sound (GH-122233)
GH-GH- Encode header parts that contain newlines
Per RFC 2047:
> [...] these encoding schemes allow the
> encoding of arbitrary octet values, mail readers that implement this
> decoding should also ensure that display of the decoded data on the
> recipient's terminal will not cause unwanted side-effects
It seems that the "quoted-word" scheme is a valid way to include
a newline character in a header value, just like we already allow
undecodable bytes or control characters.
They do need to be properly quoted when serialized to text, though.
GH-GH- Verify that email headers are well-formed
This should fail for custom fold() implementations that aren't careful
about newlines.
(cherry picked from commit 097633981879b3c9de9a1dd120d3aa585ecc2384)
Co-authored-by: Petr Viktorin <encukou@gmail.com>
Co-authored-by: Bas Bloemsaat <bas@bloemsaat.org>
Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
---
Doc/library/email.errors.rst | 7 +
Doc/library/email.policy.rst | 18 ++
Doc/whatsnew/3.11.rst | 13 ++
Lib/email/_header_value_parser.py | 12 +
Lib/email/_policybase.py | 8 +
Lib/email/errors.py | 4
Lib/email/generator.py | 13 +-
Lib/test/test_email/test_generator.py | 62 ++++++++++
Lib/test/test_email/test_policy.py | 26 ++++
Misc/NEWS.d/next/Library/2024-07-27-16-10-41.gh-issue-121650.nf6oc9.rst | 5
10 files changed, 164 insertions(+), 4 deletions(-)
create mode 100644 Misc/NEWS.d/next/Library/2024-07-27-16-10-41.gh-issue-121650.nf6oc9.rst
Index: Python-3.11.9/Doc/library/email.errors.rst
===================================================================
--- Python-3.11.9.orig/Doc/library/email.errors.rst
+++ Python-3.11.9/Doc/library/email.errors.rst
@@ -58,6 +58,13 @@ The following exception classes are defi
:class:`~email.mime.nonmultipart.MIMENonMultipart` (e.g.
:class:`~email.mime.image.MIMEImage`).
+
+.. exception:: HeaderWriteError()
+
+ Raised when an error occurs when the :mod:`~email.generator` outputs
+ headers.
+
+
.. exception:: MessageDefect()
This is the base class for all defects found when parsing email messages.
Index: Python-3.11.9/Doc/library/email.policy.rst
===================================================================
--- Python-3.11.9.orig/Doc/library/email.policy.rst
+++ Python-3.11.9/Doc/library/email.policy.rst
@@ -228,6 +228,24 @@ added matters. To illustrate::
.. versionadded:: 3.6
+
+ .. attribute:: verify_generated_headers
+
+ If ``True`` (the default), the generator will raise
+ :exc:`~email.errors.HeaderWriteError` instead of writing a header
+ that is improperly folded or delimited, such that it would
+ be parsed as multiple headers or joined with adjacent data.
+ Such headers can be generated by custom header classes or bugs
+ in the ``email`` module.
+
+ As it's a security feature, this defaults to ``True`` even in the
+ :class:`~email.policy.Compat32` policy.
+ For backwards compatible, but unsafe, behavior, it must be set to
+ ``False`` explicitly.
+
+ .. versionadded:: 3.11.10
+
+
The following :class:`Policy` method is intended to be called by code using
the email library to create policy instances with custom settings:
Index: Python-3.11.9/Doc/whatsnew/3.11.rst
===================================================================
--- Python-3.11.9.orig/Doc/whatsnew/3.11.rst
+++ Python-3.11.9/Doc/whatsnew/3.11.rst
@@ -2728,6 +2728,7 @@ OpenSSL
.. _libb2: https://www.blake2.net/
+
Notable changes in 3.11.10
==========================
@@ -2736,3 +2737,15 @@ ipaddress
* Fixed ``is_global`` and ``is_private`` behavior in ``IPv4Address``,
``IPv6Address``, ``IPv4Network`` and ``IPv6Network``.
+
+email
+-----
+
+* Headers with embedded newlines are now quoted on output.
+
+ The :mod:`~email.generator` will now refuse to serialize (write) headers
+ that are improperly folded or delimited, such that they would be parsed as
+ multiple headers or joined with adjacent data.
+ If you need to turn this safety feature off,
+ set :attr:`~email.policy.Policy.verify_generated_headers`.
+ (Contributed by Bas Bloemsaat and Petr Viktorin in :gh:`121650`.)
Index: Python-3.11.9/Lib/email/_header_value_parser.py
===================================================================
--- Python-3.11.9.orig/Lib/email/_header_value_parser.py
+++ Python-3.11.9/Lib/email/_header_value_parser.py
@@ -92,6 +92,8 @@ TOKEN_ENDS = TSPECIALS | WSP
ASPECIALS = TSPECIALS | set("*'%")
ATTRIBUTE_ENDS = ASPECIALS | WSP
EXTENDED_ATTRIBUTE_ENDS = ATTRIBUTE_ENDS - set('%')
+NLSET = {'\n', '\r'}
+SPECIALSNL = SPECIALS | NLSET
def quote_string(value):
return '"'+str(value).replace('\\', '\\\\').replace('"', r'\"')+'"'
@@ -2780,9 +2782,13 @@ def _refold_parse_tree(parse_tree, *, po
wrap_as_ew_blocked -= 1
continue
tstr = str(part)
- if part.token_type == 'ptext' and set(tstr) & SPECIALS:
- # Encode if tstr contains special characters.
- want_encoding = True
+ if not want_encoding:
+ if part.token_type == 'ptext':
+ # Encode if tstr contains special characters.
+ want_encoding = not SPECIALSNL.isdisjoint(tstr)
+ else:
+ # Encode if tstr contains newlines.
+ want_encoding = not NLSET.isdisjoint(tstr)
try:
tstr.encode(encoding)
charset = encoding
Index: Python-3.11.9/Lib/email/_policybase.py
===================================================================
--- Python-3.11.9.orig/Lib/email/_policybase.py
+++ Python-3.11.9/Lib/email/_policybase.py
@@ -157,6 +157,13 @@ class Policy(_PolicyBase, metaclass=abc.
message_factory -- the class to use to create new message objects.
If the value is None, the default is Message.
+ verify_generated_headers
+ -- if true, the generator verifies that each header
+ they are properly folded, so that a parser won't
+ treat it as multiple headers, start-of-body, or
+ part of another header.
+ This is a check against custom Header & fold()
+ implementations.
"""
raise_on_defect = False
@@ -165,6 +172,7 @@ class Policy(_PolicyBase, metaclass=abc.
max_line_length = 78
mangle_from_ = False
message_factory = None
+ verify_generated_headers = True
def handle_defect(self, obj, defect):
"""Based on policy, either raise defect or call register_defect.
Index: Python-3.11.9/Lib/email/errors.py
===================================================================
--- Python-3.11.9.orig/Lib/email/errors.py
+++ Python-3.11.9/Lib/email/errors.py
@@ -29,6 +29,10 @@ class CharsetError(MessageError):
"""An illegal charset was given."""
+class HeaderWriteError(MessageError):
+ """Error while writing headers."""
+
+
# These are parsing defects which the parser was able to work around.
class MessageDefect(ValueError):
"""Base class for a message defect."""
Index: Python-3.11.9/Lib/email/generator.py
===================================================================
--- Python-3.11.9.orig/Lib/email/generator.py
+++ Python-3.11.9/Lib/email/generator.py
@@ -14,12 +14,14 @@ import random
from copy import deepcopy
from io import StringIO, BytesIO
from email.utils import _has_surrogates
+from email.errors import HeaderWriteError
UNDERSCORE = '_'
NL = '\n' # XXX: no longer used by the code below.
NLCRE = re.compile(r'\r\n|\r|\n')
fcre = re.compile(r'^From ', re.MULTILINE)
+NEWLINE_WITHOUT_FWSP = re.compile(r'\r\n[^ \t]|\r[^ \n\t]|\n[^ \t]')
class Generator:
@@ -222,7 +224,16 @@ class Generator:
def _write_headers(self, msg):
for h, v in msg.raw_items():
- self.write(self.policy.fold(h, v))
+ folded = self.policy.fold(h, v)
+ if self.policy.verify_generated_headers:
+ linesep = self.policy.linesep
+ if not folded.endswith(self.policy.linesep):
+ raise HeaderWriteError(
+ f'folded header does not end with {linesep!r}: {folded!r}')
+ if NEWLINE_WITHOUT_FWSP.search(folded.removesuffix(linesep)):
+ raise HeaderWriteError(
+ f'folded header contains newline: {folded!r}')
+ self.write(folded)
# A blank line always separates headers from body
self.write(self._NL)
Index: Python-3.11.9/Lib/test/test_email/test_generator.py
===================================================================
--- Python-3.11.9.orig/Lib/test/test_email/test_generator.py
+++ Python-3.11.9/Lib/test/test_email/test_generator.py
@@ -6,6 +6,7 @@ from email.message import EmailMessage
from email.generator import Generator, BytesGenerator
from email.headerregistry import Address
from email import policy
+import email.errors
from test.test_email import TestEmailBase, parameterize
@@ -216,6 +217,44 @@ class TestGeneratorBase:
g.flatten(msg)
self.assertEqual(s.getvalue(), self.typ(expected))
+ def test_keep_encoded_newlines(self):
+ msg = self.msgmaker(self.typ(textwrap.dedent("""\
+ To: nobody
+ Subject: Bad subject=?UTF-8?Q?=0A?=Bcc: injection@example.com
+
+ None
+ """)))
+ expected = textwrap.dedent("""\
+ To: nobody
+ Subject: Bad subject=?UTF-8?Q?=0A?=Bcc: injection@example.com
+
+ None
+ """)
+ s = self.ioclass()
+ g = self.genclass(s, policy=self.policy.clone(max_line_length=80))
+ g.flatten(msg)
+ self.assertEqual(s.getvalue(), self.typ(expected))
+
+ def test_keep_long_encoded_newlines(self):
+ msg = self.msgmaker(self.typ(textwrap.dedent("""\
+ To: nobody
+ Subject: Bad subject=?UTF-8?Q?=0A?=Bcc: injection@example.com
+
+ None
+ """)))
+ expected = textwrap.dedent("""\
+ To: nobody
+ Subject: Bad subject
+ =?utf-8?q?=0A?=Bcc:
+ injection@example.com
+
+ None
+ """)
+ s = self.ioclass()
+ g = self.genclass(s, policy=self.policy.clone(max_line_length=30))
+ g.flatten(msg)
+ self.assertEqual(s.getvalue(), self.typ(expected))
+
class TestGenerator(TestGeneratorBase, TestEmailBase):
@@ -224,6 +263,29 @@ class TestGenerator(TestGeneratorBase, T
ioclass = io.StringIO
typ = str
+ def test_verify_generated_headers(self):
+ """gh-121650: by default the generator prevents header injection"""
+ class LiteralHeader(str):
+ name = 'Header'
+ def fold(self, **kwargs):
+ return self
+
+ for text in (
+ 'Value\r\nBad Injection\r\n',
+ 'NoNewLine'
+ ):
+ with self.subTest(text=text):
+ message = message_from_string(
+ "Header: Value\r\n\r\nBody",
+ policy=self.policy,
+ )
+
+ del message['Header']
+ message['Header'] = LiteralHeader(text)
+
+ with self.assertRaises(email.errors.HeaderWriteError):
+ message.as_string()
+
class TestBytesGenerator(TestGeneratorBase, TestEmailBase):
Index: Python-3.11.9/Lib/test/test_email/test_policy.py
===================================================================
--- Python-3.11.9.orig/Lib/test/test_email/test_policy.py
+++ Python-3.11.9/Lib/test/test_email/test_policy.py
@@ -26,6 +26,7 @@ class PolicyAPITests(unittest.TestCase):
'raise_on_defect': False,
'mangle_from_': True,
'message_factory': None,
+ 'verify_generated_headers': True,
}
# These default values are the ones set on email.policy.default.
# If any of these defaults change, the docs must be updated.
@@ -294,6 +295,31 @@ class PolicyAPITests(unittest.TestCase):
with self.assertRaises(email.errors.HeaderParseError):
policy.fold("Subject", subject)
+ def test_verify_generated_headers(self):
+ """Turning protection off allows header injection"""
+ policy = email.policy.default.clone(verify_generated_headers=False)
+ for text in (
+ 'Header: Value\r\nBad: Injection\r\n',
+ 'Header: NoNewLine'
+ ):
+ with self.subTest(text=text):
+ message = email.message_from_string(
+ "Header: Value\r\n\r\nBody",
+ policy=policy,
+ )
+ class LiteralHeader(str):
+ name = 'Header'
+ def fold(self, **kwargs):
+ return self
+
+ del message['Header']
+ message['Header'] = LiteralHeader(text)
+
+ self.assertEqual(
+ message.as_string(),
+ f"{text}\nBody",
+ )
+
# XXX: Need subclassing tests.
# For adding subclassed objects, make sure the usual rules apply (subclass
# wins), but that the order still works (right overrides left).
Index: Python-3.11.9/Misc/NEWS.d/next/Library/2024-07-27-16-10-41.gh-issue-121650.nf6oc9.rst
===================================================================
--- /dev/null
+++ Python-3.11.9/Misc/NEWS.d/next/Library/2024-07-27-16-10-41.gh-issue-121650.nf6oc9.rst
@@ -0,0 +1,5 @@
+:mod:`email` headers with embedded newlines are now quoted on output. The
+:mod:`~email.generator` will now refuse to serialize (write) headers that
+are unsafely folded or delimited; see
+:attr:`~email.policy.Policy.verify_generated_headers`. (Contributed by Bas
+Bloemsaat and Petr Viktorin in :gh:`121650`.)

View File

@ -0,0 +1,122 @@
From 44e458357fca05ca0ae2658d62c8c595b048b5ef Mon Sep 17 00:00:00 2001
From: Serhiy Storchaka <storchaka@gmail.com>
Date: Sat, 17 Aug 2024 16:30:52 +0300
Subject: [PATCH] gh-123067: Fix quadratic complexity in parsing "-quoted
cookie values with backslashes (GH-123075)
This fixes CVE-2024-7592.
---
Lib/http/cookies.py | 34 ++------
Lib/test/test_http_cookies.py | 38 ++++++++++
Misc/NEWS.d/next/Library/2024-08-16-19-13-21.gh-issue-123067.Nx9O4R.rst | 1
3 files changed, 47 insertions(+), 26 deletions(-)
create mode 100644 Misc/NEWS.d/next/Library/2024-08-16-19-13-21.gh-issue-123067.Nx9O4R.rst
--- a/Lib/http/cookies.py
+++ b/Lib/http/cookies.py
@@ -184,8 +184,13 @@ def _quote(str):
return '"' + str.translate(_Translator) + '"'
-_OctalPatt = re.compile(r"\\[0-3][0-7][0-7]")
-_QuotePatt = re.compile(r"[\\].")
+_unquote_sub = re.compile(r'\\(?:([0-3][0-7][0-7])|(.))').sub
+
+def _unquote_replace(m):
+ if m[1]:
+ return chr(int(m[1], 8))
+ else:
+ return m[2]
def _unquote(str):
# If there aren't any doublequotes,
@@ -205,30 +210,7 @@ def _unquote(str):
# \012 --> \n
# \" --> "
#
- i = 0
- n = len(str)
- res = []
- while 0 <= i < n:
- o_match = _OctalPatt.search(str, i)
- q_match = _QuotePatt.search(str, i)
- if not o_match and not q_match: # Neither matched
- res.append(str[i:])
- break
- # else:
- j = k = -1
- if o_match:
- j = o_match.start(0)
- if q_match:
- k = q_match.start(0)
- if q_match and (not o_match or k < j): # QuotePatt matched
- res.append(str[i:k])
- res.append(str[k+1])
- i = k + 2
- else: # OctalPatt matched
- res.append(str[i:j])
- res.append(chr(int(str[j+1:j+4], 8)))
- i = j + 4
- return _nulljoin(res)
+ return _unquote_sub(_unquote_replace, str)
# The _getdate() routine is used to set the expiration time in the cookie's HTTP
# header. By default, _getdate() returns the current time in the appropriate
--- a/Lib/test/test_http_cookies.py
+++ b/Lib/test/test_http_cookies.py
@@ -5,6 +5,7 @@ import unittest
import doctest
from http import cookies
import pickle
+from test import support
class CookieTests(unittest.TestCase):
@@ -58,6 +59,43 @@ class CookieTests(unittest.TestCase):
for k, v in sorted(case['dict'].items()):
self.assertEqual(C[k].value, v)
+ def test_unquote(self):
+ cases = [
+ (r'a="b=\""', 'b="'),
+ (r'a="b=\\"', 'b=\\'),
+ (r'a="b=\="', 'b=='),
+ (r'a="b=\n"', 'b=n'),
+ (r'a="b=\042"', 'b="'),
+ (r'a="b=\134"', 'b=\\'),
+ (r'a="b=\377"', 'b=\xff'),
+ (r'a="b=\400"', 'b=400'),
+ (r'a="b=\42"', 'b=42'),
+ (r'a="b=\\042"', 'b=\\042'),
+ (r'a="b=\\134"', 'b=\\134'),
+ (r'a="b=\\\""', 'b=\\"'),
+ (r'a="b=\\\042"', 'b=\\"'),
+ (r'a="b=\134\""', 'b=\\"'),
+ (r'a="b=\134\042"', 'b=\\"'),
+ ]
+ for encoded, decoded in cases:
+ with self.subTest(encoded):
+ C = cookies.SimpleCookie()
+ C.load(encoded)
+ self.assertEqual(C['a'].value, decoded)
+
+ @support.requires_resource('cpu')
+ def test_unquote_large(self):
+ n = 10**6
+ for encoded in r'\\', r'\134':
+ with self.subTest(encoded):
+ data = 'a="b=' + encoded*n + ';"'
+ C = cookies.SimpleCookie()
+ C.load(data)
+ value = C['a'].value
+ self.assertEqual(value[:3], 'b=\\')
+ self.assertEqual(value[-2:], '\\;')
+ self.assertEqual(len(value), n + 3)
+
def test_load(self):
C = cookies.SimpleCookie()
C.load('Customer="WILE_E_COYOTE"; Version=1; Path=/acme')
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2024-08-16-19-13-21.gh-issue-123067.Nx9O4R.rst
@@ -0,0 +1 @@
+Fix quadratic complexity in parsing ``"``-quoted cookie values with backslashes by :mod:`http.cookies`.

View File

@ -0,0 +1,136 @@
---
Lib/test/test_zipfile.py | 75 ++++++++++
Lib/zipfile.py | 10 +
Misc/NEWS.d/next/Library/2024-08-11-14-08-04.gh-issue-122905.7tDsxA.rst | 1
Misc/NEWS.d/next/Library/2024-08-26-13-45-20.gh-issue-123270.gXHvNJ.rst | 3
4 files changed, 87 insertions(+), 2 deletions(-)
--- a/Lib/test/test_zipfile.py
+++ b/Lib/test/test_zipfile.py
@@ -3651,6 +3651,81 @@ with zipfile.ZipFile(io.BytesIO(), "w")
zipfile.Path(zf)
zf.extractall(source_path.parent)
+ def test_malformed_paths(self):
+ """
+ Path should handle malformed paths gracefully.
+
+ Paths with leading slashes are not visible.
+
+ Paths with dots are treated like regular files.
+ """
+ data = io.BytesIO()
+ zf = zipfile.ZipFile(data, "w")
+ zf.writestr("../parent.txt", b"content")
+ zf.filename = ''
+ root = zipfile.Path(zf)
+ assert list(map(str, root.iterdir())) == ['../']
+ assert root.joinpath('..').joinpath('parent.txt').read_bytes() == b'content'
+
+ def test_unsupported_names(self):
+ """
+ Path segments with special characters are readable.
+
+ On some platforms or file systems, characters like
+ ``:`` and ``?`` are not allowed, but they are valid
+ in the zip file.
+ """
+ data = io.BytesIO()
+ zf = zipfile.ZipFile(data, "w")
+ zf.writestr("path?", b"content")
+ zf.writestr("V: NMS.flac", b"fLaC...")
+ zf.filename = ''
+ root = zipfile.Path(zf)
+ contents = root.iterdir()
+ assert next(contents).name == 'path?'
+ assert next(contents).name == 'V: NMS.flac'
+ assert root.joinpath('V: NMS.flac').read_bytes() == b"fLaC..."
+
+ def test_backslash_not_separator(self):
+ """
+ In a zip file, backslashes are not separators.
+ """
+ data = io.BytesIO()
+ zf = zipfile.ZipFile(data, "w")
+ zf.writestr(DirtyZipInfo.for_name("foo\\bar", zf), b"content")
+ zf.filename = ''
+ root = zipfile.Path(zf)
+ (first,) = root.iterdir()
+ assert not first.is_dir()
+ assert first.name == 'foo\\bar'
+
+
+class DirtyZipInfo(zipfile.ZipInfo):
+ """
+ Bypass name sanitization.
+ """
+
+ def __init__(self, filename, *args, **kwargs):
+ super().__init__(filename, *args, **kwargs)
+ self.filename = filename
+
+ @classmethod
+ def for_name(cls, name, archive):
+ """
+ Construct the same way that ZipFile.writestr does.
+
+ TODO: extract this functionality and re-use
+ """
+ self = cls(filename=name, date_time=time.localtime(time.time())[:6])
+ self.compress_type = archive.compression
+ self.compress_level = archive.compresslevel
+ if self.filename.endswith('/'): # pragma: no cover
+ self.external_attr = 0o40775 << 16 # drwxrwxr-x
+ self.external_attr |= 0x10 # MS-DOS directory flag
+ else:
+ self.external_attr = 0o600 << 16 # ?rw-------
+ return self
+
class EncodedMetadataTests(unittest.TestCase):
file_names = ['\u4e00', '\u4e8c', '\u4e09'] # Han 'one', 'two', 'three'
--- a/Lib/zipfile.py
+++ b/Lib/zipfile.py
@@ -9,6 +9,7 @@ import io
import itertools
import os
import posixpath
+import re
import shutil
import stat
import struct
@@ -2212,7 +2213,7 @@ def _parents(path):
def _ancestry(path):
"""
Given a path with elements separated by
- posixpath.sep, generate all elements of that path
+ posixpath.sep, generate all elements of that path.
>>> list(_ancestry('b/d'))
['b/d', 'b']
@@ -2224,9 +2225,14 @@ def _ancestry(path):
['b']
>>> list(_ancestry(''))
[]
+
+ Multiple separators are treated like a single.
+
+ >>> list(_ancestry('//b//d///f//'))
+ ['//b//d///f', '//b//d', '//b']
"""
path = path.rstrip(posixpath.sep)
- while path and path != posixpath.sep:
+ while path.rstrip(posixpath.sep):
yield path
path, tail = posixpath.split(path)
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2024-08-11-14-08-04.gh-issue-122905.7tDsxA.rst
@@ -0,0 +1 @@
+:class:`zipfile.Path` objects now sanitize names from the zipfile.
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2024-08-26-13-45-20.gh-issue-123270.gXHvNJ.rst
@@ -0,0 +1,3 @@
+Applied a more surgical fix for malformed payloads in :class:`zipfile.Path`
+causing infinite loops (gh-122905) without breaking contents using
+legitimate characters.

View File

@ -0,0 +1,37 @@
From ac2b8869724d7a57d9b5efbdce2f20423214e8bb Mon Sep 17 00:00:00 2001
From: "Bernhard M. Wiedemann" <bwiedemann@suse.de>
Date: Tue, 16 Jul 2024 21:39:33 +0200
Subject: [PATCH] Allow to override build date with SOURCE_DATE_EPOCH
to make builds reproducible.
See https://reproducible-builds.org/ for why this is good
and https://reproducible-builds.org/specs/source-date-epoch/
for the definition of this variable.
---
Doc/conf.py | 3 ++-
Doc/library/functions.rst | 2 +-
2 files changed, 3 insertions(+), 2 deletions(-)
--- a/Doc/conf.py
+++ b/Doc/conf.py
@@ -316,7 +316,8 @@ html_context = {
}
# This 'Last updated on:' timestamp is inserted at the bottom of every page.
-html_last_updated_fmt = time.strftime('%b %d, %Y (%H:%M UTC)', time.gmtime())
+html_time = int(os.environ.get('SOURCE_DATE_EPOCH', time.time()))
+html_last_updated_fmt = time.strftime('%b %d, %Y (%H:%M UTC)', time.gmtime(html_time))
# Path to find HTML templates.
templates_path = ['tools/templates']
--- a/Doc/library/functions.rst
+++ b/Doc/library/functions.rst
@@ -1356,7 +1356,7 @@ are always available. They are listed h
(where :func:`open` is declared), :mod:`os`, :mod:`os.path`, :mod:`tempfile`,
and :mod:`shutil`.
- .. audit-event:: open file,mode,flags open
+ .. audit-event:: open path,mode,flags open
The ``mode`` and ``flags`` arguments may have been modified or inferred from
the original call.

View File

@ -0,0 +1,35 @@
From 1b3f6523a5c83323cdc44031b33a1c062e5dc698 Mon Sep 17 00:00:00 2001
From: Xi Ruoyao <xry111@xry111.site>
Date: Fri, 7 Jun 2024 23:51:32 +0800
Subject: [PATCH] gh-120226: Fix
test_sendfile_close_peer_in_the_middle_of_receiving on Linux >= 6.10
(GH-120227)
The worst case is that the kernel buffers 17 pages with a page size of 64k.
(cherry picked from commit a7584245661102a5768c643fbd7db8395fd3c90e)
Co-authored-by: Xi Ruoyao <xry111@xry111.site>
---
Lib/test/test_asyncio/test_sendfile.py | 11 ++++-------
1 file changed, 4 insertions(+), 7 deletions(-)
--- a/Lib/test/test_asyncio/test_sendfile.py
+++ b/Lib/test/test_asyncio/test_sendfile.py
@@ -93,13 +93,10 @@ class MyProto(asyncio.Protocol):
class SendfileBase:
- # 256 KiB plus small unaligned to buffer chunk
- # Newer versions of Windows seems to have increased its internal
- # buffer and tries to send as much of the data as it can as it
- # has some form of buffering for this which is less than 256KiB
- # on newer server versions and Windows 11.
- # So DATA should be larger than 256 KiB to make this test reliable.
- DATA = b"x" * (1024 * 256 + 1)
+ # Linux >= 6.10 seems buffering up to 17 pages of data.
+ # So DATA should be large enough to make this test reliable even with a
+ # 64 KiB page configuration.
+ DATA = b"x" * (1024 * 17 * 64 + 1)
# Reduce socket buffer size to test on relative small data sets.
BUF_SIZE = 4 * 1024 # 4 KiB

View File

@ -1,3 +1,41 @@
-------------------------------------------------------------------
Thu Oct 3 10:31:41 UTC 2024 - Victor Zhestkov <vzhestkov@suse.com>
- Disabe nis module for all targets
-------------------------------------------------------------------
Tue Sep 3 10:38:48 UTC 2024 - Pablo Suárez Hernández <pablo.suarezhernandez@suse.com>
- Add CVE-2024-7592-Fix-quadratic-complexity-in-parsing-quoted.patch:
* Fix quadratic complexity in parsing -quoted cookie values with
backslashes (bsc#1229873, bsc#1230059, CVE-2024-7592)
- Add gh120226-fix-sendfile-test-kernel-610.patch to avoid
failing test_sendfile_close_peer_in_the_middle_of_receiving
tests on Linux >= 6.10 (GH-120227).
- Add CVE-2024-8088-inf-loop-zipfile_Path.patch to prevent
malformed payload to cause infinite loops in zipfile.Path
(bsc#1229704, bsc#1230058, CVE-2024-8088).
- Add CVE-2024-6923-email-hdr-inject.patch to prevent email
header injection due to unquoted newlines (bsc#1228780,
CVE-2024-6923).
- %{profileopt} variable is set according to the variable
%{do_profiling} (bsc#1227999)
- Adding bso1227999-reproducible-builds.patch fixing bsc#1227999
adding reproducibility patches from gh#python/cpython!121872
and gh#python/cpython!121883.
- Stop using %%defattr, it seems to be breaking proper executable
attributes on /usr/bin/ scripts (bsc#1227378).
- Add CVE-2024-4032-private-IP-addrs.patch to fix bsc#1226448
(CVE-2024-4032) rearranging definition of private v global IP
addresses.
-------------------------------------------------------------------
Thu May 16 07:59:48 UTC 2024 - Victor Zhestkov <vzhestkov@suse.com>
@ -16,7 +54,7 @@ Thu May 16 07:59:48 UTC 2024 - Victor Zhestkov <vzhestkov@suse.com>
* gh-114572: ssl.SSLContext.cert_store_stats() and
ssl.SSLContext.get_ca_certs() now correctly lock access to the
certificate store, when the ssl.SSLContext is shared across
multiple threads.
multiple threads (bsc#1226447, CVE-2024-0397).
* Core and Builtins
* gh-116296: Fix possible refleak in object.__reduce__() internal
error handling.

View File

@ -69,6 +69,11 @@
%else
%define tarversion %{version}
%endif
%if 0%{?do_profiling}
%bcond_without profileopt
%else
%bcond_with profileopt
%endif
%define python_pkg_name saltbundlepy
%define python_libs_pkg_name saltbundlepy-libs
# Will provide the python3-* provides
@ -195,6 +200,23 @@ Patch15: bsc1221260-test_asyncio-ResourceWarning.patch
# by SUSE
Patch16: CVE-2023-52425-libexpat-2.6.0-backport.patch
Patch17: CVE-2023-52425-remove-reparse_deferral-tests.patch
# PATCH-FIX-UPSTREAM CVE-2024-4032-private-IP-addrs.patch bsc#1226448 mcepl@suse.com
# rearrange definition of private v global IP addresses
Patch18: CVE-2024-4032-private-IP-addrs.patch
# PATCH-FIX-UPSTREAM bso1227999-reproducible-builds.patch bsc#1227999 mcepl@suse.com
# reproducibility patches
Patch19: bso1227999-reproducible-builds.patch
# PATCH-FIX-UPSTREAM CVE-2024-6923-email-hdr-inject.patch bsc#1228780 mcepl@suse.com
# prevent email header injection, patch from gh#python/cpython!122608
Patch20: CVE-2024-6923-email-hdr-inject.patch
# PATCH-FIX-UPSTREAM CVE-2024-8088-inf-loop-zipfile_Path.patch bsc#1229704 mcepl@suse.com
# avoid denial of service in zipfile
Patch21: CVE-2024-8088-inf-loop-zipfile_Path.patch
# PATCH-FIX-UPSTREAM gh120226-fix-sendfile-test-kernel-610.patch gh#python/cpython#120226 mcepl@suse.com
# Fix test_sendfile_close_peer_in_the_middle_of_receiving on Linux >= 6.10 (GH-120227)
Patch22: gh120226-fix-sendfile-test-kernel-610.patch
# PATCH-FIX-UPSTREAM CVE-2024-7592-Fix-quadratic-complexity-in-parsing-quoted.patch gh#python/cpython#123067
Patch23: CVE-2024-7592-Fix-quadratic-complexity-in-parsing-quoted.patch
# Salt Bundle related patches
Patch100: call-startup-script-always.patch
Patch111: no-strict-openssl111-dep.patch
@ -219,10 +241,6 @@ BuildRequires: pkgconfig(uuid)
BuildRequires: pkgconfig(zlib)
#!BuildIgnore: gdk-pixbuf-loader-rsvg
BuildRequires: saltbundlepy-rpm-macros
%if (0%{?suse_version} >= 1500 && 0%{?suse_version} < 1599) || 0%{?rhel} >= 8 || 0%{?fedora_version} >= 33
BuildRequires: pkgconfig(libnsl)
BuildRequires: pkgconfig(libtirpc)
%endif
%if %{with doc}
%if 0%{?suse_version} >= 1550
BuildRequires: %{python_pkg_name}-Sphinx
@ -528,6 +546,12 @@ other applications.
%patch15 -p1
%patch16 -p1
%patch17 -p1
%patch18 -p1
%patch19 -p1
%patch20 -p1
%patch21 -p1
%patch22 -p1
%patch23 -p1
# Salt Bundle related patches
@ -560,6 +584,9 @@ rm Lib/site-packages/README.txt
# Add vendored bluez-devel files
tar xvf %{SOURCE21}
# Explicitly disable nis module build
sed -i 's/DISABLED_MODULE_LIST = \[\]/DISABLED_MODULE_LIST = \["nis"\]/' setup.py
%build
%if %{with doc}
TODAY_DATE=`date -r %{SOURCE0} "+%%B %%d, %%Y"`
@ -835,7 +862,6 @@ for dir in curses dbm sqlite3; do
%endif
find "%{buildroot}/%{sitedir}/$dir"/* -maxdepth 0 -name "test" -o -exec rm -rf {} +
done
rm -fv %{buildroot}%{dynlib nis}
# overwrite the copied binary with a link
ln -sf python%{python_version} %{buildroot}%{_bindir}/python3
@ -920,42 +946,34 @@ rm -fv %{buildroot}%{sitedir}/lib-dynload/_tkinter.*
%if %{with general}
%if %{with tk}
%files -n %{python_pkg_name}-tk
%defattr(644, root, root, 755)
%{sitedir}/tkinter
%exclude %{sitedir}/tkinter/test
%{dynlib _tkinter}
%endif
%files -n %{python_pkg_name}-curses
%defattr(644, root, root, 755)
%{sitedir}/curses
%{dynlib _curses}
%{dynlib _curses_panel}
%files -n %{python_pkg_name}-dbm
%defattr(644, root, root, 755)
%{sitedir}/dbm
%{dynlib _dbm}
%{dynlib _gdbm}
%files -n %{python_pkg_name}
%defattr(644, root, root, 755)
%dir %{sitedir}
%dir %{sitedir}/lib-dynload
%{sitedir}/sqlite3
%exclude %{sitedir}/sqlite3/test
%{dynlib readline}
%{dynlib _sqlite3}
%if 0%{?suse_version} < 1599 && ! 0%{?openEuler} > 0
%{dynlib nis}
%endif
%if !%{with tk}
%exclude %{sitedir}/tkinter
%endif
%if %{with idle}
%files -n %{python_pkg_name}-idle
%defattr(644, root, root, 755)
%{sitedir}/idlelib
%dir %{_sysconfdir}/idle%{python_version}
%config %{_sysconfdir}/idle%{python_version}/*
@ -980,13 +998,11 @@ rm -fv %{buildroot}%{sitedir}/lib-dynload/_tkinter.*
%if %{with doc}
%files -n %{python_pkg_name}-doc
%defattr(644, root, root, 755)
%dir %{_docdir}/python%{python_version}
%doc %{_docdir}/python%{python_version}/Misc
%doc %{_docdir}/python%{python_version}/html
%files -n %{python_pkg_name}-doc-devhelp
%defattr(644, root, root, 755)
%dir %{_datadir}/gtk-doc
%dir %{_datadir}/gtk-doc/html
%doc %{_datadir}/gtk-doc/html/Python%{python_version}
@ -997,11 +1013,9 @@ rm -fv %{buildroot}%{sitedir}/lib-dynload/_tkinter.*
%postun -n %{python_libs_pkg_name} -p /sbin/ldconfig
%files -n %{python_libs_pkg_name}
%defattr(644, root,root, 755)
%{_libdir}/libpython%{python_abi}.so.%{so_major}.%{so_minor}
%files -n %{python_pkg_name}-tools
%defattr(644, root, root, 755)
%{sitedir}/turtledemo
%if %{primary_interpreter}
%{_bindir}/2to3
@ -1010,7 +1024,6 @@ rm -fv %{buildroot}%{sitedir}/lib-dynload/_tkinter.*
%doc %{_docdir}/%{name}/Tools
%files -n %{python_pkg_name}-devel
%defattr(644, root, root, 755)
%{_libdir}/libpython%{python_abi}.so
%{_libdir}/libpython%{python_version}.so
%if %{primary_interpreter}
@ -1019,7 +1032,6 @@ rm -fv %{buildroot}%{sitedir}/lib-dynload/_tkinter.*
%{_libdir}/pkgconfig/*
%{_includedir}/python%{python_abi}
%{sitedir}/config-%{python_abi}-*
%defattr(755, root, root, 755)
%{_bindir}/python%{python_abi}-config
%if %{primary_interpreter}
%{_bindir}/python3-config
@ -1032,7 +1044,6 @@ rm -fv %{buildroot}%{sitedir}/lib-dynload/_tkinter.*
%{_datadir}/gdb/auto-load/%{_libdir}/libpython%{python_abi}.so.%{so_major}.%{so_minor}-gdb.py
%files -n %{python_pkg_name}-testsuite
%defattr(644, root, root, 755)
%{sitedir}/test
%{sitedir}/*/test
%{sitedir}/*/tests
@ -1051,7 +1062,6 @@ rm -fv %{buildroot}%{sitedir}/lib-dynload/_tkinter.*
%endif
%files -n %{python_pkg_name}-base
%defattr(644, root, root, 755)
# docs
%dir %{_docdir}/%{name}
%doc %{_docdir}/%{name}/README.rst