diff --git a/src/zeep/loader.py b/src/zeep/loader.py index 5fd3fdbd..0d394d16 100644 --- a/src/zeep/loader.py +++ b/src/zeep/loader.py @@ -1,3 +1,4 @@ +import importlib.resources import os.path import typing from urllib.parse import urljoin, urlparse, urlunparse @@ -13,6 +14,36 @@ ) from zeep.settings import Settings +# Well known schemas which are shipped with zeep, so loading them doesn't +# depend on the availability of the remote server (see #1417). The SOAP +# encoding schema is also auto imported by zeep when a WSDL references it +# without importing it, see ``zeep.xsd.const.AUTO_IMPORT_NAMESPACES``. +BUNDLED_SCHEMAS = { + "http://schemas.xmlsoap.org/soap/encoding/": "soap-encoding.xsd", + "https://schemas.xmlsoap.org/soap/encoding/": "soap-encoding.xsd", +} + + +def load_bundled_schema(url) -> typing.Optional[bytes]: + """Return the content of the schema shipped with zeep for the given url, + or None if zeep doesn't ship one or the file is missing. + + """ + filename = BUNDLED_SCHEMAS.get(str(url)) + if filename is None: + return None + try: + return ( + importlib.resources.files("zeep") + .joinpath("schemas") + .joinpath(filename) + .read_bytes() + ) + except OSError: + # The file is missing when zeep is packaged without its package data + # (for example by PyInstaller), load it through the transport instead. + return None + class ImportResolver(Resolver): """Custom lxml resolve to use the transport object""" @@ -102,6 +133,9 @@ def load_external( else: if base_url: url = absolute_location(url, base_url) + bundled = load_bundled_schema(url) + if bundled is not None: + return parse_xml(bundled, transport, base_url, settings=settings) if not _initial and settings.forbid_external: if urlparse(str(url)).scheme in ("http", "https"): raise ExternalReferenceForbidden(url) @@ -135,6 +169,9 @@ async def load_external_async( else: if base_url: url = absolute_location(url, base_url) + bundled = load_bundled_schema(url) + if bundled is not None: + return parse_xml(bundled, transport, base_url, settings=settings) if not _initial and settings.forbid_external: if urlparse(str(url)).scheme in ("http", "https"): raise ExternalReferenceForbidden(url) diff --git a/src/zeep/schemas/soap-encoding.xsd b/src/zeep/schemas/soap-encoding.xsd new file mode 100644 index 00000000..de4a44aa --- /dev/null +++ b/src/zeep/schemas/soap-encoding.xsd @@ -0,0 +1,535 @@ + + + + + + + + + 'root' can be used to distinguish serialization roots from other + elements that are present in a serialization but are not roots of + a serialized value graph + + + + + + + + + + + + + Attributes common to all elements that function as accessors or + represent independent (multi-ref) values. The href attribute is + intended to be used in a manner like CONREF. That is, the element + content should be empty iff the href attribute appears + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 'Array' is a complex type for accessors identified by position + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/test_loader.py b/tests/test_loader.py index 33ff3f81..b13146d0 100644 --- a/tests/test_loader.py +++ b/tests/test_loader.py @@ -2,7 +2,7 @@ from tests.utils import DummyTransport from zeep.exceptions import DTDForbidden, EntitiesForbidden, ExternalReferenceForbidden -from zeep.loader import load_external, parse_xml +from zeep.loader import BUNDLED_SCHEMAS, load_external, parse_xml from zeep.settings import Settings @@ -79,3 +79,27 @@ def test_forbid_external_default_allows_load(): tree = load_external("http://example.com/a.xsd", transport) assert tree.tag == "root" + + +@pytest.mark.parametrize( + "url", + [ + "http://schemas.xmlsoap.org/soap/encoding/", + "https://schemas.xmlsoap.org/soap/encoding/", + ], +) +def test_load_external_uses_bundled_soap_encoding_schema(url): + # Nothing is bound on the transport and remote loads are forbidden, so + # only the copy shipped with zeep can be used (#1417) + settings = Settings(forbid_external=True) + tree = load_external(url, DummyTransport(), settings=settings) + assert tree.get("targetNamespace") == "http://schemas.xmlsoap.org/soap/encoding/" + + +def test_missing_bundled_schema_falls_back_to_transport(monkeypatch): + # Packagers like PyInstaller can leave out the package data + url = "http://schemas.xmlsoap.org/soap/encoding/" + monkeypatch.setitem(BUNDLED_SCHEMAS, url, "missing.xsd") + transport = DummyTransport() + transport.bind(url, b"") + assert load_external(url, transport).tag == "root" diff --git a/tests/test_wsdl_arrays.py b/tests/test_wsdl_arrays.py index 47e61a12..98a69b90 100644 --- a/tests/test_wsdl_arrays.py +++ b/tests/test_wsdl_arrays.py @@ -608,3 +608,34 @@ def test_xml_soap_enc_string(transport): obj = shoe_type.parse(node[0], schema) assert obj[0]["_value_1"] == "foo" + + +def test_soap_array_without_remote_soap_encoding_schema(): + # The SOAP encoding schema ships with zeep, so resolving SOAP-ENC:Array + # works when schemas.xmlsoap.org cannot be reached (#1417). + schema = xsd.Schema( + load_xml( + """ + + + + + + + + + + + """ + ), + transport=DummyTransport(), + ) + + ArrayOfString = schema.get_type("ns0:ArrayOfString") + node = render_node(ArrayOfString, ArrayOfString(["item", "and"])) + data = ArrayOfString.parse_xmlelement(node, schema) + assert data == ["item", "and"]