@@ -5,6 +5,51 @@ All notable changes to this project will be documented in this file.
55The format is based on [ Keep a Changelog] ( https://keepachangelog.com/en/1.1.0/ ) ,
66and this project adheres to [ Semantic Versioning] ( https://semver.org/spec/v2.0.0.html ) .
77
8+ ## [ Unreleased]
9+
10+ ### Security
11+
12+ - ** Fix exponential-time entity recursion check** (#42 , thanks @hey-jj ). The
13+ WFC: No Recursion walks in the DTD validator re-visited entities once per
14+ path, so a 474-byte document with chained entity declarations took 8+
15+ seconds to parse and a 694-byte one about 22 hours. The walks (including
16+ parameter entities and ATTLIST-default validation) now memoize entities
17+ proven acyclic, making the check linear in the size of the DTD. Cycle
18+ detection is unaffected.
19+ - ** Bound element nesting across entity expansions.** Entity replacement text
20+ is parsed by nested sub-parsers, which now inherit the outer parser's
21+ nesting depth so total element depth stays bounded by
22+ ` ParseOptions::max_depth ` instead of ` max_depth ` per expansion level.
23+
24+ ### Fixed
25+
26+ - ** General entity replacement text is parsed as content** per XML 1.0 §4.4
27+ (#43 , thanks @hey-jj ). ` EntityRef ` nodes now carry their parsed expansion
28+ as children: character references in declarations are expanded when
29+ replacement text is built (§4.5), nested entity references are included,
30+ and markup-bearing entities produce real element children instead of
31+ escaped text — while serialization still emits ` &name; ` , keeping
32+ round-trips lossless. Replacement text must match the content production
33+ (§4.3.2); unbalanced or split tags are rejected. Entity expansion is
34+ subject to the expansion counter, a nesting-depth cap, and the 5x
35+ amplification guard, matching libxml2. DTD content-model validation sees
36+ through entity references (§4.4.3), so entity-supplied elements are
37+ validated too.
38+ - ** XPath ` != ` uses its own existential semantics for node-sets** per XPath
39+ 1.0 §3.4 (#44 , thanks @hey-jj ). ` != ` was evaluated as ` not(=) ` , inverting
40+ empty-node-set comparisons and breaking multi-node sets (both ` = ` and ` != `
41+ can hold at once). Node-set vs boolean keeps boolean-conversion semantics;
42+ scalar comparisons are unchanged. An absent attribute step now also yields
43+ an empty node-set (false under both ` = ` and ` != ` ) instead of an
44+ empty-string sentinel.
45+ - ** XPath filter-path continuations navigate instead of filtering** (#20 ,
46+ thanks @ancientcatz ). ` (//a)[1]/@href ` parsed to the same AST as
47+ ` (//a)[@href] ` , so the trailing path acted as a predicate and
48+ ` string((//a)[1]/@href) ` returned the anchor text. A new
49+ ` Expr::FilterPath ` AST variant evaluates the continuation steps against
50+ the filter's node-set. ** Breaking:** downstream exhaustive matches on
51+ ` xpath::ast::Expr ` must handle the new variant.
52+
853## [ 0.4.4] - 2026-07-20
954
1055### Security
0 commit comments