Skip to content

Judge a renamed element by the name the author wrote (#445, #450) - #479

Merged
jmanico merged 1 commit into
mainfrom
fix/445-450-rename-input-name
Sep 11, 2026
Merged

Judge a renamed element by the name the author wrote (#445, #450)#479
jmanico merged 1 commit into
mainfrom
fix/445-450-rename-input-name

Conversation

@jmanico

@jmanico jmanico commented Sep 11, 2026

Copy link
Copy Markdown
Member

Fixes #445 and #450, which share one cause: writeOpenTag in the policy class judged a renamed element by the name the ElementPolicy emitted, while the lexer, the tag balancer, deferOpenTag and disallowTextIn all go by the name the author wrote.

#445 allowElements(policy, "span") registers span as a text container, but the text gate looked up the adjusted name, so span renamed to div without allowElements("div") came out as <div></div>. The gate now follows the input name. One hold-out: a rename into an element whose content a browser reads literally, such as style, still needs allowTextIn on that name, so a rename cannot get around the bar the builder sets for raw-text elements.

#450 The push onto the policy's open-element stack keyed on the adjusted name, so br renamed to span left an entry no close tag ever popped. The span stayed open until its parent closed and swallowed every sibling, and because the balancer never counts a void input, 3000 <br> came out as 3000 nested spans, past the balancer's cap of 256. The push now keys on the input name: a void input renamed to a non-void output is emitted and closed at once, giving <p>a<span></span>bcd</p><p>e</p> for the issue's example.

The reverse rename, span to br, now gets a stack entry with nothing to close, so its own close tag pops it. Before, the close tag found the nearest open element of the same input name and ended an outer span early; and disallowTextIn("span") now holds for it, as it does for every other renamed element.

Seven regression tests in HtmlPolicyBuilderTest pin each shape, including the nesting case. Both existing rename tests keep passing unchanged. Verified with ./mvnw clean verify on JDK 11, 17, 21 and 25.

🤖 Generated with Claude Code

https://claude.ai/code/session_014Ydng7gBm6Ax5zfwt4vZip

ElementAndAttributePolicyBasedSanitizerPolicy.writeOpenTag decided two
things by the name an ElementPolicy emitted an element under, while the
lexer, the tag balancer, deferOpenTag and disallowTextIn all go by the
name the author wrote.

Text: allowElements(ElementPolicy, String...) and allowTextIn register
the input name as a text container, but the gate looked up the adjusted
name, so a rename to a name not allowed in its own right kept the element
and dropped its text (#445).  The gate now follows the input name.  A
rename into an element whose content a browser reads literally, such as
style, still needs allowTextIn on that name, so a rename is not a way
around the bar the builder sets for raw-text elements.

Void-ness: the push onto the open-element stack keyed on the adjusted
name, so a void element renamed to a non-void one left an entry that no
close tag ever popped (#450).  The renamed element stayed open until its
parent closed, swallowing every sibling after it, and since the balancer
never counts a void input, such entries nested past its limit.  The push
now keys on the input name: a void input renamed to a non-void output is
emitted and closed at once, and a non-void input renamed to a void output
gets an entry with nothing to close, so its own close tag pops it rather
than an outer element of the same input name.

Fixes #445
Fixes #450

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Ydng7gBm6Ax5zfwt4vZip
@jmanico
jmanico merged commit 5d897c8 into main Sep 11, 2026
7 checks passed
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.

Text inside an element renamed by an ElementPolicy is dropped unless the new name is itself allowed

1 participant