diff --git a/doc/usage.rst b/doc/usage.rst index bb22bff..6087211 100644 --- a/doc/usage.rst +++ b/doc/usage.rst @@ -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 + + diff --git a/xmlschema/__init__.py b/xmlschema/__init__.py index d800a17..cfcf02e 100644 --- a/xmlschema/__init__.py +++ b/xmlschema/__init__.py @@ -8,6 +8,7 @@ # # @author Davide Brunato # +from . import limits from .exceptions import XMLSchemaException, XMLSchemaRegexError, XMLSchemaURLError, \ XMLSchemaNamespaceError from .etree import etree_tostring diff --git a/xmlschema/limits.py b/xmlschema/limits.py new file mode 100644 index 0000000..9ef9489 --- /dev/null +++ b/xmlschema/limits.py @@ -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 +# +"""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. +""" diff --git a/xmlschema/tests/validation/test_validation.py b/xmlschema/tests/validation/test_validation.py index 1e4a10b..083bbd1 100644 --- a/xmlschema/tests/validation/test_validation.py +++ b/xmlschema/tests/validation/test_validation.py @@ -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 diff --git a/xmlschema/tests/validators/test_schema_class.py b/xmlschema/tests/validators/test_schema_class.py index 45be457..1253a47 100644 --- a/xmlschema/tests/validators/test_schema_class.py +++ b/xmlschema/tests/validators/test_schema_class.py @@ -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): diff --git a/xmlschema/validators/elements.py b/xmlschema/validators/elements.py index a5fdc3f..2b7fe2e 100644 --- a/xmlschema/validators/elements.py +++ b/xmlschema/validators/elements.py @@ -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: diff --git a/xmlschema/validators/exceptions.py b/xmlschema/validators/exceptions.py index 4ff969a..d47d60a 100644 --- a/xmlschema/validators/exceptions.py +++ b/xmlschema/validators/exceptions.py @@ -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 diff --git a/xmlschema/validators/groups.py b/xmlschema/validators/groups.py index e5345b1..e248c0c 100644 --- a/xmlschema/validators/groups.py +++ b/xmlschema/validators/groups.py @@ -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 @@ -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: diff --git a/xmlschema/validators/models.py b/xmlschema/validators/models.py index 7a904f4..77c237f 100644 --- a/xmlschema/validators/models.py +++ b/xmlschema/validators/models.py @@ -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): diff --git a/xmlschema/validators/schema.py b/xmlschema/validators/schema.py index 1277d26..685f5dd 100644 --- a/xmlschema/validators/schema.py +++ b/xmlschema/validators/schema.py @@ -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) diff --git a/xmlschema/validators/xsdbase.py b/xmlschema/validators/xsdbase.py index 13393ee..fe04ca0 100644 --- a/xmlschema/validators/xsdbase.py +++ b/xmlschema/validators/xsdbase.py @@ -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): """