diff options
| author | robot-piglet <[email protected]> | 2025-09-23 15:06:17 +0300 |
|---|---|---|
| committer | robot-piglet <[email protected]> | 2025-09-23 15:19:14 +0300 |
| commit | 555a474f62a7f1948772f5375e7f70bb511937aa (patch) | |
| tree | 526c40c9fcffaf367cf1be4c88b2ea05f55ae368 /contrib/python | |
| parent | 02f803b5fcdd27b7865b981fd880e37c4e021b10 (diff) | |
Intermediate changes
commit_hash:ec30870932eb908aebdb8505ded59229249b6951
Diffstat (limited to 'contrib/python')
| -rw-r--r-- | contrib/python/xmltodict/py3/.dist-info/METADATA | 7 | ||||
| -rw-r--r-- | contrib/python/xmltodict/py3/tests/test_dicttoxml.py | 74 | ||||
| -rw-r--r-- | contrib/python/xmltodict/py3/xmltodict.py | 54 | ||||
| -rw-r--r-- | contrib/python/xmltodict/py3/ya.make | 2 |
4 files changed, 121 insertions, 16 deletions
diff --git a/contrib/python/xmltodict/py3/.dist-info/METADATA b/contrib/python/xmltodict/py3/.dist-info/METADATA index 6462e0a6b5c..8f05caf86de 100644 --- a/contrib/python/xmltodict/py3/.dist-info/METADATA +++ b/contrib/python/xmltodict/py3/.dist-info/METADATA @@ -1,6 +1,6 @@ Metadata-Version: 2.4 Name: xmltodict -Version: 0.15.0 +Version: 0.15.1 Summary: Makes working with XML feel like you are working with JSON Home-page: https://github.com/martinblech/xmltodict Author: Martin Blech @@ -12,9 +12,6 @@ Classifier: License :: OSI Approved :: MIT License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 -Classifier: Programming Language :: Python :: 3.6 -Classifier: Programming Language :: Python :: 3.7 -Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 @@ -22,7 +19,7 @@ Classifier: Programming Language :: Python :: 3.12 Classifier: Programming Language :: Python :: 3.13 Classifier: Programming Language :: Python :: Implementation :: PyPy Classifier: Topic :: Text Processing :: Markup :: XML -Requires-Python: >=3.6 +Requires-Python: >=3.9 Description-Content-Type: text/markdown License-File: LICENSE Dynamic: author diff --git a/contrib/python/xmltodict/py3/tests/test_dicttoxml.py b/contrib/python/xmltodict/py3/tests/test_dicttoxml.py index 67e3a880979..1fa5ba78316 100644 --- a/contrib/python/xmltodict/py3/tests/test_dicttoxml.py +++ b/contrib/python/xmltodict/py3/tests/test_dicttoxml.py @@ -263,3 +263,77 @@ xmlns:b="http://b.com/"><x a:attr="val">1</x><a:y>2</a:y><b:z>3</b:z></root>''' xml = unparse({"a": {"@attr": "1<middle>2", "#text": "x"}}, full_document=False) # The generated XML should contain escaped '<' and '>' within the attribute value self.assertIn('attr="1<middle>2"', xml) + + def test_rejects_tag_name_starting_with_question(self): + with self.assertRaises(ValueError): + unparse({"?pi": "data"}, full_document=False) + + def test_rejects_tag_name_starting_with_bang(self): + with self.assertRaises(ValueError): + unparse({"!decl": "data"}, full_document=False) + + def test_rejects_attribute_name_starting_with_question(self): + with self.assertRaises(ValueError): + unparse({"a": {"@?weird": "x"}}, full_document=False) + + def test_rejects_attribute_name_starting_with_bang(self): + with self.assertRaises(ValueError): + unparse({"a": {"@!weird": "x"}}, full_document=False) + + def test_rejects_xmlns_prefix_starting_with_question_or_bang(self): + with self.assertRaises(ValueError): + unparse({"a": {"@xmlns": {"?p": "http://e/"}}}, full_document=False) + with self.assertRaises(ValueError): + unparse({"a": {"@xmlns": {"!p": "http://e/"}}}, full_document=False) + + def test_rejects_non_string_names(self): + class Weird: + def __str__(self): + return "bad>name" + + # Non-string element key + with self.assertRaises(ValueError): + unparse({Weird(): "x"}, full_document=False) + # Non-string attribute key + with self.assertRaises(ValueError): + unparse({"a": {Weird(): "x"}}, full_document=False) + + def test_rejects_tag_name_with_slash(self): + with self.assertRaises(ValueError): + unparse({"bad/name": "x"}, full_document=False) + + def test_rejects_tag_name_with_whitespace(self): + for name in ["bad name", "bad\tname", "bad\nname"]: + with self.assertRaises(ValueError): + unparse({name: "x"}, full_document=False) + + def test_rejects_attribute_name_with_slash(self): + with self.assertRaises(ValueError): + unparse({"a": {"@bad/name": "x"}}, full_document=False) + + def test_rejects_attribute_name_with_whitespace(self): + for name in ["@bad name", "@bad\tname", "@bad\nname"]: + with self.assertRaises(ValueError): + unparse({"a": {name: "x"}}, full_document=False) + + def test_rejects_xmlns_prefix_with_slash_or_whitespace(self): + # Slash + with self.assertRaises(ValueError): + unparse({"a": {"@xmlns": {"bad/prefix": "http://e/"}}}, full_document=False) + # Whitespace + with self.assertRaises(ValueError): + unparse({"a": {"@xmlns": {"bad prefix": "http://e/"}}}, full_document=False) + + def test_rejects_names_with_quotes_and_equals(self): + # Element names + for name in ['a"b', "a'b", "a=b"]: + with self.assertRaises(ValueError): + unparse({name: "x"}, full_document=False) + # Attribute names + for name in ['@a"b', "@a'b", "@a=b"]: + with self.assertRaises(ValueError): + unparse({"a": {name: "x"}}, full_document=False) + # xmlns prefixes + for prefix in ['a"b', "a'b", "a=b"]: + with self.assertRaises(ValueError): + unparse({"a": {"@xmlns": {prefix: "http://e/"}}}, full_document=False) diff --git a/contrib/python/xmltodict/py3/xmltodict.py b/contrib/python/xmltodict/py3/xmltodict.py index c8491b354bd..4b6852ab23c 100644 --- a/contrib/python/xmltodict/py3/xmltodict.py +++ b/contrib/python/xmltodict/py3/xmltodict.py @@ -14,7 +14,7 @@ if tuple(map(int, platform.python_version_tuple()[:2])) < (3, 7): from inspect import isgenerator __author__ = 'Martin Blech' -__version__ = "0.15.0" +__version__ = "0.15.1" __license__ = 'MIT' @@ -368,7 +368,46 @@ def _has_angle_brackets(value): return isinstance(value, str) and ("<" in value or ">" in value) +def _has_invalid_name_chars(value): + """Return True if value (a str) contains any disallowed name characters. + + Disallowed: '<', '>', '/', or any whitespace character. + Non-string values return False. + """ + if not isinstance(value, str): + return False + if "<" in value or ">" in value or "/" in value: + return True + # Check for any whitespace (spaces, tabs, newlines, etc.) + return any(ch.isspace() for ch in value) + + +def _validate_name(value, kind): + """Validate an element/attribute name for XML safety. + + Raises ValueError with a specific reason when invalid. + + kind: 'element' or 'attribute' (used in error messages) + """ + if not isinstance(value, str): + raise ValueError(f"{kind} name must be a string") + if value.startswith("?") or value.startswith("!"): + raise ValueError(f'Invalid {kind} name: cannot start with "?" or "!"') + if "<" in value or ">" in value: + raise ValueError(f'Invalid {kind} name: "<" or ">" not allowed') + if "/" in value: + raise ValueError(f'Invalid {kind} name: "/" not allowed') + if '"' in value or "'" in value: + raise ValueError(f"Invalid {kind} name: quotes not allowed") + if "=" in value: + raise ValueError(f'Invalid {kind} name: "=" not allowed') + if any(ch.isspace() for ch in value): + raise ValueError(f"Invalid {kind} name: whitespace not allowed") + + def _process_namespace(name, namespaces, ns_sep=':', attr_prefix='@'): + if not isinstance(name, str): + return name if not namespaces: return name try: @@ -402,8 +441,7 @@ def _emit(key, value, content_handler, return key, value = result # Minimal validation to avoid breaking out of tag context - if _has_angle_brackets(key): - raise ValueError('Invalid element name: "<" or ">" not allowed') + _validate_name(key, "element") if not hasattr(value, '__iter__') or isinstance(value, (str, dict)): value = [value] for index, v in enumerate(value): @@ -427,23 +465,19 @@ def _emit(key, value, content_handler, if ik == cdata_key: cdata = iv continue - if ik.startswith(attr_prefix): + if isinstance(ik, str) and ik.startswith(attr_prefix): ik = _process_namespace(ik, namespaces, namespace_separator, attr_prefix) if ik == '@xmlns' and isinstance(iv, dict): for k, v in iv.items(): - if _has_angle_brackets(k): - raise ValueError( - 'Invalid attribute name: "<" or ">" not allowed' - ) + _validate_name(k, "attribute") attr = 'xmlns{}'.format(f':{k}' if k else '') attrs[attr] = str(v) continue if not isinstance(iv, str): iv = str(iv) attr_name = ik[len(attr_prefix) :] - if _has_angle_brackets(attr_name): - raise ValueError('Invalid attribute name: "<" or ">" not allowed') + _validate_name(attr_name, "attribute") attrs[attr_name] = iv continue children.append((ik, iv)) diff --git a/contrib/python/xmltodict/py3/ya.make b/contrib/python/xmltodict/py3/ya.make index 5dbf4dca8ff..19f2aa40d76 100644 --- a/contrib/python/xmltodict/py3/ya.make +++ b/contrib/python/xmltodict/py3/ya.make @@ -2,7 +2,7 @@ PY3_LIBRARY() -VERSION(0.15.0) +VERSION(0.15.1) LICENSE(MIT) |
