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"]