fix(task-4): refuse LF/CR in XDATA strings before any write
encode_values() accepted strings containing "\n" or "\r". ezdxf writes 1000-tag values verbatim, so the separator split the tag pair: the saved DXF failed to reopen (DXFStructureError) and, because every undo and transaction snapshot is also a DXF save, transaction_rollback broke after a single such write. Both engines route through encode_values, so the refusal (ValueError naming values[i]) now happens there, before either backend touches the document. Only LF and CR split a DXF tag; tabs and the other Unicode separators round-trip and stay allowed. Tests: pure encoder refusal for \n, \r, \r\n; other control characters still typed; ezdxf refuses-then-rollback-and-reopen round trip; COM fake sees no SetXData / RegisteredApplications call. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FKFStjEFzpGyLq5bMpWxTd
This commit is contained in:
co-authored by
Claude Opus 5
parent
cda8b69dba
commit
41d6d3505a
@@ -48,6 +48,23 @@ def test_string_over_255_is_refused():
|
||||
encode_values(["x" * (MAX_STRING + 1)])
|
||||
|
||||
|
||||
@pytest.mark.parametrize("sep", ["\n", "\r", "\r\n"])
|
||||
def test_line_separators_in_strings_are_refused(sep):
|
||||
# DXF is "code\nvalue\n" lines and ezdxf writes 1000 tags verbatim, so a
|
||||
# separator inside the value splits the tag pair and corrupts the file.
|
||||
with pytest.raises(ValueError, match=r"values\[1\].*line separators are not allowed"):
|
||||
encode_values(["fine", f"Inspect weld{sep}before paint"])
|
||||
|
||||
|
||||
def test_other_control_characters_are_still_allowed():
|
||||
# Only LF/CR split a DXF tag; tabs and the exotic separators round-trip.
|
||||
assert encode_values(["a\tb", "a\x0bb", "a
b"]) == [
|
||||
(1000, "a\tb"),
|
||||
(1000, "a\x0bb"),
|
||||
(1000, "a
b"),
|
||||
]
|
||||
|
||||
|
||||
def test_total_size_over_16k_is_refused():
|
||||
with pytest.raises(ValueError, match="16"):
|
||||
encode_values(["x" * 255] * (MAX_BYTES // 255 + 1))
|
||||
@@ -108,6 +125,32 @@ async def test_limits_are_enforced_before_writing(backend):
|
||||
assert (await backend.entity_get_xdata(line.handle))["xdata"] == {}
|
||||
|
||||
|
||||
async def test_newline_is_refused_before_writing_and_snapshots_still_load(backend, tmp_path):
|
||||
# Every undo/transaction snapshot is a DXF save, so an accepted newline
|
||||
# would break rollback, not just the file on disk.
|
||||
line = await backend.entity_create_line(0, 0, 1, 1)
|
||||
with pytest.raises(ValueError, match="line separators are not allowed"):
|
||||
await backend.entity_set_xdata(line.handle, "NOTES", ["Inspect weld\nbefore paint"])
|
||||
assert (await backend.entity_get_xdata(line.handle))["xdata"] == {}
|
||||
|
||||
await backend.entity_set_xdata(line.handle, "NOTES", ["Inspect weld before paint"])
|
||||
await backend.transaction_begin()
|
||||
circle = await backend.entity_create_circle(5, 5, 1)
|
||||
await backend.transaction_rollback()
|
||||
assert (await backend.entity_get_xdata(line.handle, "NOTES"))["xdata"] == {
|
||||
"NOTES": ["Inspect weld before paint"]
|
||||
}
|
||||
with pytest.raises(RuntimeError, match="not found"):
|
||||
await backend.entity_get(circle.handle)
|
||||
|
||||
path = str(tmp_path / "notes.dxf")
|
||||
await backend.drawing_save(path)
|
||||
await backend.drawing_open(path)
|
||||
assert (await backend.entity_get_xdata(line.handle, "NOTES"))["xdata"] == {
|
||||
"NOTES": ["Inspect weld before paint"]
|
||||
}
|
||||
|
||||
|
||||
# ── live engine, fake ActiveX ────────────────────────────────────────────────
|
||||
|
||||
|
||||
@@ -170,3 +213,10 @@ async def test_com_empty_values_write_only_the_marker(com_backend):
|
||||
result = await backend.entity_set_xdata("2F", "ACADMCP_PID", [])
|
||||
assert result["removed"] is True
|
||||
assert entity.stored == ([1001], ["ACADMCP_PID"])
|
||||
|
||||
|
||||
async def test_com_newline_is_refused_before_any_activex_call(com_backend):
|
||||
backend, entity, registered = com_backend
|
||||
with pytest.raises(ValueError, match="line separators are not allowed"):
|
||||
await backend.entity_set_xdata("2F", "NOTES", ["a\r\nb"])
|
||||
assert entity.stored is None and registered == []
|
||||
|
||||
Reference in New Issue
Block a user