Skip to content

Fix AOI export crashes: FILETIME overflow and unsafe attribute escaping#32

Merged
hutcheb merged 1 commit into
hutcheb:mainfrom
akivabrookler:fix/aoi-export-crashes
Jun 22, 2026
Merged

Fix AOI export crashes: FILETIME overflow and unsafe attribute escaping#32
hutcheb merged 1 commit into
hutcheb:mainfrom
akivabrookler:fix/aoi-export-crashes

Conversation

@akivabrookler

Copy link
Copy Markdown
Contributor

Summary

Two real-world .ACD projects fail to export to L5X. Both crashes originate in acd/l5x/elements.py:

  1. OverflowError: date value out of range_parse_aoi_nameless converts an AOI's Created/Edited Windows FILETIME into a datetime. On some AOI nameless records the variable-length field walk drifts and reads 8 garbage bytes, yielding a FILETIME whose year exceeds datetime's 9999 ceiling. datetime(1601, 1, 1) + timedelta(...) then raises and aborts the entire export.

  2. Non-well-formed L5X from unsafe attribute escapingL5xElement.to_xml serializes attribute values with html.escape, which only handles & < > " '. When a binary field decodes to garbage (e.g. an AOI Vendor whose length/offset drifts, decoded with errors="replace"), the attribute ends up containing raw C0 control characters and even raw newlines. The emitted L5X is then non-well-formed, and strict downstream XML parsers reject it (unterminated / "open quote" attribute).

Changes

  • Add _filetime_to_iso(ft): guards OverflowError/OSError and returns "" for empty or out-of-range FILETIMEs. Used for both the Created and Edited AOI dates (also de-duplicates the conversion that was inlined twice).
  • Add _escape_xml_attr(value): strips XML-1.0-illegal control characters and encodes the legal-but-delimiting whitespace (TAB/CR/LF) as numeric character references, in addition to html.escape. Used for all attribute-value serialization (L5xElementBuilder.to_xml, the Communications connection name, and the Routine name).
  • Add unit tests for both helpers (test/test_elements_helpers.py).

Testing

  • New helper tests pass (pytest test/test_elements_helpers.py — 7 passed).
  • The existing test_api.py::test_to_xml (full CuteLogix.ACD → L5X serialization, which exercises _escape_xml_attr) passes.
  • Verified end-to-end against the two .ACD files that originally triggered each crash: both now export to valid, well-formed L5X.

The fixes are defensive and behavior-preserving for valid input: a valid FILETIME still formats identically, and an attribute with no illegal/whitespace characters escapes exactly as before.

Two real-world .ACD projects fail to export to L5X:

1. _parse_aoi_nameless converts an AOI's Created/Edited Windows FILETIME
   to a datetime. On some AOI nameless records the variable-length field
   walk drifts and reads 8 garbage bytes, producing a FILETIME whose year
   exceeds datetime's 9999 ceiling and raising
   "OverflowError: date value out of range", aborting the whole export.

2. L5xElement.to_xml serializes attribute values with html.escape, which
   only handles & < > " '. When a binary field decodes to garbage (e.g. an
   AOI Vendor whose length/offset drifts, decoded with errors="replace"),
   the attribute ends up containing raw control characters and even raw
   newlines, producing non-well-formed L5X that downstream XML parsers
   reject ("open quote" / unterminated attribute).

Fixes:
- Add _filetime_to_iso(), which guards OverflowError/OSError and returns
  "" for empty/out-of-range FILETIMEs; use it for both AOI dates.
- Add _escape_xml_attr(), which strips XML-1.0-illegal control characters
  and encodes TAB/CR/LF as numeric character references in addition to
  html.escape; use it for all attribute-value serialization.

Adds unit tests for both helpers.

Co-authored-by: Cursor <cursoragent@cursor.com>
@hutcheb
hutcheb merged commit 019fb78 into hutcheb:main Jun 22, 2026
0 of 5 checks passed
@hutcheb

hutcheb commented Jun 22, 2026

Copy link
Copy Markdown
Owner

Thanks @akivabrookler

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