QXmlSimpleReader: limit entity expansion size at expansion time


The old check calculated the expanded size of each internal general
entity when the entity was declared. That approach has several
problems:

- It does not support forward references. When an entity was declared,
  its value was only searched for references to entities that were
  already declared. A reference to an entity declared later stayed in
  the value as plain text, and was counted as a few literal characters
  forever. Declaring the entities in reverse order was enough to bypass
  the limit.

- The expanded sizes were calculated once and cached, using whatever
  sizes of the referenced entities happened to be known at that point
  (which also depended on the hash iteration order). They were never
  updated when more entities were declared.

- An entity that does not reference other entities was never checked,
  so a single literal entity larger than the limit was accepted.

- At declaration time it is impossible to tell whether a reference to a
  not-yet-declared entity is a forward reference or a reference to an
  entity that will never be declared. That is only known once the DTD
  is complete. However, entities can be expanded before that point,
  in the default value of an attribute in an ATTLIST declaration.
  Fixing this at declaration time would require tracking
  unresolved references, re-running the calculation once the DTD ends,
  and adding extra checks wherever an entity is expanded inside the
  DTD. Propagating size updates through the unresolved entities can
  also cost quadratic time on a crafted DTD.

Instead, count the size while the entities are actually being
expanded. All internal general entities are expanded through
insertXmlRef(), both in content and in attribute values (including
the default values in ATTLIST). Before an entity value is pushed to
the reference stack, update a counter for the outermost internal
general entity that is being expanded:

- if no other internal general entity is being expanded, this is the
  outermost one, so restart the counter with the size of its value;
- if the reference comes from the value of the enclosing entity, the
  "&name;" text was already counted as a part of that value, so
  replace it with the size of the referenced value;
- if the reference comes from an external entity included by the
  enclosing entity, its text was not counted, so add the full size of
  the referenced value.

Parsing fails as soon as the counter exceeds the limit. This measures
the expanded size in the same way as before: references to unknown
entities, standard entities and character references are counted
with their literal size.

This approach is more robust:

- The order of declarations does not matter, because all entities are
  known by the time they are expanded.
- There is no need to decide whether a reference is truly unknown.
  The parser already resolves each reference when it gets to it, and
  the counter only follows what actually gets expanded.
- It covers every place where an internal general entity is expanded,
  including the ones inside the DTD, without a separate finalization
  step.
- It costs O(1) per expansion and needs no bookkeeping for the
  declared entities.

As a consequence, oversized entities are now rejected when they are
used, not when they are declared. An entity that is never referenced
is not checked. Also, the size is checked when a value is pushed, before
its own references are replaced, so a value that references an entity
shorter than its "&name;" text can be rejected even if its final
expansion would fit into the limit.

Update the dtdRecursionLimit() test accordingly: all four entities in
internal-entity-polynomial-attribute.xml are now declared before the
expansion in the attribute fails.

This fixes all tests cases that handle general entity expansion in the
entityExpansionLimit() test. Parameter entities are not covered by this
change, so the related test cases are still expected to fail.

The patch also adds a new test that checks the entity expansion limit
with an external entity resolver.

Task-number: QTBUG-149213
Pick-to: 6.12 6.8 6.5 5.15
Change-Id: Ie4d201a03ba420108db9078da37a49404596f47d
Reviewed-by: Edward Welbourne <edward.welbourne@qt.io>
commitb79352273c9a841efeddfa768646f0cc9e378a4d
parent82cccc5cd6842c51f0217842329a9961754a44c3
authorIvan Solovev <ivan.solovev@qt.io>
committerIvan Solovev <ivan.solovev@qt.io>
commit timeMon, 19 Oct 2026 18:34:00 +0000