11 Commits

Author SHA256 Message Date
efa8febd12 Accepting request 1197437 from devel:languages:python:Factory
- Add CVE-2024-8088-inf-loop-zipfile_Path.patch to prevent
  malformed payload to cause infinite loops in zipfile.Path
  (bsc#1229704, CVE-2024-8088).

OBS-URL: https://build.opensuse.org/request/show/1197437
OBS-URL: https://build.opensuse.org/package/show/openSUSE:Factory/python310?expand=0&rev=48
2024-08-30 11:29:22 +00:00
59c649b520 Update patch
OBS-URL: https://build.opensuse.org/package/show/devel:languages:python:Factory/python310?expand=0&rev=143
2024-08-29 12:14:10 +00:00
8a73c83002 - Add CVE-2024-8088-inf-loop-zipfile_Path.patch to prevent
malformed payload to cause infinite loops in zipfile.Path
  (bsc#1229704, CVE-2024-8088).

OBS-URL: https://build.opensuse.org/package/show/devel:languages:python:Factory/python310?expand=0&rev=142
2024-08-29 12:04:00 +00:00
db9a09b8c2 Accepting request 1192675 from devel:languages:python:Factory
- Add CVE-2024-6923-email-hdr-inject.patch to prevent email
  header injection due to unquoted newlines (bsc#1228780,
  CVE-2024-6923).
- Adding bso1227999-reproducible-builds.patch fixing bsc#1227999
  adding reproducibility patches from gh#python/cpython!121872
  and gh#python/cpython!121883.
- %{profileopt} variable is set according to the variable
  %{do_profiling} (bsc#1227999)
- Update bluez-devel-vendor.tar.xz

OBS-URL: https://build.opensuse.org/request/show/1192675
OBS-URL: https://build.opensuse.org/package/show/openSUSE:Factory/python310?expand=0&rev=47
2024-08-10 17:06:06 +00:00
1716dfe088 - Adding bso1227999-reproducible-builds.patch fixing bsc#1227999
adding reproducibility patches from gh#python/cpython!121872
  and gh#python/cpython!121883.

OBS-URL: https://build.opensuse.org/package/show/devel:languages:python:Factory/python310?expand=0&rev=140
2024-08-07 20:30:36 +00:00
e761be5380 - 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)
- Update bluez-devel-vendor.tar.xz

OBS-URL: https://build.opensuse.org/package/show/devel:languages:python:Factory/python310?expand=0&rev=139
2024-08-07 15:06:12 +00:00
47f6e77cdd Accepting request 1189131 from devel:languages:python:Factory
- Remove %suse_update_desktop_file macro as it is not useful any
  more.

- Stop using %%defattr, it seems to be breaking proper executable
  attributes on /usr/bin/ scripts (bsc#1227378).

OBS-URL: https://build.opensuse.org/request/show/1189131
OBS-URL: https://build.opensuse.org/package/show/openSUSE:Factory/python310?expand=0&rev=46
2024-07-24 13:33:10 +00:00
8a3ae3ab7b - Remove %suse_update_desktop_file macro as it is not useful any
more.

OBS-URL: https://build.opensuse.org/package/show/devel:languages:python:Factory/python310?expand=0&rev=137
2024-07-22 21:25:49 +00:00
c2077eafb7 - Stop using %%defattr, it seems to be breaking proper executable
attributes on /usr/bin/ scripts (bsc#1227378).

OBS-URL: https://build.opensuse.org/package/show/devel:languages:python:Factory/python310?expand=0&rev=136
2024-07-15 12:15:29 +00:00
3d3eac0a3e Accepting request 1185398 from devel:languages:python:Factory
OBS-URL: https://build.opensuse.org/request/show/1185398
OBS-URL: https://build.opensuse.org/package/show/openSUSE:Factory/python310?expand=0&rev=45
2024-07-05 17:45:12 +00:00
f4d7952bf7 - Update F00251-change-user-install-location.patch to make pip and
modern tools install directly in /usr/local when used by the user.
  bsc#1225660

OBS-URL: https://build.opensuse.org/package/show/devel:languages:python:Factory/python310?expand=0&rev=134
2024-07-04 13:17:05 +00:00
27 changed files with 1974 additions and 1590 deletions

1
.gitattributes vendored
View File

@@ -21,4 +21,3 @@
*.xz filter=lfs diff=lfs merge=lfs -text *.xz filter=lfs diff=lfs merge=lfs -text
*.zip filter=lfs diff=lfs merge=lfs -text *.zip filter=lfs diff=lfs merge=lfs -text
*.zst filter=lfs diff=lfs merge=lfs -text *.zst filter=lfs diff=lfs merge=lfs -text
*.changes merge=merge-changes

4
.gitignore vendored
View File

@@ -1,5 +1 @@
.osc .osc
*.obscpio
_build*
.pbuild
python310-*-build/

View File

@@ -0,0 +1,461 @@
---
Doc/library/email.utils.rst | 19 -
Lib/email/utils.py | 151 +++++++-
Lib/test/test_email/test_email.py | 187 +++++++++-
Misc/NEWS.d/next/Library/2023-10-20-15-28-08.gh-issue-102988.dStNO7.rst | 8
4 files changed, 344 insertions(+), 21 deletions(-)
--- 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)
--- 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
-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
+ if start < len(addr):
+ result.append(addr[start:])
+
+ return ''.join(result)
+
+
+supports_strict_parsing = True
+
+def getaddresses(fieldvalues, *, strict=True):
+ """Return a list of (REALNAME, EMAIL) or ('','') for each fieldvalue.
+
+ When parsing fails for a fieldvalue, a 2-tuple of ('', '') is returned in
+ its place.
+
+ If strict is true, use a strict parser which rejects malformed inputs.
+ """
+
+ # If strict is true, if the resulting list of parsed addresses is greater
+ # than the number of fieldvalues in the input list, a parsing error has
+ # occurred and consequently a list containing a single empty 2-tuple [('',
+ # '')] is returned in its place. This is done to avoid invalid output.
+ #
+ # Malformed input: getaddresses(['alice@example.com <bob@example.com>'])
+ # Invalid output: [('', 'alice@example.com'), ('', 'bob@example.com')]
+ # Safe output: [('', '')]
+
+ if not strict:
+ all = COMMASPACE.join(str(v) for v in fieldvalues)
+ a = _AddressList(all)
+ return a.addresslist
+
+ fieldvalues = [str(v) for v in fieldvalues]
+ fieldvalues = _pre_parse_validation(fieldvalues)
+ addr = COMMASPACE.join(fieldvalues)
+ a = _AddressList(addr)
+ result = _post_parse_validation(a.addresslist)
+
+ # Treat output as invalid if the number of addresses is not equal to the
+ # expected number of addresses.
+ n = 0
+ for v in fieldvalues:
+ # When a comma is used in the Real Name part it is not a deliminator.
+ # So strip those out before counting the commas.
+ v = _strip_quoted_realnames(v)
+ # Expected number of addresses: 1 + number of commas
+ n += 1 + v.count(',')
+ if len(result) != n:
+ return [('', '')]
+
+ return result
+
+
+def _check_parenthesis(addr):
+ # Ignore parenthesis in quoted real names.
+ addr = _strip_quoted_realnames(addr)
+
+ opens = 0
+ for pos, ch in _iter_escaped_chars(addr):
+ if ch == '(':
+ opens += 1
+ elif ch == ')':
+ opens -= 1
+ if opens < 0:
+ return False
+ return (opens == 0)
+
+
+def _pre_parse_validation(email_header_fields):
+ accepted_values = []
+ for v in email_header_fields:
+ if not _check_parenthesis(v):
+ v = "('', '')"
+ accepted_values.append(v)
+
+ return accepted_values
+
+
+def _post_parse_validation(parsed_email_header_tuples):
+ accepted_values = []
+ # The parser would have parsed a correctly formatted domain-literal
+ # The existence of an [ after parsing indicates a parsing failure
+ for v in parsed_email_header_tuples:
+ if '[' in v[1]:
+ v = ('', '')
+ accepted_values.append(v)
+
+ return accepted_values
def _format_timetuple_and_zone(timetuple, zone):
@@ -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]
--- a/Lib/test/test_email/test_email.py
+++ b/Lib/test/test_email/test_email.py
@@ -16,6 +16,7 @@ from unittest.mock import patch
import email
import email.policy
+import email.utils
from email.charset import Charset
from email.generator import Generator, DecodedGenerator, BytesGenerator
@@ -3288,15 +3289,137 @@ Foo
[('Al Person', 'aperson@dom.ain'),
('Bud Person', 'bperson@dom.ain')])
+ 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"""
@@ -3485,6 +3608,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):
--- /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.

View File

@@ -4,11 +4,9 @@
Lib/test/test_xml_etree.py | 7 +++++++ Lib/test/test_xml_etree.py | 7 +++++++
3 files changed, 14 insertions(+) 3 files changed, 14 insertions(+)
Index: Python-3.10.20/Lib/test/test_pyexpat.py --- a/Lib/test/test_pyexpat.py
=================================================================== +++ b/Lib/test/test_pyexpat.py
--- Python-3.10.20.orig/Lib/test/test_pyexpat.py 2026-03-05 19:42:14.505101236 +0100 @@ -766,6 +766,10 @@ class ReparseDeferralTest(unittest.TestC
+++ Python-3.10.20/Lib/test/test_pyexpat.py 2026-03-05 19:42:23.343680667 +0100
@@ -807,6 +807,10 @@
self.assertEqual(started, ['doc']) self.assertEqual(started, ['doc'])
def test_reparse_deferral_disabled(self): def test_reparse_deferral_disabled(self):
@@ -19,11 +17,9 @@ Index: Python-3.10.20/Lib/test/test_pyexpat.py
started = [] started = []
def start_element(name, _): def start_element(name, _):
Index: Python-3.10.20/Lib/test/test_sax.py --- a/Lib/test/test_sax.py
=================================================================== +++ b/Lib/test/test_sax.py
--- Python-3.10.20.orig/Lib/test/test_sax.py 2026-03-05 19:42:14.505101236 +0100 @@ -1240,6 +1240,9 @@ class ExpatReaderTest(XmlTestBase):
+++ Python-3.10.20/Lib/test/test_sax.py 2026-03-05 19:42:23.344649745 +0100
@@ -1240,6 +1240,9 @@
self.assertEqual(result.getvalue(), start + b"<doc></doc>") self.assertEqual(result.getvalue(), start + b"<doc></doc>")
@@ -33,11 +29,9 @@ Index: Python-3.10.20/Lib/test/test_sax.py
def test_flush_reparse_deferral_disabled(self): def test_flush_reparse_deferral_disabled(self):
result = BytesIO() result = BytesIO()
xmlgen = XMLGenerator(result) xmlgen = XMLGenerator(result)
Index: Python-3.10.20/Lib/test/test_xml_etree.py --- a/Lib/test/test_xml_etree.py
=================================================================== +++ b/Lib/test/test_xml_etree.py
--- Python-3.10.20.orig/Lib/test/test_xml_etree.py 2026-03-05 19:42:14.505101236 +0100 @@ -1420,9 +1420,13 @@ class XMLPullParserTest(unittest.TestCas
+++ Python-3.10.20/Lib/test/test_xml_etree.py 2026-03-05 19:42:23.345531779 +0100
@@ -1420,9 +1420,13 @@
self.assert_event_tags(parser, [('end', 'root')]) self.assert_event_tags(parser, [('end', 'root')])
self.assertIsNone(parser.close()) self.assertIsNone(parser.close())
@@ -51,7 +45,7 @@ Index: Python-3.10.20/Lib/test/test_xml_etree.py
def test_simple_xml_chunk_5(self): def test_simple_xml_chunk_5(self):
self.test_simple_xml(chunk_size=5, flush=True) self.test_simple_xml(chunk_size=5, flush=True)
@@ -1647,6 +1651,9 @@ @@ -1648,6 +1652,9 @@ class XMLPullParserTest(unittest.TestCas
self.assert_event_tags(parser, [('end', 'doc')]) self.assert_event_tags(parser, [('end', 'doc')])

View File

@@ -0,0 +1,376 @@
From 0740166e60b8cdae9448220beb28721f0126ee03 Mon Sep 17 00:00:00 2001
From: Petr Viktorin <encukou@gmail.com>
Date: Wed, 24 Apr 2024 14:29:30 +0200
Subject: [PATCH 1/2] 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.10.rst | 9
Lib/ipaddress.py | 99 +++++++---
Lib/test/test_ipaddress.py | 52 +++++
Misc/NEWS.d/next/Library/2024-03-14-01-38-44.gh-issue-113171.VFnObz.rst | 9
5 files changed, 187 insertions(+), 25 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
@@ -188,18 +188,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.10.15
+
+ 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.10.15
+
+ 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.10.rst
+++ b/Doc/whatsnew/3.10.rst
@@ -2348,3 +2348,12 @@ tarfile
:exc:`DeprecationWarning`.
In Python 3.14, the default will switch to ``'data'``.
(Contributed by Petr Viktorin in :pep:`706`.)
+
+Notable changes in 3.10.15
+==========================
+
+ipaddress
+---------
+
+* Fixed ``is_global`` and ``is_private`` behavior in ``IPv4Address``,
+ ``IPv6Address``, ``IPv4Network`` and ``IPv6Network``.
--- a/Lib/ipaddress.py
+++ b/Lib/ipaddress.py
@@ -1323,18 +1323,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
@@ -1538,13 +1561,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'),
@@ -1555,6 +1580,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')
@@ -1996,27 +2026,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
@@ -2257,19 +2302,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
@@ -2263,6 +2263,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)
@@ -2278,6 +2282,40 @@ class IpaddrUnitTest(unittest.TestCase):
self.assertEqual(False, ipaddress.ip_address('128.0.0.0').is_loopback)
self.assertEqual(True, ipaddress.ip_network('0.0.0.0').is_unspecified)
+ def testPrivateNetworks(self):
+ self.assertEqual(True, ipaddress.ip_network("0.0.0.0/0").is_private)
+ self.assertEqual(False, ipaddress.ip_network("1.0.0.0/8").is_private)
+
+ self.assertEqual(True, ipaddress.ip_network("0.0.0.0/8").is_private)
+ self.assertEqual(True, ipaddress.ip_network("10.0.0.0/8").is_private)
+ self.assertEqual(True, ipaddress.ip_network("127.0.0.0/8").is_private)
+ 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)
+ self.assertEqual(True, ipaddress.ip_network("198.18.0.0/15").is_private)
+ self.assertEqual(True, ipaddress.ip_network("198.51.100.0/24").is_private)
+ self.assertEqual(True, ipaddress.ip_network("203.0.113.0/24").is_private)
+ self.assertEqual(True, ipaddress.ip_network("240.0.0.0/4").is_private)
+ self.assertEqual(True, ipaddress.ip_network("255.255.255.255/32").is_private)
+
+ self.assertEqual(False, ipaddress.ip_network("::/0").is_private)
+ self.assertEqual(False, ipaddress.ip_network("::ff/128").is_private)
+
+ self.assertEqual(True, ipaddress.ip_network("::1/128").is_private)
+ 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: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)
+ self.assertEqual(True, ipaddress.ip_network("fe80::/10").is_private)
+
def testReservedIpv6(self):
self.assertEqual(True, ipaddress.ip_network('ffff::').is_multicast)
@@ -2351,6 +2389,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,335 @@
From ff3629b3cedf5e50a7a5f567283778fe5539a11e 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.10] gh-121650: Encode newlines in headers, and verify
headers are sound (GH-122233)
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.
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 | 6
Doc/library/email.policy.rst | 18 ++
Doc/whatsnew/3.10.rst | 12 +
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, 162 insertions(+), 4 deletions(-)
create mode 100644 Misc/NEWS.d/next/Library/2024-07-27-16-10-41.gh-issue-121650.nf6oc9.rst
--- a/Doc/library/email.errors.rst
+++ b/Doc/library/email.errors.rst
@@ -59,6 +59,12 @@ The following exception classes are defi
:class:`~email.mime.image.MIMEImage`).
+.. exception:: HeaderWriteError()
+
+ Raised when an error occurs when the :mod:`~email.generator` outputs
+ headers.
+
+
Here is the list of the defects that the :class:`~email.parser.FeedParser`
can find while parsing messages. Note that the defects are added to the message
where the problem was found, so for example, if a message nested inside a
--- a/Doc/library/email.policy.rst
+++ b/Doc/library/email.policy.rst
@@ -229,6 +229,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.10.15
+
+
The following :class:`Policy` method is intended to be called by code using
the email library to create policy instances with custom settings:
--- a/Doc/whatsnew/3.10.rst
+++ b/Doc/whatsnew/3.10.rst
@@ -2357,3 +2357,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`.)
--- a/Lib/email/_header_value_parser.py
+++ b/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'\"')+'"'
@@ -2778,9 +2780,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
--- a/Lib/email/_policybase.py
+++ b/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.
--- a/Lib/email/errors.py
+++ b/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."""
--- a/Lib/email/generator.py
+++ b/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]')
@@ -223,7 +225,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)
--- a/Lib/test/test_email/test_generator.py
+++ b/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):
--- a/Lib/test/test_email/test_policy.py
+++ b/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.
@@ -277,6 +278,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).
--- /dev/null
+++ b/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,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
@@ -3280,6 +3280,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 StripExtraTests(unittest.TestCase):
# Note: all of the "z" characters are technically invalid, but up
--- 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
@@ -2151,7 +2152,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']
@@ -2163,9 +2164,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

