Skip to content

fix: resolve named HTML entities longer than 10 letters in set_mathml() - #922

Merged
NSoiffer merged 1 commit into
daisy:mainfrom
mixa2010:fix/896-long-entity-names
Oct 26, 2026
Merged

NSoiffer merged 1 commit into
daisy:mainfrom
mixa2010:fix/896-long-entity-names

Conversation

@mixa2010

Copy link
Copy Markdown
Contributor

Fixes #896.

Problem

set_mathml() substitutes named HTML entities before handing the string to the
XML parser. The regex that finds them bounded the name at 10 letters:

static HTML_ENTITIES: Lazy<Regex> = lazy_regex!(r#"&([a-zA-Z]{2,10});"#);

2127 of the 2235 names in src/entities.in are longer than that, up to
CounterClockwiseContourIntegral (31 letters). Those names were never
substituted, so they reached the parser unresolved and set_mathml() rejected
the entire expression instead of translating it.

MathML emitted by MathJax uses &InvisibleTimes; (14 letters) for invisible
multiplication, which is why this surfaced immediately after 0.7.7 shipped.

Reproduction (before this change)

set_mathml("<math><mi>x</mi><mo>&InvisibleTimes;</mo><mi>y</mi></math>")
// Err: unresolved entity &InvisibleTimes; reached the parser

Fix

Widen the bound to 32 letters in src/interface.rs, matching the {1,32}
bound already used for the namespace and element-prefix regexes right above it.

Unknown names are still rejected by the XML parser, so the DoS length guard
behaviour is unchanged — the bound only decides how many characters are offered
to the entity lookup, and the lookup itself is a static phf map.

Tests

New integration test file tests/entities.rs (6 tests) covering:

  • &InvisibleTimes; — 14 letters, the exact case from 0.7.7 rejects entity names longer than 10 letters (for example &InvisibleTimes;) #896
  • &CircleTimes; — 11 letters, one over the old bound
  • &CounterClockwiseContourIntegral; — 31 letters, the longest name in the table
  • &it;, &af;, &pi;, &RightArrow;, &ImaginaryI; — names at or under the
    old bound keep resolving (regression guard)
  • &#x2062; — numeric character references are unaffected
  • &NotAnEntityNameAtAll; — unknown names are still rejected

Red before the change (4 failed), green after. Full suite: cargo test →
5210 passed, 0 failed, 33 ignored (the new file adds 6 of those 5210).

The entity regex in set_mathml() bounded the entity name at 10 letters:

    &([a-zA-Z]{2,10});

but 2127 of the names in src/entities.in are longer, up to
"CounterClockwiseContourIntegral" (31 letters). Those names were never
substituted, so they reached the XML parser unresolved and set_mathml()
rejected the whole expression with "Tag/attribute not found" instead of
translating it.

MathML produced by MathJax uses &InvisibleTimes; (14 letters) for
invisible multiplication, which is why the regression was reported by
JAWS users as soon as 0.7.7 shipped (issue daisy#896).

Widen the bound to 32 letters, matching the {1,32} bound already used
for namespace declarations and element prefixes. Unknown names are still
rejected by the parser, so the DoS guard is unaffected.

Add tests/entities.rs covering names at the old limit, one letter over
it, the longest name in the table, numeric character references, and
that unknown names are still rejected.
@github-actions

Copy link
Copy Markdown
Linux library size: 0.59 MiB (0.00%)
Revision Release liblibmathcat.so
Base (a9d481a) 0.59 MiB (618,272 bytes)
PR (ab54de5) 0.59 MiB (618,272 bytes)
Change 0 bytes (0.00%)

Built with default features, Rust 1.96.0, and Ubuntu 24.04. Workflow run.

@NSoiffer
NSoiffer merged commit 28cc717 into daisy:main Oct 26, 2026
13 checks passed
@github-project-automation github-project-automation Bot moved this from Triage to Done in MathCAT Project Board Oct 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

0.7.7 rejects entity names longer than 10 letters (for example &InvisibleTimes;)

2 participants