diff options
| author | Jacob Walls <jacobtylerwalls@gmail.com> | 2026-01-07 16:23:48 -0500 |
|---|---|---|
| committer | Jacob Walls <jacobtylerwalls@gmail.com> | 2026-01-12 16:38:32 -0500 |
| commit | 73c5e94521c5b97e27cd2fe2d5b5c2e65f402755 (patch) | |
| tree | 03a799e0dfdac608e9ba5c95c5186bc31d851754 /tests | |
| parent | a25158f5cc590f3dff4226c3a48257481e6e67a6 (diff) | |
Refs #36769 -- Raised SuspiciousOperation for unexpected nested tags in XML Deserializer.
Thanks Shai Berger and Natalia Bidart for reviews.
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/fixtures/fixtures/invalid_deeply_nested_elements.xml | 1 | ||||
| -rw-r--r-- | tests/fixtures/fixtures/invalid_deeply_nested_elements_natural_key.xml | 14 | ||||
| -rw-r--r-- | tests/fixtures/tests.py | 15 | ||||
| -rw-r--r-- | tests/serializers/test_deserialization.py | 72 |
4 files changed, 44 insertions, 58 deletions
diff --git a/tests/fixtures/fixtures/invalid_deeply_nested_elements.xml b/tests/fixtures/fixtures/invalid_deeply_nested_elements.xml index 2ed39c3d36..e3ed67f320 100644 --- a/tests/fixtures/fixtures/invalid_deeply_nested_elements.xml +++ b/tests/fixtures/fixtures/invalid_deeply_nested_elements.xml @@ -3,6 +3,7 @@ <object pk="1" model="fixtures.person"> <field type="CharField" name="name"> + <!-- This <em> is unexpected & invalid --> Django <em>pony</em> </field> </object> diff --git a/tests/fixtures/fixtures/invalid_deeply_nested_elements_natural_key.xml b/tests/fixtures/fixtures/invalid_deeply_nested_elements_natural_key.xml new file mode 100644 index 0000000000..7433fad6c7 --- /dev/null +++ b/tests/fixtures/fixtures/invalid_deeply_nested_elements_natural_key.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="utf-8"?> +<django-objects version="1.0"> + + <object pk="1" model="fixtures.book"> + <field type="CharField" name="name">Music for all ages</field> + <field to="fixtures.person" name="authors" rel="ManyToManyRel"> + <object> + <!-- This <em> is unexpected & invalid --> + <natural>Artist formerly known as <em>Prince</em></natural> + </object> + </field> + </object> + +</django-objects> diff --git a/tests/fixtures/tests.py b/tests/fixtures/tests.py index 7511569d21..dd03343027 100644 --- a/tests/fixtures/tests.py +++ b/tests/fixtures/tests.py @@ -10,6 +10,7 @@ from unittest import mock from django.apps import apps from django.contrib.sites.models import Site from django.core import management +from django.core.exceptions import SuspiciousOperation from django.core.files.temp import NamedTemporaryFile from django.core.management import CommandError from django.core.management.commands.dumpdata import ProxyModelWarning @@ -23,7 +24,6 @@ from .models import ( CircularA, CircularB, NaturalKeyThing, - Person, PrimaryKeyUUIDModel, ProxySpy, Spy, @@ -522,12 +522,13 @@ class FixtureLoadingTests(DumpDataAssertMixin, TestCase): ) def test_deeply_nested_elements(self): - """Text inside deeply-nested tags is skipped.""" - management.call_command( - "loaddata", "invalid_deeply_nested_elements.xml", verbosity=0 - ) - person = Person.objects.get(pk=1) - self.assertEqual(person.name, "Django") # not "Django pony" + """Text inside deeply-nested tags raises SuspiciousOperation.""" + for file in [ + "invalid_deeply_nested_elements.xml", + "invalid_deeply_nested_elements_natural_key.xml", + ]: + with self.subTest(file=file), self.assertRaises(SuspiciousOperation): + management.call_command("loaddata", file, verbosity=0) def test_dumpdata_with_excludes(self): # Load fixture1 which has a site, two articles, and a category diff --git a/tests/serializers/test_deserialization.py b/tests/serializers/test_deserialization.py index a718a99038..b6a0818c42 100644 --- a/tests/serializers/test_deserialization.py +++ b/tests/serializers/test_deserialization.py @@ -1,15 +1,14 @@ import json -import time +import textwrap import unittest +from django.core.exceptions import SuspiciousOperation from django.core.serializers.base import DeserializationError, DeserializedObject from django.core.serializers.json import Deserializer as JsonDeserializer from django.core.serializers.jsonl import Deserializer as JsonlDeserializer from django.core.serializers.python import Deserializer from django.core.serializers.xml_serializer import Deserializer as XMLDeserializer -from django.db import models from django.test import SimpleTestCase -from django.test.utils import garbage_collect from .models import Author @@ -138,52 +137,23 @@ class TestDeserializer(SimpleTestCase): self.assertEqual(first_item.object, self.jane) self.assertEqual(second_item.object, self.joe) - def test_crafted_xml_performance(self): - """The time to process invalid inputs is not quadratic.""" - - def build_crafted_xml(depth, leaf_text_len): - nested_open = "<nested>" * depth - nested_close = "</nested>" * depth - leaf = "x" * leaf_text_len - field_content = f"{nested_open}{leaf}{nested_close}" - return f""" - <django-objects version="1.0"> - <object model="contenttypes.contenttype" pk="1"> - <field name="app_label">{field_content}</field> - <field name="model">m</field> - </object> - </django-objects> - """ - - def deserialize(crafted_xml): - iterator = XMLDeserializer(crafted_xml) - garbage_collect() - - start_time = time.perf_counter() - result = list(iterator) - end_time = time.perf_counter() - - self.assertEqual(len(result), 1) - self.assertIsInstance(result[0].object, models.Model) - return end_time - start_time - - def assertFactor(label, params, factor=2): - factors = [] - prev_time = None - for depth, length in params: - crafted_xml = build_crafted_xml(depth, length) - elapsed = deserialize(crafted_xml) - if prev_time is not None: - factors.append(elapsed / prev_time) - prev_time = elapsed - - with self.subTest(label): - # Assert based on the average factor to reduce test flakiness. - self.assertLessEqual(sum(factors) / len(factors), factor) - - assertFactor( - "varying depth, varying length", - [(50, 2000), (100, 4000), (200, 8000), (400, 16000), (800, 32000)], - 2, + def test_crafted_xml_rejected(self): + depth = 100 + leaf_text_len = 1000 + nested_open = "<nested>" * depth + nested_close = "</nested>" * depth + leaf = "x" * leaf_text_len + field_content = f"{nested_open}{leaf}{nested_close}" + crafted_xml = textwrap.dedent( + f""" + <django-objects version="1.0"> + <object model="contenttypes.contenttype" pk="1"> + <field name="app_label">{field_content}</field> + <field name="model">m</field> + </object> + </django-objects>""" ) - assertFactor("constant depth, varying length", [(100, 1), (100, 1000)], 2) + + msg = "Unexpected element: 'nested'" + with self.assertRaisesMessage(SuspiciousOperation, msg): + list(XMLDeserializer(crafted_xml)) |
