forked from pool/python38
OBS-URL: https://build.opensuse.org/package/show/devel:languages:python:Factory/python38?expand=0&rev=174
314 lines
12 KiB
Diff
314 lines
12 KiB
Diff
From d7133c7e0f91b14c390aa30a5689c353ef754fb6 Mon Sep 17 00:00:00 2001
|
||
From: Sebastian Pipping <sebastian@pipping.org>
|
||
Date: Wed, 7 Feb 2024 15:32:45 +0100
|
||
Subject: [PATCH] Fix etree XMLPullParser tests for Expat >=2.6.0 with reparse
|
||
deferral
|
||
|
||
Combined with gh#python/cpython!31453
|
||
bpo-46811: Make test suite support Expat >=2.4.5 (GH-31453)
|
||
|
||
Curly brackets were never allowed in namespace URIs
|
||
according to RFC 3986, and so-called namespace-validating
|
||
XML parsers have the right to reject them a invalid URIs.
|
||
|
||
libexpat >=2.4.5 has become strcter in that regard due to
|
||
related security issues; with ET.XML instantiating a
|
||
namespace-aware parser under the hood, this test has no
|
||
future in CPython.
|
||
|
||
References:
|
||
- https://datatracker.ietf.org/doc/html/rfc3968
|
||
- https://www.w3.org/TR/xml-names/
|
||
|
||
Also, test_minidom.py: Support Expat >=2.4.5
|
||
(cherry picked from commit 2cae93832f46b245847bdc252456ddf7742ef45e)
|
||
|
||
Co-authored-by: Sebastian Pipping <sebastian@pipping.org>
|
||
Fixes: gh#python/cpython#115133
|
||
From-PR: gh#python/cpython!115138
|
||
Patch: CVE-2023-52425-libexpat-2.6.0-backport.patch
|
||
---
|
||
Lib/test/support/__init__.py | 15 ++
|
||
Lib/test/test_minidom.py | 22 +--
|
||
Lib/test/test_pyexpat.py | 61 +++++++++-
|
||
Lib/test/test_sax.py | 54 ++++++++
|
||
Lib/test/test_xml_etree.py | 13 +-
|
||
Misc/NEWS.d/next/Library/2022-02-20-21-03-31.bpo-46811.8BxgdQ.rst | 1
|
||
Misc/NEWS.d/next/Tests/2024-02-07-15-49-37.gh-issue-115133.WBajNr.rst | 1
|
||
7 files changed, 146 insertions(+), 21 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/Tests/2024-02-07-15-49-37.gh-issue-115133.WBajNr.rst
|
||
|
||
--- a/Lib/test/support/__init__.py
|
||
+++ b/Lib/test/support/__init__.py
|
||
@@ -23,6 +23,7 @@ import platform
|
||
import re
|
||
import shutil
|
||
import socket
|
||
+import pyexpat
|
||
import stat
|
||
import struct
|
||
import subprocess
|
||
@@ -119,9 +120,11 @@ __all__ = [
|
||
"run_with_locale", "swap_item",
|
||
"swap_attr", "Matcher", "set_memlimit", "SuppressCrashReport", "sortdict",
|
||
"run_with_tz", "PGO", "missing_compiler_executable", "fd_count",
|
||
- "ALWAYS_EQ", "LARGEST", "SMALLEST"
|
||
+ "ALWAYS_EQ", "LARGEST", "SMALLEST",
|
||
+ "fails_with_expat_2_6_0", "is_expat_2_6_0"
|
||
]
|
||
|
||
+
|
||
class Error(Exception):
|
||
"""Base class for regression test exceptions."""
|
||
|
||
@@ -3411,3 +3414,13 @@ def adjust_int_max_str_digits(max_digits
|
||
yield
|
||
finally:
|
||
sys.set_int_max_str_digits(current)
|
||
+
|
||
+
|
||
+@functools.lru_cache(maxsize=32)
|
||
+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
|
||
@@ -1149,13 +1149,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 doesn’t 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()
|
||
@@ -1601,12 +1599,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 doesn’t 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
|
||
@@ -11,7 +11,7 @@ import traceback
|
||
from xml.parsers import expat
|
||
from xml.parsers.expat import errors
|
||
|
||
-from test.support import sortdict
|
||
+from test.support import sortdict, is_expat_2_6_0
|
||
|
||
|
||
class SetAttributeTest(unittest.TestCase):
|
||
@@ -778,6 +778,65 @@ class ReparseDeferralTest(unittest.TestC
|
||
|
||
for chunk in (b'<doc', b'/>'):
|
||
parser.Parse(chunk, False)
|
||
+
|
||
+ # The key test: Have handlers already fired? Expecting: yes.
|
||
+ self.assertEqual(started, ['doc'])
|
||
+
|
||
+
|
||
+class ReparseDeferralTest(unittest.TestCase):
|
||
+ def test_getter_setter_round_trip(self):
|
||
+ if not is_expat_2_6_0:
|
||
+ self.skipTest("Linked libexpat doesn't support reparse deferral")
|
||
+
|
||
+ parser = expat.ParserCreate()
|
||
+ enabled = (expat.version_info >= (2, 6, 0))
|
||
+
|
||
+ self.assertIs(parser.GetReparseDeferralEnabled(), enabled)
|
||
+ parser.SetReparseDeferralEnabled(False)
|
||
+ self.assertIs(parser.GetReparseDeferralEnabled(), False)
|
||
+ parser.SetReparseDeferralEnabled(True)
|
||
+ self.assertIs(parser.GetReparseDeferralEnabled(), enabled)
|
||
+
|
||
+ def test_reparse_deferral_enabled(self):
|
||
+ if not is_expat_2_6_0:
|
||
+ self.skipTest("Linked libexpat doesn't support reparse deferral")
|
||
+
|
||
+ started = []
|
||
+
|
||
+ def start_element(name, _):
|
||
+ started.append(name)
|
||
+
|
||
+ parser = expat.ParserCreate()
|
||
+ parser.StartElementHandler = start_element
|
||
+ self.assertTrue(parser.GetReparseDeferralEnabled())
|
||
+
|
||
+ for chunk in (b'<doc', b'/>'):
|
||
+ parser.Parse(chunk, False)
|
||
+
|
||
+ # The key test: Have handlers already fired? Expecting: no.
|
||
+ self.assertEqual(started, [])
|
||
+
|
||
+ parser.Parse(b'', True)
|
||
+
|
||
+ self.assertEqual(started, ['doc'])
|
||
+
|
||
+ def test_reparse_deferral_disabled(self):
|
||
+ if not is_expat_2_6_0:
|
||
+ self.skipTest("Linked libexpat doesn't support reparse deferral")
|
||
+
|
||
+ started = []
|
||
+
|
||
+ def start_element(name, _):
|
||
+ started.append(name)
|
||
+
|
||
+ parser = expat.ParserCreate()
|
||
+ parser.StartElementHandler = start_element
|
||
+ if is_expat_2_6_0:
|
||
+ parser.SetReparseDeferralEnabled(False)
|
||
+ self.assertFalse(parser.GetReparseDeferralEnabled())
|
||
+
|
||
+ for chunk in (b'<doc', b'/>'):
|
||
+ parser.Parse(chunk, False)
|
||
|
||
# The key test: Have handlers already fired? Expecting: yes.
|
||
self.assertEqual(started, ['doc'])
|
||
--- a/Lib/test/test_sax.py
|
||
+++ b/Lib/test/test_sax.py
|
||
@@ -22,7 +22,7 @@ import pyexpat
|
||
import shutil
|
||
from urllib.error import URLError
|
||
from test import support
|
||
-from test.support import findfile, run_unittest, FakePath, TESTFN
|
||
+from test.support import findfile, run_unittest, FakePath, TESTFN, is_expat_2_6_0
|
||
|
||
TEST_XMLFILE = findfile("test.xml", subdir="xmltestdata")
|
||
TEST_XMLFILE_OUT = findfile("test.xml.out", subdir="xmltestdata")
|
||
@@ -1247,6 +1247,58 @@ class ExpatReaderTest(XmlTestBase):
|
||
|
||
self.assertFalse(parser._parser.GetReparseDeferralEnabled())
|
||
|
||
+ parser.flush()
|
||
+
|
||
+ self.assertFalse(parser._parser.GetReparseDeferralEnabled())
|
||
+ self.assertEqual(result.getvalue(), start + b"<doc>")
|
||
+
|
||
+ parser.feed("</doc>")
|
||
+ parser.close()
|
||
+
|
||
+ self.assertEqual(result.getvalue(), start + b"<doc></doc>")
|
||
+
|
||
+ 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()
|
||
+ parser.setContentHandler(xmlgen)
|
||
+
|
||
+ for chunk in ("<doc", ">"):
|
||
+ parser.feed(chunk)
|
||
+
|
||
+ self.assertEqual(result.getvalue(), start) # i.e. no elements started
|
||
+ self.assertTrue(parser._parser.GetReparseDeferralEnabled())
|
||
+
|
||
+ parser.flush()
|
||
+
|
||
+ self.assertTrue(parser._parser.GetReparseDeferralEnabled())
|
||
+ self.assertEqual(result.getvalue(), start + b"<doc>")
|
||
+
|
||
+ parser.feed("</doc>")
|
||
+ parser.close()
|
||
+
|
||
+ 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()
|
||
+ parser.setContentHandler(xmlgen)
|
||
+
|
||
+ for chunk in ("<doc", ">"):
|
||
+ parser.feed(chunk)
|
||
+
|
||
+ parser._parser.SetReparseDeferralEnabled(False)
|
||
+ self.assertEqual(result.getvalue(), start) # i.e. no elements started
|
||
+
|
||
+ self.assertFalse(parser._parser.GetReparseDeferralEnabled())
|
||
+
|
||
parser.flush()
|
||
|
||
self.assertFalse(parser._parser.GetReparseDeferralEnabled())
|
||
--- a/Lib/test/test_xml_etree.py
|
||
+++ b/Lib/test/test_xml_etree.py
|
||
@@ -25,7 +25,8 @@ import weakref
|
||
from functools import partial
|
||
from itertools import product, islice
|
||
from test import support
|
||
-from test.support import TESTFN, findfile, import_fresh_module, gc_collect, swap_attr
|
||
+from test.support import (TESTFN, findfile, import_fresh_module,
|
||
+ gc_collect, swap_attr, is_expat_2_6_0, fails_with_expat_2_6_0)
|
||
|
||
# pyET is the pure-Python implementation.
|
||
#
|
||
@@ -1271,6 +1272,7 @@ class XMLPullParserTest(unittest.TestCas
|
||
expected)
|
||
|
||
def test_simple_xml(self, chunk_size=None, flush=False):
|
||
+ expected_events = []
|
||
parser = ET.XMLPullParser()
|
||
self.assert_event_tags(parser, [])
|
||
self._feed(parser, "<!-- comment -->\n", chunk_size, flush)
|
||
@@ -1280,16 +1282,17 @@ class XMLPullParserTest(unittest.TestCas
|
||
chunk_size, flush)
|
||
self.assert_event_tags(parser, [])
|
||
self._feed(parser, ">\n", chunk_size, flush)
|
||
- self.assert_event_tags(parser, [('end', 'element')])
|
||
+ expected_events += [('end', 'element')]
|
||
self._feed(parser, "<element>text</element>tail\n", chunk_size, flush)
|
||
self._feed(parser, "<empty-element/>\n", chunk_size, flush)
|
||
- self.assert_event_tags(parser, [
|
||
+ expected_events += [
|
||
('end', 'element'),
|
||
('end', 'empty-element'),
|
||
- ])
|
||
+ ]
|
||
self._feed(parser, "</root>\n", chunk_size, flush)
|
||
- self.assert_event_tags(parser, [('end', 'root')])
|
||
+ expected_events += [('end', 'root')]
|
||
self.assertIsNone(parser.close())
|
||
+ self.assert_event_tags(parser, expected_events)
|
||
|
||
def test_simple_xml_chunk_1(self):
|
||
self.test_simple_xml(chunk_size=1, flush=True)
|
||
--- /dev/null
|
||
+++ b/Misc/NEWS.d/next/Library/2022-02-20-21-03-31.bpo-46811.8BxgdQ.rst
|
||
@@ -0,0 +1 @@
|
||
+Make test suite support Expat >=2.4.5
|
||
--- /dev/null
|
||
+++ b/Misc/NEWS.d/next/Tests/2024-02-07-15-49-37.gh-issue-115133.WBajNr.rst
|
||
@@ -0,0 +1 @@
|
||
+Fix etree XMLPullParser tests for Expat >=2.6.0 with reparse deferral
|