pub struct ParseOptions {Show 24 fields
pub max_entity_expansion_bytes: u64,
pub max_element_depth: u32,
pub external_resolver: Option<Arc<dyn EntityResolver>>,
pub xml10_fourth_edition: bool,
pub namespace_aware: bool,
pub validating: bool,
pub skip_name_validation: bool,
pub skip_end_tag_check: bool,
pub skip_xml_char_validation: bool,
pub stream_owned_names: bool,
pub skip_entity_expansion: bool,
pub resolve_entities: bool,
pub skip_inter_element_whitespace: bool,
pub skip_attr_validation: bool,
pub libxml2_compat: bool,
pub recovery_mode: bool,
pub auto_transcode: bool,
pub forced_encoding: Option<Encoding>,
pub load_external_dtd: bool,
pub resolve_external_entities: bool,
pub base_url: Option<String>,
pub remove_comments: bool,
pub remove_pis: bool,
pub cdata_as_text: bool,
}Expand description
Security limits and feature flags for the XML parser.
Construct with ParseOptions::default() for safe defaults, then override
individual fields as needed. All external-loading features are off by
default to prevent XXE and SSRF attacks. Limits mirror libxml2’s defaults
where applicable.
§Example
use sup_xml_core::{ParseOptions, parse_str};
let opts = ParseOptions {
max_element_depth: 64, // tighten from the default of 256
..ParseOptions::default()
};
let doc = parse_str("<root/>", &opts).unwrap();§Copy removal
ParseOptions was Copy before adding external_resolver,
which holds an Arc<dyn EntityResolver> (not Copy). The
derive is now Clone only. In practice almost no caller
needs Copy for an options struct — construct once, clone
when you need a second instance.
Fields§
§max_entity_expansion_bytes: u64Maximum total bytes produced across all entity expansions in a single document. Prevents “billion laughs” (CVE-2003-1564) and similar amplification attacks. Default: 1,000,000 (1 MB).
max_element_depth: u32Maximum element nesting depth. Documents deeper than this limit are rejected with a fatal error. Default: 256.
external_resolver: Option<Arc<dyn EntityResolver>>External resource resolver. None (default) = the parser
refuses to load any externally-referenced DTD or entity —
this is the XXE-safe stance and what every untrusted-input
pipeline should keep.
To opt into external loading, set this to one of:
FilesystemResolver— loads from a configured allowlist of local directories, optionally consulting an OASIS catalog first.NetworkResolver(behind thenetwork-resolverfeature) — fetches over HTTPS from a configured host allowlist with SSRF defenses.ChainedResolver— composes multiple resolvers; tries each in order.- A custom impl of the
EntityResolvertrait for bespoke setups (in-memory bundles, S3, audit-logging, …).
Replaces the allow_external_entities and allow_external_dtd
boolean flags from earlier versions; the presence of a
resolver IS the opt-in.
xml10_fourth_edition: boolUse XML 1.0 4th-edition (pre-2008) character class tables — BaseChar,
CombiningChar, Digit, Extender — instead of the simplified 5th-edition
Unicode-category rules. Equivalent to libxml2’s XML_PARSE_OLD10.
Default: false (5th edition, same as libxml2’s default).
namespace_aware: boolEnforce XML Namespaces 1.0 constraints during parsing.
When true, the parser rejects colons in PI targets, entity names, and
notation names (NCName requirement). Element and attribute QName checks
are applied by [resolve_namespaces][crate::resolve_namespaces].
Default: false.
validating: boolEnable DTD-based validation.
When true, the parser collects element and attribute declarations from
the internal DTD subset and verifies the document against them. This
has a small performance cost because declaration names must be interned
rather than skipped. Default: false.
skip_name_validation: boolSkip the XML Name production validation on element / attribute / PI names. Names are still scanned (we need to know where they end) but non-ASCII bytes are accepted without checking the Unicode-range rules that the XML 1.0 spec specifies.
Trade-off: a small speed-up on non-ASCII-heavy XML, at the cost of
silently accepting malformed names like <3foo> or names containing
disallowed Unicode characters. Use only with trusted input.
Default: false (validate per the spec).
skip_end_tag_check: boolSkip end-tag-matches-start-tag verification. Mirrors quick-xml’s
check_end_names: false. With this flag set the parser does not
maintain an element stack, does not check that </bar> closes the
matching <bar>, and does not enforce the max-depth limit.
Trade-off: noticeable speed-up on element-heavy XML (no per-element
Vec push / pop / name compare), at the cost of accepting malformed
documents like <a><b></a></b> silently. Use only with trusted
input — specifically, when an earlier pass has already verified
structural well-formedness.
Default: false (verify each end tag).
skip_xml_char_validation: boolSkip the XML 1.0 § 2.2 Char-production validation that runs once over
the whole input before parsing begins. When false (the default),
documents containing illegal control characters (NUL, form-feed, etc.)
or U+FFFE / U+FFFF are rejected with a fatal error.
Trade-off: ~5–10% speed-up by skipping one O(n) scan over the input, at the cost of accepting documents that violate XML 1.0 § 2.2. Use only with trusted input.
Default: false (validate per the spec).
stream_owned_names: boolInternal flag used by the streaming reader wrapper
(crate::streaming_reader::XmlByteStreamReader). When true,
the reader stores element-stack names as owned Strings
instead of byte ranges into the source buffer. This is
required when the source buffer is rolling (compacted /
reallocated between events) because byte ranges captured at
start-tag time would point at stale bytes by end-tag time.
Costs a small allocation per start tag — only set this when
running under the streaming wrapper. Slurped callers should
leave it false (the default) for zero-copy name storage.
skip_entity_expansion: boolSkip entity expansion in Event::Text
payloads. When false (the default), text events contain fully
decoded content — & is expanded to &, user-declared entities
have their replacement text included, etc. When true, text
events carry the raw source slice with entity references left in
place (& stays &), and the caller is responsible for
decoding them later — typically via
unescape.
Trade-off: dramatic speed-up on entity-heavy text (HTML / wiki
exports, RSS, Atom — anywhere & appears repeatedly inside
<text> bodies) because the text-content fast path becomes a
single memchr(b'<', …) over the entire body instead of a
memchr3(b'<', b'&', b']', …) that stops at each entity. Cost: the
caller must call unescape (or do the equivalent themselves)
before treating the text as decoded content.
Only applies to SAX text events; DOM text nodes are always decoded. Attribute values are also always decoded — attributes are typically inspected eagerly so the gain wouldn’t outweigh the API split.
Default: false (expand per the spec).
resolve_entities: boolReplace user-defined entity references (&foo; declared in
the DTD) with their replacement text during parsing. When
true (the default, spec behaviour) &foo; is expanded
inline into a Text node. When false, a dedicated
NodeKind::EntityRef node is emitted instead — the tree
preserves the original &foo; source form, the serializer
rewrites it verbatim, and consumers can walk the tree to
see which references appear where.
Mirrors lxml.etree.XMLParser(resolve_entities=False).
Predefined entities (&/</>/"/')
and numeric character references (A/A) are
ALWAYS expanded — those are part of the character data
production, not the entity-reference machinery.
Default: true (resolve / expand).
skip_inter_element_whitespace: boolIf true, suppress Text events between elements when their
content is only ASCII whitespace (spaces, tabs, CR, LF).
Useful for data-XML workloads (SOAP envelopes, RSS / Atom,
Maven POMs, configuration files, anything where indentation is
purely formatting). Lets the consumer skip the per-event work
of dispatching on text-event variants only to discard the
payload anyway. Mirrors quick-xml’s Reader::trim_text and
libxml2’s XML_PARSE_NOBLANKS.
Don’t enable this for document-style XML — XHTML, DocBook,
any mixed-content format where the spaces between sibling
elements are semantically significant. In <p>foo <b>bar</b></p>,
the space between “foo” and <b> is content; skipping it would
silently corrupt rendering.
Only the leading whitespace inside an element is suppressed —
trailing whitespace inside non-whitespace text events (e.g.,
<p>foo </p> → Text("foo ")) is preserved unchanged. This
matches the XML data model: the Text event is the same
content the application would see; we just don’t emit a
separate event for the inter-element indent.
Default: false (preserve every text event, correct per spec).
skip_attr_validation: boolSkip the eager attribute-syntax validation pass that runs on
every start tag. When false (the default), the parser
catches all of:
- bare
<in attribute values (§ 3.1 WFC) - bare
¬ part of a reference (§ 4.1) - unquoted values (§ 3.1 [41])
- invalid attribute name-start chars (§ 2.3 [4])
- missing whitespace between attrs (§ 3.1 [40])
- duplicate attribute names (§ 3.1 WFC)
- undefined / cyclic / external entity references in
attribute values (§ 4.1 / § 4.4.4)
at parse time, regardless of whether the caller iterates
BytesAttrs. This is what makes sup-xml’s compliance score on the W3C XML Conformance Test Suite (244/257) higher than libxml2’s (237/257).
Trade-off: validation walks each attribute, so attribute- heavy fixtures (OSM, RSS feeds, anything with many small attrs per element) run ~30–50 % slower than they would with no validation.
When true, the eager pass is skipped — attribute validation
is deferred to BytesAttrs::next() and only fires for tags
the caller actually iterates. Mirrors quick-xml’s default
behaviour (their Attributes iterator’s with_checks: true
is also lazy and only catches a subset). Use only with
trusted input — silently lets through every WFC the eager
path catches when the caller doesn’t read attributes.
Default: false (validate per the spec).
libxml2_compat: boolMatch libxml2’s accept/reject behaviour on edge-case documents even when our parser would otherwise be stricter than the spec requires libxml2 to be. Intended for migrations from libxml2-using code where existing documents may rely on libxml2’s specific implementation quirks.
What this enables:
- External entity references treated as opaque. Our default
rejects
&extName;whenextNamewas declaredSYSTEM/PUBLICand we never loaded the replacement text — the spec calls this “undefined entity.” libxml2 silently expands to nothing when it can’t read the external file (a class of silent-failure bug), and existing code may rely on that.
What this does NOT enable:
- External entity / DTD loading. XXE protection stays on
regardless — set
external_resolverto opt into external loading. - Anything else that’s a security regression. Compat mode relaxes correctness checks; it does not weaken security.
Default: false (strict — recommended).
recovery_mode: boolContinue parsing past non-fatal well-formedness errors,
accumulating them on the reader instead of returning the
first one as a Result::Err. Mirrors libxml2’s
XML_PARSE_RECOVER flag.
Two-tier error model:
ErrorLevel::Fatal— the input is unrecoverable (truncated mid-construct, invalid UTF-8, entity-expansion budget exceeded, depth-limit exceeded). Recovery cannot help;next()returnsErreven in recover mode.ErrorLevel::Error— non-fatal well-formedness violations (mismatched end tag, unclosed at EOF, bare&in text, undefined entity, duplicate attribute names, etc.). In recover mode the parser logs the error to [XmlBytesReader::recovered_errors] and applies a heuristic repair so it can continue. In strict mode (default) these are returned asErr.ErrorLevel::Warning— informational (rare). Always logged, never stops parsing.
Use cases:
- Web crawlers / RSS readers / feed aggregators handling third-party XML where one bad publisher shouldn’t break the whole pipeline.
- Diagnostic tools that want to show “here’s the partial tree we built, here are the problems we found.”
- Migration tools converting legacy malformed data into something cleaner.
Not for adversarial input. The existing security limits
(entity-expansion budget, depth limit) still apply — they
are ErrorLevel::Fatal and aren’t recovered from. But
recovery itself is a security-sensitive surface; treat the
flag as “trusted-source-but-buggy” semantics, not “accept
arbitrary input.”
Default: false (fail on the first non-trivial error).
auto_transcode: boolAuto-detect the input’s character encoding and transcode to UTF-8 before parsing.
On by default — matches libxml2’s behaviour and the XML 1.0
spec’s requirement (§ 4.3.3) that processors accept both UTF-8 and
UTF-16. With this on, parse_bytes accepts
any encoding the encoding module can detect:
UTF-8, US-ASCII, ISO-8859-1, Windows-1252, UTF-16 LE/BE, UTF-32
LE/BE, IBM037 EBCDIC, and (with the default full-encodings
feature) the full WHATWG set — Shift_JIS, GBK, Big5, ISO-8859-2…16,
KOI8-R, Windows-1250…1258, etc.
Detection follows XML 1.0 Appendix F — BOM first, then the
four-byte autodetect signatures for UTF-32 / UTF-16 / EBCDIC, then
the <?xml encoding="..."?> declaration.
For UTF-8 input this is zero-copy — the transcoder returns a borrow of the original bytes, so the only cost is a ~100-byte detection scan. Non-UTF-8 input pays one allocation for the decoded buffer.
Set to false to require the input to already be UTF-8. Use
that mode when your inputs are guaranteed-UTF-8 (you control the
producer), when you want to reject non-UTF-8 input as part of a
security posture, or when you want to skip the detection scan.
Default: true.
forced_encoding: Option<Encoding>Force a specific input encoding, overriding auto-detection.
When Some, the parser transcodes the input as this encoding
regardless of any BOM or <?xml encoding="…"?> declaration —
the behaviour of libxml2’s explicit encoding argument to
xmlReadMemory / xmlCtxtReadMemory and of xmlSwitchEncoding.
None (the default) auto-detects per auto_transcode.
load_external_dtd: boolLoad and parse the external DTD subset when a <!DOCTYPE r SYSTEM "path.dtd"> (or PUBLIC ... "path.dtd") declaration
is present. The external subset’s <!ELEMENT> and
<!ATTLIST> declarations are merged into
XmlBytesReader::dtd,
alongside any internal-subset declarations.
libxml2 calls the same feature XML_PARSE_DTDLOAD. Off by
default because loading arbitrary local files referenced
inside a document is a security-sensitive surface (classic
XXE vector) — turn it on only when you trust the input.
Scope: when on, this enables loading for both the
external DTD subset AND external general entities declared
like <!ENTITY x SYSTEM "file.txt">. The latter is the
hotter XXE channel — references such as &x; substitute
the file’s contents into the parsed tree, letting a
malicious document exfiltrate any file the parser process
can read. Both load behaviours share this flag because
libxml2 treats them as a single switch and the entity
declarations live inside the DTD anyway. Need finer
control? Wire an external_resolver
— it’s consulted first and can whitelist or deny per
request.
Resolution: the SYSTEM literal is treated as a filesystem
path; relative paths resolve against
base_url when set, otherwise against the
process’s current working directory. Network URIs
(http://...) are NOT fetched — they’re silently treated
as a missing file and ignored.
Default: false.
resolve_external_entities: boolWhether a declared external general entity may be loaded (via
external_resolver) and inlined when it
is referenced in content. When false, the resolver is still used
for the external DTD subset and parameter entities, but a reference
to an external general entity is treated as undefined — the entity’s
content is never fetched.
This is the second gate libxml2 applies: external general-entity
expansion requires both a loader and the caller having opted in
(libxml2’s XML_PARSE_NOENT together with a non-restricting
getEntity). lxml’s default (resolve_entities='internal') sets
this false to avoid inlining attacker-controlled external content
(XXE/SSRF). Only consulted when an external_resolver is set.
Default: true (the resolver’s presence is the opt-in; callers that
want the stricter stance set this false).
base_url: Option<String>Base path used to resolve relative SYSTEM literals during
external-DTD and external-entity loading. When Some, a
relative SYSTEM literal is joined against this path’s parent
directory rather than the process’s current working
directory. Has no effect when
load_external_dtd is false.
Typically populated by the parse entry point with the
document’s source URL (e.g. xmlReadFile’s filename
argument), so a file at /data/doc.xml referencing
<!DOCTYPE r SYSTEM "schema.dtd"> finds /data/schema.dtd.
remove_comments: boolDrop comment nodes during the parse instead of building them
into the tree. Mirrors libxml2’s effect when a consumer NULLs
the SAX comment callback (lxml’s XMLParser(remove_comments= True)). Default: false.
remove_pis: boolDrop processing-instruction nodes during the parse. Mirrors
libxml2’s effect when a consumer NULLs the SAX
processingInstruction callback (lxml’s
XMLParser(remove_pis=True)). Default: false.
cdata_as_text: boolDeliver CDATA-section content as ordinary text nodes instead of
dedicated CDATA nodes (libxml2’s XML_PARSE_NOCDATA; lxml’s
XMLParser(strip_cdata=True), which is its default). On
serialization the content round-trips as escaped text rather
than <![CDATA[…]]>. Default: false (CDATA preserved).
Trait Implementations§
Source§impl Clone for ParseOptions
impl Clone for ParseOptions
Source§fn clone(&self) -> ParseOptions
fn clone(&self) -> ParseOptions
1.0.0 (const: unstable) · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read more