Skip to content

Limit how deep the parser nests elements, as the HTML parser does - #42

Merged
FlorianRappl merged 2 commits into
AngleSharp:develfrom
michaelthyregod:feature/xml-maximum-depth
Nov 2, 2026
Merged

FlorianRappl merged 2 commits into
AngleSharp:develfrom
michaelthyregod:feature/xml-maximum-depth

Conversation

@michaelthyregod

Copy link
Copy Markdown
Contributor

Not benchmarked yet: I'll run the benchmarks once my pending AngleSharp core change (part of an AngleSharp.Css fix) is settled, since it may affect the results. The check per start tag is one comparison.

Follows up on #39: the XML parser gets the same nesting limit as core's HTML parser.

Change

  • XmlParserOptions.MaximumDepth mirrors core's HtmlParserOptions.MaximumDepth:
    • the deepest level the parser nests elements at, counting the root as level 1;
    • default 512 (XmlParserOptions.DefaultMaximumDepth);
    • zero or a negative value restores the default, and Int32.MaxValue lifts the limit.
  • Beyond the limit:
    • In strict mode, the parser throws XmlParseError.MaximumDepthExceeded (0x314), because nesting the element elsewhere would change an XML document.
    • With IsSuppressingErrors, the element is attached at the limit as a sibling of the deepest element, as the HTML parser does. Text that follows a closed sibling stays in its own element.
  • Breaking for documents nested deeper than 512 levels, which parsed before.
  • Tests and benchmarks that go deeper: the deep-nesting tests (20,000 levels) and the deep benchmark shapes still need deep documents, so they lift the limit. Their documents and assertions are unchanged. The benchmark sets the option via reflection, so the same benchmark code still compiles against older builds.
  • Docs: the API tutorial, AGENTS.md and the CHANGELOG are updated.

Tests

XmlMaximumDepth covers:

  • the default and how the property is resolved;
  • 512 levels parse and 513 throw, both synchronously and asynchronously;
  • custom limits, including 1;
  • Int32.MaxValue;
  • self-closing elements beyond the limit;
  • attaching at the limit in recovery mode, including keeping the root as the parent.

621 tests pass.

🤖 Generated with Claude Code

XmlParserOptions.MaximumDepth mirrors core's HtmlParserOptions.MaximumDepth:
the deepest level the parser nests elements at, counting the root as
level 1, defaulting to 512; zero or a negative value restores the default
and Int32.MaxValue lifts the limit. An element beyond it throws
MaximumDepthExceeded (0x314), since nesting it elsewhere changes an XML
document; with IsSuppressingErrors it is attached at the limit as a
sibling of the deepest element, as the HTML parser does.

The deep-nesting tests and the deep benchmark shapes go beyond 512 levels
to exercise the explicit-stack walks, so they lift the limit.
Comment thread CHANGELOG.md Outdated
- Fixed unbounded expansion of nested entities ("billion laughs"), which let a small document exhaust memory; expansion now stops at `XmlParserOptions.MaxCharactersFromEntities`
- Added trimming and Native AOT compatibility (`IsAotCompatible`) for the .NET 8 and .NET 10 targets
- Added `XmlParserOptions.MaxCharactersFromEntities` (default 10,000,000, 0 for no limit), like `System.Xml`'s setting of the same name
- Added `XmlParserOptions.MaximumDepth` (default 512), like `HtmlParserOptions.MaximumDepth`: deeper elements throw, or are attached at the limit when errors are suppressed (breaking for documents nested deeper than 512 levels)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would remove the (breaking for documents nested deeper than 512 levels). Those are most likely hostile documents.

In any other case somebody needs to change the default, which is imho fine as it uncovers that there are some potentially ill-formed documents being parsed.

@FlorianRappl
FlorianRappl merged commit fddbe52 into AngleSharp:devel Nov 2, 2026
5 checks passed
@michaelthyregod
michaelthyregod deleted the feature/xml-maximum-depth branch November 3, 2026 06:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants