fix(task-9): DIMDSEP refuses control characters and code points above the 16-bit DXF group

check_dim_value's char branch only checked len == 1, so '\n', '\t', '\x00'
and non-BMP characters passed and the headless writer stored ord(value)
unguarded: '\n' split the dimension text over two lines, NUL read back as a
comma, and U+1F600 reached $DIMDSEP / group 278 as 128512, outside the 16-bit
range and refused by AutoCAD as "not a single character". ezdxf's audit
caught none of it. The same hole 827719f closed for names and fonts, left
open for the one variable stored as a character code.

The gate now refuses code < 32, DEL, and code > DIMDSEP_MAX_CODE (0x7FFF,
the signed short DWG stores) with a ValueError naming DIMDSEP, so nothing
reaches a backend. Pinned by a parametrized refusal test (newline, tab, NUL,
DEL, U+1F600, U+8000, U+FFFF), a positive test that the middle dot, the
Arabic decimal separator and U+7FFF still pass, and the ezdxf refusal test
now proves dimstyle_create / dimstyle_modify write nothing for these.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FKFStjEFzpGyLq5bMpWxTd
This commit is contained in:
Umut CAN
2026-09-17 14:52:57 +03:00
co-authored by Claude Opus 5
parent 2ee18727cd
commit 4d246b17b8
3 changed files with 63 additions and 3 deletions
+26 -3
View File
@@ -12,9 +12,13 @@ Every value in ``PRESETS`` is pinned by name in
lettering, .125 in arrowheads) in metric values: 3 mm text and arrowheads,
text centred in a gap and horizontal, decimal point.
``DIMDSEP`` is a one-character *string* here. DXF stores the character code and
ezdxf raises on a string in the header, so the translation belongs to the
backends, not to the data.
``DIMDSEP`` is a one-character *string* here. DXF stores the character code
(group 278, a 16-bit value) and ezdxf raises on a string in the header, so the
translation belongs to the backends, not to the data — but the data is where
the refusal starts: a control character (``\\n`` splits the dimension text over
two lines, NUL reads back as a comma) or a code point above ``0x7FFF``
(``ezdxf`` writes it, AutoCAD refuses it as "not a single character") is
refused here, before ``ord()`` runs in a backend.
Pure data plus validation: no I/O, no ezdxf, no COM, so the backends, the
tools and the provenance tests all read one copy.
@@ -270,6 +274,15 @@ def ezdxf_arrowhead(canonical: str) -> str:
return canonical
DIMDSEP_MAX_CODE = 0x7FFF
"""The largest character code ``DIMDSEP`` accepts.
DXF group 278 is a 16-bit integer and the DWG field a signed short; AutoCAD
refuses anything above it as "not a single character", while ezdxf would
write it verbatim (measured: ``'\\U0001F600'`` reached the file as 128512).
"""
def _number(key: str, value: Any) -> float:
if isinstance(value, bool) or not isinstance(value, (int, float)):
raise TypeError(f"{key}: expected a number, got {type(value).__name__}")
@@ -324,6 +337,16 @@ def check_dim_value(name: str, value: Any) -> Any:
raise TypeError(f"{key}: expected a string, got {type(value).__name__}")
if len(value) != 1: # "char"
raise ValueError(f"{key}: {value!r} must be exactly one character")
code = ord(value)
if code < 32 or code == 127:
raise ValueError(
f"{key}: {value!r} is a control character, which a DXF cannot hold as a separator"
)
if code > DIMDSEP_MAX_CODE:
raise ValueError(
f"{key}: {value!r} (U+{code:04X}) is outside the 16-bit character code DXF group "
f"278 stores (at most U+{DIMDSEP_MAX_CODE:04X})"
)
return value