fix(task-16): custom properties refuse pre-R2004 headlessly, line breaks, and AutoCAD's invalid keys before any write

Three header-only-write / half-write classes in drawing_properties_set:

1. Measured: ezdxf emits $CUSTOMPROPERTYTAG / $CUSTOMPROPERTY only after
   $LASTSAVEDBY, which does not exist before R2004, so on an R12 (AC1009) or
   R2000 (AC1015) document the write reported ok, drawing_properties_get
   echoed it from memory, and drawing_save_as (version kept) wrote nothing --
   the class f3ff703 removed for CANNOSCALE. The headless engine now refuses
   with a ValueError naming the DXF version and "save as R2004 or newer",
   before custom_vars is touched; the dwgprops capability reason no longer
   claims custom_properties_supported unconditionally.

2. A line break in a custom key or value passed validation, ezdxf wrote it
   unescaped, and the saved DXF failed to reopen (DXFStructureError) -- the
   whole drawing lost, reported as success. Refused by name in the shared
   validator.

3. Measured on AutoCAD 2026: AddCustomInfo rejects keys with leading/trailing
   whitespace and keys containing '=', ';' or a no-break space as 'Invalid
   key' -- mid-write, after the summary and earlier keys were applied, as an
   unnamed COM error -- while the headless engine wrote them. The shared
   validator now mirrors those rules, so both engines refuse before writing
   (internal spaces, tabs, unicode and 300-char keys stay accepted, as
   AutoCAD accepts them).

Pinned by save+reopen tests on R12/R2000 (refused, file reads back empty),
R2004 (round-trips), the newline case (refused, the file still opens), and
the fake-SummaryInfo COM test (no ActiveX call reaches SummaryInfo).

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-21 18:45:02 +03:00
co-authored by Claude Opus 5
parent 10a7372ce7
commit 2b9791fbcd
4 changed files with 219 additions and 4 deletions
+17 -1
View File
@@ -1117,7 +1117,7 @@ class EzdxfBackend(AutoCADBackend):
),
"dwgprops": FeatureCapability(
False,
reason="summary_fields_need_live_autocad;custom_properties_supported",
reason="summary_fields_need_live_autocad;custom_properties_need_r2004_or_newer",
),
},
)
@@ -6125,6 +6125,22 @@ class EzdxfBackend(AutoCADBackend):
def _sync():
doc = self._require_doc()
if (to_write or to_delete) and doc.dxfversion < "AC1018":
# Measured: ezdxf emits $CUSTOMPROPERTYTAG / $CUSTOMPROPERTY only
# after $LASTSAVEDBY, a header variable that does not exist
# before R2004 (its own CustomVars docstring says so). On an
# R12 or R2000 document the in-memory write "succeeded",
# drawing_properties_get echoed it back, and drawing_save_as
# (version kept) wrote nothing -- the header-only write that
# vanishes on save, the class f3ff703 removed for CANNOSCALE.
# Refused here, before custom_vars is touched.
raise ValueError(
f"custom properties: this document is DXF {doc.dxfversion} "
f"({doc.acad_release}), and $CUSTOMPROPERTYTAG / $CUSTOMPROPERTY "
"header pairs are only written for R2004 (AC1018) or newer, so "
f"{sorted(to_write) + sorted(to_delete)} would be lost on save. "
"Save as R2004 or newer first."
)
custom_vars = doc.header.custom_vars
for key, value in to_write.items():
if custom_vars.has_tag(key):