.. versionadded:: 3.13
-:class:`!xmlparser` objects have the following methods to mitigate some
-common XML vulnerabilities.
+:class:`!xmlparser` objects have the following methods to tune protections
+against some common XML vulnerabilities.
+
+.. method:: xmlparser.SetBillionLaughsAttackProtectionActivationThreshold(threshold, /)
+
+ Sets the number of output bytes needed to activate protection against
+ `billion laughs`_ attacks.
+
+ The number of output bytes includes amplification from entity expansion
+ and reading DTD files.
+
+ Parser objects usually have a protection activation threshold of 8 MiB,
+ but the actual default value depends on the underlying Expat library.
+
+ An :exc:`ExpatError` is raised if this method is called on a
+ |xml-non-root-parser| parser.
+ The corresponding :attr:`~ExpatError.lineno` and :attr:`~ExpatError.offset`
+ should not be used as they may have no special meaning.
+
+ .. note::
+
+ Activation thresholds below 4 MiB are known to break support for DITA 1.3
+ payload and are hence not recommended.
+
+ .. versionadded:: next
+
+.. method:: xmlparser.SetBillionLaughsAttackProtectionMaximumAmplification(max_factor, /)
+
+ Sets the maximum tolerated amplification factor for protection against
+ `billion laughs`_ attacks.
+
+ The amplification factor is calculated as ``(direct + indirect) / direct``
+ while parsing, where ``direct`` is the number of bytes read from
+ the primary document in parsing and ``indirect`` is the number of
+ bytes added by expanding entities and reading of external DTD files.
+
+ The *max_factor* value must be a non-NaN :class:`float` value greater than
+ or equal to 1.0. Peak amplifications of factor 15,000 for the entire payload
+ and of factor 30,000 in the middle of parsing have been observed with small
+ benign files in practice. In particular, the activation threshold should be
+ carefully chosen to avoid false positives.
+
+ Parser objects usually have a maximum amplification factor of 100,
+ but the actual default value depends on the underlying Expat library.
+
+ An :exc:`ExpatError` is raised if this method is called on a
+ |xml-non-root-parser| parser or if *max_factor* is outside the valid range.
+ The corresponding :attr:`~ExpatError.lineno` and :attr:`~ExpatError.offset`
+ should not be used as they may have no special meaning.
+
+ .. note::
+
+ The maximum amplification factor is only considered if the threshold
+ that can be adjusted by :meth:`.SetBillionLaughsAttackProtectionActivationThreshold`
+ is exceeded.
+
+ .. versionadded:: next
.. method:: xmlparser.SetAllocTrackerActivationThreshold(threshold, /)
Sets the number of allocated bytes of dynamic memory needed to activate
protection against disproportionate use of RAM.
- By default, parser objects have an allocation activation threshold of 64 MiB,
- or equivalently 67,108,864 bytes.
+ Parser objects usually have an allocation activation threshold of 64 MiB,
+ but the actual default value depends on the underlying Expat library.
An :exc:`ExpatError` is raised if this method is called on a
|xml-non-root-parser| parser.
near the start of parsing even with benign files in practice. In particular,
the activation threshold should be carefully chosen to avoid false positives.
- By default, parser objects have a maximum amplification factor of 100.0.
+ Parser objects usually have a maximum amplification factor of 100,
+ but the actual default value depends on the underlying Expat library.
An :exc:`ExpatError` is raised if this method is called on a
|xml-non-root-parser| parser or if *max_factor* is outside the valid range.
not. See https://www.w3.org/TR/2006/REC-xml11-20060816/#NT-EncodingDecl
and https://www.iana.org/assignments/character-sets/character-sets.xhtml.
+
+.. _billion laughs: https://en.wikipedia.org/wiki/Billion_laughs_attack
.. |xml-non-root-parser| replace:: :ref:`non-root <xmlparser-non-root>`
* Add :meth:`~xml.parsers.expat.xmlparser.SetAllocTrackerActivationThreshold`
and :meth:`~xml.parsers.expat.xmlparser.SetAllocTrackerMaximumAmplification`
- to :ref:`xmlparser <xmlparser-objects>` objects to prevent use of
- disproportional amounts of dynamic memory from within an Expat parser.
+ to :ref:`xmlparser <xmlparser-objects>` objects to tune protections against
+ disproportional amounts of dynamic memory usage from within an Expat parser.
(Contributed by Bénédikt Tran in :gh:`90949`.)
+* Add :meth:`~xml.parsers.expat.xmlparser.SetBillionLaughsAttackProtectionActivationThreshold`
+ and :meth:`~xml.parsers.expat.xmlparser.SetBillionLaughsAttackProtectionMaximumAmplification`
+ to :ref:`xmlparser <xmlparser-objects>` objects to tune protections against
+ `billion laughs`_ attacks.
+ (Contributed by Bénédikt Tran in :gh:`90949`.)
+
+ .. _billion laughs: https://en.wikipedia.org/wiki/Billion_laughs_attack
+
zlib
----
XML_Parser parser, unsigned long long activationThresholdBytes);
XML_Bool (*SetAllocTrackerMaximumAmplification)(
XML_Parser parser, float maxAmplificationFactor);
+ /* might be NULL for expat < 2.4.0 */
+ XML_Bool (*SetBillionLaughsAttackProtectionActivationThreshold)(
+ XML_Parser parser, unsigned long long activationThresholdBytes);
+ XML_Bool (*SetBillionLaughsAttackProtectionMaximumAmplification)(
+ XML_Parser parser, float maxAmplificationFactor);
/* always add new stuff to the end! */
};
self.assert_root_parser_failure(setter, 123.45)
+@unittest.skipIf(expat.version_info < (2, 4, 0), "requires Expat >= 2.4.0")
+class ExpansionProtectionTest(AttackProtectionTestBase, unittest.TestCase):
+
+ def assert_rejected(self, func, /, *args, **kwargs):
+ """Check that func(*args, **kwargs) hits the allocation limit."""
+ msg = (
+ r"limit on input amplification factor \(from DTD and entities\) "
+ r"breached: line \d+, column \d+"
+ )
+ self.assertRaisesRegex(expat.ExpatError, msg, func, *args, **kwargs)
+
+ def set_activation_threshold(self, parser, threshold):
+ return parser.SetBillionLaughsAttackProtectionActivationThreshold(threshold)
+
+ def set_maximum_amplification(self, parser, max_factor):
+ return parser.SetBillionLaughsAttackProtectionMaximumAmplification(max_factor)
+
+ def test_set_activation_threshold__threshold_reached(self):
+ parser = expat.ParserCreate()
+ # Choose a threshold expected to be always reached.
+ self.set_activation_threshold(parser, 3)
+ # Check that the threshold is reached by choosing a small factor
+ # and a payload whose peak amplification factor exceeds it.
+ self.assertIsNone(self.set_maximum_amplification(parser, 1.0))
+ payload = self.exponential_expansion_payload(ncols=10, nrows=4)
+ self.assert_rejected(parser.Parse, payload, True)
+
+ def test_set_activation_threshold__threshold_not_reached(self):
+ parser = expat.ParserCreate()
+ # Choose a threshold expected to be never reached.
+ self.set_activation_threshold(parser, pow(10, 5))
+ # Check that the threshold is reached by choosing a small factor
+ # and a payload whose peak amplification factor exceeds it.
+ self.assertIsNone(self.set_maximum_amplification(parser, 1.0))
+ payload = self.exponential_expansion_payload(ncols=10, nrows=4)
+ self.assertIsNotNone(parser.Parse(payload, True))
+
+ def test_set_maximum_amplification__amplification_exceeded(self):
+ parser = expat.ParserCreate()
+ # Unconditionally enable maximum activation factor.
+ self.set_activation_threshold(parser, 0)
+ # Choose a max amplification factor expected to always be exceeded.
+ self.assertIsNone(self.set_maximum_amplification(parser, 1.0))
+ # Craft a payload for which the peak amplification factor is > 1.0.
+ payload = self.exponential_expansion_payload(ncols=1, nrows=2)
+ self.assert_rejected(parser.Parse, payload, True)
+
+ def test_set_maximum_amplification__amplification_not_exceeded(self):
+ parser = expat.ParserCreate()
+ # Unconditionally enable maximum activation factor.
+ self.set_activation_threshold(parser, 0)
+ # Choose a max amplification factor expected to never be exceeded.
+ self.assertIsNone(self.set_maximum_amplification(parser, 1e4))
+ # Craft a payload for which the peak amplification factor is < 1e4.
+ payload = self.exponential_expansion_payload(ncols=1, nrows=2)
+ self.assertIsNotNone(parser.Parse(payload, True))
+
+
@unittest.skipIf(expat.version_info < (2, 7, 2), "requires Expat >= 2.7.2")
class MemoryProtectionTest(AttackProtectionTestBase, unittest.TestCase):
Add :meth:`~xml.parsers.expat.xmlparser.SetAllocTrackerActivationThreshold`
and :meth:`~xml.parsers.expat.xmlparser.SetAllocTrackerMaximumAmplification`
-to :ref:`xmlparser <xmlparser-objects>` objects to prevent use of
-disproportional amounts of dynamic memory from within an Expat parser.
+to :ref:`xmlparser <xmlparser-objects>` objects to tune protections against
+disproportional amounts of dynamic memory usage from within an Expat parser.
Patch by Bénédikt Tran.
--- /dev/null
+Add
+:meth:`~xml.parsers.expat.xmlparser.SetBillionLaughsAttackProtectionActivationThreshold`
+and
+:meth:`~xml.parsers.expat.xmlparser.SetBillionLaughsAttackProtectionMaximumAmplification`
+to :ref:`xmlparser <xmlparser-objects>` objects to tune protections against
+`billion laughs <https://en.wikipedia.org/wiki/Billion_laughs_attack>`_ attacks.
+Patch by Bénédikt Tran.
#endif /* (XML_COMBINED_VERSION >= 19505) */
+#if (XML_COMBINED_VERSION >= 20400)
+
+PyDoc_STRVAR(pyexpat_xmlparser_SetBillionLaughsAttackProtectionActivationThreshold__doc__,
+"SetBillionLaughsAttackProtectionActivationThreshold($self, threshold, /)\n"
+"--\n"
+"\n"
+"Sets the number of output bytes needed to activate protection against billion laughs attacks.\n"
+"\n"
+"The number of output bytes includes amplification from entity expansion\n"
+"and reading DTD files.\n"
+"\n"
+"Parser objects usually have a protection activation threshold of 8 MiB,\n"
+"but the actual default value depends on the underlying Expat library.\n"
+"\n"
+"Activation thresholds below 4 MiB are known to break support for DITA 1.3\n"
+"payload and are hence not recommended.");
+
+#define PYEXPAT_XMLPARSER_SETBILLIONLAUGHSATTACKPROTECTIONACTIVATIONTHRESHOLD_METHODDEF \
+ {"SetBillionLaughsAttackProtectionActivationThreshold", _PyCFunction_CAST(pyexpat_xmlparser_SetBillionLaughsAttackProtectionActivationThreshold), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, pyexpat_xmlparser_SetBillionLaughsAttackProtectionActivationThreshold__doc__},
+
+static PyObject *
+pyexpat_xmlparser_SetBillionLaughsAttackProtectionActivationThreshold_impl(xmlparseobject *self,
+ PyTypeObject *cls,
+ unsigned long long threshold);
+
+static PyObject *
+pyexpat_xmlparser_SetBillionLaughsAttackProtectionActivationThreshold(PyObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
+{
+ PyObject *return_value = NULL;
+ #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)
+ # define KWTUPLE (PyObject *)&_Py_SINGLETON(tuple_empty)
+ #else
+ # define KWTUPLE NULL
+ #endif
+
+ static const char * const _keywords[] = {"", NULL};
+ static _PyArg_Parser _parser = {
+ .keywords = _keywords,
+ .fname = "SetBillionLaughsAttackProtectionActivationThreshold",
+ .kwtuple = KWTUPLE,
+ };
+ #undef KWTUPLE
+ PyObject *argsbuf[1];
+ unsigned long long threshold;
+
+ args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser,
+ /*minpos*/ 1, /*maxpos*/ 1, /*minkw*/ 0, /*varpos*/ 0, argsbuf);
+ if (!args) {
+ goto exit;
+ }
+ if (!_PyLong_UnsignedLongLong_Converter(args[0], &threshold)) {
+ goto exit;
+ }
+ return_value = pyexpat_xmlparser_SetBillionLaughsAttackProtectionActivationThreshold_impl((xmlparseobject *)self, cls, threshold);
+
+exit:
+ return return_value;
+}
+
+#endif /* (XML_COMBINED_VERSION >= 20400) */
+
+#if (XML_COMBINED_VERSION >= 20400)
+
+PyDoc_STRVAR(pyexpat_xmlparser_SetBillionLaughsAttackProtectionMaximumAmplification__doc__,
+"SetBillionLaughsAttackProtectionMaximumAmplification($self, max_factor,\n"
+" /)\n"
+"--\n"
+"\n"
+"Sets the maximum tolerated amplification factor for protection against billion laughs attacks.\n"
+"\n"
+"The amplification factor is calculated as \"(direct + indirect) / direct\"\n"
+"while parsing, where \"direct\" is the number of bytes read from the primary\n"
+"document in parsing and \"indirect\" is the number of bytes added by expanding\n"
+"entities and reading external DTD files, combined.\n"
+"\n"
+"The \'max_factor\' value must be a non-NaN floating point value greater than\n"
+"or equal to 1.0. Amplification factors greater than 30,000 can be observed\n"
+"in the middle of parsing even with benign files in practice. In particular,\n"
+"the activation threshold should be carefully chosen to avoid false positives.\n"
+"\n"
+"Parser objects usually have a maximum amplification factor of 100,\n"
+"but the actual default value depends on the underlying Expat library.");
+
+#define PYEXPAT_XMLPARSER_SETBILLIONLAUGHSATTACKPROTECTIONMAXIMUMAMPLIFICATION_METHODDEF \
+ {"SetBillionLaughsAttackProtectionMaximumAmplification", _PyCFunction_CAST(pyexpat_xmlparser_SetBillionLaughsAttackProtectionMaximumAmplification), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, pyexpat_xmlparser_SetBillionLaughsAttackProtectionMaximumAmplification__doc__},
+
+static PyObject *
+pyexpat_xmlparser_SetBillionLaughsAttackProtectionMaximumAmplification_impl(xmlparseobject *self,
+ PyTypeObject *cls,
+ float max_factor);
+
+static PyObject *
+pyexpat_xmlparser_SetBillionLaughsAttackProtectionMaximumAmplification(PyObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
+{
+ PyObject *return_value = NULL;
+ #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)
+ # define KWTUPLE (PyObject *)&_Py_SINGLETON(tuple_empty)
+ #else
+ # define KWTUPLE NULL
+ #endif
+
+ static const char * const _keywords[] = {"", NULL};
+ static _PyArg_Parser _parser = {
+ .keywords = _keywords,
+ .fname = "SetBillionLaughsAttackProtectionMaximumAmplification",
+ .kwtuple = KWTUPLE,
+ };
+ #undef KWTUPLE
+ PyObject *argsbuf[1];
+ float max_factor;
+
+ args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser,
+ /*minpos*/ 1, /*maxpos*/ 1, /*minkw*/ 0, /*varpos*/ 0, argsbuf);
+ if (!args) {
+ goto exit;
+ }
+ if (PyFloat_CheckExact(args[0])) {
+ max_factor = (float) (PyFloat_AS_DOUBLE(args[0]));
+ }
+ else
+ {
+ max_factor = (float) PyFloat_AsDouble(args[0]);
+ if (max_factor == -1.0 && PyErr_Occurred()) {
+ goto exit;
+ }
+ }
+ return_value = pyexpat_xmlparser_SetBillionLaughsAttackProtectionMaximumAmplification_impl((xmlparseobject *)self, cls, max_factor);
+
+exit:
+ return return_value;
+}
+
+#endif /* (XML_COMBINED_VERSION >= 20400) */
+
#if (XML_COMBINED_VERSION >= 20702)
PyDoc_STRVAR(pyexpat_xmlparser_SetAllocTrackerActivationThreshold__doc__,
"\n"
"Sets the number of allocated bytes of dynamic memory needed to activate protection against disproportionate use of RAM.\n"
"\n"
-"By default, parser objects have an allocation activation threshold of 64 MiB.");
+"Parser objects usually have an allocation activation threshold of 64 MiB,\n"
+"but the actual default value depends on the underlying Expat library.");
#define PYEXPAT_XMLPARSER_SETALLOCTRACKERACTIVATIONTHRESHOLD_METHODDEF \
{"SetAllocTrackerActivationThreshold", _PyCFunction_CAST(pyexpat_xmlparser_SetAllocTrackerActivationThreshold), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, pyexpat_xmlparser_SetAllocTrackerActivationThreshold__doc__},
"near the start of parsing even with benign files in practice. In particular,\n"
"the activation threshold should be carefully chosen to avoid false positives.\n"
"\n"
-"By default, parser objects have a maximum amplification factor of 100.0.");
+"Parser objects usually have a maximum amplification factor of 100,\n"
+"but the actual default value depends on the underlying Expat library.");
#define PYEXPAT_XMLPARSER_SETALLOCTRACKERMAXIMUMAMPLIFICATION_METHODDEF \
{"SetAllocTrackerMaximumAmplification", _PyCFunction_CAST(pyexpat_xmlparser_SetAllocTrackerMaximumAmplification), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, pyexpat_xmlparser_SetAllocTrackerMaximumAmplification__doc__},
#define PYEXPAT_XMLPARSER_USEFOREIGNDTD_METHODDEF
#endif /* !defined(PYEXPAT_XMLPARSER_USEFOREIGNDTD_METHODDEF) */
+#ifndef PYEXPAT_XMLPARSER_SETBILLIONLAUGHSATTACKPROTECTIONACTIVATIONTHRESHOLD_METHODDEF
+ #define PYEXPAT_XMLPARSER_SETBILLIONLAUGHSATTACKPROTECTIONACTIVATIONTHRESHOLD_METHODDEF
+#endif /* !defined(PYEXPAT_XMLPARSER_SETBILLIONLAUGHSATTACKPROTECTIONACTIVATIONTHRESHOLD_METHODDEF) */
+
+#ifndef PYEXPAT_XMLPARSER_SETBILLIONLAUGHSATTACKPROTECTIONMAXIMUMAMPLIFICATION_METHODDEF
+ #define PYEXPAT_XMLPARSER_SETBILLIONLAUGHSATTACKPROTECTIONMAXIMUMAMPLIFICATION_METHODDEF
+#endif /* !defined(PYEXPAT_XMLPARSER_SETBILLIONLAUGHSATTACKPROTECTIONMAXIMUMAMPLIFICATION_METHODDEF) */
+
#ifndef PYEXPAT_XMLPARSER_SETALLOCTRACKERACTIVATIONTHRESHOLD_METHODDEF
#define PYEXPAT_XMLPARSER_SETALLOCTRACKERACTIVATIONTHRESHOLD_METHODDEF
#endif /* !defined(PYEXPAT_XMLPARSER_SETALLOCTRACKERACTIVATIONTHRESHOLD_METHODDEF) */
#ifndef PYEXPAT_XMLPARSER_SETALLOCTRACKERMAXIMUMAMPLIFICATION_METHODDEF
#define PYEXPAT_XMLPARSER_SETALLOCTRACKERMAXIMUMAMPLIFICATION_METHODDEF
#endif /* !defined(PYEXPAT_XMLPARSER_SETALLOCTRACKERMAXIMUMAMPLIFICATION_METHODDEF) */
-/*[clinic end generated code: output=e73935658c04c83e input=a9049054013a1b77]*/
+/*[clinic end generated code: output=81101a16a409daf6 input=a9049054013a1b77]*/
}
#endif
-#if XML_COMBINED_VERSION >= 20702
+#if XML_COMBINED_VERSION >= 20400
static PyObject *
set_activation_threshold(xmlparseobject *self,
PyTypeObject *cls,
}
#endif
+#if XML_COMBINED_VERSION >= 20400
+/*[clinic input]
+@permit_long_summary
+@permit_long_docstring_body
+pyexpat.xmlparser.SetBillionLaughsAttackProtectionActivationThreshold
+
+ cls: defining_class
+ threshold: unsigned_long_long
+ /
+
+Sets the number of output bytes needed to activate protection against billion laughs attacks.
+
+The number of output bytes includes amplification from entity expansion
+and reading DTD files.
+
+Parser objects usually have a protection activation threshold of 8 MiB,
+but the actual default value depends on the underlying Expat library.
+
+Activation thresholds below 4 MiB are known to break support for DITA 1.3
+payload and are hence not recommended.
+[clinic start generated code]*/
+
+static PyObject *
+pyexpat_xmlparser_SetBillionLaughsAttackProtectionActivationThreshold_impl(xmlparseobject *self,
+ PyTypeObject *cls,
+ unsigned long long threshold)
+/*[clinic end generated code: output=0c082342f1c78114 input=fa2f91f26b62a42a]*/
+{
+ return set_activation_threshold(
+ self, cls, threshold,
+ XML_SetBillionLaughsAttackProtectionActivationThreshold
+ );
+}
+#endif
+
+#if XML_COMBINED_VERSION >= 20400
+/*[clinic input]
+@permit_long_summary
+@permit_long_docstring_body
+pyexpat.xmlparser.SetBillionLaughsAttackProtectionMaximumAmplification
+
+ cls: defining_class
+ max_factor: float
+ /
+
+Sets the maximum tolerated amplification factor for protection against billion laughs attacks.
+
+The amplification factor is calculated as "(direct + indirect) / direct"
+while parsing, where "direct" is the number of bytes read from the primary
+document in parsing and "indirect" is the number of bytes added by expanding
+entities and reading external DTD files, combined.
+
+The 'max_factor' value must be a non-NaN floating point value greater than
+or equal to 1.0. Amplification factors greater than 30,000 can be observed
+in the middle of parsing even with benign files in practice. In particular,
+the activation threshold should be carefully chosen to avoid false positives.
+
+Parser objects usually have a maximum amplification factor of 100,
+but the actual default value depends on the underlying Expat library.
+[clinic start generated code]*/
+
+static PyObject *
+pyexpat_xmlparser_SetBillionLaughsAttackProtectionMaximumAmplification_impl(xmlparseobject *self,
+ PyTypeObject *cls,
+ float max_factor)
+/*[clinic end generated code: output=c590439eadf463fa input=cc1e97c1fd2bd950]*/
+{
+ return set_maximum_amplification(
+ self, cls, max_factor,
+ XML_SetBillionLaughsAttackProtectionMaximumAmplification
+ );
+}
+#endif
+
#if XML_COMBINED_VERSION >= 20702
/*[clinic input]
@permit_long_summary
Sets the number of allocated bytes of dynamic memory needed to activate protection against disproportionate use of RAM.
-By default, parser objects have an allocation activation threshold of 64 MiB.
+Parser objects usually have an allocation activation threshold of 64 MiB,
+but the actual default value depends on the underlying Expat library.
[clinic start generated code]*/
static PyObject *
pyexpat_xmlparser_SetAllocTrackerActivationThreshold_impl(xmlparseobject *self,
PyTypeObject *cls,
unsigned long long threshold)
-/*[clinic end generated code: output=bed7e93207ba08c5 input=54182cd71ad69978]*/
+/*[clinic end generated code: output=bed7e93207ba08c5 input=b7a7a3e3d054286a]*/
{
return set_activation_threshold(
self, cls, threshold,
near the start of parsing even with benign files in practice. In particular,
the activation threshold should be carefully chosen to avoid false positives.
-By default, parser objects have a maximum amplification factor of 100.0.
+Parser objects usually have a maximum amplification factor of 100,
+but the actual default value depends on the underlying Expat library.
[clinic start generated code]*/
static PyObject *
pyexpat_xmlparser_SetAllocTrackerMaximumAmplification_impl(xmlparseobject *self,
PyTypeObject *cls,
float max_factor)
-/*[clinic end generated code: output=6e44bd48c9b112a0 input=3544abf9dd7ae055]*/
+/*[clinic end generated code: output=6e44bd48c9b112a0 input=c6af7ccb76ae5c6b]*/
{
return set_maximum_amplification(
self, cls, max_factor,
PYEXPAT_XMLPARSER_EXTERNALENTITYPARSERCREATE_METHODDEF
PYEXPAT_XMLPARSER_SETPARAMENTITYPARSING_METHODDEF
PYEXPAT_XMLPARSER_USEFOREIGNDTD_METHODDEF
+ PYEXPAT_XMLPARSER_SETBILLIONLAUGHSATTACKPROTECTIONACTIVATIONTHRESHOLD_METHODDEF
+ PYEXPAT_XMLPARSER_SETBILLIONLAUGHSATTACKPROTECTIONMAXIMUMAMPLIFICATION_METHODDEF
PYEXPAT_XMLPARSER_SETALLOCTRACKERACTIVATIONTHRESHOLD_METHODDEF
PYEXPAT_XMLPARSER_SETALLOCTRACKERMAXIMUMAMPLIFICATION_METHODDEF
PYEXPAT_XMLPARSER_SETREPARSEDEFERRALENABLED_METHODDEF
capi->SetAllocTrackerActivationThreshold = NULL;
capi->SetAllocTrackerMaximumAmplification = NULL;
#endif
+#if XML_COMBINED_VERSION >= 20400
+ capi->SetBillionLaughsAttackProtectionActivationThreshold = XML_SetBillionLaughsAttackProtectionActivationThreshold;
+ capi->SetBillionLaughsAttackProtectionMaximumAmplification = XML_SetBillionLaughsAttackProtectionMaximumAmplification;
+#else
+ capi->SetAllocTrackerActivationThreshold = NULL;
+ capi->SetAllocTrackerMaximumAmplification = NULL;
+#endif
/* export using capsule */
PyObject *capi_object = PyCapsule_New(capi, PyExpat_CAPSULE_NAME,