@@ -1,56 +0,0 @@
From d6c6f0880dbc6ffd770b859087f4cd749a1d0dbb Mon Sep 17 00:00:00 2001
From: Seth Michael Larson <seth@python.org>
Date: Tue, 20 Jan 2026 14:45:58 -0600
Subject: [PATCH] [3.10] gh-143925: Reject control characters in data: URL
mediatypes (cherry picked from commit
f25509e78e8be6ea73c811ac2b8c928c28841b9f) (cherry picked from commit
2c9c746077d8119b5bcf5142316992e464594946)
Co-authored-by: Seth Michael Larson <seth@python.org>
---
Lib/test/test_urllib.py | 7 +++++++
Lib/urllib/request.py | 5 +++++
Misc/NEWS.d/next/Security/2026-01-16-11-51-19.gh-issue-143925.mrtcHW.rst | 1 +
3 files changed, 13 insertions(+)
create mode 100644 Misc/NEWS.d/next/Security/2026-01-16-11-51-19.gh-issue-143925.mrtcHW.rst
Index: Python-3.10.20/Lib/test/test_urllib.py
===================================================================
--- Python-3.10.20.orig/Lib/test/test_urllib.py 2026-03-05 19:39:02.061358156 +0100
+++ Python-3.10.20/Lib/test/test_urllib.py 2026-03-05 23:19:43.575732909 +0100
@@ -607,6 +607,13 @@
"https://localhost", cafile="/nonexistent/path", context=context
)
+ def test_invalid_mediatype(self):
+ for c0 in control_characters_c0():
+ self.assertRaises(ValueError,urllib.request.urlopen,
+ f'data:text/html;{c0},data')
+ for c0 in control_characters_c0():
+ self.assertRaises(ValueError,urllib.request.urlopen,
+ f'data:text/html{c0};base64,ZGF0YQ==')
class urlopen_DataTests(unittest.TestCase):
"""Test urlopen() opening a data URL."""
Index: Python-3.10.20/Lib/urllib/request.py
===================================================================
--- Python-3.10.20.orig/Lib/urllib/request.py 2026-03-05 19:39:02.551702670 +0100
+++ Python-3.10.20/Lib/urllib/request.py 2026-03-05 23:19:43.576415166 +0100
@@ -1659,6 +1659,11 @@
raise ValueError(
"Control characters not allowed in data: mediatype")
+ # Disallow control characters within mediatype.
+ if re.search(r"[\x00-\x1F\x7F]", mediatype):
+ raise ValueError(
+ "Control characters not allowed in data: mediatype")
+
# even base64 encoded data URLs might be quoted so unquote in any case:
data = unquote_to_bytes(data)
if mediatype.endswith(";base64"):
Index: Python-3.10.20/Misc/NEWS.d/next/Security/2026-01-16-11-51-19.gh-issue-143925.mrtcHW.rst
===================================================================
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
+++ Python-3.10.20/Misc/NEWS.d/next/Security/2026-01-16-11-51-19.gh-issue-143925.mrtcHW.rst 2026-03-05 23:19:43.576850667 +0100
@@ -0,0 +1 @@
+Reject control characters in ``data:`` URL media types.

View File

@@ -1,56 +0,0 @@
From 7485ee5e2cf81d3e5ad0d9c3be73cecd2ab4eec7 Mon Sep 17 00:00:00 2001
From: Seth Michael Larson <seth@python.org>
Date: Fri, 16 Jan 2026 10:54:09 -0600
Subject: [PATCH 1/2] Add 'test.support' fixture for C0 control characters
---
Lib/imaplib.py | 4 +++-
Lib/test/test_imaplib.py | 6 ++++++
Misc/NEWS.d/next/Security/2026-01-16-11-41-06.gh-issue-143921.AeCOor.rst | 1 +
3 files changed, 10 insertions(+), 1 deletion(-)
Index: Python-3.10.20/Lib/imaplib.py
===================================================================
--- Python-3.10.20.orig/Lib/imaplib.py 2026-03-05 19:38:59.446918283 +0100
+++ Python-3.10.20/Lib/imaplib.py 2026-03-05 23:19:20.515035897 +0100
@@ -132,7 +132,7 @@
# We compile these in _mode_xxx.
_Literal = br'.*{(?P<size>\d+)}$'
_Untagged_status = br'\* (?P<data>\d+) (?P<type>[A-Z-]+)( (?P<data2>.*))?'
-
+_control_chars = re.compile(b'[\x00-\x1F\x7F]')
class IMAP4:
@@ -994,6 +994,8 @@
if arg is None: continue
if isinstance(arg, str):
arg = bytes(arg, self._encoding)
+ if _control_chars.search(arg):
+ raise ValueError("Control characters not allowed in commands")
data = data + b' ' + arg
literal = self.literal
Index: Python-3.10.20/Lib/test/test_imaplib.py
===================================================================
--- Python-3.10.20.orig/Lib/test/test_imaplib.py 2026-03-05 19:39:01.155920382 +0100
+++ Python-3.10.20/Lib/test/test_imaplib.py 2026-03-05 23:19:20.517235411 +0100
@@ -538,6 +538,12 @@
self.assertEqual(data[0], b'Returned to authenticated state. (Success)')
self.assertEqual(client.state, 'AUTH')
+ def test_control_characters(self):
+ client, _ = self._setup(SimpleIMAPHandler)
+ for c0 in support.control_characters_c0():
+ with self.assertRaises(ValueError):
+ client.login(f'user{c0}', 'pass')
+
class NewIMAPTests(NewIMAPTestsMixin, unittest.TestCase):
imap_class = imaplib.IMAP4
Index: Python-3.10.20/Misc/NEWS.d/next/Security/2026-01-16-11-41-06.gh-issue-143921.AeCOor.rst
===================================================================
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
+++ Python-3.10.20/Misc/NEWS.d/next/Security/2026-01-16-11-41-06.gh-issue-143921.AeCOor.rst 2026-03-05 23:19:20.517573599 +0100
@@ -0,0 +1 @@
+Reject control characters in IMAP commands.

View File

@@ -1,56 +0,0 @@
From b6f733b285b1c4f27dacb5c2e1f292c914e8b933 Mon Sep 17 00:00:00 2001
From: Seth Michael Larson <seth@python.org>
Date: Fri, 16 Jan 2026 10:54:09 -0600
Subject: [PATCH 1/2] Add 'test.support' fixture for C0 control characters
---
Lib/poplib.py | 2 ++
Lib/test/test_poplib.py | 8 ++++++++
Misc/NEWS.d/next/Security/2026-01-16-11-43-47.gh-issue-143923.DuytMe.rst | 1 +
3 files changed, 11 insertions(+)
Index: Python-3.10.20/Lib/poplib.py
===================================================================
--- Python-3.10.20.orig/Lib/poplib.py 2026-03-08 23:15:49.317939172 +0100
+++ Python-3.10.20/Lib/poplib.py 2026-03-08 23:16:17.565025049 +0100
@@ -122,6 +122,8 @@
def _putcmd(self, line):
if self._debugging: print('*cmd*', repr(line))
line = bytes(line, self.encoding)
+ if re.search(b'[\x00-\x1F\x7F]', line):
+ raise ValueError('Control characters not allowed in commands')
self._putline(line)
Index: Python-3.10.20/Lib/test/test_poplib.py
===================================================================
--- Python-3.10.20.orig/Lib/test/test_poplib.py 2026-03-08 23:15:49.317939172 +0100
+++ Python-3.10.20/Lib/test/test_poplib.py 2026-03-08 23:16:17.565463185 +0100
@@ -15,6 +15,7 @@
from test.support import hashlib_helper
from test.support import socket_helper
from test.support import threading_helper
+from test.support import control_characters_c0
import warnings
with warnings.catch_warnings():
@@ -365,6 +366,13 @@
self.assertIsNone(self.client.sock)
self.assertIsNone(self.client.file)
+ def test_control_characters(self):
+ for c0 in control_characters_c0():
+ with self.assertRaises(ValueError):
+ self.client.user(f'user{c0}')
+ with self.assertRaises(ValueError):
+ self.client.pass_(f'{c0}pass')
+
@requires_ssl
def test_stls_capa(self):
capa = self.client.capa()
Index: Python-3.10.20/Misc/NEWS.d/next/Security/2026-01-16-11-43-47.gh-issue-143923.DuytMe.rst
===================================================================
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
+++ Python-3.10.20/Misc/NEWS.d/next/Security/2026-01-16-11-43-47.gh-issue-143923.DuytMe.rst 2026-03-08 23:16:17.565764242 +0100
@@ -0,0 +1 @@
+Reject control characters in POP3 commands.

BIN
Python-3.10.14.tar.xz LFS Normal file

Binary file not shown.

16
Python-3.10.14.tar.xz.asc Normal file
View File

@@ -0,0 +1,16 @@
-----BEGIN PGP SIGNATURE-----
iQIzBAABCAAdFiEEz9yiRbEEPPKl+Xhl/+h0BBaL2EcFAmX76AkACgkQ/+h0BBaL
2EeKvhAAuN+7X3iFv8tYwUTbKJT9x9fLsADI5wOn5j8xuDiXQCOMzsqqyB1RSdEd
tbCQXg9XJj1bVHc4DY337vUix9jvFcTqbQqlzUm/peX4buY2bKkZu2quti1iWSJf
IN26jDYO2TobPvGdvNiH2Hceqe1dc5tU7iYEfaLR5ImgO4aGgK6x4DiLdmFqo2bk
ZZWZLkXbwenrSdLVmUZLP5Gg2dsfMkbfFpydau9Zk3RVl6mVYATwzJaY9K5otC0K
7kc+nKPwkTxKAjndbznjsVrWK0Xcr4hrlMHs4Re2Nrdqa2mVd1jAAFO5xETJJtd7
YqL6mQuJ9wQfEEq2QWz1hEi67l8g8VeEgzYQOjZ6pTxwYYt0YDfKBjRtRCWuJ11c
w6Q+pniGcgIHAMkQGjZds88CwAdIiyG7IAIT2ovW+xVxH/JqLPHeRsHMKYx4DPqL
2y23Tchw+gBUvmbwCdObXWL1eq5R3Xz3ikkdX/I6zknmEvgPTi5N59C1IQqh0W/6
8uMrHOdELz9I5Fd+zGTJ8iyh/wrecMiIx+HOsBTYv/FYbMVnQUshUBOiD70geUb5
uSeHyxl/P7VK/0phbxOznU4oDot2fHPmZRK3q+K67J9L16q7pEou1AJAw8E7ed5C
Ywf+y2tdxsuqChQK/OA6uuqW6rXjZPuCoG5Bn6YIEuU769LsHcY=
=1PBR
-----END PGP SIGNATURE-----

