Add XML data depth limits
- Add module xmlschema.limits for store processing limits - Add max_depth optional argument to decode methods - Code cleaning for iter_decode() kwargs (elements and groups)
This commit is contained in:
parent
a374d15805
commit
df6eb23516
|
@ -526,35 +526,6 @@ For example you can build a schema using a *strict* mode and then decode XML dat
|
|||
using the *validation* argument setted to 'lax'.
|
||||
|
||||
|
||||
XML entity-based attacks protection
|
||||
-----------------------------------
|
||||
|
||||
The XML data resource loading is protected using the `SafeXMLParser` class, a subclass of
|
||||
the pure Python version of XMLParser that forbids the use of entities.
|
||||
The protection is applied both to XSD schemas and to XML data. The usage of this feature is
|
||||
regulated by the XMLSchema's argument *defuse*.
|
||||
For default this argument has value *'remote'* that means the protection on XML data is
|
||||
applied only to data loaded from remote. Other values for this argument can be *'always'*
|
||||
and *'never'*.
|
||||
|
||||
|
||||
Limit on model groups checking
|
||||
------------------------------
|
||||
|
||||
From release v1.0.11 the model groups of the schemas are checked against restriction violations
|
||||
and *Unique Particle Attribution* violations.
|
||||
|
||||
To avoids XSD model recursion attacks a limit of ``MAX_MODEL_DEPTH = 15`` is set. If this limit
|
||||
is exceeded an ``XMLSchemaModelDepthError`` is raised, the error is caught and a warning is generated.
|
||||
If you need to set an higher limit for checking all your groups you can import the library and change
|
||||
the value in the specific module that processes the model checks:
|
||||
|
||||
.. doctest::
|
||||
|
||||
>>> import xmlschema
|
||||
>>> xmlschema.validators.models.MAX_MODEL_DEPTH = 20
|
||||
|
||||
|
||||
Lazy validation
|
||||
---------------
|
||||
|
||||
|
@ -570,3 +541,53 @@ From release v1.0.14 XSD 1.1 support has been added to the library through the c
|
|||
:class:`XMLSchema11`. You have to use this class for XSD 1.1 schemas instead the default
|
||||
class :class:`XMLSchema` that is still linked to XSD 1.0 validator :class:`XMLSchema10`.
|
||||
From next minor release (v1.1) the default class will become :class:`XMLSchema11`.
|
||||
|
||||
|
||||
XML entity-based attacks protection
|
||||
...................................
|
||||
|
||||
The XML data resource loading is protected using the `SafeXMLParser` class, a subclass of
|
||||
the pure Python version of XMLParser that forbids the use of entities.
|
||||
The protection is applied both to XSD schemas and to XML data. The usage of this feature is
|
||||
regulated by the XMLSchema's argument *defuse*.
|
||||
For default this argument has value *'remote'* that means the protection on XML data is
|
||||
applied only to data loaded from remote. Other values for this argument can be *'always'*
|
||||
and *'never'*.
|
||||
|
||||
Processing limits
|
||||
-----------------
|
||||
|
||||
From release v1.0.16 a module has been added in order to group constants that define
|
||||
processing limits, generally to protect against attacks prepared to exhaust system
|
||||
resources. These limits usually don't need to be changed, but this possibility has
|
||||
been left at the module level for situations where a different setting is needed.
|
||||
|
||||
Limit on XSD model groups checking
|
||||
..................................
|
||||
|
||||
Model groups of the schemas are checked against restriction violations and *Unique Particle
|
||||
Attribution* violations. To avoids XSD model recursion attacks a depth limit of 15 levels
|
||||
is set. If this limit is exceeded an ``XMLSchemaModelDepthError`` is raised, the error is
|
||||
caught and a warning is generated. If you need to set an higher limit for checking all your
|
||||
groups you can import the library and change the value of ``MAX_MODEL_DEPTH`` in the limits
|
||||
module:
|
||||
|
||||
.. doctest::
|
||||
|
||||
>>> import xmlschema
|
||||
>>> xmlschema.limits.MAX_MODEL_DEPTH = 20
|
||||
|
||||
|
||||
Limit on XML data depth
|
||||
.......................
|
||||
|
||||
A limit of 9999 on maximum depth is set for XML validation/decoding/encoding to avoid
|
||||
attacks based on extremely deep XML data. To increase or decrease this limit change the
|
||||
value of ``MAX_XML_DEPTH`` in the module *limits* after the import of the package:
|
||||
|
||||
.. doctest::
|
||||
|
||||
>>> import xmlschema
|
||||
>>> xmlschema.limits.MAX_XML_DEPTH = 1000
|
||||
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
#
|
||||
# @author Davide Brunato <brunato@sissa.it>
|
||||
#
|
||||
from . import limits
|
||||
from .exceptions import XMLSchemaException, XMLSchemaRegexError, XMLSchemaURLError, \
|
||||
XMLSchemaNamespaceError
|
||||
from .etree import etree_tostring
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c), 2016-2019, SISSA (International School for Advanced Studies).
|
||||
# All rights reserved.
|
||||
# This file is distributed under the terms of the MIT License.
|
||||
# See the file 'LICENSE' in the root directory of the present
|
||||
# distribution, or http://opensource.org/licenses/MIT.
|
||||
#
|
||||
# @author Davide Brunato <brunato@sissa.it>
|
||||
#
|
||||
"""Package protection limits. Values can be changed after import to set different limits."""
|
||||
|
||||
MAX_XML_DEPTH = 9999
|
||||
"""
|
||||
Maximum depth of XML data. An `XMLSchemaValidationError` is raised if this limit is exceeded.
|
||||
"""
|
||||
|
||||
MAX_MODEL_DEPTH = 15
|
||||
"""
|
||||
Maximum XSD model group depth. An `XMLSchemaModelDepthError` is raised if this limit is exceeded.
|
||||
"""
|
|
@ -77,13 +77,33 @@ class TestValidation(XsdValidatorTestCase):
|
|||
|
||||
self.assertRaises(XMLSchemaValidationError, xsd_element.decode, source.root, namespaces=namespaces)
|
||||
|
||||
# Testing adding 'no_depth' argument
|
||||
for result in xsd_element.iter_decode(source.root, 'strict', namespaces=namespaces,
|
||||
source=source, no_depth=True):
|
||||
source=source, max_depth=1):
|
||||
del result
|
||||
|
||||
self.assertIsNone(xmlschema.validate(self.col_xml_file, lazy=True))
|
||||
|
||||
def test_max_depth_argument(self):
|
||||
schema = self.schema_class(self.col_xsd_file)
|
||||
self.assertEqual(
|
||||
schema.decode(self.col_xml_file, max_depth=1),
|
||||
{'@xmlns:col': 'http://example.com/ns/collection',
|
||||
'@xmlns:xsi': 'http://www.w3.org/2001/XMLSchema-instance',
|
||||
'@xsi:schemaLocation': 'http://example.com/ns/collection collection.xsd'})
|
||||
|
||||
xmlschema.limits.MAX_XML_DEPTH = 1
|
||||
with self.assertRaises(XMLSchemaValidationError):
|
||||
self.assertEqual(schema.decode(self.col_xml_file))
|
||||
xmlschema.limits.MAX_XML_DEPTH = 9999
|
||||
|
||||
self.assertEqual(
|
||||
schema.decode(self.col_xml_file, max_depth=2),
|
||||
{'@xmlns:col': 'http://example.com/ns/collection',
|
||||
'@xmlns:xsi': 'http://www.w3.org/2001/XMLSchema-instance',
|
||||
'@xsi:schemaLocation': 'http://example.com/ns/collection collection.xsd',
|
||||
'object': [{'@id': 'b0836217462', '@available': True},
|
||||
{'@id': 'b0836217463', '@available': True}]})
|
||||
|
||||
|
||||
class TestValidation11(TestValidation):
|
||||
schema_class = XMLSchema11
|
||||
|
|
|
@ -142,10 +142,12 @@ class TestXMLSchema10(XsdValidatorTestCase):
|
|||
"Remote networks are not accessible or avoid SSL verification error on Windows.")
|
||||
def test_remote_schemas_loading(self):
|
||||
col_schema = self.schema_class("https://raw.githubusercontent.com/brunato/xmlschema/master/"
|
||||
"xmlschema/tests/test_cases/examples/collection/collection.xsd")
|
||||
"xmlschema/tests/test_cases/examples/collection/collection.xsd",
|
||||
timeout=300)
|
||||
self.assertTrue(isinstance(col_schema, self.schema_class))
|
||||
vh_schema = self.schema_class("https://raw.githubusercontent.com/brunato/xmlschema/master/"
|
||||
"xmlschema/tests/test_cases/examples/vehicles/vehicles.xsd")
|
||||
"xmlschema/tests/test_cases/examples/vehicles/vehicles.xsd",
|
||||
timeout=300)
|
||||
self.assertTrue(isinstance(vh_schema, self.schema_class))
|
||||
|
||||
def test_schema_defuse(self):
|
||||
|
|
|
@ -458,14 +458,12 @@ class XsdElement(XsdComponent, ValidationMixin, ParticleMixin, ElementPathMixin)
|
|||
text = self.fixed if self.fixed is not None else self.default
|
||||
return self.type.text_decode(text)
|
||||
|
||||
def iter_decode(self, elem, validation='lax', converter=None, level=0, **kwargs):
|
||||
def iter_decode(self, elem, validation='lax', **kwargs):
|
||||
"""
|
||||
Creates an iterator for decoding an Element instance.
|
||||
|
||||
:param elem: the Element that has to be decoded.
|
||||
:param validation: the validation mode, can be 'lax', 'strict' or 'skip.
|
||||
:param converter: an :class:`XMLSchemaConverter` subclass or instance to use for the decoding.
|
||||
:param level: the depth of the element in the tree structure.
|
||||
:param kwargs: keyword arguments for the decoding process.
|
||||
:return: yields a decoded object, eventually preceded by a sequence of \
|
||||
validation or decoding errors.
|
||||
|
@ -473,8 +471,19 @@ class XsdElement(XsdComponent, ValidationMixin, ParticleMixin, ElementPathMixin)
|
|||
if self.abstract:
|
||||
yield self.validation_error(validation, "cannot use an abstract element for validation", elem, **kwargs)
|
||||
|
||||
if not isinstance(converter, XMLSchemaConverter):
|
||||
converter = self.schema.get_converter(converter, level=level, **kwargs)
|
||||
try:
|
||||
level = kwargs['level']
|
||||
except KeyError:
|
||||
level = 0
|
||||
|
||||
try:
|
||||
converter = kwargs['converter']
|
||||
except KeyError:
|
||||
converter = kwargs['converter'] = self.get_converter(**kwargs)
|
||||
else:
|
||||
if not isinstance(converter, XMLSchemaConverter):
|
||||
converter = kwargs['converter'] = self.get_converter(**kwargs)
|
||||
|
||||
inherited = kwargs.get('inherited')
|
||||
value = content = attributes = None
|
||||
|
||||
|
@ -492,7 +501,7 @@ class XsdElement(XsdComponent, ValidationMixin, ParticleMixin, ElementPathMixin)
|
|||
|
||||
# Decode attributes
|
||||
attribute_group = self.get_attributes(xsd_type)
|
||||
for result in attribute_group.iter_decode(elem.attrib, validation, level=level, **kwargs):
|
||||
for result in attribute_group.iter_decode(elem.attrib, validation, **kwargs):
|
||||
if isinstance(result, XMLSchemaValidationError):
|
||||
yield self.validation_error(validation, result, elem, **kwargs)
|
||||
else:
|
||||
|
@ -529,8 +538,7 @@ class XsdElement(XsdComponent, ValidationMixin, ParticleMixin, ElementPathMixin)
|
|||
for error in assertion(elem, **kwargs):
|
||||
yield self.validation_error(validation, error, **kwargs)
|
||||
|
||||
for result in xsd_type.content_type.iter_decode(
|
||||
elem, validation, converter, level + 1, **kwargs):
|
||||
for result in xsd_type.content_type.iter_decode(elem, validation, **kwargs):
|
||||
if isinstance(result, XMLSchemaValidationError):
|
||||
yield self.validation_error(validation, result, elem, **kwargs)
|
||||
else:
|
||||
|
@ -601,29 +609,40 @@ class XsdElement(XsdComponent, ValidationMixin, ParticleMixin, ElementPathMixin)
|
|||
del content
|
||||
|
||||
if validation != 'skip':
|
||||
for constraint in self.identities.values():
|
||||
if isinstance(constraint, XsdKeyref) and '_no_deep' in kwargs: # TODO: Complete lazy validation
|
||||
continue
|
||||
for error in constraint(elem, converter):
|
||||
yield self.validation_error(validation, error, elem, **kwargs)
|
||||
if 'max_depth' in kwargs:
|
||||
# Don't check key references with lazy or shallow validation
|
||||
for constraint in filter(lambda x: not isinstance(x, XsdKeyref), self.identities.values()):
|
||||
for error in constraint(elem, converter):
|
||||
yield self.validation_error(validation, error, elem, **kwargs)
|
||||
else:
|
||||
for constraint in self.identities.values():
|
||||
for error in constraint(elem, converter):
|
||||
yield self.validation_error(validation, error, elem, **kwargs)
|
||||
|
||||
def iter_encode(self, obj, validation='lax', converter=None, level=0, **kwargs):
|
||||
def iter_encode(self, obj, validation='lax', **kwargs):
|
||||
"""
|
||||
Creates an iterator for encoding data to an Element.
|
||||
|
||||
:param obj: the data that has to be encoded.
|
||||
:param validation: the validation mode: can be 'lax', 'strict' or 'skip'.
|
||||
:param converter: an :class:`XMLSchemaConverter` subclass or instance to use \
|
||||
for the encoding.
|
||||
:param level: the depth of the element data in the tree structure.
|
||||
:param kwargs: keyword arguments for the encoding process.
|
||||
:return: yields an Element, eventually preceded by a sequence of \
|
||||
validation or encoding errors.
|
||||
"""
|
||||
if not isinstance(converter, XMLSchemaConverter):
|
||||
converter = self.schema.get_converter(converter, level=level, **kwargs)
|
||||
element_data = converter.element_encode(obj, self, level)
|
||||
try:
|
||||
converter = kwargs['converter']
|
||||
except KeyError:
|
||||
converter = kwargs['converter'] = self.get_converter(**kwargs)
|
||||
else:
|
||||
if not isinstance(converter, XMLSchemaConverter):
|
||||
converter = kwargs['converter'] = self.get_converter(**kwargs)
|
||||
|
||||
try:
|
||||
level = kwargs['level']
|
||||
except KeyError:
|
||||
level = 0
|
||||
|
||||
element_data = converter.element_encode(obj, self, level)
|
||||
errors = []
|
||||
tag = element_data.tag
|
||||
text = None
|
||||
|
@ -683,8 +702,7 @@ class XsdElement(XsdComponent, ValidationMixin, ParticleMixin, ElementPathMixin)
|
|||
else:
|
||||
text = result
|
||||
else:
|
||||
for result in xsd_type.content_type.iter_encode(
|
||||
element_data, validation, converter, level + 1, **kwargs):
|
||||
for result in xsd_type.content_type.iter_encode(element_data, validation, **kwargs):
|
||||
if isinstance(result, XMLSchemaValidationError):
|
||||
errors.append(result)
|
||||
elif result:
|
||||
|
|
|
@ -15,7 +15,6 @@ from __future__ import unicode_literals
|
|||
|
||||
from ..compat import PY3, string_base_type
|
||||
from ..exceptions import XMLSchemaException, XMLSchemaWarning, XMLSchemaValueError
|
||||
from ..namespaces import get_namespace
|
||||
from ..qnames import qname_to_prefixed
|
||||
from ..etree import etree_tostring, etree_getpath
|
||||
from ..helpers import is_etree_element
|
||||
|
|
|
@ -14,6 +14,7 @@ This module contains classes for XML Schema model groups.
|
|||
from __future__ import unicode_literals
|
||||
import warnings
|
||||
|
||||
from .. import limits
|
||||
from ..compat import unicode_type
|
||||
from ..exceptions import XMLSchemaValueError
|
||||
from ..etree import etree_element
|
||||
|
@ -555,15 +556,12 @@ class XsdGroup(XsdComponent, ModelGroup, ValidationMixin):
|
|||
msg = "Maybe a not equivalent type table between elements %r and %r." % (self, xsd_element)
|
||||
warnings.warn(msg, XMLSchemaTypeTableWarning, stacklevel=3)
|
||||
|
||||
def iter_decode(self, elem, validation='lax', converter=None, level=0, **kwargs):
|
||||
def iter_decode(self, elem, validation='lax', **kwargs):
|
||||
"""
|
||||
Creates an iterator for decoding an Element content.
|
||||
|
||||
:param elem: the Element that has to be decoded.
|
||||
:param validation: the validation mode, can be 'lax', 'strict' or 'skip.
|
||||
:param converter: an :class:`XMLSchemaConverter` subclass or instance \
|
||||
to use for the decoding.
|
||||
:param level: the depth of the element in the tree structure.
|
||||
:param kwargs: keyword arguments for the decoding process.
|
||||
:return: yields a list of 3-tuples (key, decoded data, decoder), \
|
||||
eventually preceded by a sequence of validation or decoding errors.
|
||||
|
@ -590,16 +588,21 @@ class XsdGroup(XsdComponent, ModelGroup, ValidationMixin):
|
|||
result_list.append((cdata_index, text, None))
|
||||
cdata_index += 1
|
||||
|
||||
model = ModelVisitor(self)
|
||||
errors = []
|
||||
level = kwargs['level'] = kwargs.pop('level', 0) + 1
|
||||
if level > limits.MAX_XML_DEPTH:
|
||||
reason = "XML data depth exceeded (MAX_XML_DEPTH=%r)" % limits.MAX_XML_DEPTH
|
||||
self.validation_error('strict', reason, elem, **kwargs)
|
||||
|
||||
try:
|
||||
default_namespace = converter.get('')
|
||||
except (AttributeError, TypeError):
|
||||
converter = self.schema.get_converter(converter, level=level, **kwargs)
|
||||
default_namespace = converter.get('')
|
||||
converter = kwargs['converter']
|
||||
except KeyError:
|
||||
converter = kwargs['converter'] = self.get_converter(**kwargs)
|
||||
|
||||
default_namespace = converter.get('')
|
||||
model = ModelVisitor(self)
|
||||
errors = []
|
||||
model_broken = False
|
||||
|
||||
for index, child in enumerate(elem):
|
||||
if callable(child.tag):
|
||||
continue # child is a <class 'lxml.etree._Comment'>
|
||||
|
@ -646,12 +649,13 @@ class XsdGroup(XsdComponent, ModelGroup, ValidationMixin):
|
|||
xsd_element = None
|
||||
model_broken = True
|
||||
|
||||
if xsd_element is None or kwargs.get('no_depth'):
|
||||
# TODO: use a default decoder str-->str??
|
||||
if 'max_depth' in kwargs and kwargs['max_depth'] <= level:
|
||||
continue
|
||||
elif xsd_element is None:
|
||||
# TODO: apply a default decoder str-->str??
|
||||
continue
|
||||
|
||||
for result in xsd_element.iter_decode(
|
||||
child, validation, converter=converter, level=level, **kwargs):
|
||||
for result in xsd_element.iter_decode(child, validation, **kwargs):
|
||||
if isinstance(result, XMLSchemaValidationError):
|
||||
yield result
|
||||
else:
|
||||
|
@ -678,16 +682,12 @@ class XsdGroup(XsdComponent, ModelGroup, ValidationMixin):
|
|||
|
||||
yield result_list
|
||||
|
||||
def iter_encode(self, element_data, validation='lax', converter=None, level=0, indent=4, **kwargs):
|
||||
def iter_encode(self, element_data, validation='lax', **kwargs):
|
||||
"""
|
||||
Creates an iterator for encoding data to a list containing Element data.
|
||||
|
||||
:param element_data: an ElementData instance with unencoded data.
|
||||
:param validation: the validation mode: can be 'lax', 'strict' or 'skip'.
|
||||
:param converter: an :class:`XMLSchemaConverter` subclass or instance to use \
|
||||
for the encoding.
|
||||
:param level: the depth of the element data in the tree structure.
|
||||
:param indent: number of spaces for XML indentation (default is 4).
|
||||
:param kwargs: keyword arguments for the encoding process.
|
||||
:return: yields a couple with the text of the Element and a list of 3-tuples \
|
||||
(key, decoded data, decoder), eventually preceded by a sequence of validation \
|
||||
|
@ -697,19 +697,26 @@ class XsdGroup(XsdComponent, ModelGroup, ValidationMixin):
|
|||
yield element_data.content
|
||||
return
|
||||
|
||||
level = kwargs['level'] = kwargs.pop('level', 0) + 1
|
||||
errors = []
|
||||
text = None
|
||||
children = []
|
||||
try:
|
||||
indent = kwargs['indent']
|
||||
except KeyError:
|
||||
indent = 4
|
||||
|
||||
padding = '\n' + ' ' * indent * level
|
||||
|
||||
try:
|
||||
default_namespace = converter.get('')
|
||||
except (AttributeError, TypeError):
|
||||
converter = self.schema.get_converter(converter, level=level, **kwargs)
|
||||
default_namespace = converter.get('')
|
||||
converter = kwargs['converter']
|
||||
except KeyError:
|
||||
converter = kwargs['converter'] = self.get_converter(**kwargs)
|
||||
|
||||
default_namespace = converter.get('')
|
||||
model = ModelVisitor(self)
|
||||
cdata_index = 0
|
||||
|
||||
if isinstance(element_data.content, dict) or kwargs.get('unordered'):
|
||||
content = model.iter_unordered_content(element_data.content)
|
||||
elif not isinstance(element_data.content, list):
|
||||
|
@ -766,8 +773,7 @@ class XsdGroup(XsdComponent, ModelGroup, ValidationMixin):
|
|||
yield self.validation_error(validation, reason, value, **kwargs)
|
||||
continue
|
||||
|
||||
for result in xsd_element.iter_encode(
|
||||
value, validation, converter=converter, level=level, indent=indent, **kwargs):
|
||||
for result in xsd_element.iter_encode(value, validation, **kwargs):
|
||||
if isinstance(result, XMLSchemaValidationError):
|
||||
yield result
|
||||
else:
|
||||
|
|
|
@ -14,17 +14,13 @@ This module contains classes and functions for processing XSD content models.
|
|||
from __future__ import unicode_literals
|
||||
from collections import defaultdict, deque, Counter
|
||||
|
||||
from .. import limits
|
||||
from ..compat import PY3, MutableSequence
|
||||
from ..exceptions import XMLSchemaValueError
|
||||
from .exceptions import XMLSchemaModelError, XMLSchemaModelDepthError
|
||||
from .xsdbase import ParticleMixin
|
||||
from .wildcards import XsdAnyElement, Xsd11AnyElement
|
||||
|
||||
MAX_MODEL_DEPTH = 15
|
||||
"""Limit depth for safe visiting of models"""
|
||||
|
||||
XSD_GROUP_MODELS = {'sequence', 'choice', 'all'}
|
||||
|
||||
|
||||
class ModelGroup(MutableSequence, ParticleMixin):
|
||||
"""
|
||||
|
@ -34,7 +30,6 @@ class ModelGroup(MutableSequence, ParticleMixin):
|
|||
parent = None
|
||||
|
||||
def __init__(self, model):
|
||||
assert model in XSD_GROUP_MODELS, "Not a valid value for 'model'"
|
||||
self._group = []
|
||||
self.model = model
|
||||
|
||||
|
@ -61,7 +56,7 @@ class ModelGroup(MutableSequence, ParticleMixin):
|
|||
|
||||
def __setattr__(self, name, value):
|
||||
if name == 'model' and value is not None:
|
||||
if value not in XSD_GROUP_MODELS:
|
||||
if value not in {'sequence', 'choice', 'all'}:
|
||||
raise XMLSchemaValueError("invalid model group %r." % value)
|
||||
if self.model is not None and value != self.model and self.model != 'all':
|
||||
raise XMLSchemaValueError("cannot change group model from %r to %r" % (self.model, value))
|
||||
|
@ -165,11 +160,11 @@ class ModelGroup(MutableSequence, ParticleMixin):
|
|||
"""
|
||||
A generator function iterating elements and groups of a model group. Skips pointless groups,
|
||||
iterating deeper through them. Raises `XMLSchemaModelDepthError` if the argument *depth* is
|
||||
over `MAX_MODEL_DEPTH` value.
|
||||
over `limits.MAX_MODEL_DEPTH` value.
|
||||
|
||||
:param depth: guard for protect model nesting bombs, incremented at each deepest recursion.
|
||||
"""
|
||||
if depth > MAX_MODEL_DEPTH:
|
||||
if depth > limits.MAX_MODEL_DEPTH:
|
||||
raise XMLSchemaModelDepthError(self)
|
||||
for item in self:
|
||||
if not isinstance(item, ModelGroup):
|
||||
|
@ -183,11 +178,11 @@ class ModelGroup(MutableSequence, ParticleMixin):
|
|||
def iter_elements(self, depth=0):
|
||||
"""
|
||||
A generator function iterating model's elements. Raises `XMLSchemaModelDepthError` if the
|
||||
argument *depth* is over `MAX_MODEL_DEPTH` value.
|
||||
argument *depth* is over `limits.MAX_MODEL_DEPTH` value.
|
||||
|
||||
:param depth: guard for protect model nesting bombs, incremented at each deepest recursion.
|
||||
"""
|
||||
if depth > MAX_MODEL_DEPTH:
|
||||
if depth > limits.MAX_MODEL_DEPTH:
|
||||
raise XMLSchemaModelDepthError(self)
|
||||
for item in self:
|
||||
if isinstance(item, ModelGroup):
|
||||
|
@ -203,12 +198,12 @@ class ModelGroup(MutableSequence, ParticleMixin):
|
|||
:raises: an `XMLSchemaModelError` at first violated constraint.
|
||||
"""
|
||||
def safe_iter_path(group, depth):
|
||||
if depth > MAX_MODEL_DEPTH:
|
||||
if not depth:
|
||||
raise XMLSchemaModelDepthError(group)
|
||||
for item in group:
|
||||
if isinstance(item, ModelGroup):
|
||||
current_path.append(item)
|
||||
for _item in safe_iter_path(item, depth + 1):
|
||||
for _item in safe_iter_path(item, depth - 1):
|
||||
yield _item
|
||||
current_path.pop()
|
||||
else:
|
||||
|
@ -221,7 +216,7 @@ class ModelGroup(MutableSequence, ParticleMixin):
|
|||
except AttributeError:
|
||||
any_element = None
|
||||
|
||||
for e in safe_iter_path(self, 0):
|
||||
for e in safe_iter_path(self, limits.MAX_MODEL_DEPTH):
|
||||
for pe, previous_path in paths.values():
|
||||
# EDC check
|
||||
if not e.is_consistent(pe) or any_element and not any_element.is_consistent(pe):
|
||||
|
|
|
@ -822,27 +822,6 @@ class XMLSchemaBase(XsdValidator, ValidationMixin, ElementPathMixin):
|
|||
except KeyError:
|
||||
return []
|
||||
|
||||
def get_converter(self, converter=None, namespaces=None, **kwargs):
|
||||
"""
|
||||
Returns a new converter instance.
|
||||
|
||||
:param converter: can be a converter class or instance. If it's an instance \
|
||||
the new instance is copied from it and configured with the provided arguments.
|
||||
:param namespaces: is an optional mapping from namespace prefix to URI.
|
||||
:param kwargs: optional arguments for initialize the converter instance.
|
||||
:return: a converter instance.
|
||||
"""
|
||||
if converter is None:
|
||||
converter = getattr(self, 'converter', XMLSchemaConverter)
|
||||
|
||||
if isinstance(converter, XMLSchemaConverter):
|
||||
return converter.copy(namespaces=namespaces, **kwargs)
|
||||
elif issubclass(converter, XMLSchemaConverter):
|
||||
return converter(namespaces, **kwargs)
|
||||
else:
|
||||
msg = "'converter' argument must be a %r subclass or instance: %r"
|
||||
raise XMLSchemaTypeError(msg % (XMLSchemaConverter, converter))
|
||||
|
||||
def get_element(self, tag, path=None, namespaces=None):
|
||||
if not path:
|
||||
return self.find(tag, namespaces)
|
||||
|
@ -1223,16 +1202,14 @@ class XMLSchemaBase(XsdValidator, ValidationMixin, ElementPathMixin):
|
|||
inherited = {}
|
||||
|
||||
if source.is_lazy() and path is None:
|
||||
# TODO: Document validation in lazy mode.
|
||||
# Validation is done pushing a _no_deep argument for root node and with
|
||||
# a path='*' for validating children. This is a feature under test.
|
||||
xsd_element = self.get_element(source.root.tag, schema_path)
|
||||
if xsd_element is None:
|
||||
yield self.validation_error('lax', "%r is not an element of the schema" % source.root, source.root)
|
||||
msg = "%r is not an element of the schema"
|
||||
yield self.validation_error('lax', msg % source.root, source.root)
|
||||
|
||||
for result in xsd_element.iter_decode(source.root, source=source, namespaces=namespaces,
|
||||
use_defaults=use_defaults, id_map=id_map, no_depth=True,
|
||||
inherited=inherited, drop_results=True):
|
||||
use_defaults=use_defaults, id_map=id_map,
|
||||
inherited=inherited, max_depth=1):
|
||||
if isinstance(result, XMLSchemaValidationError):
|
||||
yield result
|
||||
else:
|
||||
|
@ -1249,7 +1226,7 @@ class XMLSchemaBase(XsdValidator, ValidationMixin, ElementPathMixin):
|
|||
|
||||
for result in xsd_element.iter_decode(elem, source=source, namespaces=namespaces,
|
||||
use_defaults=use_defaults, id_map=id_map,
|
||||
inherited=inherited, drop_results=True):
|
||||
inherited=inherited):
|
||||
if isinstance(result, XMLSchemaValidationError):
|
||||
yield result
|
||||
else:
|
||||
|
@ -1264,7 +1241,7 @@ class XMLSchemaBase(XsdValidator, ValidationMixin, ElementPathMixin):
|
|||
|
||||
def iter_decode(self, source, path=None, schema_path=None, validation='lax', process_namespaces=True,
|
||||
namespaces=None, use_defaults=True, decimal_type=None, datetime_types=False,
|
||||
converter=None, filler=None, fill_missing=False, **kwargs):
|
||||
converter=None, filler=None, fill_missing=False, max_depth=None, **kwargs):
|
||||
"""
|
||||
Creates an iterator for decoding an XML source to a data structure.
|
||||
|
||||
|
@ -1292,6 +1269,7 @@ class XMLSchemaBase(XsdValidator, ValidationMixin, ElementPathMixin):
|
|||
an attribute declaration. If not provided undecodable data is replaced by `None`.
|
||||
:param fill_missing: if set to `True` the decoder fills also missing attributes. \
|
||||
The filling value is `None` or a typed value if the *filler* callback is provided.
|
||||
:param max_depth: maximum level of decoding. For default has no limit.
|
||||
:param kwargs: keyword arguments with other options for converter and decoder.
|
||||
:return: yields a decoded data object, eventually preceded by a sequence of validation \
|
||||
or decoding errors.
|
||||
|
@ -1323,6 +1301,8 @@ class XMLSchemaBase(XsdValidator, ValidationMixin, ElementPathMixin):
|
|||
kwargs['decimal_type'] = decimal_type
|
||||
if filler is not None:
|
||||
kwargs['filler'] = filler
|
||||
if max_depth is not None:
|
||||
kwargs['max_depth'] = max_depth
|
||||
|
||||
for elem in source.iterfind(path, namespaces):
|
||||
xsd_element = self.get_element(elem.tag, schema_path, namespaces)
|
||||
|
|
|
@ -21,6 +21,7 @@ from ..qnames import XSD_ANNOTATION, XSD_APPINFO, XSD_DOCUMENTATION, XML_LANG, \
|
|||
get_qname, local_name, qname_to_prefixed
|
||||
from ..etree import etree_tostring
|
||||
from ..helpers import is_etree_element
|
||||
from ..converters import XMLSchemaConverter
|
||||
from .exceptions import XMLSchemaParseError, XMLSchemaValidationError, \
|
||||
XMLSchemaDecodeError, XMLSchemaEncodeError
|
||||
|
||||
|
@ -195,6 +196,27 @@ class XsdValidator(object):
|
|||
self.parse_error(msg % (value, ' | '.join(admitted_values)), elem)
|
||||
return ''
|
||||
|
||||
def get_converter(self, converter=None, namespaces=None, **kwargs):
|
||||
"""
|
||||
Returns a new converter instance.
|
||||
|
||||
:param converter: can be a converter class or instance. If it's an instance \
|
||||
the new instance is copied from it and configured with the provided arguments.
|
||||
:param namespaces: is an optional mapping from namespace prefix to URI.
|
||||
:param kwargs: optional arguments for initialize the converter instance.
|
||||
:return: a converter instance.
|
||||
"""
|
||||
if converter is None:
|
||||
converter = getattr(self, 'converter', XMLSchemaConverter)
|
||||
|
||||
if isinstance(converter, XMLSchemaConverter):
|
||||
return converter.copy(namespaces=namespaces, **kwargs)
|
||||
elif issubclass(converter, XMLSchemaConverter):
|
||||
return converter(namespaces, **kwargs)
|
||||
else:
|
||||
msg = "'converter' argument must be a %r subclass or instance: %r"
|
||||
raise XMLSchemaTypeError(msg % (XMLSchemaConverter, converter))
|
||||
|
||||
|
||||
class XsdComponent(XsdValidator):
|
||||
"""
|
||||
|
|
Loading…
Reference in New Issue