Sync from SUSE:ALP:Source:Standard:1.0 saltbundlepy revision c070c26d2cb9d389ddf882594ed4a94a

This commit is contained in:
Adrian Schröter 2024-07-12 12:15:30 +02:00
commit b0c198dfbe
36 changed files with 10548 additions and 0 deletions

23
.gitattributes vendored Normal file
View File

@ -0,0 +1,23 @@
## Default LFS
*.7z filter=lfs diff=lfs merge=lfs -text
*.bsp filter=lfs diff=lfs merge=lfs -text
*.bz2 filter=lfs diff=lfs merge=lfs -text
*.gem filter=lfs diff=lfs merge=lfs -text
*.gz filter=lfs diff=lfs merge=lfs -text
*.jar filter=lfs diff=lfs merge=lfs -text
*.lz filter=lfs diff=lfs merge=lfs -text
*.lzma filter=lfs diff=lfs merge=lfs -text
*.obscpio filter=lfs diff=lfs merge=lfs -text
*.oxt filter=lfs diff=lfs merge=lfs -text
*.pdf filter=lfs diff=lfs merge=lfs -text
*.png filter=lfs diff=lfs merge=lfs -text
*.rpm filter=lfs diff=lfs merge=lfs -text
*.tbz filter=lfs diff=lfs merge=lfs -text
*.tbz2 filter=lfs diff=lfs merge=lfs -text
*.tgz filter=lfs diff=lfs merge=lfs -text
*.ttf filter=lfs diff=lfs merge=lfs -text
*.txz filter=lfs diff=lfs merge=lfs -text
*.whl filter=lfs diff=lfs merge=lfs -text
*.xz filter=lfs diff=lfs merge=lfs -text
*.zip filter=lfs diff=lfs merge=lfs -text
*.zst filter=lfs diff=lfs merge=lfs -text

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 may contain 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
@@ -17,6 +17,7 @@ from unittest.mock import patch
import email
import email.policy
+import email.utils
from email.charset import Charset
from email.generator import Generator, DecodedGenerator, BytesGenerator
@@ -3336,15 +3337,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"""
@@ -3535,6 +3658,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

@ -0,0 +1,223 @@
---
Lib/test/support/__init__.py | 16 ++++++++++++++--
Lib/test/test_minidom.py | 23 +++++++++--------------
Lib/test/test_pyexpat.py | 12 +++++-------
Lib/test/test_sax.py | 18 +++++++++---------
Lib/test/test_xml_etree.py | 12 ------------
5 files changed, 37 insertions(+), 44 deletions(-)
--- a/Lib/test/support/__init__.py
+++ b/Lib/test/support/__init__.py
@@ -8,6 +8,7 @@ import dataclasses
import functools
import os
import re
+import pyexpat
import stat
import sys
import sysconfig
@@ -56,7 +57,7 @@ __all__ = [
"run_with_tz", "PGO", "missing_compiler_executable",
"ALWAYS_EQ", "NEVER_EQ", "LARGEST", "SMALLEST",
"LOOPBACK_TIMEOUT", "INTERNET_TIMEOUT", "SHORT_TIMEOUT", "LONG_TIMEOUT",
- "skip_on_s390x",
+ "skip_on_s390x", "fails_with_expat_2_6_0", "is_expat_2_6_0"
]
@@ -2240,6 +2241,17 @@ def copy_python_src_ignore(path, names):
}
return ignored
-#Windows doesn't have os.uname() but it doesn't support s390x.
+
+# Windows doesn't have os.uname() but it doesn't support s390x.
skip_on_s390x = unittest.skipIf(hasattr(os, 'uname') and os.uname().machine == 's390x',
'skipped on s390x')
+
+
+@functools.lru_cache
+def _is_expat_2_6_0():
+ return hasattr(pyexpat.ParserCreate(), 'SetReparseDeferralEnabled')
+is_expat_2_6_0 = _is_expat_2_6_0()
+
+fails_with_expat_2_6_0 = (unittest.expectedFailure
+ if is_expat_2_6_0
+ else lambda test: test)
--- a/Lib/test/test_minidom.py
+++ b/Lib/test/test_minidom.py
@@ -6,7 +6,6 @@ import io
from test import support
import unittest
-import pyexpat
import xml.dom.minidom
from xml.dom.minidom import parse, Attr, Node, Document, parseString
@@ -1163,13 +1162,11 @@ class MinidomTest(unittest.TestCase):
# Verify that character decoding errors raise exceptions instead
# of crashing
- if pyexpat.version_info >= (2, 4, 5):
- self.assertRaises(ExpatError, parseString,
- b'<fran\xe7ais></fran\xe7ais>')
- self.assertRaises(ExpatError, parseString,
- b'<franais>Comment \xe7a va ? Tr\xe8s bien ?</franais>')
- else:
- self.assertRaises(UnicodeDecodeError, parseString,
+ # It doesnt make any sense to insist on the exact text of the
+ # error message, or even the exact Exception … it is enough that
+ # the error has been discovered.
+ with self.assertRaises((UnicodeDecodeError, ExpatError)):
+ parseString(
b'<fran\xe7ais>Comment \xe7a va ? Tr\xe8s bien ?</fran\xe7ais>')
doc.unlink()
@@ -1631,12 +1628,10 @@ class MinidomTest(unittest.TestCase):
self.confirm(doc2.namespaceURI == xml.dom.EMPTY_NAMESPACE)
def testExceptionOnSpacesInXMLNSValue(self):
- if pyexpat.version_info >= (2, 4, 5):
- context = self.assertRaisesRegex(ExpatError, 'syntax error')
- else:
- context = self.assertRaisesRegex(ValueError, 'Unsupported syntax')
-
- with context:
+ # It doesnt make any sense to insist on the exact text of the
+ # error message, or even the exact Exception … it is enough that
+ # the error has been discovered.
+ with self.assertRaises((ExpatError, ValueError)):
parseString('<element xmlns:abc="http:abc.com/de f g/hi/j k"><abc:foo /></element>')
def testDocRemoveChild(self):
--- a/Lib/test/test_pyexpat.py
+++ b/Lib/test/test_pyexpat.py
@@ -14,8 +14,7 @@ from test.support import os_helper
from xml.parsers import expat
from xml.parsers.expat import errors
-from test.support import sortdict, is_emscripten, is_wasi
-
+from test.support import sortdict, is_emscripten, is_wasi, is_expat_2_6_0
class SetAttributeTest(unittest.TestCase):
def setUp(self):
@@ -770,9 +769,8 @@ class ReparseDeferralTest(unittest.TestC
self.assertIs(parser.GetReparseDeferralEnabled(), enabled)
def test_reparse_deferral_enabled(self):
- if expat.version_info < (2, 6, 0):
- self.skipTest(f'Expat {expat.version_info} does not '
- 'support reparse deferral')
+ if not is_expat_2_6_0:
+ self.skipTest("Linked libexpat doesn't support reparse deferral")
started = []
@@ -801,9 +799,9 @@ class ReparseDeferralTest(unittest.TestC
parser = expat.ParserCreate()
parser.StartElementHandler = start_element
- if expat.version_info >= (2, 6, 0):
+ if is_expat_2_6_0:
parser.SetReparseDeferralEnabled(False)
- self.assertFalse(parser.GetReparseDeferralEnabled())
+ self.assertFalse(parser.GetReparseDeferralEnabled())
for chunk in (b'<doc', b'/>'):
parser.Parse(chunk, False)
--- a/Lib/test/test_sax.py
+++ b/Lib/test/test_sax.py
@@ -19,13 +19,11 @@ from xml.sax.xmlreader import InputSourc
from io import BytesIO, StringIO
import codecs
import os.path
-import pyexpat
import shutil
import sys
from urllib.error import URLError
import urllib.request
-from test.support import os_helper
-from test.support import findfile
+from test.support import os_helper, findfile, is_expat_2_6_0
from test.support.os_helper import FakePath, TESTFN
@@ -1215,10 +1213,10 @@ class ExpatReaderTest(XmlTestBase):
self.assertEqual(result.getvalue(), start + b"<doc>text</doc>")
- @unittest.skipIf(pyexpat.version_info < (2, 6, 0),
- f'Expat {pyexpat.version_info} does not '
- 'support reparse deferral')
def test_flush_reparse_deferral_enabled(self):
+ if not is_expat_2_6_0:
+ self.skipTest("Linked libexpat doesn't support reparse deferral")
+
result = BytesIO()
xmlgen = XMLGenerator(result)
parser = create_parser()
@@ -1241,6 +1239,9 @@ class ExpatReaderTest(XmlTestBase):
self.assertEqual(result.getvalue(), start + b"<doc></doc>")
def test_flush_reparse_deferral_disabled(self):
+ if not is_expat_2_6_0:
+ self.skipTest("Linked libexpat doesn't support reparse deferral")
+
result = BytesIO()
xmlgen = XMLGenerator(result)
parser = create_parser()
@@ -1249,9 +1250,8 @@ class ExpatReaderTest(XmlTestBase):
for chunk in ("<doc", ">"):
parser.feed(chunk)
- if pyexpat.version_info >= (2, 6, 0):
- parser._parser.SetReparseDeferralEnabled(False)
- self.assertEqual(result.getvalue(), start) # i.e. no elements started
+ parser._parser.SetReparseDeferralEnabled(False)
+ self.assertEqual(result.getvalue(), start) # i.e. no elements started
self.assertFalse(parser._parser.GetReparseDeferralEnabled())
--- a/Lib/test/test_xml_etree.py
+++ b/Lib/test/test_xml_etree.py
@@ -13,7 +13,6 @@ import itertools
import operator
import os
import pickle
-import pyexpat
import sys
import textwrap
import types
@@ -1424,12 +1423,6 @@ class XMLPullParserTest(unittest.TestCas
self.assert_event_tags(parser, [('end', 'root')])
self.assertIsNone(parser.close())
- def test_simple_xml_chunk_1(self):
- self.test_simple_xml(chunk_size=1, flush=True)
-
- def test_simple_xml_chunk_5(self):
- self.test_simple_xml(chunk_size=5, flush=True)
-
def test_simple_xml_chunk_22(self):
self.test_simple_xml(chunk_size=22)
@@ -1627,9 +1620,6 @@ class XMLPullParserTest(unittest.TestCas
with self.assertRaises(ValueError):
ET.XMLPullParser(events=('start', 'end', 'bogus'))
- @unittest.skipIf(pyexpat.version_info < (2, 6, 0),
- f'Expat {pyexpat.version_info} does not '
- 'support reparse deferral')
def test_flush_reparse_deferral_enabled(self):
parser = ET.XMLPullParser(events=('start', 'end'))
@@ -1656,8 +1646,6 @@ class XMLPullParserTest(unittest.TestCas
for chunk in ("<doc", ">"):
parser.feed(chunk)
-
- if pyexpat.version_info >= (2, 6, 0):
if not ET is pyET:
self.skipTest(f'XMLParser.(Get|Set)ReparseDeferralEnabled '
'methods not available in C')

View File

@ -0,0 +1,60 @@
---
Lib/test/test_pyexpat.py | 2 ++
Lib/test/test_sax.py | 2 ++
Lib/test/test_xml_etree.py | 2 ++
3 files changed, 6 insertions(+)
--- a/Lib/test/test_pyexpat.py
+++ b/Lib/test/test_pyexpat.py
@@ -768,6 +768,7 @@ class ReparseDeferralTest(unittest.TestC
parser.SetReparseDeferralEnabled(True)
self.assertIs(parser.GetReparseDeferralEnabled(), enabled)
+ @unittest.skip('Tests are failing.')
def test_reparse_deferral_enabled(self):
if not is_expat_2_6_0:
self.skipTest("Linked libexpat doesn't support reparse deferral")
@@ -791,6 +792,7 @@ class ReparseDeferralTest(unittest.TestC
self.assertEqual(started, ['doc'])
+ @unittest.skip('Tests are failing.')
def test_reparse_deferral_disabled(self):
started = []
--- a/Lib/test/test_sax.py
+++ b/Lib/test/test_sax.py
@@ -1213,6 +1213,7 @@ class ExpatReaderTest(XmlTestBase):
self.assertEqual(result.getvalue(), start + b"<doc>text</doc>")
+ @unittest.skip('Tests are failing.')
def test_flush_reparse_deferral_enabled(self):
if not is_expat_2_6_0:
self.skipTest("Linked libexpat doesn't support reparse deferral")
@@ -1238,6 +1239,7 @@ class ExpatReaderTest(XmlTestBase):
self.assertEqual(result.getvalue(), start + b"<doc></doc>")
+ @unittest.skip('Tests are failing.')
def test_flush_reparse_deferral_disabled(self):
if not is_expat_2_6_0:
self.skipTest("Linked libexpat doesn't support reparse deferral")
--- a/Lib/test/test_xml_etree.py
+++ b/Lib/test/test_xml_etree.py
@@ -1620,6 +1620,7 @@ class XMLPullParserTest(unittest.TestCas
with self.assertRaises(ValueError):
ET.XMLPullParser(events=('start', 'end', 'bogus'))
+ @unittest.skip('Tests are failing.')
def test_flush_reparse_deferral_enabled(self):
parser = ET.XMLPullParser(events=('start', 'end'))
@@ -1641,6 +1642,7 @@ class XMLPullParserTest(unittest.TestCas
self.assert_event_tags(parser, [('end', 'doc')])
+ @unittest.skip('Tests are failing.')
def test_flush_reparse_deferral_disabled(self):
parser = ET.XMLPullParser(events=('start', 'end'))

View File

@ -0,0 +1,62 @@
From 910f38d9768d39d4d31426743ae4081ed1ab66b6 Mon Sep 17 00:00:00 2001
From: Michal Cyprian <m.cyprian@gmail.com>
Date: Mon, 26 Jun 2017 16:32:56 +0200
Subject: [PATCH] 00251: Change user install location
Set values of prefix and exec_prefix in distutils install command
to /usr/local if executable is /usr/bin/python* and RPM build
is not detected to make pip and distutils install into separate location.
Fedora Change: https://fedoraproject.org/wiki/Changes/Making_sudo_pip_safe
---
Lib/distutils/command/install.py | 15 +++++++++++++--
Lib/site.py | 9 ++++++++-
2 files changed, 21 insertions(+), 3 deletions(-)
Index: Python-3.11.8/Lib/distutils/command/install.py
===================================================================
--- Python-3.11.8.orig/Lib/distutils/command/install.py
+++ Python-3.11.8/Lib/distutils/command/install.py
@@ -441,8 +441,19 @@ class install(Command):
raise DistutilsOptionError(
"must not supply exec-prefix without prefix")
- self.prefix = os.path.normpath(sys.prefix)
- self.exec_prefix = os.path.normpath(sys.exec_prefix)
+ # self.prefix is set to sys.prefix + /local/
+ # if neither RPM build nor virtual environment is
+ # detected to make pip and distutils install packages
+ # into the separate location.
+ if (not (hasattr(sys, 'real_prefix') or
+ sys.prefix != sys.base_prefix) and
+ 'RPM_BUILD_ROOT' not in os.environ):
+ addition = "/local"
+ else:
+ addition = ""
+
+ self.prefix = os.path.normpath(sys.prefix) + addition
+ self.exec_prefix = os.path.normpath(sys.exec_prefix) + addition
else:
if self.exec_prefix is None:
Index: Python-3.11.8/Lib/site.py
===================================================================
--- Python-3.11.8.orig/Lib/site.py
+++ Python-3.11.8/Lib/site.py
@@ -387,8 +387,15 @@ def getsitepackages(prefixes=None):
return sitepackages
def addsitepackages(known_paths, prefixes=None):
- """Add site-packages to sys.path"""
+ """Add site-packages to sys.path
+
+ '/usr/local' is included in PREFIXES if RPM build is not detected
+ to make packages installed into this location visible.
+
+ """
_trace("Processing global site-packages")
+ if ENABLE_USER_SITE and 'RPM_BUILD_ROOT' not in os.environ:
+ PREFIXES.insert(0, "/usr/local")
for sitedir in getsitepackages(prefixes):
if os.path.isdir(sitedir):
addsitedir(sitedir, known_paths)

26
PACKAGING-NOTES Normal file
View File

@ -0,0 +1,26 @@
Notes for packagers of Python3
==============================
0. Faster build turnaround
--------------------------
By default, python builds with profile-guided optimization. This needs
an additional run of the test suite and it is generally slow.
PGO build takes around 50 minutes.
For development, use "--without profileopt" option to disable PGO. This
shortens the build time to ~5 minutes including test suite.
1. import_failed.map
----------------------
This is a mechanism installed as part of python3-base, that places shim modules
on python's path (through a generated zzzz-import-failed-hooks.pth file, so that
it is imported as much at the end as makes sense; and an _import_failed subdir
of /usr/lib/pythonX.Y). Then when the user tries to import a module that is part
of a subpackage, the ImportError will contain a helpful message telling them
which missing subpackage to install.
This can sometimes cause problems on non-standard configurations, if the pth
gets included too early (for instance if you are using a script to include all
pths by hand in some strange order). Just something to look out for.

BIN
Python-3.11.9.tar.xz (Stored with Git LFS) Normal file

Binary file not shown.

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

@ -0,0 +1,16 @@
-----BEGIN PGP SIGNATURE-----
iQIzBAABCAAdFiEEz9yiRbEEPPKl+Xhl/+h0BBaL2EcFAmYNMEcACgkQ/+h0BBaL
2EeHhxAAuuIM9bl0dgAWOjbgRjCeXR8aFdfcI4dkO7bZrUy8eKbM+XCvPUUvloRJ
vzGkxYyTmI4kcNPOHfscUwH7AVVij8nGv7WeaXBUZGIXNwfHwvqOxvYvSsNNNFnr
70yJB7Df8/2s0XqFx3X1aWcnyMDerWKpfJ/VI/NPmCVxkYXGshuTTSFcCMTSFBQB
sNrIb5NWAsBF4R85uRQDlCg1AoyaKOdJNQkPo1Nrjol1ExJ+MHE7+E+QL9pQkUWG
SBISPUhJySBAegxolw6YR5dz1L4nukueQDJz3NizUeQGDvH7h1ImY8cypRi44U61
SUUHhBfmUBiC2dS/tTQawySULWcgbkV4GJ6cJZfDd95uffd4S/GDJCa2wCE2UTlA
XzQHwbcnIeoL064gX7ruBuFHJ6n/Oz7nZkFqbH2aqLTAWgLiUq31xH3HY734sL6X
zIJQRbcK1EM7cnNjKMVPlnHpAeKbsbHbU6yzWwZ7reIoyWlZ7vEGrfXO7Kmul93K
wVaWu0AiOY566ugekdDx4cKV+FQN6oppAN63yTfPJ2Ddcmxs4KNrtozw9OAgDTPE
GTPFD6V1CMuyQj/jOpAmbj+4bRD4Mx3u2PSittvrIeopxrXPsGGSZ5kdl62Xa2+A
DzKyYNXzcmxqS9lGdFb+OWCTyAIXxwZrdz1Q61g5xDvR9z/wZiI=
=Br9/
-----END PGP SIGNATURE-----

43
README.SUSE Normal file
View File

@ -0,0 +1,43 @@
Python 3 in SUSE
==============
* Subpackages *
Python 3 is split into several subpackages, based on external dependencies.
The main package 'python3' has soft dependencies on all subpackages needed to
assemble the standard library; however, these might not all be installed by default.
If you attempt to import a module that is currently not installed, an ImportError is thrown,
with instructions to install the missing subpackage. Installing the subpackage might result
in installing libraries that the subpackage requires to function.
* ensurepip *
The 'ensurepip' module from Python 3 standard library (PEP 453) is supposed to deploy
a bundled copy of the pip installer. This makes no sense in a managed distribution like SUSE.
Instead, you need to install package 'python3-pip'. Usually this will be installed automatically
with 'python3'.
Using 'ensurepip' when pip is not installed will result in an ImportError with instructions
to install 'python3-pip'.
* Documentation *
You can find documentation in seprarate packages: python3-doc and
python3-doc-pdf. These contan following documents:
Tutorial, What's New in Python, Global Module Index, Library Reference,
Macintosh Module Reference, Installing Python Modules, Distributing Python
Modules, Language Reference, Extending and Embedding, Python/C API,
Documenting Python
The python3-doc package constains many text files from source tarball.
* Interactive mode *
Interactive mode is by default enhanced with of history and command completion.
If you don't like these features, you can unset the PYTHONSTARTUP variable
in your .profile or disable it system wide in /etc/profile.d/python.sh.

3
_multibuild Normal file
View File

@ -0,0 +1,3 @@
<multibuild>
<package>base</package>
</multibuild>

BIN
bluez-devel-vendor.tar.xz (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -0,0 +1,173 @@
From 5754521af1d51aa8e445cba07a093bbc0c88596d Mon Sep 17 00:00:00 2001
From: Zackery Spytz <zspytz@gmail.com>
Date: Mon, 16 Dec 2019 18:24:08 -0700
Subject: [PATCH] bpo-31046: ensurepip does not honour the value of $(prefix)
Co-Authored-By: Xavier de Gaye <xdegaye@gmail.com>
---
Doc/library/ensurepip.rst | 9 +++--
Lib/ensurepip/__init__.py | 18 +++++++---
Lib/test/test_ensurepip.py | 11 ++++++
Makefile.pre.in | 4 +-
Misc/NEWS.d/next/Build/2019-12-16-17-50-42.bpo-31046.XA-Qfr.rst | 1
5 files changed, 34 insertions(+), 9 deletions(-)
create mode 100644 Misc/NEWS.d/next/Build/2019-12-16-17-50-42.bpo-31046.XA-Qfr.rst
Index: Python-3.11.8/Doc/library/ensurepip.rst
===================================================================
--- Python-3.11.8.orig/Doc/library/ensurepip.rst
+++ Python-3.11.8/Doc/library/ensurepip.rst
@@ -59,7 +59,9 @@ is at least as recent as the one availab
By default, ``pip`` is installed into the current virtual environment
(if one is active) or into the system site packages (if there is no
active virtual environment). The installation location can be controlled
-through two additional command line options:
+through some additional command line options:
+
+* ``--prefix <dir>``: Installs ``pip`` using the given directory prefix.
* :samp:`--root {dir}`: Installs ``pip`` relative to the given root directory
rather than the root of the currently active virtual environment (if any)
@@ -92,7 +94,7 @@ Module API
Returns a string specifying the available version of pip that will be
installed when bootstrapping an environment.
-.. function:: bootstrap(root=None, upgrade=False, user=False, \
+.. function:: bootstrap(root=None, prefix=None, upgrade=False, user=False, \
altinstall=False, default_pip=False, \
verbosity=0)
@@ -102,6 +104,8 @@ Module API
If *root* is ``None``, then installation uses the default install location
for the current environment.
+ *prefix* specifies the directory prefix to use when installing.
+
*upgrade* indicates whether or not to upgrade an existing installation
of an earlier version of ``pip`` to the available version.
@@ -122,6 +126,8 @@ Module API
*verbosity* controls the level of output to :data:`sys.stdout` from the
bootstrapping operation.
+ .. versionchanged:: 3.9 the *prefix* parameter was added.
+
.. audit-event:: ensurepip.bootstrap root ensurepip.bootstrap
.. note::
Index: Python-3.11.8/Lib/ensurepip/__init__.py
===================================================================
--- Python-3.11.8.orig/Lib/ensurepip/__init__.py
+++ Python-3.11.8/Lib/ensurepip/__init__.py
@@ -122,27 +122,27 @@ def _disable_pip_configuration_settings(
os.environ['PIP_CONFIG_FILE'] = os.devnull
-def bootstrap(*, root=None, upgrade=False, user=False,
+def bootstrap(*, root=None, prefix=None, upgrade=False, user=False,
altinstall=False, default_pip=False,
verbosity=0):
"""
Bootstrap pip into the current Python installation (or the given root
- directory).
+ and directory prefix).
Note that calling this function will alter both sys.path and os.environ.
"""
# Discard the return value
- _bootstrap(root=root, upgrade=upgrade, user=user,
+ _bootstrap(root=root, prefix=prefix, upgrade=upgrade, user=user,
altinstall=altinstall, default_pip=default_pip,
verbosity=verbosity)
-def _bootstrap(*, root=None, upgrade=False, user=False,
+def _bootstrap(*, root=None, prefix=None, upgrade=False, user=False,
altinstall=False, default_pip=False,
verbosity=0):
"""
Bootstrap pip into the current Python installation (or the given root
- directory). Returns pip command status code.
+ and directory prefix). Returns pip command status code.
Note that calling this function will alter both sys.path and os.environ.
"""
@@ -192,6 +192,8 @@ def _bootstrap(*, root=None, upgrade=Fal
args = ["install", "--no-cache-dir", "--no-index", "--find-links", tmpdir]
if root:
args += ["--root", root]
+ if prefix:
+ args += ["--prefix", prefix]
if upgrade:
args += ["--upgrade"]
if user:
@@ -267,6 +269,11 @@ def _main(argv=None):
help="Install everything relative to this alternate root directory.",
)
parser.add_argument(
+ "--prefix",
+ default=None,
+ help="Install everything using this prefix.",
+ )
+ parser.add_argument(
"--altinstall",
action="store_true",
default=False,
@@ -285,6 +292,7 @@ def _main(argv=None):
return _bootstrap(
root=args.root,
+ prefix=args.prefix,
upgrade=args.upgrade,
user=args.user,
verbosity=args.verbosity,
Index: Python-3.11.8/Lib/test/test_ensurepip.py
===================================================================
--- Python-3.11.8.orig/Lib/test/test_ensurepip.py
+++ Python-3.11.8/Lib/test/test_ensurepip.py
@@ -112,6 +112,17 @@ class TestBootstrap(EnsurepipMixin, unit
unittest.mock.ANY,
)
+ def test_bootstrapping_with_prefix(self):
+ ensurepip.bootstrap(prefix="/foo/bar/")
+ self.run_pip.assert_called_once_with(
+ [
+ "install", "--no-cache-dir", "--no-index", "--find-links",
+ unittest.mock.ANY, "--prefix", "/foo/bar/",
+ "setuptools", "pip",
+ ],
+ unittest.mock.ANY,
+ )
+
def test_bootstrapping_with_user(self):
ensurepip.bootstrap(user=True)
Index: Python-3.11.8/Makefile.pre.in
===================================================================
--- Python-3.11.8.orig/Makefile.pre.in
+++ Python-3.11.8/Makefile.pre.in
@@ -1761,7 +1761,7 @@ install: @FRAMEWORKINSTALLFIRST@ commoni
install|*) ensurepip="" ;; \
esac; \
$(RUNSHARED) $(PYTHON_FOR_BUILD) -m ensurepip \
- $$ensurepip --root=$(DESTDIR)/ ; \
+ $$ensurepip --root=$(DESTDIR)/ --prefix=$(prefix) ; \
fi
altinstall: commoninstall
@@ -1771,7 +1771,7 @@ altinstall: commoninstall
install|*) ensurepip="--altinstall" ;; \
esac; \
$(RUNSHARED) $(PYTHON_FOR_BUILD) -m ensurepip \
- $$ensurepip --root=$(DESTDIR)/ ; \
+ $$ensurepip --root=$(DESTDIR)/ --prefix=$(prefix) ; \
fi
commoninstall: check-clean-src @FRAMEWORKALTINSTALLFIRST@ \
Index: Python-3.11.8/Misc/NEWS.d/next/Build/2019-12-16-17-50-42.bpo-31046.XA-Qfr.rst
===================================================================
--- /dev/null
+++ Python-3.11.8/Misc/NEWS.d/next/Build/2019-12-16-17-50-42.bpo-31046.XA-Qfr.rst
@@ -0,0 +1 @@
+A directory prefix can now be specified when using :mod:`ensurepip`.

View File

@ -0,0 +1,21 @@
From a3052035485bd2836e40f5284657ca105382cbfd Mon Sep 17 00:00:00 2001
From: sobolevn <mail@sobolevn.me>
Date: Tue, 5 Mar 2024 20:24:16 +0300
Subject: [PATCH] gh-116112: Fix `ResourceWarning` in
`test_asyncio.test_stream`
Co-authored-by: @CendioOssman
---
Lib/test/test_asyncio/test_streams.py | 1 +
1 file changed, 1 insertion(+)
--- a/Lib/test/test_asyncio/test_streams.py
+++ b/Lib/test/test_asyncio/test_streams.py
@@ -1156,6 +1156,7 @@ os.close(fd)
def test_unhandled_cancel(self):
async def handle_echo(reader, writer):
+ writer.close()
asyncio.current_task().cancel()
messages = self._basetest_unhandled_exceptions(handle_echo)
self.assertEqual(messages, [])

View File

@ -0,0 +1,35 @@
--- a/Modules/main.c
+++ b/Modules/main.c
@@ -487,10 +487,6 @@
Py_InspectFlag = 0; /* do exit on SystemExit */
int exitcode;
- if (pymain_run_startup(config, &exitcode)) {
- return exitcode;
- }
-
if (pymain_run_interactive_hook(&exitcode)) {
return exitcode;
}
@@ -588,6 +584,10 @@
pymain_header(config);
+ if (pymain_run_startup(config, exitcode)) {
+ return;
+ }
+
if (config->run_command) {
*exitcode = pymain_run_command(config->run_command);
}
--- a/Python/initconfig.c
+++ b/Python/initconfig.c
@@ -108,7 +108,7 @@
/* Envvars that don't have equivalent command-line options are listed first */
static const char usage_envvars[] =
"Environment variables that change behavior:\n"
-"PYTHONSTARTUP : file executed on interactive startup (no default)\n"
+"PYTHONSTARTUP : file executed on Python startup\n"
"PYTHONPATH : '%lc'-separated list of directories prefixed to the\n"
" default module search path. The result is sys.path.\n"
"PYTHONHOME : alternate <prefix> directory (or <prefix>%lc<exec_prefix>).\n"

View File

@ -0,0 +1,17 @@
---
Lib/distutils/util.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
Index: Python-3.11.8/Lib/distutils/util.py
===================================================================
--- Python-3.11.8.orig/Lib/distutils/util.py
+++ Python-3.11.8/Lib/distutils/util.py
@@ -436,7 +436,7 @@ byte_compile(files, optimize=%r, force=%
else:
from py_compile import compile
- for file in py_files:
+ for file in sorted(py_files):
if file[-3:] != ".py":
# This lets us be lazy and not filter filenames in
# the "install_lib" command.

40
fix_configure_rst.patch Normal file
View File

@ -0,0 +1,40 @@
---
Doc/using/configure.rst | 3 ---
Misc/NEWS | 2 +-
2 files changed, 1 insertion(+), 4 deletions(-)
--- a/Doc/using/configure.rst
+++ b/Doc/using/configure.rst
@@ -43,7 +43,6 @@ General Options
See :data:`sys.int_info.bits_per_digit <sys.int_info>`.
-.. option:: --with-cxx-main
.. option:: --with-cxx-main=COMPILER
Compile the Python ``main()`` function and link Python executable with C++
@@ -529,13 +528,11 @@ macOS Options
See ``Mac/README.rst``.
-.. option:: --enable-universalsdk
.. option:: --enable-universalsdk=SDKDIR
Create a universal binary build. *SDKDIR* specifies which macOS SDK should
be used to perform the build (default is no).
-.. option:: --enable-framework
.. option:: --enable-framework=INSTALLDIR
Create a Python.framework rather than a traditional Unix install. Optional
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -9768,7 +9768,7 @@ C API
- bpo-40939: Removed documentation for the removed ``PyParser_*`` C API.
- bpo-43795: The list in :ref:`limited-api-list` now shows the public name
- :c:struct:`PyFrameObject` rather than ``_frame``. The non-existing entry
+ :c:type:`PyFrameObject` rather than ``_frame``. The non-existing entry
``_node`` no longer appears in the list.
- bpo-44378: :c:func:`Py_IS_TYPE` no longer uses :c:func:`Py_TYPE` to avoid

35
idle3.appdata.xml Normal file
View File

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Copyright 2017 Zbigniew Jędrzejewski-Szmek -->
<application>
<id type="desktop">idle3.desktop</id>
<name>IDLE3</name>
<metadata_licence>CC0</metadata_licence>
<project_license>Python-2.0</project_license>
<summary>Python 3 Integrated Development and Learning Environment</summary>
<description>
<p>
IDLE is Pythons Integrated Development and Learning Environment.
The GUI is uniform between Windows, Unix, and Mac OS X.
IDLE provides an easy way to start writing, running, and debugging
Python code.
</p>
<p>
IDLE is written in pure Python, and uses the tkinter GUI toolkit.
It provides:
</p>
<ul>
<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>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>
</ul>
</description>
<url type="homepage">https://docs.python.org/3/library/idle.html</url>
<screenshots>
<screenshot type="default">http://in.waw.pl/~zbyszek/fedora/idle3-appdata/idle3-main-window.png</screenshot>
<screenshot>http://in.waw.pl/~zbyszek/fedora/idle3-appdata/idle3-class-browser.png</screenshot>
<screenshot>http://in.waw.pl/~zbyszek/fedora/idle3-appdata/idle3-code-viewer.png</screenshot>
</screenshots>
<update_contact>zbyszek@in.waw.pl</update_contact>
</application>

12
idle3.desktop Normal file
View File

@ -0,0 +1,12 @@
[Desktop Entry]
Version=1.0
Name=IDLE 3
GenericName=Python 3 IDE
Comment=Python 3 Integrated Development and Learning Environment
Exec=idle3 %F
TryExec=idle3
Terminal=false
Type=Application
Icon=idle3
Categories=Development;IDE;
MimeType=text/x-python;

7
import_failed.map Normal file
View File

@ -0,0 +1,7 @@
python311-curses: curses _curses _curses_panel
python311-dbm: dbm _dbm _gdbm
python311-idle: idlelib
python311-testsuite: test _ctypes_test _testbuffer _testcapi _testinternalcapi _testimportmultiple _testmultiphase xxlimited
python311-tk: tkinter _tkinter
python311-tools: turtledemo
python311: sqlite3 readline _sqlite3 nis

23
import_failed.py Normal file
View File

@ -0,0 +1,23 @@
import sys, os
from sysconfig import get_path
failed_map_path = os.path.join(get_path('stdlib'), '_import_failed', 'import_failed.map')
if __spec__:
failed_name = __spec__.name
else:
failed_name = __name__
with open(failed_map_path) as fd:
for line in fd:
package = line.split(':')[0]
imports = line.split(':')[1]
if failed_name in imports:
raise ImportError(f"""Module '{failed_name}' is not installed.
Use:
sudo zypper install {package}
to install it.""")
raise ImportError(f"""Module '{failed_name}' is not installed.
It is supposed to be part of python3 distribution, but missing from failed import map.
Please file a bug on the SUSE Bugzilla.""")

28
macros.python3 Normal file
View File

@ -0,0 +1,28 @@
%have_python3 1
# commented out legacy macro definitions
#py3_prefix /usr
#py3_incdir /usr/include/python3.5m
#py3_ver 3.5
# these should now be provided by macros.python_all
#python3_sitearch /usr/lib64/python3.5/site-packages
#python3_sitelib /usr/lib/python3.5/site-packages
#python3_version 3.5
# hard to say if anyone ever used these?
#py3_soflags cpython-35m-x86_64-linux-gnu
#py3_abiflags m
%cpython3_soabi %(python3 -c "import sysconfig; print(sysconfig.get_config_var('SOABI'))")
%py3_soflags %cpython3_soabi
# compilation macros that might be in use somewhere
%py3_compile(O) \
find %1 -name '*.pyc' -exec rm -f {} ";"\
python3 -c "import sys, os, compileall; br='%{buildroot}'; compileall.compile_dir(sys.argv[1], ddir=br and (sys.argv[1][len(os.path.abspath(br)):]+'/') or None)" %1\
%{-O:\
find %1 -name '*.pyo' -exec rm -f {} ";"\
python3 -O -c "import sys, os, compileall; br='%{buildroot}'; compileall.compile_dir(sys.argv[1], ddir=br and (sys.argv[1][len(os.path.abspath(br)):]+'/') or None)" %1\
}

653
no-skipif-doctests.patch Normal file
View File

@ -0,0 +1,653 @@
only in patch2:
unchanged:
---
Doc/library/turtle.rst | 82 -------------------------------------------------
1 file changed, 82 deletions(-)
--- a/Doc/library/turtle.rst
+++ b/Doc/library/turtle.rst
@@ -440,7 +440,6 @@ Turtle motion
turtle is headed.
.. doctest::
- :skipif: _tkinter is None
>>> turtle.position()
(0.00,0.00)
@@ -467,7 +466,6 @@ Turtle motion
>>> turtle.goto(0, 0)
.. doctest::
- :skipif: _tkinter is None
>>> turtle.position()
(0.00,0.00)
@@ -486,13 +484,11 @@ Turtle motion
orientation depends on the turtle mode, see :func:`mode`.
.. doctest::
- :skipif: _tkinter is None
:hide:
>>> turtle.setheading(22)
.. doctest::
- :skipif: _tkinter is None
>>> turtle.heading()
22.0
@@ -511,13 +507,11 @@ Turtle motion
orientation depends on the turtle mode, see :func:`mode`.
.. doctest::
- :skipif: _tkinter is None
:hide:
>>> turtle.setheading(22)
.. doctest::
- :skipif: _tkinter is None
>>> turtle.heading()
22.0
@@ -540,13 +534,11 @@ Turtle motion
not change the turtle's orientation.
.. doctest::
- :skipif: _tkinter is None
:hide:
>>> turtle.goto(0, 0)
.. doctest::
- :skipif: _tkinter is None
>>> tp = turtle.pos()
>>> tp
@@ -570,13 +562,11 @@ Turtle motion
unchanged.
.. doctest::
- :skipif: _tkinter is None
:hide:
>>> turtle.goto(0, 240)
.. doctest::
- :skipif: _tkinter is None
>>> turtle.position()
(0.00,240.00)
@@ -592,13 +582,11 @@ Turtle motion
Set the turtle's second coordinate to *y*, leave first coordinate unchanged.
.. doctest::
- :skipif: _tkinter is None
:hide:
>>> turtle.goto(0, 40)
.. doctest::
- :skipif: _tkinter is None
>>> turtle.position()
(0.00,40.00)
@@ -625,7 +613,6 @@ Turtle motion
=================== ====================
.. doctest::
- :skipif: _tkinter is None
>>> turtle.setheading(90)
>>> turtle.heading()
@@ -638,14 +625,12 @@ Turtle motion
its start-orientation (which depends on the mode, see :func:`mode`).
.. doctest::
- :skipif: _tkinter is None
:hide:
>>> turtle.setheading(90)
>>> turtle.goto(0, -10)
.. doctest::
- :skipif: _tkinter is None
>>> turtle.heading()
90.0
@@ -677,7 +662,6 @@ Turtle motion
calculated automatically. May be used to draw regular polygons.
.. doctest::
- :skipif: _tkinter is None
>>> turtle.home()
>>> turtle.position()
@@ -706,7 +690,6 @@ Turtle motion
.. doctest::
- :skipif: _tkinter is None
>>> turtle.home()
>>> turtle.dot()
@@ -724,7 +707,6 @@ Turtle motion
it by calling ``clearstamp(stamp_id)``.
.. doctest::
- :skipif: _tkinter is None
>>> turtle.color("blue")
>>> turtle.stamp()
@@ -740,7 +722,6 @@ Turtle motion
Delete stamp with given *stampid*.
.. doctest::
- :skipif: _tkinter is None
>>> turtle.position()
(150.00,-0.00)
@@ -785,7 +766,6 @@ Turtle motion
undo actions is determined by the size of the undobuffer.
.. doctest::
- :skipif: _tkinter is None
>>> for i in range(4):
... turtle.fd(50); turtle.lt(80)
@@ -818,7 +798,6 @@ Turtle motion
turtle turn instantly.
.. doctest::
- :skipif: _tkinter is None
>>> turtle.speed()
3
@@ -839,7 +818,6 @@ Tell Turtle's state
Return the turtle's current location (x,y) (as a :class:`Vec2D` vector).
.. doctest::
- :skipif: _tkinter is None
>>> turtle.pos()
(440.00,-0.00)
@@ -855,7 +833,6 @@ Tell Turtle's state
orientation which depends on the mode - "standard"/"world" or "logo".
.. doctest::
- :skipif: _tkinter is None
>>> turtle.goto(10, 10)
>>> turtle.towards(0,0)
@@ -867,7 +844,6 @@ Tell Turtle's state
Return the turtle's x coordinate.
.. doctest::
- :skipif: _tkinter is None
>>> turtle.home()
>>> turtle.left(50)
@@ -883,7 +859,6 @@ Tell Turtle's state
Return the turtle's y coordinate.
.. doctest::
- :skipif: _tkinter is None
>>> turtle.home()
>>> turtle.left(60)
@@ -900,7 +875,6 @@ Tell Turtle's state
:func:`mode`).
.. doctest::
- :skipif: _tkinter is None
>>> turtle.home()
>>> turtle.left(67)
@@ -917,7 +891,6 @@ Tell Turtle's state
other turtle, in turtle step units.
.. doctest::
- :skipif: _tkinter is None
>>> turtle.home()
>>> turtle.distance(30,40)
@@ -941,7 +914,6 @@ Settings for measurement
Default value is 360 degrees.
.. doctest::
- :skipif: _tkinter is None
>>> turtle.home()
>>> turtle.left(90)
@@ -964,7 +936,6 @@ Settings for measurement
``degrees(2*math.pi)``.
.. doctest::
- :skipif: _tkinter is None
>>> turtle.home()
>>> turtle.left(90)
@@ -975,7 +946,6 @@ Settings for measurement
1.5707963267948966
.. doctest::
- :skipif: _tkinter is None
:hide:
>>> turtle.degrees(360)
@@ -1011,7 +981,6 @@ Drawing state
thickness. If no argument is given, the current pensize is returned.
.. doctest::
- :skipif: _tkinter is None
>>> turtle.pensize()
1
@@ -1043,7 +1012,6 @@ Drawing state
attributes in one statement.
.. doctest::
- :skipif: _tkinter is None
:options: +NORMALIZE_WHITESPACE
>>> turtle.pen(fillcolor="black", pencolor="red", pensize=10)
@@ -1066,7 +1034,6 @@ Drawing state
Return ``True`` if pen is down, ``False`` if it's up.
.. doctest::
- :skipif: _tkinter is None
>>> turtle.penup()
>>> turtle.isdown()
@@ -1107,7 +1074,6 @@ Color control
newly set pencolor.
.. doctest::
- :skipif: _tkinter is None
>>> colormode()
1.0
@@ -1156,7 +1122,6 @@ Color control
with the newly set fillcolor.
.. doctest::
- :skipif: _tkinter is None
>>> turtle.fillcolor("violet")
>>> turtle.fillcolor()
@@ -1195,7 +1160,6 @@ Color control
with the newly set colors.
.. doctest::
- :skipif: _tkinter is None
>>> turtle.color("red", "green")
>>> turtle.color()
@@ -1212,7 +1176,6 @@ Filling
~~~~~~~
.. doctest::
- :skipif: _tkinter is None
:hide:
>>> turtle.home()
@@ -1222,7 +1185,6 @@ Filling
Return fillstate (``True`` if filling, ``False`` else).
.. doctest::
- :skipif: _tkinter is None
>>> turtle.begin_fill()
>>> if turtle.filling():
@@ -1247,7 +1209,6 @@ Filling
above may be either all yellow or have some white regions.
.. doctest::
- :skipif: _tkinter is None
>>> turtle.color("black", "red")
>>> turtle.begin_fill()
@@ -1264,7 +1225,6 @@ More drawing control
variables to the default values.
.. doctest::
- :skipif: _tkinter is None
>>> turtle.goto(0,-22)
>>> turtle.left(100)
@@ -1315,7 +1275,6 @@ Visibility
drawing observably.
.. doctest::
- :skipif: _tkinter is None
>>> turtle.hideturtle()
@@ -1326,7 +1285,6 @@ Visibility
Make the turtle visible.
.. doctest::
- :skipif: _tkinter is None
>>> turtle.showturtle()
@@ -1357,7 +1315,6 @@ Appearance
deal with shapes see Screen method :func:`register_shape`.
.. doctest::
- :skipif: _tkinter is None
>>> turtle.shape()
'classic'
@@ -1383,7 +1340,6 @@ Appearance
``resizemode("user")`` is called by :func:`shapesize` when used with arguments.
.. doctest::
- :skipif: _tkinter is None
>>> turtle.resizemode()
'noresize'
@@ -1407,7 +1363,6 @@ Appearance
of the shape's outline.
.. doctest::
- :skipif: _tkinter is None
>>> turtle.shapesize()
(1.0, 1.0, 1)
@@ -1432,7 +1387,6 @@ Appearance
heading of the turtle are sheared.
.. doctest::
- :skipif: _tkinter is None
>>> turtle.shape("circle")
>>> turtle.shapesize(5,2)
@@ -1449,7 +1403,6 @@ Appearance
change the turtle's heading (direction of movement).
.. doctest::
- :skipif: _tkinter is None
>>> turtle.reset()
>>> turtle.shape("circle")
@@ -1469,7 +1422,6 @@ Appearance
(direction of movement).
.. doctest::
- :skipif: _tkinter is None
>>> turtle.reset()
>>> turtle.shape("circle")
@@ -1495,7 +1447,6 @@ Appearance
turtle (its direction of movement).
.. doctest::
- :skipif: _tkinter is None
>>> turtle.reset()
>>> turtle.shape("circle")
@@ -1524,7 +1475,6 @@ Appearance
given matrix.
.. doctest::
- :skipif: _tkinter is None
>>> turtle = Turtle()
>>> turtle.shape("square")
@@ -1540,7 +1490,6 @@ Appearance
can be used to define a new shape or components of a compound shape.
.. doctest::
- :skipif: _tkinter is None
>>> turtle.shape("square")
>>> turtle.shapetransform(4, -1, 0, 2)
@@ -1565,7 +1514,6 @@ Using events
procedural way:
.. doctest::
- :skipif: _tkinter is None
>>> def turn(x, y):
... left(180)
@@ -1586,7 +1534,6 @@ Using events
``None``, existing bindings are removed.
.. doctest::
- :skipif: _tkinter is None
>>> class MyTurtle(Turtle):
... def glow(self,x,y):
@@ -1614,7 +1561,6 @@ Using events
mouse-click event on that turtle.
.. doctest::
- :skipif: _tkinter is None
>>> turtle.ondrag(turtle.goto)
@@ -1642,7 +1588,6 @@ Special Turtle methods
Return the last recorded polygon.
.. doctest::
- :skipif: _tkinter is None
>>> turtle.home()
>>> turtle.begin_poly()
@@ -1662,7 +1607,6 @@ Special Turtle methods
turtle properties.
.. doctest::
- :skipif: _tkinter is None
>>> mick = Turtle()
>>> joe = mick.clone()
@@ -1675,7 +1619,6 @@ Special Turtle methods
return the "anonymous turtle":
.. doctest::
- :skipif: _tkinter is None
>>> pet = getturtle()
>>> pet.fd(50)
@@ -1689,7 +1632,6 @@ Special Turtle methods
TurtleScreen methods can then be called for that object.
.. doctest::
- :skipif: _tkinter is None
>>> ts = turtle.getscreen()
>>> ts
@@ -1707,7 +1649,6 @@ Special Turtle methods
``None``, the undobuffer is disabled.
.. doctest::
- :skipif: _tkinter is None
>>> turtle.setundobuffer(42)
@@ -1717,7 +1658,6 @@ Special Turtle methods
Return number of entries in the undobuffer.
.. doctest::
- :skipif: _tkinter is None
>>> while undobufferentries():
... undo()
@@ -1740,7 +1680,6 @@ below:
For example:
.. doctest::
- :skipif: _tkinter is None
>>> s = Shape("compound")
>>> poly1 = ((0,0),(10,-5),(0,10),(-10,-5))
@@ -1751,7 +1690,6 @@ below:
3. Now add the Shape to the Screen's shapelist and use it:
.. doctest::
- :skipif: _tkinter is None
>>> register_shape("myshape", s)
>>> shape("myshape")
@@ -1771,7 +1709,6 @@ Most of the examples in this section ref
``screen``.
.. doctest::
- :skipif: _tkinter is None
:hide:
>>> screen = Screen()
@@ -1788,7 +1725,6 @@ Window control
Set or return background color of the TurtleScreen.
.. doctest::
- :skipif: _tkinter is None
>>> screen.bgcolor("orange")
>>> screen.bgcolor()
@@ -1880,7 +1816,6 @@ Window control
distorted.
.. doctest::
- :skipif: _tkinter is None
>>> screen.reset()
>>> screen.setworldcoordinates(-50,-7.5,50,7.5)
@@ -1891,7 +1826,6 @@ Window control
... left(45); fd(2) # a regular octagon
.. doctest::
- :skipif: _tkinter is None
:hide:
>>> screen.reset()
@@ -1913,7 +1847,6 @@ Animation control
Optional argument:
.. doctest::
- :skipif: _tkinter is None
>>> screen.delay()
10
@@ -1935,7 +1868,6 @@ Animation control
:func:`delay`).
.. doctest::
- :skipif: _tkinter is None
>>> screen.tracer(8, 25)
>>> dist = 2
@@ -1972,7 +1904,6 @@ Using screen events
must have the focus. (See method :func:`listen`.)
.. doctest::
- :skipif: _tkinter is None
>>> def f():
... fd(50)
@@ -1993,7 +1924,6 @@ Using screen events
must have focus. (See method :func:`listen`.)
.. doctest::
- :skipif: _tkinter is None
>>> def f():
... fd(50)
@@ -2018,7 +1948,6 @@ Using screen events
named ``turtle``:
.. doctest::
- :skipif: _tkinter is None
>>> screen.onclick(turtle.goto) # Subsequently clicking into the TurtleScreen will
>>> # make the turtle move to the clicked point.
@@ -2038,7 +1967,6 @@ Using screen events
Install a timer that calls *fun* after *t* milliseconds.
.. doctest::
- :skipif: _tkinter is None
>>> running = True
>>> def f():
@@ -2120,7 +2048,6 @@ Settings and special methods
============ ========================= ===================
.. doctest::
- :skipif: _tkinter is None
>>> mode("logo") # resets turtle heading to north
>>> mode()
@@ -2135,7 +2062,6 @@ Settings and special methods
values of color triples have to be in the range 0..*cmode*.
.. doctest::
- :skipif: _tkinter is None
>>> screen.colormode(1)
>>> turtle.pencolor(240, 160, 80)
@@ -2156,7 +2082,6 @@ Settings and special methods
do with a Tkinter Canvas.
.. doctest::
- :skipif: _tkinter is None
>>> cv = screen.getcanvas()
>>> cv
@@ -2168,7 +2093,6 @@ Settings and special methods
Return a list of names of all currently available turtle shapes.
.. doctest::
- :skipif: _tkinter is None
>>> screen.getshapes()
['arrow', 'blank', 'circle', ..., 'turtle']
@@ -2192,7 +2116,6 @@ Settings and special methods
coordinates: Install the corresponding polygon shape.
.. doctest::
- :skipif: _tkinter is None
>>> screen.register_shape("triangle", ((5,-3), (0,5), (-5,-3)))
@@ -2208,7 +2131,6 @@ Settings and special methods
Return the list of turtles on the screen.
.. doctest::
- :skipif: _tkinter is None
>>> for turtle in screen.turtles():
... turtle.color("red")
@@ -2270,7 +2192,6 @@ Methods specific to Screen, not inherite
center window vertically
.. doctest::
- :skipif: _tkinter is None
>>> screen.setup (width=200, height=200, startx=0, starty=0)
>>> # sets window to 200x200 pixels, in upper left of screen
@@ -2286,7 +2207,6 @@ Methods specific to Screen, not inherite
Set title of turtle window to *titlestring*.
.. doctest::
- :skipif: _tkinter is None
>>> screen.title("Welcome to the turtle zoo!")
@@ -2357,7 +2277,6 @@ Public classes
Example:
.. doctest::
- :skipif: _tkinter is None
>>> poly = ((0,0),(10,-5),(0,10),(-10,-5))
>>> s = Shape("compound")
@@ -2743,7 +2662,6 @@ Changes since Python 3.0
.. doctest::
- :skipif: _tkinter is None
:hide:
>>> for turtle in turtles():

View File

@ -0,0 +1,92 @@
--- a/configure
+++ b/configure
@@ -23246,20 +23246,18 @@
#include <openssl/opensslv.h>
#include <openssl/ssl.h>
- #if OPENSSL_VERSION_NUMBER < 0x10101000L
- #error "OpenSSL >= 1.1.1 is required"
- #endif
- static void keylog_cb(const SSL *ssl, const char *line) {}
int
main ()
{
-
- SSL_CTX *ctx = SSL_CTX_new(TLS_client_method());
- SSL_CTX_set_keylog_callback(ctx, keylog_cb);
+ const SSL_METHOD *method = NULL;
+#ifdef TLS_client_method
+ method = TLS_client_method()
+#else
+ method = TLSv1_method();
+#endif
+ SSL_CTX *ctx = SSL_CTX_new(method);
SSL *ssl = SSL_new(ctx);
- X509_VERIFY_PARAM *param = SSL_get0_param(ssl);
- X509_VERIFY_PARAM_set1_host(param, "python.org", 0);
SSL_free(ssl);
SSL_CTX_free(ctx);
@@ -23307,9 +23305,6 @@
#include <openssl/opensslv.h>
#include <openssl/evp.h>
- #if OPENSSL_VERSION_NUMBER < 0x10101000L
- #error "OpenSSL >= 1.1.1 is required"
- #endif
int
main ()
@@ -23317,9 +23312,6 @@
OBJ_nid2sn(NID_md5);
OBJ_nid2sn(NID_sha1);
- OBJ_nid2sn(NID_sha3_512);
- OBJ_nid2sn(NID_blake2b512);
- EVP_PBE_scrypt(NULL, 0, NULL, 0, 2, 8, 1, 0, NULL, 0);
;
return 0;
--- a/configure.ac
+++ b/configure.ac
@@ -6689,16 +6689,15 @@
AC_LINK_IFELSE([AC_LANG_PROGRAM([
#include <openssl/opensslv.h>
#include <openssl/ssl.h>
- #if OPENSSL_VERSION_NUMBER < 0x10101000L
- #error "OpenSSL >= 1.1.1 is required"
- #endif
- static void keylog_cb(const SSL *ssl, const char *line) {}
], [
- SSL_CTX *ctx = SSL_CTX_new(TLS_client_method());
- SSL_CTX_set_keylog_callback(ctx, keylog_cb);
+ const SSL_METHOD *method = NULL;
+#ifdef TLS_client_method
+ method = TLS_client_method()
+#else
+ method = TLSv1_method();
+#endif
+ SSL_CTX *ctx = SSL_CTX_new(method);
SSL *ssl = SSL_new(ctx);
- X509_VERIFY_PARAM *param = SSL_get0_param(ssl);
- X509_VERIFY_PARAM_set1_host(param, "python.org", 0);
SSL_free(ssl);
SSL_CTX_free(ctx);
])], [ac_cv_working_openssl_ssl=yes], [ac_cv_working_openssl_ssl=no])
@@ -6714,15 +6713,9 @@
AC_LINK_IFELSE([AC_LANG_PROGRAM([
#include <openssl/opensslv.h>
#include <openssl/evp.h>
- #if OPENSSL_VERSION_NUMBER < 0x10101000L
- #error "OpenSSL >= 1.1.1 is required"
- #endif
], [
OBJ_nid2sn(NID_md5);
OBJ_nid2sn(NID_sha1);
- OBJ_nid2sn(NID_sha3_512);
- OBJ_nid2sn(NID_blake2b512);
- EVP_PBE_scrypt(NULL, 0, NULL, 0, 2, 8, 1, 0, NULL, 0);
])], [ac_cv_working_openssl_hashlib=yes], [ac_cv_working_openssl_hashlib=no])
])
])

View File

@ -0,0 +1,702 @@
--- a/Modules/clinic/_ssl.c.h
+++ b/Modules/clinic/_ssl.c.h
@@ -88,6 +88,7 @@
return return_value;
}
+#if OPENSSL_VERSION_1_1
PyDoc_STRVAR(_ssl__SSLSocket_get_verified_chain__doc__,
"get_verified_chain($self, /)\n"
"--\n"
@@ -121,6 +122,7 @@
{
return _ssl__SSLSocket_get_unverified_chain_impl(self);
}
+#endif
PyDoc_STRVAR(_ssl__SSLSocket_shared_ciphers__doc__,
"shared_ciphers($self, /)\n"
--- a/Modules/_hashopenssl.c
+++ b/Modules/_hashopenssl.c
@@ -43,12 +43,55 @@
# error "OPENSSL_THREADS is not defined, Python requires thread-safe OpenSSL"
#endif
+#if (OPENSSL_VERSION_NUMBER < 0x10100000L) || defined(LIBRESSL_VERSION_NUMBER)
+/* OpenSSL < 1.1.0 */
+#define EVP_MD_CTX_new EVP_MD_CTX_create
+#define EVP_MD_CTX_free EVP_MD_CTX_destroy
+HMAC_CTX *
+HMAC_CTX_new(void)
+{
+ HMAC_CTX *ctx = OPENSSL_malloc(sizeof(HMAC_CTX));
+ if (ctx != NULL) {
+ memset(ctx, 0, sizeof(HMAC_CTX));
+ HMAC_CTX_init(ctx);
+ }
+ return ctx;
+}
+
+void
+HMAC_CTX_free(HMAC_CTX *ctx)
+{
+ if (ctx != NULL) {
+ HMAC_CTX_cleanup(ctx);
+ OPENSSL_free(ctx);
+ }
+}
+
+const EVP_MD *
+HMAC_CTX_get_md(const HMAC_CTX *ctx)
+{
+ return ctx->md;
+}
+#endif
+
#define MUNCH_SIZE INT_MAX
+#if ! ((OPENSSL_VERSION_NUMBER < 0x10100000L) || defined(LIBRESSL_VERSION_NUMBER))
+/* OpenSSL < 1.1.0 */
#define PY_OPENSSL_HAS_SCRYPT 1
+#endif
+#ifdef NID_sha512_224
+#define PY_OPENSSL_HAS_SHA2_TRUNCATED 1
+#endif
+#ifdef NID_sha3_224
#define PY_OPENSSL_HAS_SHA3 1
+#endif
+#ifdef NID_shake128
#define PY_OPENSSL_HAS_SHAKE 1
+#endif
+#ifdef NID_blake2s256
#define PY_OPENSSL_HAS_BLAKE2 1
+#endif
#if OPENSSL_VERSION_NUMBER >= 0x30000000L
#define PY_EVP_MD EVP_MD
@@ -119,20 +162,28 @@
PY_HASH_ENTRY(Py_hash_sha256, "SHA256", SN_sha256, NID_sha256),
PY_HASH_ENTRY(Py_hash_sha384, "SHA384", SN_sha384, NID_sha384),
PY_HASH_ENTRY(Py_hash_sha512, "SHA512", SN_sha512, NID_sha512),
+#ifdef PY_OPENSSL_HAS_SHA2_TRUNCATED
/* truncated sha2 */
PY_HASH_ENTRY(Py_hash_sha512_224, "SHA512_224", SN_sha512_224, NID_sha512_224),
PY_HASH_ENTRY(Py_hash_sha512_256, "SHA512_256", SN_sha512_256, NID_sha512_256),
+#endif
+#ifdef PY_OPENSSL_HAS_SHA3
/* sha3 */
PY_HASH_ENTRY(Py_hash_sha3_224, NULL, SN_sha3_224, NID_sha3_224),
PY_HASH_ENTRY(Py_hash_sha3_256, NULL, SN_sha3_256, NID_sha3_256),
PY_HASH_ENTRY(Py_hash_sha3_384, NULL, SN_sha3_384, NID_sha3_384),
PY_HASH_ENTRY(Py_hash_sha3_512, NULL, SN_sha3_512, NID_sha3_512),
+#endif
+#ifdef PY_OPENSSL_HAS_SHAKE
/* sha3 shake */
PY_HASH_ENTRY(Py_hash_shake_128, NULL, SN_shake128, NID_shake128),
PY_HASH_ENTRY(Py_hash_shake_256, NULL, SN_shake256, NID_shake256),
+#endif
+#ifdef PY_OPENSSL_HAS_BLAKE
/* blake2 digest */
PY_HASH_ENTRY(Py_hash_blake2s, "blake2s256", SN_blake2s256, NID_blake2s256),
PY_HASH_ENTRY(Py_hash_blake2b, "blake2b512", SN_blake2b512, NID_blake2b512),
+#endif
PY_HASH_ENTRY(NULL, NULL, NULL, 0),
};
@@ -874,11 +925,15 @@
goto exit;
}
+#ifdef PY_OPENSSL_HAS_SHAKE
if ((EVP_MD_flags(digest) & EVP_MD_FLAG_XOF) == EVP_MD_FLAG_XOF) {
type = get_hashlib_state(module)->EVPXOFtype;
} else {
type = get_hashlib_state(module)->EVPtype;
}
+#else
+ type = get_hashlib_state(module)->EVPtype;
+#endif
self = newEVPobject(type);
if (self == NULL) {
--- a/Modules/_ssl/cert.c
+++ b/Modules/_ssl/cert.c
@@ -6,6 +6,8 @@
#include "openssl/pem.h"
#include "openssl/x509.h"
+#if OPENSSL_VERSION_1_1
+
/*[clinic input]
module _ssl
class _ssl.Certificate "PySSLCertificate *" "PySSLCertificate_Type"
@@ -243,3 +245,5 @@
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_DISALLOW_INSTANTIATION | Py_TPFLAGS_IMMUTABLETYPE,
PySSLCertificate_slots,
};
+
+#endif
--- a/Modules/_ssl/debughelpers.c
+++ b/Modules/_ssl/debughelpers.c
@@ -114,6 +114,8 @@
return 0;
}
+#ifdef HAVE_OPENSSL_KEYLOG
+
static void
_PySSL_keylog_callback(const SSL *ssl, const char *line)
{
@@ -217,3 +219,5 @@
SSL_CTX_set_keylog_callback(self->ctx, _PySSL_keylog_callback);
return 0;
}
+
+#endif
--- a/Modules/_ssl.c
+++ b/Modules/_ssl.c
@@ -115,6 +115,7 @@
#if (OPENSSL_VERSION_NUMBER >= 0x30000000L)
#include "_ssl_data_300.h"
#elif (OPENSSL_VERSION_NUMBER >= 0x10101000L) && !defined(LIBRESSL_VERSION_NUMBER)
+#define HAVE_OPENSSL_KEYLOG 1
#include "_ssl_data_111.h"
#else
#include "_ssl_data.h"
@@ -141,6 +142,76 @@
/* OpenSSL 1.1 does not have SSL 2.0 */
#define OPENSSL_NO_SSL2
+#if (OPENSSL_VERSION_NUMBER < 0x10100000L)
+/* OpenSSL 1.1 API shims for OpenSSL < 1.1.0 and LibreSSL < 2.7.0 */
+
+#define TLS_method SSLv23_method
+#define TLS_client_method SSLv23_client_method
+#define TLS_server_method SSLv23_server_method
+#define ASN1_STRING_get0_data ASN1_STRING_data
+#define X509_get0_notBefore X509_get_notBefore
+#define X509_get0_notAfter X509_get_notAfter
+#define OpenSSL_version_num SSLeay
+#define OpenSSL_version SSLeay_version
+#define OPENSSL_VERSION SSLEAY_VERSION
+static int X509_NAME_ENTRY_set(const X509_NAME_ENTRY *ne)
+{
+ return ne->set;
+}
+
+#ifndef OPENSSL_NO_COMP
+/* LCOV_EXCL_START */
+static int COMP_get_type(const COMP_METHOD *meth)
+{
+ return meth->type;
+}
+/* LCOV_EXCL_STOP */
+#endif
+
+static pem_password_cb *SSL_CTX_get_default_passwd_cb(SSL_CTX *ctx)
+{
+ return ctx->default_passwd_callback;
+}
+
+static void *SSL_CTX_get_default_passwd_cb_userdata(SSL_CTX *ctx)
+{
+ return ctx->default_passwd_callback_userdata;
+}
+
+static int X509_OBJECT_get_type(X509_OBJECT *x)
+{
+ return x->type;
+}
+
+static X509 *X509_OBJECT_get0_X509(X509_OBJECT *x)
+{
+ return x->data.x509;
+}
+
+static int BIO_up_ref(BIO *b)
+{
+ CRYPTO_add(&b->references, 1, CRYPTO_LOCK_BIO);
+ return 1;
+}
+
+static STACK_OF(X509_OBJECT) *X509_STORE_get0_objects(X509_STORE *store) {
+ return store->objs;
+}
+
+static int
+SSL_SESSION_has_ticket(const SSL_SESSION *s)
+{
+ return (s->tlsext_ticklen > 0) ? 1 : 0;
+}
+
+static unsigned long
+SSL_SESSION_get_ticket_lifetime_hint(const SSL_SESSION *s)
+{
+ return s->tlsext_tick_lifetime_hint;
+}
+
+#endif /* OpenSSL < 1.1.0 or LibreSSL < 2.7.0 */
+
/* Default cipher suites */
#ifndef PY_SSL_DEFAULT_CIPHERS
#define PY_SSL_DEFAULT_CIPHERS 1
@@ -167,7 +238,11 @@
* Based on Hynek's excellent blog post (update 2021-02-11)
* https://hynek.me/articles/hardening-your-web-servers-ssl-ciphers/
*/
+#ifdef OPENSSL_VERSION_1_1
#define PY_SSL_DEFAULT_CIPHER_STRING "@SECLEVEL=2:ECDH+AESGCM:ECDH+CHACHA20:ECDH+AES:DHE+AES:!aNULL:!eNULL:!aDSS:!SHA1:!AESCCM"
+#else
+ #define PY_SSL_DEFAULT_CIPHER_STRING "DEFAULT:!aNULL:!eNULL:!MD5:!3DES:!DES:!RC4:!IDEA:!SEED:!aDSS:!SRP:!PSK"
+#endif
#ifndef PY_SSL_MIN_PROTOCOL
#define PY_SSL_MIN_PROTOCOL TLS1_2_VERSION
#endif
@@ -1850,6 +1925,7 @@
return result;
}
+#if OPENSSL_VERSION_1_1
/*[clinic input]
_ssl._SSLSocket.get_verified_chain
@@ -1914,6 +1990,8 @@
return retval;
}
+#endif
+
static PyObject *
cipher_to_tuple(const SSL_CIPHER *cipher)
{
@@ -1978,6 +2056,7 @@
buf[len-1] = '\0';
strength_bits = SSL_CIPHER_get_bits(cipher, &alg_bits);
+#if OPENSSL_VERSION_1_1
aead = SSL_CIPHER_is_aead(cipher);
nid = SSL_CIPHER_get_cipher_nid(cipher);
skcipher = nid != NID_undef ? OBJ_nid2ln(nid) : NULL;
@@ -1987,10 +2066,13 @@
kx = nid != NID_undef ? OBJ_nid2ln(nid) : NULL;
nid = SSL_CIPHER_get_auth_nid(cipher);
auth = nid != NID_undef ? OBJ_nid2ln(nid) : NULL;
+#endif
return Py_BuildValue(
"{sksssssssisi"
+#if OPENSSL_VERSION_1_1
"sOssssssss"
+#endif
"}",
"id", cipher_id,
"name", cipher_name,
@@ -1998,11 +2080,13 @@
"description", buf,
"strength_bits", strength_bits,
"alg_bits", alg_bits
+#if OPENSSL_VERSION_1_1
,"aead", aead ? Py_True : Py_False,
"symmetric", skcipher,
"digest", digest,
"kea", kx,
"auth", auth
+#endif
);
}
@@ -2031,9 +2115,12 @@
server_ciphers = SSL_get_ciphers(self->ssl);
if (!server_ciphers)
Py_RETURN_NONE;
+#if ! ((OPENSSL_VERSION_NUMBER < 0x10100000L) || defined(LIBRESSL_VERSION_NUMBER))
+/* OpenSSL < 1.1.0 */
client_ciphers = SSL_get_client_ciphers(self->ssl);
if (!client_ciphers)
Py_RETURN_NONE;
+#endif
res = PyList_New(sk_SSL_CIPHER_num(server_ciphers));
if (!res)
@@ -2041,8 +2128,11 @@
len = 0;
for (i = 0; i < sk_SSL_CIPHER_num(server_ciphers); i++) {
cipher = sk_SSL_CIPHER_value(server_ciphers, i);
+#if ! ((OPENSSL_VERSION_NUMBER < 0x10100000L) || defined(LIBRESSL_VERSION_NUMBER))
+/* OpenSSL < 1.1.0 */
if (sk_SSL_CIPHER_find(client_ciphers, cipher) < 0)
continue;
+#endif
PyObject *tup = cipher_to_tuple(cipher);
if (!tup) {
@@ -2339,6 +2429,9 @@
_ssl__SSLSocket_write_impl(PySSLSocket *self, Py_buffer *b)
/*[clinic end generated code: output=aa7a6be5527358d8 input=77262d994fe5100a]*/
{
+#ifndef SSL_write_ex
+ int len;
+#endif
size_t count = 0;
int retval;
int sockstate;
@@ -2358,6 +2451,14 @@
Py_INCREF(sock);
}
+#ifndef SSL_write_ex
+ if (b->len > INT_MAX) {
+ PyErr_Format(PyExc_OverflowError,
+ "string longer than %d bytes", INT_MAX);
+ goto error;
+ }
+#endif
+
if (sock != NULL) {
/* just in case the blocking state of the socket has been changed */
nonblocking = (sock->sock_timeout >= 0);
@@ -2388,8 +2489,13 @@
do {
PySSL_BEGIN_ALLOW_THREADS
+#ifdef SSL_write_ex
retval = SSL_write_ex(self->ssl, b->buf, (size_t)b->len, &count);
err = _PySSL_errno(retval == 0, self->ssl, retval);
+#else
+ len = SSL_write(self->ssl, b->buf, (size_t)b->len);
+ err = _PySSL_errno(len <= 0, self->ssl, len);
+#endif
PySSL_END_ALLOW_THREADS
self->err = err;
@@ -2423,11 +2529,20 @@
err.ssl == SSL_ERROR_WANT_WRITE);
Py_XDECREF(sock);
+#ifdef SSL_write_ex
if (retval == 0)
return PySSL_SetError(self, retval, __FILE__, __LINE__);
+#else
+ if (len <= 0)
+ return PySSL_SetError(self, len, __FILE__, __LINE__);
+#endif
if (PySSL_ChainExceptions(self) < 0)
return NULL;
+#ifdef SSL_write_ex
return PyLong_FromSize_t(count);
+#else
+ return PyLong_FromLong(len);
+#endif
error:
Py_XDECREF(sock);
PySSL_ChainExceptions(self);
@@ -2477,7 +2592,11 @@
{
PyObject *dest = NULL;
char *mem;
+#ifdef SSL_read_ex
size_t count = 0;
+#else
+ int count;
+#endif
int retval;
int sockstate;
_PySSLError err;
@@ -2541,8 +2660,13 @@
do {
PySSL_BEGIN_ALLOW_THREADS
+#ifdef SSL_read_ex
retval = SSL_read_ex(self->ssl, mem, (size_t)len, &count);
err = _PySSL_errno(retval == 0, self->ssl, retval);
+#else
+ count = SSL_read(self->ssl, mem, (size_t)len);
+ err = _PySSL_errno(count <= 0, self->ssl, count);
+#endif
PySSL_END_ALLOW_THREADS
self->err = err;
@@ -2576,8 +2700,13 @@
} while (err.ssl == SSL_ERROR_WANT_READ ||
err.ssl == SSL_ERROR_WANT_WRITE);
+#ifdef SSL_read_ex
if (retval == 0) {
PySSL_SetError(self, retval, __FILE__, __LINE__);
+#else
+ if (count <= 0) {
+ PySSL_SetError(self, count, __FILE__, __LINE__);
+#endif
goto error;
}
if (self->exc_type != NULL)
@@ -2590,7 +2719,11 @@
return dest;
}
else {
+#ifdef SSL_read_ex
return PyLong_FromSize_t(count);
+#else
+ return PyLong_FromLong(count);
+#endif
}
error:
@@ -2954,8 +3087,10 @@
_SSL__SSLSOCKET_COMPRESSION_METHODDEF
_SSL__SSLSOCKET_SHUTDOWN_METHODDEF
_SSL__SSLSOCKET_VERIFY_CLIENT_POST_HANDSHAKE_METHODDEF
+#if OPENSSL_VERSION_1_1
_SSL__SSLSOCKET_GET_UNVERIFIED_CHAIN_METHODDEF
_SSL__SSLSOCKET_GET_VERIFIED_CHAIN_METHODDEF
+#endif
{NULL, NULL}
};
@@ -3041,7 +3176,9 @@
switch(proto_version) {
#if defined(SSL3_VERSION) && !defined(OPENSSL_NO_SSL3)
case PY_SSL_VERSION_SSL3:
+#ifdef OPENSSL_VERSION_1_1
PY_SSL_DEPRECATED("ssl.PROTOCOL_SSLv3 is deprecated", 2, NULL);
+#endif
method = SSLv3_method();
break;
#endif
@@ -3049,7 +3186,9 @@
!defined(OPENSSL_NO_TLS1) && \
!defined(OPENSSL_NO_TLS1_METHOD))
case PY_SSL_VERSION_TLS1:
+#ifdef OPENSSL_VERSION_1_1
PY_SSL_DEPRECATED("ssl.PROTOCOL_TLSv1 is deprecated", 2, NULL);
+#endif
method = TLSv1_method();
break;
#endif
@@ -3057,7 +3196,9 @@
!defined(OPENSSL_NO_TLS1_1) && \
!defined(OPENSSL_NO_TLS1_1_METHOD))
case PY_SSL_VERSION_TLS1_1:
+#ifdef OPENSSL_VERSION_1_1
PY_SSL_DEPRECATED("ssl.PROTOCOL_TLSv1_1 is deprecated", 2, NULL);
+#endif
method = TLSv1_1_method();
break;
#endif
@@ -3065,12 +3206,16 @@
!defined(OPENSSL_NO_TLS1_2) && \
!defined(OPENSSL_NO_TLS1_2_METHOD))
case PY_SSL_VERSION_TLS1_2:
+#ifdef OPENSSL_VERSION_1_1
PY_SSL_DEPRECATED("ssl.PROTOCOL_TLSv1_2 is deprecated", 2, NULL);
+#endif
method = TLSv1_2_method();
break;
#endif
case PY_SSL_VERSION_TLS:
+#ifdef OPENSSL_VERSION_1_1
PY_SSL_DEPRECATED("ssl.PROTOCOL_TLS is deprecated", 2, NULL);
+#endif
method = TLS_method();
break;
case PY_SSL_VERSION_TLS_CLIENT:
@@ -3170,7 +3315,7 @@
"No cipher can be selected.");
goto error;
}
-#ifdef PY_SSL_MIN_PROTOCOL
+#if defined(PY_SSL_MIN_PROTOCOL) && defined(OPENSSL_VERSION_1_1)
switch(proto_version) {
case PY_SSL_VERSION_TLS:
case PY_SSL_VERSION_TLS_CLIENT:
@@ -3459,6 +3604,8 @@
}
/* Getter and setter for protocol version */
+#if defined(SSL_CTRL_GET_MAX_PROTO_VERSION)
+/* Getter and setter for protocol version */
static int
set_min_max_proto_version(PySSLContext *self, PyObject *arg, int what)
{
@@ -3576,6 +3723,7 @@
{
return set_min_max_proto_version(self, arg, 1);
}
+#endif
#ifdef TLS1_3_VERSION
static PyObject *
@@ -3610,12 +3758,14 @@
"Control the number of TLSv1.3 session tickets");
#endif /* TLS1_3_VERSION */
+#if OPENSSL_VERSION_1_1
static PyObject *
get_security_level(PySSLContext *self, void *c)
{
return PyLong_FromLong(SSL_CTX_get_security_level(self->ctx));
}
PyDoc_STRVAR(PySSLContext_security_level_doc, "The current security level");
+#endif
static PyObject *
get_options(PySSLContext *self, void *c)
@@ -3633,7 +3783,10 @@
uint64_t new_opts, opts, clear, set;
uint64_t opt_no = (
SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1 |
- SSL_OP_NO_TLSv1_1 | SSL_OP_NO_TLSv1_2 | SSL_OP_NO_TLSv1_3
+ SSL_OP_NO_TLSv1_1 | SSL_OP_NO_TLSv1_2
+#ifdef SSL_OP_NO_TLSv1_3
+ | SSL_OP_NO_TLSv1_3
+#endif
);
if (!PyArg_Parse(arg, "O!", &PyLong_Type, &new_opts_obj)) {
@@ -4541,7 +4694,7 @@
return 0;
}
-#if OPENSSL_VERSION_NUMBER < 0x30300000L
+#if OPENSSL_VERSION_NUMBER >= 0x10100000L && OPENSSL_VERSION_NUMBER < 0x30300000L
static X509_OBJECT *x509_object_dup(const X509_OBJECT *obj)
{
int ok;
@@ -4614,7 +4767,11 @@
int x509 = 0, crl = 0, ca = 0, i;
store = SSL_CTX_get_cert_store(self->ctx);
+#if OPENSSL_VERSION_NUMBER >= 0x10100000L
objs = X509_STORE_get1_objects(store);
+#else
+ objs = X509_STORE_get0_objects(store);
+#endif
if (objs == NULL) {
PyErr_SetString(PyExc_MemoryError, "failed to query cert store");
return NULL;
@@ -4637,7 +4794,9 @@
break;
}
}
+#if OPENSSL_VERSION_NUMBER >= 0x10100000L
sk_X509_OBJECT_pop_free(objs, X509_OBJECT_free);
+#endif
return Py_BuildValue("{sisisi}", "x509", x509, "crl", crl,
"x509_ca", ca);
}
@@ -4669,7 +4828,11 @@
}
store = SSL_CTX_get_cert_store(self->ctx);
+#if OPENSSL_VERSION_NUMBER >= 0x10100000L
objs = X509_STORE_get1_objects(store);
+#else
+ objs = X509_STORE_get0_objects(store);
+#endif
if (objs == NULL) {
PyErr_SetString(PyExc_MemoryError, "failed to query cert store");
goto error;
@@ -4702,11 +4865,15 @@
}
Py_CLEAR(ci);
}
+#if OPENSSL_VERSION_NUMBER >= 0x10100000L
sk_X509_OBJECT_pop_free(objs, X509_OBJECT_free);
+#endif
return rlist;
error:
+#if OPENSSL_VERSION_NUMBER >= 0x10100000L
sk_X509_OBJECT_pop_free(objs, X509_OBJECT_free);
+#endif
Py_XDECREF(ci);
Py_XDECREF(rlist);
return NULL;
@@ -4718,12 +4885,16 @@
(setter) set_check_hostname, NULL},
{"_host_flags", (getter) get_host_flags,
(setter) set_host_flags, NULL},
+#if defined(SSL_CTRL_GET_MAX_PROTO_VERSION)
{"minimum_version", (getter) get_minimum_version,
(setter) set_minimum_version, NULL},
{"maximum_version", (getter) get_maximum_version,
(setter) set_maximum_version, NULL},
+#endif
+#ifdef HAVE_OPENSSL_KEYLOG
{"keylog_filename", (getter) _PySSLContext_get_keylog_filename,
(setter) _PySSLContext_set_keylog_filename, NULL},
+#endif
{"_msg_callback", (getter) _PySSLContext_get_msg_callback,
(setter) _PySSLContext_set_msg_callback, NULL},
{"sni_callback", (getter) get_sni_callback,
@@ -4747,8 +4918,10 @@
(setter) set_verify_flags, NULL},
{"verify_mode", (getter) get_verify_mode,
(setter) set_verify_mode, NULL},
+#if OPENSSL_VERSION_1_1
{"security_level", (getter) get_security_level,
NULL, PySSLContext_security_level_doc},
+#endif
{NULL}, /* sentinel */
};
@@ -5839,6 +6012,14 @@
_sslmodulestate *state = get_ssl_state(module);
PySocketModule_APIObject *sockmod = PySocketModule_ImportModuleAndAPI();
+#if (OPENSSL_VERSION_NUMBER < 0x10100000L)
+ /* Load all algorithms and initialize cpuid */
+ OPENSSL_add_all_algorithms_noconf();
+ /* Init OpenSSL */
+ SSL_load_error_strings();
+ SSL_library_init();
+#endif
+
if ((sockmod == NULL) || (sockmod->Sock_Type == NULL)) {
return -1;
}
@@ -6266,11 +6447,13 @@
if (state->PySSLSession_Type == NULL)
return -1;
+#if OPENSSL_VERSION_1_1
state->PySSLCertificate_Type = (PyTypeObject *)PyType_FromModuleAndSpec(
module, &PySSLCertificate_spec, NULL
);
if (state->PySSLCertificate_Type == NULL)
return -1;
+#endif
if (PyModule_AddType(module, state->PySSLContext_Type))
return -1;
@@ -6280,8 +6463,10 @@
return -1;
if (PyModule_AddType(module, state->PySSLSession_Type))
return -1;
+#if OPENSSL_VERSION_1_1
if (PyModule_AddType(module, state->PySSLCertificate_Type))
return -1;
+#endif
return 0;
}
@@ -6328,7 +6513,9 @@
Py_VISIT(state->PySSLSocket_Type);
Py_VISIT(state->PySSLMemoryBIO_Type);
Py_VISIT(state->PySSLSession_Type);
+#if OPENSSL_VERSION_1_1
Py_VISIT(state->PySSLCertificate_Type);
+#endif
Py_VISIT(state->PySSLErrorObject);
Py_VISIT(state->PySSLCertVerificationErrorObject);
Py_VISIT(state->PySSLZeroReturnErrorObject);
@@ -6353,7 +6540,9 @@
Py_CLEAR(state->PySSLSocket_Type);
Py_CLEAR(state->PySSLMemoryBIO_Type);
Py_CLEAR(state->PySSLSession_Type);
+#if OPENSSL_VERSION_1_1
Py_CLEAR(state->PySSLCertificate_Type);
+#endif
Py_CLEAR(state->PySSLErrorObject);
Py_CLEAR(state->PySSLCertVerificationErrorObject);
Py_CLEAR(state->PySSLZeroReturnErrorObject);

78
pre_checkin.sh Normal file
View File

@ -0,0 +1,78 @@
#!/bin/bash
export LC_ALL=C
master=python*.spec
# create import_failed.map from package definitions
pkgname=$(grep python_pkg_name $master |grep define |awk -F' ' '{print $3}')
MAPFILE=import_failed.map
function new_map_line () {
package=$1
package=$(echo $1 |sed -e "s:%{python_pkg_name}:$pkgname:")
modules=$2
if [ -z "$package" -o -z "$modules" ]; then
return
fi
if [[ "$package" =~ "-base" ]]; then
return
fi
echo "$package:$modules" >> $MAPFILE.tmp
}
for spec in *.spec; do
basename=${spec%.spec}
package=
modules=
while read line; do
case $line in
"%files -n "*)
new_map_line $package "$modules"
package=${line#"%files -n "}
modules=
;;
"%files "*)
new_map_line $package "$modules"
package=$basename-${line#"%files "}
modules=
;;
"%files")
new_map_line $package "$modules"
package=$basename
modules=
;;
"%{sitedir}/config-"*)
# ignore
;;
"%{sitedir}/"*)
word=${line#"%{sitedir}/"}
if ! echo $word | grep -q /; then
modules="$modules $word"
fi
;;
"%{dynlib "*"}")
word=${line#"%{dynlib "}
word=${word%"}"}
modules="$modules $word"
;;
esac
done < $spec
new_map_line $package "$modules"
done
cat $MAPFILE.tmp |sort -u > $MAPFILE
rm $MAPFILE.tmp
# run test inclusion check
tar xJf Python-*.xz
python3 skipped_tests.py
# generate baselibs.conf
VERSION=$(grep ^Version $master|awk -F':' '{print $2}' |sed -e 's/ //g')
python_version=${VERSION:0:3} # 3.3
python_version_abitag=${python_version//./} # 33
python_version_soname=${python_version//./_} # 3_3
echo "$pkgname-base" > baselibs.conf
echo "$pkgname" >> baselibs.conf
echo "libpython$python_version_soname-1_0" >> baselibs.conf

View File

@ -0,0 +1,27 @@
---
Makefile.pre.in | 7 +++++++
1 file changed, 7 insertions(+)
Index: Python-3.11.8/Makefile.pre.in
===================================================================
--- Python-3.11.8.orig/Makefile.pre.in
+++ Python-3.11.8/Makefile.pre.in
@@ -1240,11 +1240,18 @@ Modules/getbuildinfo.o: $(PARSER_OBJS) \
$(DTRACE_OBJS) \
$(srcdir)/Modules/getbuildinfo.c
$(CC) -c $(PY_CORE_CFLAGS) \
+ -DDATE="\"`date -u -r Makefile.pre.in +"%b %d %Y"`\"" \
+ -DTIME="\"`date -u -r Makefile.pre.in +"%T"`\"" \
-DGITVERSION="\"`LC_ALL=C $(GITVERSION)`\"" \
-DGITTAG="\"`LC_ALL=C $(GITTAG)`\"" \
-DGITBRANCH="\"`LC_ALL=C $(GITBRANCH)`\"" \
-o $@ $(srcdir)/Modules/getbuildinfo.c
+Python/getcompiler.o: $(srcdir)/Python/getcompiler.c Makefile
+ $(CC) -c $(PY_CORE_CFLAGS) \
+ -DCOMPILER='"[GCC]"' \
+ -o $@ $(srcdir)/Python/getcompiler.c
+
Modules/getpath.o: $(srcdir)/Modules/getpath.c Python/frozen_modules/getpath.h Makefile $(PYTHON_HEADERS)
$(CC) -c $(PY_CORE_CFLAGS) -DPYTHONPATH='"$(PYTHONPATH)"' \
-DPREFIX='"$(prefix)"' \

View File

@ -0,0 +1,13 @@
Index: Python-3.11.8/Lib/site.py
===================================================================
--- Python-3.11.8.orig/Lib/site.py
+++ Python-3.11.8/Lib/site.py
@@ -77,7 +77,7 @@ import io
import stat
# Prefixes for site-packages; add additional prefixes like /usr/local here
-PREFIXES = [sys.prefix, sys.exec_prefix]
+PREFIXES = [sys.prefix, sys.exec_prefix, '/usr/local']
# Enable per user site-packages directory
# set it to False to disable the feature or True to force the feature
ENABLE_USER_SITE = None

View File

@ -0,0 +1,17 @@
---
Lib/test/test_posix.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
Index: Python-3.11.8/Lib/test/test_posix.py
===================================================================
--- Python-3.11.8.orig/Lib/test/test_posix.py
+++ Python-3.11.8/Lib/test/test_posix.py
@@ -430,7 +430,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)

109
python.keyring Normal file
View File

@ -0,0 +1,109 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
mQINBFq+ToQBEADRYvIVtbK6owynD3j3nxwpW2KEk/p+aDvtXmc2SR2dBcZ8sFW2
R5vEsG8d3/D3wgv5pcL3KfNNXQYUnXVbobrFUUWQYc79qIsE3MgiPf5NVOtwKPUR
i5g9YJgKvpBxkQfqp3LYGm9ZBtwo3DVLA3yn7KsazCmAgTNFJYw7ku1XxgmIzY6K
5J30DfbJiqDqj4f9GslCCCCH3qiPnuLG/HUyVLHMpbWlaiy9NI0GcaLxjJewHj9w
W2D2lydkxe5JGo7egUkV3ILcuLVSVKA35SKY27dYqfuyqp9tAzaRbjDYjsYdHA6G
BqrNrKBn/GwlFDPrVdcvN3ZSY2wMLTxWE3Axc/FweuHxFnou/80FwX7F3JD+oEQ6
rofmcxOBCC7J98I7HZAhP9jBn88XIS2hztbLq8d6rZJZRtcz0k61VR0ddO+TrFmf
9rMYCPgCckRtVxeFIVIabrN1IzKynLFeo040h8hSGswd6YKDOVwjJY6Oa6EmVefZ
a8QSt4+M65RSzH6SEPY008F3nJUAK6MEkzTak+tFltZNrVWu8p2xd1j9nmxAwEhZ
/lgbxLqzYgaUWmfyHeZ8yVA0MhHzdiAL8nVUEdG3KecIq0RWCJLGLWWIjd6KAJl1
yAmhRYKK/sjPDsL3elHsFACfZbyx3o5GGQNlas1FYoPLWbaNGaJtgFTF2QARAQAB
tCtQYWJsbyBHYWxpbmRvIFNhbGdhZG8gPHBhYmxvZ3NhbEBnbWFpbC5jb20+iQJO
BBMBCgA4FiEEoDXIwZIZuoIezqhrZOYo+NaEaW0FAlq+ToQCGwMFCwkIBwMFFQoJ
CAsFFgIDAQACHgECF4AACgkQZOYo+NaEaW2bmA/+PXIap2udLoUVOHxnsIBdqYwp
sv1Aj5lfIJmNhmxPbHShwp1Jg+w4urxe+2Dj5ofKVlIo1i83bQkvnKJMDXDVuc/K
P6zqhBJ3rT4Q3qx2mzX8bIfQoJ2JHuH4lkP+I7doDcHHRyeNASyk72VdQmU4twNw
Ibn8nSNV6ThKHdoPYzVnO2rZUFcGIqH5HNsvR+B7cc1MBCHsgURYwSVhSePIFGlZ
iasdBD6QQkDSe4QWi7AcJFWFElw4kbOKJWxAWsrEk+tMXJVGRjnmL289EmPCx/vx
BqKy7Mse0yWCSRR3vB+O6TB1S5SgEyEgqlYsfGNv1qf/rfRD4KkyCbNU3LhY1Aim
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
=6Zb4
-----END PGP PUBLIC KEY BLOCK-----

2
saltbundlepy-rpmlintrc Normal file
View File

@ -0,0 +1,2 @@
addFilter('shlib-policy-name-error')
addFilter("pem-certificate.*/usr/lib.*/python.*/test/*.pem")

6225
saltbundlepy.changes Normal file

File diff suppressed because it is too large Load Diff

1191
saltbundlepy.spec Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,23 @@
---
Lib/test/test_capi/test_misc.py | 3 +++
1 file changed, 3 insertions(+)
--- a/Lib/test/test_capi/test_misc.py
+++ b/Lib/test/test_capi/test_misc.py
@@ -40,6 +40,8 @@ import _testinternalcapi
# Were we compiled --with-pydebug or with #define Py_DEBUG?
Py_DEBUG = hasattr(sys, 'gettotalrefcount')
+# Which version of the SLE distro we build on?
+SLE_VERSION = int(os.environ.get('SLE_VERSION', '0'), 10)
NULL = None
@@ -1281,6 +1283,7 @@ class PyMemDebugTests(unittest.TestCase)
def test_pyobject_forbidden_bytes_is_freed(self):
self.check_pyobject_is_freed('check_pyobject_forbidden_bytes_is_freed')
+ @unittest.skipIf(0 < SLE_VERSION < 150300, 'Failing on Leap 15.*')
def test_pyobject_freed_is_freed(self):
self.check_pyobject_is_freed('check_pyobject_freed_is_freed')

View File

@ -0,0 +1,15 @@
---
Lib/test/support/__init__.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
--- a/Lib/test/support/__init__.py
+++ b/Lib/test/support/__init__.py
@@ -384,7 +384,7 @@ def skip_if_buildbot(reason=None):
if not reason:
reason = 'not suitable for buildbots'
try:
- isbuildbot = getpass.getuser().lower() == 'buildbot'
+ isbuildbot = getpass.getuser().lower() in ['buildbot', 'abuild']
except (KeyError, EnvironmentError) as err:
warnings.warn(f'getpass.getuser() failed {err}.', RuntimeWarning)
isbuildbot = False

69
skipped_tests.py Normal file
View File

@ -0,0 +1,69 @@
#!/usr/bin/python3
"""
Simple regexp-based skipped test checker.
It lists tests that are mentioned (presumably for exclusion)
in BASE, and in MAIN (presumably for inclusion)
and reports discrepancies.
This will have a number of
"""
MAIN = "python39.spec"
import glob
import re
from os.path import basename
alltests = set()
qemu_exclusions = set()
for item in glob.glob("Python-*/Lib/test/test_*"):
testname = basename(item)
if testname.endswith(".py"):
testname = testname[:-3]
alltests.add(testname)
testre = re.compile(r'[\s"](test_\w+)\b')
def find_tests_in_spec(specname):
global qemu_exclusions
found_tests = set()
with open(specname) as spec:
in_qemu = False
for line in spec:
line = line.strip()
if "#" in line:
line = line[:line.index("#")]
tests = set(testre.findall(line))
found_tests |= tests
if line == "%if 0%{?qemu_user_space_build} > 0":
in_qemu = True
if in_qemu:
if line == "%endif":
in_qemu = False
qemu_exclusions |= tests
return found_tests
excluded = find_tests_in_spec(MAIN)
#print("--- excluded tests:", " ".join(sorted(excluded)))
#print("--- included tests:", " ".join(sorted(included)))
mentioned = excluded
nonexistent = mentioned - alltests
missing = excluded - qemu_exclusions
print("--- the following tests are excluded for QEMU and not tested in python")
print("--- (that probably means we don't need to worry about them)")
for test in sorted(qemu_exclusions - excluded):
print(test)
print("--- the following tests might be excluded in python:")
for test in sorted(missing):
print(test)
if nonexistent:
print("--- the following tests don't exist:")
for test in sorted(nonexistent):
print(test)

View File

@ -0,0 +1,18 @@
---
Lib/test/test_subprocess.py | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
Index: Python-3.11.8/Lib/test/test_subprocess.py
===================================================================
--- Python-3.11.8.orig/Lib/test/test_subprocess.py
+++ Python-3.11.8/Lib/test/test_subprocess.py
@@ -280,7 +280,8 @@ class ProcessTestCase(BaseTestCase):
"time.sleep(3600)"],
# Some heavily loaded buildbots (sparc Debian 3.x) require
# this much time to start and print.
- timeout=3)
+ # OBS might require even more
+ timeout=10)
self.fail("Expected TimeoutExpired.")
self.assertEqual(c.exception.output, b'BDFL')