Binary file not shown.

File diff suppressed because one or more lines are too long

Binary file not shown.

View File

@@ -12,11 +12,9 @@ for the definition of this variable.
Doc/library/functions.rst | 2 +- Doc/library/functions.rst | 2 +-
2 files changed, 3 insertions(+), 2 deletions(-) 2 files changed, 3 insertions(+), 2 deletions(-)
Index: Python-3.10.20/Doc/conf.py --- a/Doc/conf.py
=================================================================== +++ b/Doc/conf.py
--- Python-3.10.20.orig/Doc/conf.py 2026-03-05 19:42:18.977099103 +0100 @@ -89,7 +89,8 @@ html_short_title = '%s Documentation' %
+++ Python-3.10.20/Doc/conf.py 2026-03-05 19:42:32.395647783 +0100
@@ -89,7 +89,8 @@
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format. # using the given strftime format.
@@ -26,11 +24,9 @@ Index: Python-3.10.20/Doc/conf.py
# Path to find HTML templates. # Path to find HTML templates.
templates_path = ['tools/templates'] templates_path = ['tools/templates']
Index: Python-3.10.20/Doc/library/functions.rst --- a/Doc/library/functions.rst
=================================================================== +++ b/Doc/library/functions.rst
--- Python-3.10.20.orig/Doc/library/functions.rst 2026-03-05 19:42:18.980496911 +0100 @@ -1320,7 +1320,7 @@ are always available. They are listed h
+++ Python-3.10.20/Doc/library/functions.rst 2026-03-05 19:42:32.397607125 +0100
@@ -1320,7 +1320,7 @@
(where :func:`open` is declared), :mod:`os`, :mod:`os.path`, :mod:`tempfile`, (where :func:`open` is declared), :mod:`os`, :mod:`os.path`, :mod:`tempfile`,
and :mod:`shutil`. and :mod:`shutil`.

File diff suppressed because it is too large Load Diff

View File

@@ -3,11 +3,9 @@
Misc/NEWS | 2 +- Misc/NEWS | 2 +-
2 files changed, 1 insertion(+), 4 deletions(-) 2 files changed, 1 insertion(+), 4 deletions(-)
Index: Python-3.10.20/Doc/using/configure.rst --- a/Doc/using/configure.rst
=================================================================== +++ b/Doc/using/configure.rst
--- Python-3.10.20.orig/Doc/using/configure.rst 2026-03-03 01:49:35.000000000 +0100 @@ -42,7 +42,6 @@ General Options
+++ Python-3.10.20/Doc/using/configure.rst 2026-03-05 19:41:57.289556663 +0100
@@ -42,7 +42,6 @@
See :data:`sys.int_info.bits_per_digit <sys.int_info>`. See :data:`sys.int_info.bits_per_digit <sys.int_info>`.
@@ -15,7 +13,7 @@ Index: Python-3.10.20/Doc/using/configure.rst
.. cmdoption:: --with-cxx-main=COMPILER .. cmdoption:: --with-cxx-main=COMPILER
Compile the Python ``main()`` function and link Python executable with C++ Compile the Python ``main()`` function and link Python executable with C++
@@ -473,13 +472,11 @@ @@ -473,13 +472,11 @@ macOS Options
See ``Mac/README.rst``. See ``Mac/README.rst``.
@@ -29,11 +27,9 @@ Index: Python-3.10.20/Doc/using/configure.rst
.. cmdoption:: --enable-framework=INSTALLDIR .. cmdoption:: --enable-framework=INSTALLDIR
Create a Python.framework rather than a traditional Unix install. Optional Create a Python.framework rather than a traditional Unix install. Optional
Index: Python-3.10.20/Misc/NEWS --- a/Misc/NEWS
=================================================================== +++ b/Misc/NEWS
--- Python-3.10.20.orig/Misc/NEWS 2026-03-03 01:49:35.000000000 +0100 @@ -3731,7 +3731,7 @@ C API
+++ Python-3.10.20/Misc/NEWS 2026-03-05 19:41:57.302556681 +0100
@@ -4112,7 +4112,7 @@
----- -----
- bpo-43795: The list in :ref:`stable-abi-list` now shows the public name - bpo-43795: The list in :ref:`stable-abi-list` now shows the public name

View File

@@ -1,37 +0,0 @@
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(-)
Index: Python-3.10.20/Lib/test/test_asyncio/test_sendfile.py
===================================================================
--- Python-3.10.20.orig/Lib/test/test_asyncio/test_sendfile.py 2026-03-05 19:39:00.579333997 +0100
+++ Python-3.10.20/Lib/test/test_asyncio/test_sendfile.py 2026-03-05 19:42:33.925609321 +0100
@@ -93,13 +93,10 @@
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,36 +0,0 @@
From 19b61747df3d62c822285c488753d6fbdf91e3ac Mon Sep 17 00:00:00 2001
From: Daniel Garcia Moreno <daniel.garcia@suse.com>
Date: Tue, 23 Sep 2025 10:20:16 +0200
Subject: [PATCH 1/2] gh-139257: Support docutils >= 0.22
---
Doc/tools/extensions/pyspecific.py | 12 +++++++++++-
1 file changed, 11 insertions(+), 1 deletion(-)
Index: Python-3.10.20/Doc/tools/extensions/pyspecific.py
===================================================================
--- Python-3.10.20.orig/Doc/tools/extensions/pyspecific.py 2026-03-05 19:42:35.193611143 +0100
+++ Python-3.10.20/Doc/tools/extensions/pyspecific.py 2026-03-05 19:42:36.490804092 +0100
@@ -55,11 +55,21 @@
SOURCE_URI = 'https://github.com/python/cpython/tree/3.10/%s'
# monkey-patch reST parser to disable alphabetic and roman enumerated lists
+def _disable_alphabetic_and_roman(text):
+ try:
+ # docutils >= 0.22
+ from docutils.parsers.rst.states import InvalidRomanNumeralError
+ raise InvalidRomanNumeralError(text)
+ except ImportError:
+ # docutils < 0.22
+ return None
+
+
from docutils.parsers.rst.states import Body
Body.enum.converters['loweralpha'] = \
Body.enum.converters['upperalpha'] = \
Body.enum.converters['lowerroman'] = \
- Body.enum.converters['upperroman'] = lambda x: None
+ Body.enum.converters['upperroman'] = _disable_alphabetic_and_roman
# Support for marking up and linking to bugs.python.org issues

View File

@@ -1,16 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<component type="desktop-application"> <!-- Copyright 2017 Zbigniew Jędrzejewski-Szmek -->
<id>org.python.IDLE3</id> <application>
<launchable type="desktop-id">idle3.desktop</launchable> <id type="desktop">idle3.desktop</id>
<name>IDLE3</name> <name>IDLE3</name>
<metadata_licence>CC0</metadata_licence>
<project_license>Python-2.0</project_license>
<summary>Python 3 Integrated Development and Learning Environment</summary> <summary>Python 3 Integrated Development and Learning Environment</summary>
<description> <description>
<p> <p>
IDLE is Pythons Integrated Development and Learning Environment. IDLE is Pythons Integrated Development and Learning Environment.
The GUI is uniform between Windows, Unix, and macOS. The GUI is uniform between Windows, Unix, and Mac OS X.
IDLE provides an easy way to start writing, running, and debugging IDLE provides an easy way to start writing, running, and debugging
Python code. Python code.
</p> </p>
@@ -19,33 +19,17 @@
It provides: It provides:
</p> </p>
<ul> <ul>
<li>a Python shell window (interactive interpreter) with colorizing of code input, output, and error messages,</li> <li>a Python shell window (interactive interpreter) with colorizing of code input, output, and error messages,</li>
<li>a multi-window text editor with multiple undo, Python colorizing, smart indent, call tips, auto completion, and other features,</li> <li>a multi-window text editor with multiple undo, Python colorizing, smart indent, call tips, auto completion, and other features,</li>
<li>search within any window, replace within editor windows, and search through multiple files (grep),</li> <li>search within any window, replace within editor windows, and search through multiple files (grep),</li>
<li>a debugger with persistent breakpoints, stepping, and viewing of global and local namespaces.</li> <li>a debugger with persistent breakpoints, stepping, and viewing of global and local namespaces.</li>
</ul> </ul>
</description> </description>
<developer id="org.python">
<name>Python Software Foundation</name>
</developer>
<url type="homepage">https://docs.python.org/3/library/idle.html</url> <url type="homepage">https://docs.python.org/3/library/idle.html</url>
<screenshots> <screenshots>
<screenshot type="default"> <screenshot type="default">http://in.waw.pl/~zbyszek/fedora/idle3-appdata/idle3-main-window.png</screenshot>
<image>https://in.waw.pl/~zbyszek/fedora/idle3-appdata/idle3-main-window.png</image> <screenshot>http://in.waw.pl/~zbyszek/fedora/idle3-appdata/idle3-class-browser.png</screenshot>
</screenshot> <screenshot>http://in.waw.pl/~zbyszek/fedora/idle3-appdata/idle3-code-viewer.png</screenshot>
<screenshot>
<image>https://in.waw.pl/~zbyszek/fedora/idle3-appdata/idle3-class-browser.png</image>
</screenshot>
<screenshot>
<image>https://in.waw.pl/~zbyszek/fedora/idle3-appdata/idle3-code-viewer.png</image>
</screenshot>
</screenshots> </screenshots>
<project_license>Python-2.0</project_license>
<metadata_license>CC0-1.0</metadata_license>
<update_contact>zbyszek@in.waw.pl</update_contact> <update_contact>zbyszek@in.waw.pl</update_contact>
</component> </application>

View File

@@ -4,11 +4,9 @@ unchanged:
Doc/library/turtle.rst | 82 ------------------------------------------------- Doc/library/turtle.rst | 82 -------------------------------------------------
1 file changed, 82 deletions(-) 1 file changed, 82 deletions(-)
Index: Python-3.10.19/Doc/library/turtle.rst --- a/Doc/library/turtle.rst
=================================================================== +++ b/Doc/library/turtle.rst
--- Python-3.10.19.orig/Doc/library/turtle.rst 2025-10-09 17:25:03.000000000 +0200 @@ -250,7 +250,6 @@ Turtle motion
+++ Python-3.10.19/Doc/library/turtle.rst 2025-12-19 23:10:03.998503888 +0100
@@ -250,7 +250,6 @@
turtle is headed. turtle is headed.
.. doctest:: .. doctest::
@@ -16,7 +14,7 @@ Index: Python-3.10.19/Doc/library/turtle.rst
>>> turtle.position() >>> turtle.position()
(0.00,0.00) (0.00,0.00)
@@ -277,7 +276,6 @@ @@ -277,7 +276,6 @@ Turtle motion
>>> turtle.goto(0, 0) >>> turtle.goto(0, 0)
.. doctest:: .. doctest::
@@ -24,7 +22,7 @@ Index: Python-3.10.19/Doc/library/turtle.rst
>>> turtle.position() >>> turtle.position()
(0.00,0.00) (0.00,0.00)
@@ -296,13 +294,11 @@ @@ -296,13 +294,11 @@ Turtle motion
orientation depends on the turtle mode, see :func:`mode`. orientation depends on the turtle mode, see :func:`mode`.
.. doctest:: .. doctest::
@@ -38,7 +36,7 @@ Index: Python-3.10.19/Doc/library/turtle.rst
>>> turtle.heading() >>> turtle.heading()
22.0 22.0
@@ -321,13 +317,11 @@ @@ -321,13 +317,11 @@ Turtle motion
orientation depends on the turtle mode, see :func:`mode`. orientation depends on the turtle mode, see :func:`mode`.
.. doctest:: .. doctest::
@@ -52,7 +50,7 @@ Index: Python-3.10.19/Doc/library/turtle.rst
>>> turtle.heading() >>> turtle.heading()
22.0 22.0
@@ -350,13 +344,11 @@ @@ -350,13 +344,11 @@ Turtle motion
not change the turtle's orientation. not change the turtle's orientation.
.. doctest:: .. doctest::
@@ -66,7 +64,7 @@ Index: Python-3.10.19/Doc/library/turtle.rst
>>> tp = turtle.pos() >>> tp = turtle.pos()
>>> tp >>> tp
@@ -380,13 +372,11 @@ @@ -380,13 +372,11 @@ Turtle motion
unchanged. unchanged.
.. doctest:: .. doctest::
@@ -80,7 +78,7 @@ Index: Python-3.10.19/Doc/library/turtle.rst
>>> turtle.position() >>> turtle.position()
(0.00,240.00) (0.00,240.00)
@@ -402,13 +392,11 @@ @@ -402,13 +392,11 @@ Turtle motion
Set the turtle's second coordinate to *y*, leave first coordinate unchanged. Set the turtle's second coordinate to *y*, leave first coordinate unchanged.
.. doctest:: .. doctest::
@@ -94,7 +92,7 @@ Index: Python-3.10.19/Doc/library/turtle.rst
>>> turtle.position() >>> turtle.position()
(0.00,40.00) (0.00,40.00)
@@ -435,7 +423,6 @@ @@ -435,7 +423,6 @@ Turtle motion
=================== ==================== =================== ====================
.. doctest:: .. doctest::
@@ -102,7 +100,7 @@ Index: Python-3.10.19/Doc/library/turtle.rst
>>> turtle.setheading(90) >>> turtle.setheading(90)
>>> turtle.heading() >>> turtle.heading()
@@ -448,14 +435,12 @@ @@ -448,14 +435,12 @@ Turtle motion
its start-orientation (which depends on the mode, see :func:`mode`). its start-orientation (which depends on the mode, see :func:`mode`).
.. doctest:: .. doctest::
@@ -117,7 +115,7 @@ Index: Python-3.10.19/Doc/library/turtle.rst
>>> turtle.heading() >>> turtle.heading()
90.0 90.0
@@ -487,7 +472,6 @@ @@ -487,7 +472,6 @@ Turtle motion
calculated automatically. May be used to draw regular polygons. calculated automatically. May be used to draw regular polygons.
.. doctest:: .. doctest::
@@ -125,7 +123,7 @@ Index: Python-3.10.19/Doc/library/turtle.rst
>>> turtle.home() >>> turtle.home()
>>> turtle.position() >>> turtle.position()
@@ -516,7 +500,6 @@ @@ -516,7 +500,6 @@ Turtle motion
.. doctest:: .. doctest::
@@ -133,7 +131,7 @@ Index: Python-3.10.19/Doc/library/turtle.rst
>>> turtle.home() >>> turtle.home()
>>> turtle.dot() >>> turtle.dot()
@@ -534,7 +517,6 @@ @@ -534,7 +517,6 @@ Turtle motion
it by calling ``clearstamp(stamp_id)``. it by calling ``clearstamp(stamp_id)``.
.. doctest:: .. doctest::
@@ -141,7 +139,7 @@ Index: Python-3.10.19/Doc/library/turtle.rst
>>> turtle.color("blue") >>> turtle.color("blue")
>>> turtle.stamp() >>> turtle.stamp()
@@ -550,7 +532,6 @@ @@ -550,7 +532,6 @@ Turtle motion
Delete stamp with given *stampid*. Delete stamp with given *stampid*.
.. doctest:: .. doctest::
@@ -149,7 +147,7 @@ Index: Python-3.10.19/Doc/library/turtle.rst
>>> turtle.position() >>> turtle.position()
(150.00,-0.00) (150.00,-0.00)
@@ -595,7 +576,6 @@ @@ -595,7 +576,6 @@ Turtle motion
undo actions is determined by the size of the undobuffer. undo actions is determined by the size of the undobuffer.
.. doctest:: .. doctest::
@@ -157,7 +155,7 @@ Index: Python-3.10.19/Doc/library/turtle.rst
>>> for i in range(4): >>> for i in range(4):
... turtle.fd(50); turtle.lt(80) ... turtle.fd(50); turtle.lt(80)
@@ -628,7 +608,6 @@ @@ -628,7 +608,6 @@ Turtle motion
turtle turn instantly. turtle turn instantly.
.. doctest:: .. doctest::
@@ -165,7 +163,7 @@ Index: Python-3.10.19/Doc/library/turtle.rst
>>> turtle.speed() >>> turtle.speed()
3 3
@@ -649,7 +628,6 @@ @@ -649,7 +628,6 @@ Tell Turtle's state
Return the turtle's current location (x,y) (as a :class:`Vec2D` vector). Return the turtle's current location (x,y) (as a :class:`Vec2D` vector).
.. doctest:: .. doctest::
@@ -173,7 +171,7 @@ Index: Python-3.10.19/Doc/library/turtle.rst
>>> turtle.pos() >>> turtle.pos()
(440.00,-0.00) (440.00,-0.00)
@@ -665,7 +643,6 @@ @@ -665,7 +643,6 @@ Tell Turtle's state
orientation which depends on the mode - "standard"/"world" or "logo". orientation which depends on the mode - "standard"/"world" or "logo".
.. doctest:: .. doctest::
@@ -181,7 +179,7 @@ Index: Python-3.10.19/Doc/library/turtle.rst
>>> turtle.goto(10, 10) >>> turtle.goto(10, 10)
>>> turtle.towards(0,0) >>> turtle.towards(0,0)
@@ -677,7 +654,6 @@ @@ -677,7 +654,6 @@ Tell Turtle's state
Return the turtle's x coordinate. Return the turtle's x coordinate.
.. doctest:: .. doctest::
@@ -189,7 +187,7 @@ Index: Python-3.10.19/Doc/library/turtle.rst
>>> turtle.home() >>> turtle.home()
>>> turtle.left(50) >>> turtle.left(50)
@@ -693,7 +669,6 @@ @@ -693,7 +669,6 @@ Tell Turtle's state
Return the turtle's y coordinate. Return the turtle's y coordinate.
.. doctest:: .. doctest::
@@ -197,7 +195,7 @@ Index: Python-3.10.19/Doc/library/turtle.rst
>>> turtle.home() >>> turtle.home()
>>> turtle.left(60) >>> turtle.left(60)
@@ -710,7 +685,6 @@ @@ -710,7 +685,6 @@ Tell Turtle's state
:func:`mode`). :func:`mode`).
.. doctest:: .. doctest::
@@ -205,7 +203,7 @@ Index: Python-3.10.19/Doc/library/turtle.rst
>>> turtle.home() >>> turtle.home()
>>> turtle.left(67) >>> turtle.left(67)
@@ -727,7 +701,6 @@ @@ -727,7 +701,6 @@ Tell Turtle's state
other turtle, in turtle step units. other turtle, in turtle step units.
.. doctest:: .. doctest::
@@ -213,7 +211,7 @@ Index: Python-3.10.19/Doc/library/turtle.rst
>>> turtle.home() >>> turtle.home()
>>> turtle.distance(30,40) >>> turtle.distance(30,40)
@@ -751,7 +724,6 @@ @@ -751,7 +724,6 @@ Settings for measurement
Default value is 360 degrees. Default value is 360 degrees.
.. doctest:: .. doctest::
@@ -221,7 +219,7 @@ Index: Python-3.10.19/Doc/library/turtle.rst
>>> turtle.home() >>> turtle.home()
>>> turtle.left(90) >>> turtle.left(90)
@@ -774,7 +746,6 @@ @@ -774,7 +746,6 @@ Settings for measurement
``degrees(2*math.pi)``. ``degrees(2*math.pi)``.
.. doctest:: .. doctest::
@@ -229,7 +227,7 @@ Index: Python-3.10.19/Doc/library/turtle.rst
>>> turtle.home() >>> turtle.home()
>>> turtle.left(90) >>> turtle.left(90)
@@ -785,7 +756,6 @@ @@ -785,7 +756,6 @@ Settings for measurement
1.5707963267948966 1.5707963267948966
.. doctest:: .. doctest::
@@ -237,7 +235,7 @@ Index: Python-3.10.19/Doc/library/turtle.rst
:hide: :hide:
>>> turtle.degrees(360) >>> turtle.degrees(360)
@@ -821,7 +791,6 @@ @@ -821,7 +791,6 @@ Drawing state
thickness. If no argument is given, the current pensize is returned. thickness. If no argument is given, the current pensize is returned.
.. doctest:: .. doctest::
@@ -245,7 +243,7 @@ Index: Python-3.10.19/Doc/library/turtle.rst
>>> turtle.pensize() >>> turtle.pensize()
1 1
@@ -853,7 +822,6 @@ @@ -853,7 +822,6 @@ Drawing state
attributes in one statement. attributes in one statement.
.. doctest:: .. doctest::
@@ -253,7 +251,7 @@ Index: Python-3.10.19/Doc/library/turtle.rst
:options: +NORMALIZE_WHITESPACE :options: +NORMALIZE_WHITESPACE
>>> turtle.pen(fillcolor="black", pencolor="red", pensize=10) >>> turtle.pen(fillcolor="black", pencolor="red", pensize=10)
@@ -876,7 +844,6 @@ @@ -876,7 +844,6 @@ Drawing state
Return ``True`` if pen is down, ``False`` if it's up. Return ``True`` if pen is down, ``False`` if it's up.
.. doctest:: .. doctest::
@@ -261,7 +259,7 @@ Index: Python-3.10.19/Doc/library/turtle.rst
>>> turtle.penup() >>> turtle.penup()
>>> turtle.isdown() >>> turtle.isdown()
@@ -917,7 +884,6 @@ @@ -917,7 +884,6 @@ Color control
newly set pencolor. newly set pencolor.
.. doctest:: .. doctest::
@@ -269,7 +267,7 @@ Index: Python-3.10.19/Doc/library/turtle.rst
>>> colormode() >>> colormode()
1.0 1.0
@@ -966,7 +932,6 @@ @@ -966,7 +932,6 @@ Color control
with the newly set fillcolor. with the newly set fillcolor.
.. doctest:: .. doctest::
@@ -277,7 +275,7 @@ Index: Python-3.10.19/Doc/library/turtle.rst
>>> turtle.fillcolor("violet") >>> turtle.fillcolor("violet")
>>> turtle.fillcolor() >>> turtle.fillcolor()
@@ -1005,7 +970,6 @@ @@ -1005,7 +970,6 @@ Color control
with the newly set colors. with the newly set colors.
.. doctest:: .. doctest::
@@ -285,7 +283,7 @@ Index: Python-3.10.19/Doc/library/turtle.rst
>>> turtle.color("red", "green") >>> turtle.color("red", "green")
>>> turtle.color() >>> turtle.color()
@@ -1022,7 +986,6 @@ @@ -1022,7 +986,6 @@ Filling
~~~~~~~ ~~~~~~~
.. doctest:: .. doctest::
@@ -293,7 +291,7 @@ Index: Python-3.10.19/Doc/library/turtle.rst
:hide: :hide:
>>> turtle.home() >>> turtle.home()
@@ -1032,7 +995,6 @@ @@ -1032,7 +995,6 @@ Filling
Return fillstate (``True`` if filling, ``False`` else). Return fillstate (``True`` if filling, ``False`` else).
.. doctest:: .. doctest::
@@ -301,7 +299,7 @@ Index: Python-3.10.19/Doc/library/turtle.rst
>>> turtle.begin_fill() >>> turtle.begin_fill()
>>> if turtle.filling(): >>> if turtle.filling():
@@ -1057,7 +1019,6 @@ @@ -1057,7 +1019,6 @@ Filling
above may be either all yellow or have some white regions. above may be either all yellow or have some white regions.
.. doctest:: .. doctest::
@@ -309,7 +307,7 @@ Index: Python-3.10.19/Doc/library/turtle.rst
>>> turtle.color("black", "red") >>> turtle.color("black", "red")
>>> turtle.begin_fill() >>> turtle.begin_fill()
@@ -1074,7 +1035,6 @@ @@ -1074,7 +1035,6 @@ More drawing control
variables to the default values. variables to the default values.
.. doctest:: .. doctest::
@@ -317,7 +315,7 @@ Index: Python-3.10.19/Doc/library/turtle.rst
>>> turtle.goto(0,-22) >>> turtle.goto(0,-22)
>>> turtle.left(100) >>> turtle.left(100)
@@ -1125,7 +1085,6 @@ @@ -1125,7 +1085,6 @@ Visibility
drawing observably. drawing observably.
.. doctest:: .. doctest::
@@ -325,7 +323,7 @@ Index: Python-3.10.19/Doc/library/turtle.rst
>>> turtle.hideturtle() >>> turtle.hideturtle()
@@ -1136,7 +1095,6 @@ @@ -1136,7 +1095,6 @@ Visibility
Make the turtle visible. Make the turtle visible.
.. doctest:: .. doctest::
@@ -333,7 +331,7 @@ Index: Python-3.10.19/Doc/library/turtle.rst
>>> turtle.showturtle() >>> turtle.showturtle()
@@ -1167,7 +1125,6 @@ @@ -1167,7 +1125,6 @@ Appearance
deal with shapes see Screen method :func:`register_shape`. deal with shapes see Screen method :func:`register_shape`.
.. doctest:: .. doctest::
@@ -341,7 +339,7 @@ Index: Python-3.10.19/Doc/library/turtle.rst
>>> turtle.shape() >>> turtle.shape()
'classic' 'classic'
@@ -1193,7 +1150,6 @@ @@ -1193,7 +1150,6 @@ Appearance
``resizemode("user")`` is called by :func:`shapesize` when used with arguments. ``resizemode("user")`` is called by :func:`shapesize` when used with arguments.
.. doctest:: .. doctest::
@@ -349,15 +347,15 @@ Index: Python-3.10.19/Doc/library/turtle.rst
>>> turtle.resizemode() >>> turtle.resizemode()
'noresize' 'noresize'
@@ -1217,7 +1173,6 @@ @@ -1217,7 +1173,6 @@ Appearance
of the shape's outline. of the shapes's outline.
.. doctest:: .. doctest::
- :skipif: _tkinter is None - :skipif: _tkinter is None
>>> turtle.shapesize() >>> turtle.shapesize()
(1.0, 1.0, 1) (1.0, 1.0, 1)
@@ -1242,7 +1197,6 @@ @@ -1242,7 +1197,6 @@ Appearance
heading of the turtle are sheared. heading of the turtle are sheared.
.. doctest:: .. doctest::
@@ -365,7 +363,7 @@ Index: Python-3.10.19/Doc/library/turtle.rst
>>> turtle.shape("circle") >>> turtle.shape("circle")
>>> turtle.shapesize(5,2) >>> turtle.shapesize(5,2)
@@ -1259,7 +1213,6 @@ @@ -1259,7 +1213,6 @@ Appearance
change the turtle's heading (direction of movement). change the turtle's heading (direction of movement).
.. doctest:: .. doctest::
@@ -373,7 +371,7 @@ Index: Python-3.10.19/Doc/library/turtle.rst
>>> turtle.reset() >>> turtle.reset()
>>> turtle.shape("circle") >>> turtle.shape("circle")
@@ -1279,7 +1232,6 @@ @@ -1279,7 +1232,6 @@ Appearance
(direction of movement). (direction of movement).
.. doctest:: .. doctest::
@@ -381,7 +379,7 @@ Index: Python-3.10.19/Doc/library/turtle.rst
>>> turtle.reset() >>> turtle.reset()
>>> turtle.shape("circle") >>> turtle.shape("circle")
@@ -1305,7 +1257,6 @@ @@ -1305,7 +1257,6 @@ Appearance
turtle (its direction of movement). turtle (its direction of movement).
.. doctest:: .. doctest::
@@ -389,7 +387,7 @@ Index: Python-3.10.19/Doc/library/turtle.rst
>>> turtle.reset() >>> turtle.reset()
>>> turtle.shape("circle") >>> turtle.shape("circle")
@@ -1334,7 +1285,6 @@ @@ -1334,7 +1285,6 @@ Appearance
given matrix. given matrix.
.. doctest:: .. doctest::
@@ -397,7 +395,7 @@ Index: Python-3.10.19/Doc/library/turtle.rst
>>> turtle = Turtle() >>> turtle = Turtle()
>>> turtle.shape("square") >>> turtle.shape("square")
@@ -1350,7 +1300,6 @@ @@ -1350,7 +1300,6 @@ Appearance
can be used to define a new shape or components of a compound shape. can be used to define a new shape or components of a compound shape.
.. doctest:: .. doctest::
@@ -405,7 +403,7 @@ Index: Python-3.10.19/Doc/library/turtle.rst
>>> turtle.shape("square") >>> turtle.shape("square")
>>> turtle.shapetransform(4, -1, 0, 2) >>> turtle.shapetransform(4, -1, 0, 2)
@@ -1375,7 +1324,6 @@ @@ -1375,7 +1324,6 @@ Using events
procedural way: procedural way:
.. doctest:: .. doctest::
@@ -413,7 +411,7 @@ Index: Python-3.10.19/Doc/library/turtle.rst
>>> def turn(x, y): >>> def turn(x, y):
... left(180) ... left(180)
@@ -1396,7 +1344,6 @@ @@ -1396,7 +1344,6 @@ Using events
``None``, existing bindings are removed. ``None``, existing bindings are removed.
.. doctest:: .. doctest::
@@ -421,7 +419,7 @@ Index: Python-3.10.19/Doc/library/turtle.rst
>>> class MyTurtle(Turtle): >>> class MyTurtle(Turtle):
... def glow(self,x,y): ... def glow(self,x,y):
@@ -1424,7 +1371,6 @@ @@ -1424,7 +1371,6 @@ Using events
mouse-click event on that turtle. mouse-click event on that turtle.
.. doctest:: .. doctest::
@@ -429,7 +427,7 @@ Index: Python-3.10.19/Doc/library/turtle.rst
>>> turtle.ondrag(turtle.goto) >>> turtle.ondrag(turtle.goto)
@@ -1452,7 +1398,6 @@ @@ -1452,7 +1398,6 @@ Special Turtle methods
Return the last recorded polygon. Return the last recorded polygon.
.. doctest:: .. doctest::
@@ -437,7 +435,7 @@ Index: Python-3.10.19/Doc/library/turtle.rst
>>> turtle.home() >>> turtle.home()
>>> turtle.begin_poly() >>> turtle.begin_poly()
@@ -1472,7 +1417,6 @@ @@ -1472,7 +1417,6 @@ Special Turtle methods
turtle properties. turtle properties.
.. doctest:: .. doctest::
@@ -445,7 +443,7 @@ Index: Python-3.10.19/Doc/library/turtle.rst
>>> mick = Turtle() >>> mick = Turtle()
>>> joe = mick.clone() >>> joe = mick.clone()
@@ -1485,7 +1429,6 @@ @@ -1485,7 +1429,6 @@ Special Turtle methods
return the "anonymous turtle": return the "anonymous turtle":
.. doctest:: .. doctest::
@@ -453,7 +451,7 @@ Index: Python-3.10.19/Doc/library/turtle.rst
>>> pet = getturtle() >>> pet = getturtle()
>>> pet.fd(50) >>> pet.fd(50)
@@ -1499,7 +1442,6 @@ @@ -1499,7 +1442,6 @@ Special Turtle methods
TurtleScreen methods can then be called for that object. TurtleScreen methods can then be called for that object.
.. doctest:: .. doctest::
@@ -461,7 +459,7 @@ Index: Python-3.10.19/Doc/library/turtle.rst
>>> ts = turtle.getscreen() >>> ts = turtle.getscreen()
>>> ts >>> ts
@@ -1517,7 +1459,6 @@ @@ -1517,7 +1459,6 @@ Special Turtle methods
``None``, the undobuffer is disabled. ``None``, the undobuffer is disabled.
.. doctest:: .. doctest::
@@ -469,7 +467,7 @@ Index: Python-3.10.19/Doc/library/turtle.rst
>>> turtle.setundobuffer(42) >>> turtle.setundobuffer(42)
@@ -1527,7 +1468,6 @@ @@ -1527,7 +1468,6 @@ Special Turtle methods
Return number of entries in the undobuffer. Return number of entries in the undobuffer.
.. doctest:: .. doctest::
@@ -477,7 +475,7 @@ Index: Python-3.10.19/Doc/library/turtle.rst
>>> while undobufferentries(): >>> while undobufferentries():
... undo() ... undo()
@@ -1550,7 +1490,6 @@ @@ -1550,7 +1490,6 @@ below:
For example: For example:
.. doctest:: .. doctest::
@@ -485,7 +483,7 @@ Index: Python-3.10.19/Doc/library/turtle.rst
>>> s = Shape("compound") >>> s = Shape("compound")
>>> poly1 = ((0,0),(10,-5),(0,10),(-10,-5)) >>> poly1 = ((0,0),(10,-5),(0,10),(-10,-5))
@@ -1561,7 +1500,6 @@ @@ -1561,7 +1500,6 @@ below:
3. Now add the Shape to the Screen's shapelist and use it: 3. Now add the Shape to the Screen's shapelist and use it:
.. doctest:: .. doctest::
@@ -493,7 +491,7 @@ Index: Python-3.10.19/Doc/library/turtle.rst
>>> register_shape("myshape", s) >>> register_shape("myshape", s)
>>> shape("myshape") >>> shape("myshape")
@@ -1581,7 +1519,6 @@ @@ -1581,7 +1519,6 @@ Most of the examples in this section ref
``screen``. ``screen``.
.. doctest:: .. doctest::
@@ -501,7 +499,7 @@ Index: Python-3.10.19/Doc/library/turtle.rst
:hide: :hide:
>>> screen = Screen() >>> screen = Screen()
@@ -1598,7 +1535,6 @@ @@ -1598,7 +1535,6 @@ Window control
Set or return background color of the TurtleScreen. Set or return background color of the TurtleScreen.
.. doctest:: .. doctest::
@@ -509,7 +507,7 @@ Index: Python-3.10.19/Doc/library/turtle.rst
>>> screen.bgcolor("orange") >>> screen.bgcolor("orange")
>>> screen.bgcolor() >>> screen.bgcolor()
@@ -1690,7 +1626,6 @@ @@ -1690,7 +1626,6 @@ Window control
distorted. distorted.
.. doctest:: .. doctest::
@@ -517,7 +515,7 @@ Index: Python-3.10.19/Doc/library/turtle.rst
>>> screen.reset() >>> screen.reset()
>>> screen.setworldcoordinates(-50,-7.5,50,7.5) >>> screen.setworldcoordinates(-50,-7.5,50,7.5)
@@ -1701,7 +1636,6 @@ @@ -1701,7 +1636,6 @@ Window control
... left(45); fd(2) # a regular octagon ... left(45); fd(2) # a regular octagon
.. doctest:: .. doctest::
@@ -525,7 +523,7 @@ Index: Python-3.10.19/Doc/library/turtle.rst
:hide: :hide:
>>> screen.reset() >>> screen.reset()
@@ -1723,7 +1657,6 @@ @@ -1723,7 +1657,6 @@ Animation control
Optional argument: Optional argument:
.. doctest:: .. doctest::
@@ -533,7 +531,7 @@ Index: Python-3.10.19/Doc/library/turtle.rst
>>> screen.delay() >>> screen.delay()
10 10
@@ -1745,7 +1678,6 @@ @@ -1745,7 +1678,6 @@ Animation control
:func:`delay`). :func:`delay`).
.. doctest:: .. doctest::
@@ -541,7 +539,7 @@ Index: Python-3.10.19/Doc/library/turtle.rst
>>> screen.tracer(8, 25) >>> screen.tracer(8, 25)
>>> dist = 2 >>> dist = 2
@@ -1782,7 +1714,6 @@ @@ -1782,7 +1714,6 @@ Using screen events
must have the focus. (See method :func:`listen`.) must have the focus. (See method :func:`listen`.)
.. doctest:: .. doctest::
@@ -549,7 +547,7 @@ Index: Python-3.10.19/Doc/library/turtle.rst
>>> def f(): >>> def f():
... fd(50) ... fd(50)
@@ -1803,7 +1734,6 @@ @@ -1803,7 +1734,6 @@ Using screen events
must have focus. (See method :func:`listen`.) must have focus. (See method :func:`listen`.)
.. doctest:: .. doctest::
@@ -557,7 +555,7 @@ Index: Python-3.10.19/Doc/library/turtle.rst
>>> def f(): >>> def f():
... fd(50) ... fd(50)
@@ -1828,7 +1758,6 @@ @@ -1828,7 +1758,6 @@ Using screen events
named ``turtle``: named ``turtle``:
.. doctest:: .. doctest::
@@ -565,7 +563,7 @@ Index: Python-3.10.19/Doc/library/turtle.rst
>>> screen.onclick(turtle.goto) # Subsequently clicking into the TurtleScreen will >>> screen.onclick(turtle.goto) # Subsequently clicking into the TurtleScreen will
>>> # make the turtle move to the clicked point. >>> # make the turtle move to the clicked point.
@@ -1848,7 +1777,6 @@ @@ -1848,7 +1777,6 @@ Using screen events
Install a timer that calls *fun* after *t* milliseconds. Install a timer that calls *fun* after *t* milliseconds.
.. doctest:: .. doctest::
@@ -573,7 +571,7 @@ Index: Python-3.10.19/Doc/library/turtle.rst
>>> running = True >>> running = True
>>> def f(): >>> def f():
@@ -1930,7 +1858,6 @@ @@ -1930,7 +1858,6 @@ Settings and special methods
============ ========================= =================== ============ ========================= ===================
.. doctest:: .. doctest::
@@ -581,7 +579,7 @@ Index: Python-3.10.19/Doc/library/turtle.rst
>>> mode("logo") # resets turtle heading to north >>> mode("logo") # resets turtle heading to north
>>> mode() >>> mode()
@@ -1945,7 +1872,6 @@ @@ -1945,7 +1872,6 @@ Settings and special methods
values of color triples have to be in the range 0..\ *cmode*. values of color triples have to be in the range 0..\ *cmode*.
.. doctest:: .. doctest::
@@ -589,7 +587,7 @@ Index: Python-3.10.19/Doc/library/turtle.rst
>>> screen.colormode(1) >>> screen.colormode(1)
>>> turtle.pencolor(240, 160, 80) >>> turtle.pencolor(240, 160, 80)
@@ -1966,7 +1892,6 @@ @@ -1966,7 +1892,6 @@ Settings and special methods
do with a Tkinter Canvas. do with a Tkinter Canvas.
.. doctest:: .. doctest::
@@ -597,7 +595,7 @@ Index: Python-3.10.19/Doc/library/turtle.rst
>>> cv = screen.getcanvas() >>> cv = screen.getcanvas()
>>> cv >>> cv
@@ -1978,7 +1903,6 @@ @@ -1978,7 +1903,6 @@ Settings and special methods
Return a list of names of all currently available turtle shapes. Return a list of names of all currently available turtle shapes.
.. doctest:: .. doctest::
@@ -605,7 +603,7 @@ Index: Python-3.10.19/Doc/library/turtle.rst
>>> screen.getshapes() >>> screen.getshapes()
['arrow', 'blank', 'circle', ..., 'turtle'] ['arrow', 'blank', 'circle', ..., 'turtle']
@@ -2002,7 +1926,6 @@ @@ -2002,7 +1926,6 @@ Settings and special methods
coordinates: Install the corresponding polygon shape. coordinates: Install the corresponding polygon shape.
.. doctest:: .. doctest::
@@ -613,7 +611,7 @@ Index: Python-3.10.19/Doc/library/turtle.rst
>>> screen.register_shape("triangle", ((5,-3), (0,5), (-5,-3))) >>> screen.register_shape("triangle", ((5,-3), (0,5), (-5,-3)))
@@ -2018,7 +1941,6 @@ @@ -2018,7 +1941,6 @@ Settings and special methods
Return the list of turtles on the screen. Return the list of turtles on the screen.
.. doctest:: .. doctest::
@@ -621,7 +619,7 @@ Index: Python-3.10.19/Doc/library/turtle.rst
>>> for turtle in screen.turtles(): >>> for turtle in screen.turtles():
... turtle.color("red") ... turtle.color("red")
@@ -2080,7 +2002,6 @@ @@ -2080,7 +2002,6 @@ Methods specific to Screen, not inherite
center window vertically center window vertically
.. doctest:: .. doctest::
@@ -629,7 +627,7 @@ Index: Python-3.10.19/Doc/library/turtle.rst
>>> screen.setup (width=200, height=200, startx=0, starty=0) >>> screen.setup (width=200, height=200, startx=0, starty=0)
>>> # sets window to 200x200 pixels, in upper left of screen >>> # sets window to 200x200 pixels, in upper left of screen
@@ -2096,7 +2017,6 @@ @@ -2096,7 +2017,6 @@ Methods specific to Screen, not inherite
Set title of turtle window to *titlestring*. Set title of turtle window to *titlestring*.
.. doctest:: .. doctest::
@@ -637,7 +635,7 @@ Index: Python-3.10.19/Doc/library/turtle.rst
>>> screen.title("Welcome to the turtle zoo!") >>> screen.title("Welcome to the turtle zoo!")
@@ -2167,7 +2087,6 @@ @@ -2167,7 +2087,6 @@ Public classes
Example: Example:
.. doctest:: .. doctest::
@@ -645,7 +643,7 @@ Index: Python-3.10.19/Doc/library/turtle.rst
>>> poly = ((0,0),(10,-5),(0,10),(-10,-5)) >>> poly = ((0,0),(10,-5),(0,10),(-10,-5))
>>> s = Shape("compound") >>> s = Shape("compound")
@@ -2518,7 +2437,6 @@ @@ -2514,7 +2433,6 @@ Changes since Python 3.0
.. doctest:: .. doctest::

View File

@@ -0,0 +1,15 @@
---
Lib/test/test_posix.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
--- a/Lib/test/test_posix.py
+++ b/Lib/test/test_posix.py
@@ -425,7 +425,7 @@ class PosixTester(unittest.TestCase):
def test_posix_fadvise(self):
fd = os.open(os_helper.TESTFN, os.O_RDONLY)
try:
- posix.posix_fadvise(fd, 0, 0, posix.POSIX_FADV_WILLNEED)
+ posix.posix_fadvise(fd, 0, 0, posix.POSIX_FADV_RANDOM)
finally:
os.close(fd)

View File

@@ -1,502 +1,3 @@
-------------------------------------------------------------------
Thu Mar 5 18:38:39 UTC 2026 - Matej Cepl <mcepl@cepl.eu>
- Update to 3.10.20:
- Security
- gh-144125: BytesGenerator will now refuse to serialize
(write) headers that are unsafely folded or delimited; see
verify_generated_headers. (Contributed by Bas Bloemsaat and
Petr Viktorin in gh-121650).
- gh-143935: Fixed a bug in the folding of comments when
flattening an email message using a modern email policy.
Comments consisting of a very long sequence of non-foldable
characters could trigger a forced line wrap that omitted
the required leading space on the continuation line,
causing the remainder of the comment to be interpreted as
a new header field. This enabled header injection with
carefully crafted inputs (bsc#1257029 CVE-2025-11468).
- gh-143925: Reject control characters in data: URL media
types.
- gh-143919: Reject control characters in http.cookies.Morsel
fields and values (bsc#1257031, CVE-2026-0672).
- gh-143916: Reject C0 control characters within
wsgiref.headers.Headers fields, values, and parameters
(bsc#1257042, CVE-2026-0865).
- gh-142145: Remove quadratic behavior in xml.minidom node ID
cache clearing. In order to do this without breaking
existing users, we also add the ownerDocument attribute to
xml.dom.minidom elements and attributes created by directly
instantiating the Element or Attr class. Note that this way
of creating nodes is not supported; creator functions like
xml.dom.Document.documentElement() should be used instead
(bsc#1254997, CVE-2025-12084).
- gh-137836: Add support of the “plaintext” element, RAWTEXT
elements “xmp”, “iframe”, “noembed” and “noframes”, and
optionally RAWTEXT element “noscript” in
html.parser.HTMLParser.
- gh-136063: email.message: ensure linear complexity for
legacy HTTP parameters parsing. Patch by Bénédikt Tran.
- gh-136065: Fix quadratic complexity in
os.path.expandvars() (bsc#1252974, CVE-2025-6075).
- gh-119451: Fix a potential memory denial of service in the
http.client module. When connecting to a malicious server,
it could cause an arbitrary amount of memory to be
allocated. This could have led to symptoms including
a MemoryError, swapping, out of memory (OOM) killed
processes or containers, or even system crashes
(CVE-2025-13836, bsc#1254400).
- gh-119452: Fix a potential memory denial of service in the
http.server module. When a malicious user is connected to
the CGI server on Windows, it could cause an arbitrary
amount of memory to be allocated. This could have led to
symptoms including a MemoryError, swapping, out of memory
(OOM) killed processes or containers, or even system
crashes.
- gh-119342: Fix a potential memory denial of service in the
plistlib module. When reading a Plist file received from
untrusted source, it could cause an arbitrary amount of
memory to be allocated. This could have led to symptoms
including a MemoryError, swapping, out of memory (OOM)
killed processes or containers, or even system crashes
(bsc#1254401, CVE-2025-13837).
- Library
- gh-144833: Fixed a use-after-free in ssl when SSL_new()
returns NULL in newPySSLSocket(). The error was reported
via a dangling pointer after the object had already been
freed.
- gh-144363: Update bundled libexpat to 2.7.4
- gh-90949: Add SetAllocTrackerActivationThreshold() and
SetAllocTrackerMaximumAmplification() to xmlparser objects
to prevent use of disproportional amounts of dynamic memory
from within an Expat parser. Patch by Bénédikt Tran.
- Core and Builtins
- gh-120384: Fix an array out of bounds crash in
list_ass_subscript, which could be invoked via some
specificly tailored input: including concurrent
modification of a list object, where one thread assigns
a slice and another clears it.
- gh-120298: Fix use-after free in list_richcompare_impl
which can be invoked via some specificly tailored evil
input.
- Remove upstreamed patches:
- CVE-2025-11468-email-hdr-fold-comment.patch
- CVE-2025-12084-minidom-quad-search.patch
- CVE-2025-13836-http-resp-cont-len.patch
- CVE-2025-13837-plistlib-mailicious-length.patch
- CVE-2025-6075-expandvars-perf-degrad.patch
- CVE-2026-0672-http-hdr-inject-cookie-Morsel.patch
- CVE-2026-0865-wsgiref-ctrl-chars.patch
-------------------------------------------------------------------
Wed Feb 11 23:49:49 CET 2026 - Matej Cepl <mcepl@suse.com>
- CVE-2025-11468: preserving parens when folding comments in
email headers (bsc#1257029, gh#python/cpython#143935).
CVE-2025-11468-email-hdr-fold-comment.patch
- CVE-2026-0672: rejects control characters in http cookies.
(bsc#1257031, gh#python/cpython#143919)
CVE-2026-0672-http-hdr-inject-cookie-Morsel.patch
- CVE-2026-0865: rejecting control characters in
wsgiref.headers.Headers, which could be abused for injecting
false HTTP headers. (bsc#1257042, gh#python/cpython#143916)
CVE-2026-0865-wsgiref-ctrl-chars.patch
- CVE-2025-15366: basically the same as the previous patch for
IMAP protocol. (bsc#1257044, gh#python/cpython#143921)
CVE-2025-15366-imap-ctrl-chars.patch
- CVE-2025-15282: basically the same as the previous patch for
urllib library. (bsc#1257046, gh#python/cpython#143925)
CVE-2025-15282-urllib-ctrl-chars.patch
- CVE-2025-15367: basically the same as the previous patch for
poplib library. (bsc#1257041, gh#python/cpython#143923)
CVE-2025-15367-poplib-ctrl-chars.patch
-------------------------------------------------------------------
Thu Dec 18 10:33:44 UTC 2025 - Matej Cepl <mcepl@cepl.eu>
- Add CVE-2025-13836-http-resp-cont-len.patch (bsc#1254400,
CVE-2025-13836) to prevent reading an HTTP response from
a server, if no read amount is specified, with using
Content-Length per default as the length.
- Add CVE-2025-12084-minidom-quad-search.patch prevent quadratic
behavior in node ID cache clearing (CVE-2025-12084,
bsc#1254997).
- Add CVE-2025-13837-plistlib-mailicious-length.patch protect
against OOM when loading malicious content (CVE-2025-13837,
bsc#1254401).
-------------------------------------------------------------------
Thu Dec 18 10:33:44 UTC 2025 - Matej Cepl <mcepl@cepl.eu>
- Add CVE-2025-13836-http-resp-cont-len.patch (bsc#1254400,
CVE-2025-13836) to prevent reading an HTTP response from
a server, if no read amount is specified, with using
Content-Length per default as the length.
-------------------------------------------------------------------
Thu Nov 13 17:13:03 UTC 2025 - Matej Cepl <mcepl@cepl.eu>
- Add CVE-2025-6075-expandvars-perf-degrad.patch avoid simple
quadratic complexity vulnerabilities of os.path.expandvars()
(CVE-2025-6075, bsc#1252974).
-------------------------------------------------------------------
Wed Oct 15 08:43:33 UTC 2025 - Daniel Garcia <daniel.garcia@suse.com>
- Update to 3.10.19:
- Security
- gh-139700: Check consistency of the zip64 end of central
directory record. Support records with “zip64 extensible data”
if there are no bytes prepended to the ZIP file
(CVE-2025-8291, bsc#1251305).
- gh-139400: xml.parsers.expat: Make sure that parent Expat
parsers are only garbage-collected once they are no longer
referenced by subparsers created by
ExternalEntityParserCreate(). Patch by Sebastian Pipping.
- gh-135661: Fix parsing start and end tags in
html.parser.HTMLParser according to the HTML5 standard.
* Whitespaces no longer accepted between </ and the tag name.
E.g. </ script> does not end the script section.
* Vertical tabulation (\v) and non-ASCII whitespaces no longer
recognized as whitespaces. The only whitespaces are \t\n\r\f
and space.
* Null character (U+0000) no longer ends the tag name.
* Attributes and slashes after the tag name in end tags are now
ignored, instead of terminating after the first > in quoted
attribute value. E.g. </script/foo=">"/>.
* Multiple slashes and whitespaces between the last attribute
and closing > are now ignored in both start and end tags. E.g.
<a foo=bar/ //>.
* Multiple = between attribute name and value are no longer
collapsed. E.g. <a foo==bar> produces attribute “foo” with
value “=bar”.
- gh-135661: Fix CDATA section parsing in html.parser.HTMLParser
according to the HTML5 standard: ] ]> and ]] > no longer end the
CDATA section. Add private method _set_support_cdata() which can
be used to specify how to parse <[CDATA[ — as a CDATA section in
foreign content (SVG or MathML) or as a bogus comment in the
HTML namespace.
- gh-102555: Fix comment parsing in html.parser.HTMLParser
according to the HTML5 standard. --!> now ends the comment. -- >
no longer ends the comment. Support abnormally ended empty
comments <--> and <--->.
- gh-135462: Fix quadratic complexity in processing specially
crafted input in html.parser.HTMLParser. End-of-file errors are
now handled according to the HTML5 specs comments and
declarations are automatically closed, tags are ignored.
- gh-118350: Fix support of escapable raw text mode (elements
“textarea” and “title”) in html.parser.HTMLParser.
- gh-86155: html.parser.HTMLParser.close() no longer loses data
when the <script> tag is not closed. Patch by Waylan Limberg.
- Library
- gh-139312: Upgrade bundled libexpat to 2.7.3
- gh-138998: Update bundled libexpat to 2.7.2
- gh-130577: tarfile now validates archives to ensure member
offsets are non-negative. (Contributed by Alexander Enrique
Urieles Nieto in gh-130577.)
- gh-135374: Update the bundled copy of setuptools to 79.0.1.
- Drop upstreamed patches:
- CVE-2025-8194-tarfile-no-neg-offsets.patch
- CVE-2025-6069-quad-complex-HTMLParser.patch
-------------------------------------------------------------------
Mon Sep 29 06:52:07 UTC 2025 - Daniel Garcia <daniel.garcia@suse.com>
- Add gh139257-Support-docutils-0.22.patch to fix build with latest
docutils (>=0.22) gh#python/cpython#139257
-------------------------------------------------------------------
Thu Sep 18 08:15:31 UTC 2025 - Dominique Leuenberger <dimstar@opensuse.org>
- Require AppStream to validate appdata file instead of deprecated
appstream-glib.
- Update idle3.appdata.xml to pass the more pedantic appstreamcli.
-------------------------------------------------------------------
Fri Aug 1 20:09:24 UTC 2025 - Matej Cepl <mcepl@cepl.eu>
- Add CVE-2025-8194-tarfile-no-neg-offsets.patch which now
validates archives to ensure member offsets are non-negative
(gh#python/cpython#130577, CVE-2025-8194, bsc#1247249).
-------------------------------------------------------------------
Wed Jul 2 14:47:20 UTC 2025 - Matej Cepl <mcepl@cepl.eu>
- Add CVE-2025-6069-quad-complex-HTMLParser.patch to avoid worst
case quadratic complexity when processing certain crafted
malformed inputs with HTMLParser (CVE-2025-6069, bsc#1244705).
-------------------------------------------------------------------
Mon Jun 9 16:53:24 UTC 2025 - Matej Cepl <mcepl@cepl.eu>
- Update to 3.10.18:
- Security
- gh-135034: Fixes multiple issues that allowed tarfile
extraction filters (filter="data" and filter="tar")
to be bypassed using crafted symlinks and hard links.
Addresses CVE-2024-12718 (bsc#1244056), CVE-2025-4138
(bsc#1244059), CVE-2025-4330 (bsc#1244060), and
CVE-2025-4517 (bsc#1244032). Also addresses CVE-2025-4435
(gh#135034, bsc#1244061).
- gh-133767: Fix use-after-free in the “unicode-escape”
decoder with a non-“strict” error handler (CVE-2025-4516,
bsc#1243273).
- gh-128840: Short-circuit the processing of long IPv6
addresses early in ipaddress to prevent excessive memory
consumption and a minor denial-of-service.
- Library
- gh-128840: Fix parsing long IPv6 addresses with embedded
IPv4 address.
- gh-134062: ipaddress: fix collisions in __hash__() for
IPv4Network and IPv6Network objects.
- gh-123409: Fix ipaddress.IPv6Address.reverse_pointer output
according to RFC 3596, §2.5. Patch by Bénédikt Tran.
- bpo-43633: Improve the textual representation of
IPv4-mapped IPv6 addresses (RFC 4291 Sections 2.2, 2.5.5.2)
in ipaddress. Patch by Oleksandr Pavliuk.
- Remove upstreamed patches:
- gh-126572-test_ssl-no-stop-ThreadedEchoServer-OSError.patch
- CVE-2025-4516-DecodeError-handler.patch
-------------------------------------------------------------------
Thu May 22 13:01:17 UTC 2025 - Matej Cepl <mcepl@cepl.eu>
- Add CVE-2025-4516-DecodeError-handler.patch fixing
CVE-2025-4516 (bsc#1243273) blocking DecodeError handling
vulnerability, which could lead to DoS.
-------------------------------------------------------------------
Sat May 17 10:02:27 UTC 2025 - Matej Cepl <mcepl@cepl.eu>
- Use extended %autopatch.
-------------------------------------------------------------------
Sat May 10 11:38:22 UTC 2025 - Matej Cepl <mcepl@cepl.eu>
- Remove python-3.3.0b1-test-posix_fadvise.patch (not needed
since kernel 3.6-rc1)
-------------------------------------------------------------------
Fri Apr 11 08:12:14 UTC 2025 - Matej Cepl <mcepl@cepl.eu>
- Update to 3.10.17:
- gh-131809: Update bundled libexpat to 2.7.1
- gh-131261: Upgrade to libexpat 2.7.0
- gh-105704: When using urllib.parse.urlsplit() and
urllib.parse.urlparse() host parsing would not reject domain
names containing square brackets ([ and ]). Square brackets
are only valid for IPv6 and IPvFuture hosts according to RFC
3986 Section 3.2.2 (bsc#1236705, CVE-2025-0938,
gh#python/cpython#105704).
- gh-121284: Fix bug in the folding of rfc2047 encoded-words
when flattening an email message using a modern email
policy. Previously when an encoded-word was too long for
a line, it would be decoded, split across lines, and
re-encoded. But commas and other special characters in the
original text could be left unencoded and unquoted. This
could theoretically be used to spoof header lines using a
carefully constructed encoded-word if the resulting rendered
email was transmitted or re-parsed.
- gh-80222: Fix bug in the folding of quoted strings
when flattening an email message using a modern email
policy. Previously when a quoted string was folded so that
it spanned more than one line, the surrounding quotes and
internal escapes would be omitted. This could theoretically
be used to spoof header lines using a carefully constructed
quoted string if the resulting rendered email was transmitted
or re-parsed.
- gh-119511: Fix a potential denial of service in the imaplib
module. When connecting to a malicious server, it could
cause an arbitrary amount of memory to be allocated. On many
systems this is harmless as unused virtual memory is only
a mapping, but if this hit a virtual address size limit
it could lead to a MemoryError or other process crash. On
unusual systems or builds where all allocated memory is
touched and backed by actual ram or storage it couldve
consumed resources doing so until similarly crashing.
- gh-127257: In ssl, system call failures that OpenSSL reports
using ERR_LIB_SYS are now raised as OSError.
- gh-121277: Writers of CPythons documentation can now use
next as the version for the versionchanged, versionadded,
deprecated directives.
- Add gh-126572-test_ssl-no-stop-ThreadedEchoServer-OSError.patch
which makes test_ssl not to stop ThreadedEchoServer on OSError,
which makes test_ssl pass with OpenSSL 3.5 (bsc#1241067,
gh#python/cpython!126572)
- Remote upstreamed patch:
- CVE-2025-0938-sq-brackets-domain-names.patch
-------------------------------------------------------------------
Mon Mar 10 15:44:31 UTC 2025 - Bernhard Wiedemann <bwiedemann@suse.com>
- Skip PGO with %want_reproducible_builds (bsc#1239210)
-------------------------------------------------------------------
Tue Feb 4 14:43:13 UTC 2025 - Matej Cepl <mcepl@cepl.eu>
- Add CVE-2025-0938-sq-brackets-domain-names.patch which
disallows square brackets ([ and ]) in domain names for parsed
URLs (bsc#1236705, CVE-2025-0938, gh#python/cpython#105704)
-------------------------------------------------------------------
Wed Dec 4 21:23:20 UTC 2024 - Matej Cepl <mcepl@cepl.eu>
- Update to 3.10.16:
- Tests
- gh-125041: Re-enable skipped tests for zlib on the
s390x architecture: only skip checks of the compressed
bytes, which can be different between zlibs software
implementation and the hardware-accelerated implementation.
- gh-109396: Fix test_socket.test_hmac_sha1() in FIPS
mode. Use a longer key: FIPS mode requires at least of at
least 112 bits. The previous key was only 32 bits. Patch by
Victor Stinner.
- Security
- gh-126623: Upgrade libexpat to 2.6.4
- gh-122792: Changed IPv4-mapped ipaddress.IPv6Address to
consistently use the mapped IPv4 address value for deciding
properties. Properties which have their behavior fixed are
is_multicast, is_reserved, is_link_local, is_global, and
is_unspecified (bsc#1233307, CVE-2024-11168).
- Library
- gh-124651: Properly quote template strings in venv
activation scripts (bsc#1232241, CVE-2024-9287).
- gh-103848: Add checks to ensure that [ bracketed ] hosts
found by urllib.parse.urlsplit() are of IPv6 or IPvFuture
format.
- Removed upstreamed patches:
- CVE-2024-9287-venv_path_unquoted.patch
- CVE-2024-11168-validation-IPv6-addrs.patch
-------------------------------------------------------------------
Thu Nov 14 07:06:20 UTC 2024 - Matej Cepl <mcepl@cepl.eu>
- Remove -IVendor/ from python-config boo#1231795
- Apply sphinx-72.patch only conditionally for non-SLE-15 builds.
-------------------------------------------------------------------
Wed Nov 13 13:25:01 UTC 2024 - Matej Cepl <mcepl@cepl.eu>
- Add CVE-2024-11168-validation-IPv6-addrs.patch
fixing bsc#1233307 (CVE-2024-11168,
gh#python/cpython#103848): Improper validation of IPv6 and
IPvFuture addresses.
-------------------------------------------------------------------
Mon Nov 4 21:49:20 UTC 2024 - Matej Cepl <mcepl@cepl.eu>
- Update sphinx-72.patch to include renaming :noindex: option to
:no-index: in Sphinx 7.2 (bsc#1232750).
- While renaming drop fix-sphinx-72.patch.
-------------------------------------------------------------------
Fri Nov 1 21:38:45 UTC 2024 - Matej Cepl <mcepl@cepl.eu>
- Update CVE-2024-9287-venv_path_unquoted.patch according to the
upstream PR gh#python/cpython!126301.
-------------------------------------------------------------------
Thu Oct 24 16:09:00 UTC 2024 - Matej Cepl <mcepl@cepl.eu>
- Add CVE-2024-9287-venv_path_unquoted.patch to properly quote
path names provided when creating a virtual environment
(bsc#1232241, CVE-2024-9287)
-------------------------------------------------------------------
Wed Oct 2 16:18:29 UTC 2024 - Matej Cepl <mcepl@cepl.eu>
- Drop .pyc files from docdir for reproducible builds
(bsc#1230906).
-------------------------------------------------------------------
Mon Sep 9 13:41:07 UTC 2024 - Matej Cepl <mcepl@cepl.eu>
- Update to 3.10.15:
- Tests
- gh-112769: The tests now correctly compare zlib version
when :const:`zlib.ZLIB_RUNTIME_VERSION` contains
non-integer suffixes. For example zlib-ng defines the
version as ``1.3.0.zlib-ng``.
- gh-117187: Fix XML tests for vanilla Expat <2.6.0.
- gh-100454: Fix SSL tests CI for OpenSSL 3.1+
- Security
- gh-123678: Upgrade libexpat to 2.6.3
- gh-121957: Fixed missing audit events around interactive
use of Python, now also properly firing for ``python -i``,
as well as for ``python -m asyncio``. The event in question
is ``cpython.run_stdin``.
- gh-122133: Authenticate the socket connection for the
``socket.socketpair()`` fallback on platforms where
``AF_UNIX`` is not available like Windows. Patch by
Gregory P. Smith <greg@krypto.org> and Seth Larson
<seth@python.org>. Reported by Ellie <el@horse64.org>
- gh-121285: Remove backtracking from tarfile header
parsing for ``hdrcharset``, PAX, and GNU sparse headers
(bsc#1230227, CVE-2024-6232).
- gh-118486: :func:`os.mkdir` on Windows now accepts
*mode* of ``0o700`` to restrict the new directory to
the current user. This fixes CVE-2024-4030 affecting
:func:`tempfile.mkdtemp` in scenarios where the base
temporary directory is more permissive than the default.
- gh-116741: Update bundled libexpat to 2.6.2
- Library
- gh-123693: Use platform-agnostic behavior when computing
``zipfile.Path.name``.
- gh-123270: Applied a more surgical fix for malformed
payloads in :class:`zipfile.Path` causing infinite loops
(gh-122905) without breaking contents using legitimate
characters (bsc#1229704, CVE-2024-8088).
- gh-123067: Fix quadratic complexity in parsing ``"``-quoted
cookie values with backslashes by :mod:`http.cookies`
(bsc#1229596, CVE-2024-7592).
- gh-122905: :class:`zipfile.Path` objects now sanitize names
from the zipfile.
- gh-121650: :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.; CVE-2024-6923, bsc#1228780).
- gh-113171: 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.
Fixes bsc#1226448 (CVE-2024-4032).
- gh-102988: :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 (bsc#1210638).
- gh-67693: Fix :func:`urllib.parse.urlunparse` and
:func:`urllib.parse.urlunsplit` for URIs with path starting with multiple
slashes and no authority. Based on patch by Ashwin Ramaswami.
- Core and Builtins
- gh-112275: A deadlock involving ``pystate.c``'s
``HEAD_LOCK`` in ``posixmodule.c`` at fork is now
fixed. Patch by ChuBoning based on previous Python 3.12 fix
by Victor Stinner.
- Remove upstreamed patches:
- CVE-2023-27043-email-parsing-errors.patch
- CVE-2024-4032-private-IP-addrs.patch
- CVE-2024-6923-email-hdr-inject.patch
- CVE-2024-8088-inf-loop-zipfile_Path.patch
- Add sphinx-802.patch to overcome working both with the most
recent and older Sphinx versions.
-------------------------------------------------------------------
Mon Sep 2 09:44:26 UTC 2024 - Matej Cepl <mcepl@cepl.eu>
- 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).
------------------------------------------------------------------- -------------------------------------------------------------------
Wed Aug 28 16:54:34 UTC 2024 - Matej Cepl <mcepl@cepl.eu> Wed Aug 28 16:54:34 UTC 2024 - Matej Cepl <mcepl@cepl.eu>
@@ -621,7 +122,7 @@ Fri Feb 23 01:06:42 UTC 2024 - Matej Cepl <mcepl@suse.com>
Tue Feb 20 22:14:02 UTC 2024 - Matej Cepl <mcepl@cepl.eu> Tue Feb 20 22:14:02 UTC 2024 - Matej Cepl <mcepl@cepl.eu>
- Remove double definition of /usr/bin/idle%%{version} in - Remove double definition of /usr/bin/idle%%{version} in
%%files. %%files.
------------------------------------------------------------------- -------------------------------------------------------------------
Thu Feb 15 10:29:07 UTC 2024 - Daniel Garcia <daniel.garcia@suse.com> Thu Feb 15 10:29:07 UTC 2024 - Daniel Garcia <daniel.garcia@suse.com>

View File

@@ -1,7 +1,7 @@
# #
# spec file for package python310 # spec file for package python310
# #
# Copyright (c) 2025 SUSE LLC and contributors # Copyright (c) 2024 SUSE LLC
# #
# All modifications and additions to the file contributed by third parties # All modifications and additions to the file contributed by third parties
# remain the property of their copyright owners, unless otherwise agreed # remain the property of their copyright owners, unless otherwise agreed
@@ -36,7 +36,7 @@
%bcond_without general %bcond_without general
%endif %endif
%if 0%{?do_profiling} && !0%{?want_reproducible_builds} %if 0%{?do_profiling}
%bcond_without profileopt %bcond_without profileopt
%else %else
%bcond_with profileopt %bcond_with profileopt
@@ -108,13 +108,13 @@ Obsoletes: python39%{?1:-%{1}}
# _md5.cpython-38m-x86_64-linux-gnu.so # _md5.cpython-38m-x86_64-linux-gnu.so
%define dynlib() %{sitedir}/lib-dynload/%{1}.cpython-%{abi_tag}-%{archname}-%{_os}%{?_gnu}%{?armsuffix}.so %define dynlib() %{sitedir}/lib-dynload/%{1}.cpython-%{abi_tag}-%{archname}-%{_os}%{?_gnu}%{?armsuffix}.so
Name: %{python_pkg_name}%{psuffix} Name: %{python_pkg_name}%{psuffix}
Version: 3.10.20 Version: 3.10.14
Release: 0 Release: 0
Summary: Python 3 Interpreter Summary: Python 3 Interpreter
License: Python-2.0 License: Python-2.0
URL: https://www.python.org/ URL: https://www.python.org/
Source0: https://www.python.org/ftp/python/%{folderversion}/%{tarname}.tar.xz Source0: https://www.python.org/ftp/python/%{folderversion}/%{tarname}.tar.xz
Source1: https://www.python.org/ftp/python/%{folderversion}/%{tarname}.tar.xz.sigstore Source1: https://www.python.org/ftp/python/%{folderversion}/%{tarname}.tar.xz.asc
Source2: baselibs.conf Source2: baselibs.conf
Source3: README.SUSE Source3: README.SUSE
Source7: macros.python3 Source7: macros.python3
@@ -152,6 +152,8 @@ Patch02: distutils-reproducible-compile.patch
Patch03: python-3.3.0b1-localpath.patch Patch03: python-3.3.0b1-localpath.patch
# replace DATE, TIME and COMPILER by fixed definitions to aid reproducible builds # replace DATE, TIME and COMPILER by fixed definitions to aid reproducible builds
Patch04: python-3.3.0b1-fix_date_time_compiler.patch Patch04: python-3.3.0b1-fix_date_time_compiler.patch
# POSIX_FADV_WILLNEED throws EINVAL. Use a different constant in test
Patch05: python-3.3.0b1-test-posix_fadvise.patch
# Raise timeout value for test_subprocess # Raise timeout value for test_subprocess
Patch06: subprocess-raise-timeout.patch Patch06: subprocess-raise-timeout.patch
# PATCH-FEATURE-UPSTREAM bpo-31046_ensurepip_honours_prefix.patch bpo#31046 mcepl@suse.com # PATCH-FEATURE-UPSTREAM bpo-31046_ensurepip_honours_prefix.patch bpo#31046 mcepl@suse.com
@@ -160,6 +162,7 @@ Patch07: bpo-31046_ensurepip_honours_prefix.patch
# PATCH-FIX-SLE no-skipif-doctests.patch jsc#SLE-13738 mcepl@suse.com # PATCH-FIX-SLE no-skipif-doctests.patch jsc#SLE-13738 mcepl@suse.com
# SLE-15 version of Sphinx doesn't know about skipif directive in doctests. # SLE-15 version of Sphinx doesn't know about skipif directive in doctests.
Patch11: no-skipif-doctests.patch Patch11: no-skipif-doctests.patch
# PATCH-FIX-SLE skip-test_pyobject_freed_is_freed.patch mcepl@suse.com # PATCH-FIX-SLE skip-test_pyobject_freed_is_freed.patch mcepl@suse.com
# skip a test failing on SLE-15 # skip a test failing on SLE-15
Patch15: skip-test_pyobject_freed_is_freed.patch Patch15: skip-test_pyobject_freed_is_freed.patch
@@ -175,6 +178,11 @@ Patch18: bpo-37596-make-set-marshalling.patch
# PATCH-FIX-UPSTREAM gh-78214-marshal_stabilize_FLAG_REF.patch bsc#1213463 mcepl@suse.com # PATCH-FIX-UPSTREAM gh-78214-marshal_stabilize_FLAG_REF.patch bsc#1213463 mcepl@suse.com
# marshal: Stabilize FLAG_REF usage # marshal: Stabilize FLAG_REF usage
Patch19: gh-78214-marshal_stabilize_FLAG_REF.patch Patch19: gh-78214-marshal_stabilize_FLAG_REF.patch
# PATCH-FIX-UPSTREAM CVE-2023-27043-email-parsing-errors.patch bsc#1210638 mcepl@suse.com
# Detect email address parsing errors and return empty tuple to
# indicate the parsing error (old API), from gh#python/cpython!105127
# Patch carries a REGRESSION (gh#python/cpython#106669), so it has been also partially REVERTED
Patch20: CVE-2023-27043-email-parsing-errors.patch
# PATCH-FIX-UPSTREAM fix-sphinx-72.patch gh#python/cpython#97950 # PATCH-FIX-UPSTREAM fix-sphinx-72.patch gh#python/cpython#97950
# This is a patch with a lot of PR combined to make the doc work with # This is a patch with a lot of PR combined to make the doc work with
# sphinx 7.2 # sphinx 7.2
@@ -188,31 +196,22 @@ Patch19: gh-78214-marshal_stabilize_FLAG_REF.patch
# * gh#python/cpython#104163 # * gh#python/cpython#104163
# * gh#python/cpython#104221 # * gh#python/cpython#104221
# * gh#python/cpython#107246 # * gh#python/cpython#107246
Patch21: sphinx-72.patch Patch21: fix-sphinx-72.patch
# PATCH-FIX-UPSTREAM CVE-2023-52425-libexpat-2.6.0-backport.patch gh#python/cpython#117187 mcepl@suse.com # PATCH-FIX-UPSTREAM CVE-2023-52425-libexpat-2.6.0-backport.patch gh#python/cpython#117187 mcepl@suse.com
# Make the test suite work with libexpat < 2.6.0 # Make the test suite work with libexpat < 2.6.0
Patch22: CVE-2023-52425-libexpat-2.6.0-backport.patch Patch22: CVE-2023-52425-libexpat-2.6.0-backport.patch
# PATCH-FIX-UPSTREAM CVE-2024-4032-private-IP-addrs.patch bsc#1226448 mcepl@suse.com
# rearrange definition of private v global IP addresses
Patch23: CVE-2024-4032-private-IP-addrs.patch
# PATCH-FIX-UPSTREAM bso1227999-reproducible-builds.patch bsc#1227999 mcepl@suse.com # PATCH-FIX-UPSTREAM bso1227999-reproducible-builds.patch bsc#1227999 mcepl@suse.com
# reproducibility patches # reproducibility patches
Patch24: bso1227999-reproducible-builds.patch Patch24: bso1227999-reproducible-builds.patch
# PATCH-FIX-UPSTREAM gh120226-fix-sendfile-test-kernel-610.patch gh#python/cpython#120226 mcepl@suse.com # PATCH-FIX-UPSTREAM CVE-2024-6923-email-hdr-inject.patch bsc#1228780 mcepl@suse.com
# Fix test_sendfile_close_peer_in_the_middle_of_receiving on Linux >= 6.10 (GH-120227) # prevent email header injection, patch from gh#python/cpython!122608
Patch27: gh120226-fix-sendfile-test-kernel-610.patch Patch25: CVE-2024-6923-email-hdr-inject.patch
# PATCH-FIX-UPSTREAM sphinx-802.patch mcepl@suse.com # PATCH-FIX-UPSTREAM CVE-2024-8088-inf-loop-zipfile_Path.patch bsc#1229704 mcepl@suse.com
# status_iterator method moved between the Sphinx versions # avoid denial of service in zipfile
Patch28: sphinx-802.patch Patch26: CVE-2024-8088-inf-loop-zipfile_Path.patch
# PATCH-FIX-OPENSUSE gh139257-Support-docutils-0.22.patch gh#python/cpython#139257 daniel.garcia@suse.com
Patch29: gh139257-Support-docutils-0.22.patch
# PATCH-FIX-UPSTREAM CVE-2025-15366-imap-ctrl-chars.patch bsc#1257044 mcepl@suse.com
# Reject control characters in wsgiref.headers.Headers
Patch38: CVE-2025-15366-imap-ctrl-chars.patch
# PATCH-FIX-UPSTREAM CVE-2025-15282-urllib-ctrl-chars.patch bsc#1257046 mcepl@suse.com
# Reject control characters in urllib
Patch39: CVE-2025-15282-urllib-ctrl-chars.patch
# PATCH-FIX-UPSTREAM CVE-2025-15367-poplib-ctrl-chars.patch bsc#1257041 mcepl@suse.com
# Reject control characters in poplib
Patch40: CVE-2025-15367-poplib-ctrl-chars.patch
### END OF PATCHES
BuildRequires: autoconf-archive BuildRequires: autoconf-archive
BuildRequires: automake BuildRequires: automake
BuildRequires: fdupes BuildRequires: fdupes
@@ -251,7 +250,7 @@ BuildRequires: python3-python-docs-theme >= 2022.1
%endif %endif
%if %{with general} %if %{with general}
# required for idle3 (.desktop and .appdata.xml files) # required for idle3 (.desktop and .appdata.xml files)
BuildRequires: AppStream BuildRequires: appstream-glib
BuildRequires: gcc-c++ BuildRequires: gcc-c++
BuildRequires: gdbm-devel BuildRequires: gdbm-devel
BuildRequires: gettext BuildRequires: gettext
@@ -470,15 +469,30 @@ other applications.
%prep %prep
%setup -q -n %{tarname} %setup -q -n %{tarname}
%autopatch -p1 -M 07 %patch -p1 -P 01
%patch -p1 -P 02
%patch -p1 -P 03
%patch -p1 -P 04
%patch -p1 -P 05
%patch -p1 -P 06
%patch -p1 -P 07
%if 0%{?sle_version} && 0%{?sle_version} <= 150300 %if 0%{?sle_version} && 0%{?sle_version} <= 150300
%patch -p1 -P 11 %patch -P 11 -p1
%endif %endif
%autopatch -p1 -m 12 -M 20
%if ! 0%{?sle_version} || 0%{?sle_version} >= 160000 %patch -p1 -P 15
%patch -p1 -P 16
%patch -p1 -P 17
%patch -p1 -P 18
%patch -p1 -P 19
%patch -p1 -P 20
%patch -p1 -P 21 %patch -p1 -P 21
%endif %patch -p1 -P 22
%autopatch -p1 -m 22 %patch -p1 -P 23
%patch -p1 -P 24
%patch -p1 -P 25
%patch -p1 -P 26
# drop Autoconf version requirement # drop Autoconf version requirement
sed -i 's/^AC_PREREQ/dnl AC_PREREQ/' configure.ac sed -i 's/^AC_PREREQ/dnl AC_PREREQ/' configure.ac
@@ -512,9 +526,6 @@ rm Lib/site-packages/README.txt
# Add vendored bluez-devel files # Add vendored bluez-devel files
tar xvf %{SOURCE21} tar xvf %{SOURCE21}
# Don't fail on warnings when building documentation
sed -i -e '/^SPHINXERRORHANDLING/s/-W//' Doc/Makefile
%build %build
%if %{with doc} %if %{with doc}
TODAY_DATE=`date -r %{SOURCE0} "+%%B %%d, %%Y"` TODAY_DATE=`date -r %{SOURCE0} "+%%B %%d, %%Y"`
@@ -717,7 +728,7 @@ install -m 644 -D -t %{buildroot}%{_datadir}/applications idle%{python_version}.
cp %{SOURCE20} idle%{python_version}.appdata.xml cp %{SOURCE20} idle%{python_version}.appdata.xml
sed -i -e 's:idle3.desktop:idle%{python_version}.desktop:g' idle%{python_version}.appdata.xml sed -i -e 's:idle3.desktop:idle%{python_version}.desktop:g' idle%{python_version}.appdata.xml
install -m 644 -D -t %{buildroot}%{_datadir}/metainfo idle%{python_version}.appdata.xml install -m 644 -D -t %{buildroot}%{_datadir}/metainfo idle%{python_version}.appdata.xml
appstreamcli validate --no-net %{buildroot}%{_datadir}/metainfo/idle%{python_version}.appdata.xml appstream-util validate-relax --nonet %{buildroot}%{_datadir}/metainfo/idle%{python_version}.appdata.xml
%fdupes %{buildroot}/%{_libdir}/python%{python_version} %fdupes %{buildroot}/%{_libdir}/python%{python_version}
%endif %endif
@@ -790,9 +801,6 @@ install -m 755 -D Tools/gdb/libpython.py %{buildroot}%{_datadir}/gdb/auto-load/%
# install devel files to /config # install devel files to /config
#cp Makefile Makefile.pre.in Makefile.pre $RPM_BUILD_ROOT%{sitedir}/config-%{python_abi}/ #cp Makefile Makefile.pre.in Makefile.pre $RPM_BUILD_ROOT%{sitedir}/config-%{python_abi}/
# Remove -IVendor/ from python-config boo#1231795
sed -i 's/-IVendor\///' %{buildroot}%{_bindir}/python%{python_abi}-config
# RPM macros # RPM macros
%if %{primary_interpreter} %if %{primary_interpreter}
mkdir -p %{buildroot}%{_rpmconfigdir}/macros.d/ mkdir -p %{buildroot}%{_rpmconfigdir}/macros.d/
@@ -821,11 +829,6 @@ LD_LIBRARY_PATH=. ./python -O -c "from py_compile import compile; compile('$FAIL
echo %{sitedir}/_import_failed > %{buildroot}/%{sitedir}/site-packages/zzzz-import-failed-hooks.pth echo %{sitedir}/_import_failed > %{buildroot}/%{sitedir}/site-packages/zzzz-import-failed-hooks.pth
%endif %endif
# For the purposes of reproducibility, it is necessary to eliminate any *.pyc files inside documentation dirs
if [ -d %{buildroot}%{_defaultdocdir} ] ; then
find %{buildroot}%{_defaultdocdir} -type f -name \*.pyc -ls -exec rm -vf '{}' \;
fi
%if %{with general} %if %{with general}
%files -n %{python_pkg_name}-tk %files -n %{python_pkg_name}-tk
%{sitedir}/tkinter %{sitedir}/tkinter

View File

@@ -1,23 +0,0 @@
---
Doc/tools/extensions/pyspecific.py | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
Index: Python-3.10.20/Doc/tools/extensions/pyspecific.py
===================================================================
--- Python-3.10.20.orig/Doc/tools/extensions/pyspecific.py 2026-03-05 19:42:18.992268971 +0100
+++ Python-3.10.20/Doc/tools/extensions/pyspecific.py 2026-03-08 23:16:00.413991280 +0100
@@ -28,7 +28,13 @@
except ImportError:
from sphinx.environment import NoUri
from sphinx.locale import _ as sphinx_gettext
-from sphinx.util import status_iterator, logging
+try:
+ from sphinx.util.display import status_iterator
+except ImportError:
+ # This method was moved into sphinx.util.display in Sphinx 6.1.0. Before
+ # that it resided in sphinx.util.
+ from sphinx.util import status_iterator
+from sphinx.util import logging
from sphinx.util.nodes import split_explicit_title
from sphinx.writers.text import TextWriter, TextTranslator
from sphinx.writers.latex import LaTeXTranslator

View File

@@ -27,19 +27,17 @@ Co-authored-by: Sebastian Pipping <sebastian@pipping.org>
1 file changed, 9 insertions(+), 14 deletions(-) 1 file changed, 9 insertions(+), 14 deletions(-)
create mode 100644 Misc/NEWS.d/next/Library/2022-02-20-21-03-31.bpo-46811.8BxgdQ.rst create mode 100644 Misc/NEWS.d/next/Library/2022-02-20-21-03-31.bpo-46811.8BxgdQ.rst
Index: Python-3.10.20/Lib/test/test_minidom.py --- a/Lib/test/test_minidom.py
=================================================================== +++ b/Lib/test/test_minidom.py
--- Python-3.10.20.orig/Lib/test/test_minidom.py 2026-03-05 19:39:01.567696516 +0100 @@ -6,7 +6,6 @@ import io
+++ Python-3.10.20/Lib/test/test_minidom.py 2026-03-05 19:42:02.323563898 +0100
@@ -7,7 +7,6 @@
from test import support from test import support
import unittest import unittest
-import pyexpat -import pyexpat
import xml.dom.minidom import xml.dom.minidom
from xml.dom.minidom import parse, Attr, Node, Document, Element, parseString from xml.dom.minidom import parse, Attr, Node, Document, parseString
@@ -1194,13 +1193,11 @@ @@ -1163,13 +1162,11 @@ class MinidomTest(unittest.TestCase):
# Verify that character decoding errors raise exceptions instead # Verify that character decoding errors raise exceptions instead
# of crashing # of crashing
@@ -58,7 +56,7 @@ Index: Python-3.10.20/Lib/test/test_minidom.py
b'<fran\xe7ais>Comment \xe7a va ? Tr\xe8s bien ?</fran\xe7ais>') b'<fran\xe7ais>Comment \xe7a va ? Tr\xe8s bien ?</fran\xe7ais>')
doc.unlink() doc.unlink()
@@ -1662,12 +1659,10 @@ @@ -1631,12 +1628,10 @@ class MinidomTest(unittest.TestCase):
self.confirm(doc2.namespaceURI == xml.dom.EMPTY_NAMESPACE) self.confirm(doc2.namespaceURI == xml.dom.EMPTY_NAMESPACE)
def testExceptionOnSpacesInXMLNSValue(self): def testExceptionOnSpacesInXMLNSValue(self):