Describe the bug
dictdumper's PLIST writer does not escape XML special characters in string values, so any dumped
value containing < or > produces malformed XML. pcapkit's own nameless pseudo-member renders as
<unknown>::<unassigned> [N] (pcapkit/dumpkit/common.py:168, render_enum), so a plist dump
containing one is unparseable.
Reproduction — three lines, no pcapkit involved:
import dictdumper, tempfile, os, xml.etree.ElementTree as ET
p = os.path.join(tempfile.mkdtemp(), 't.plist')
dictdumper.PLIST(p)({'val': '<unknown>::<unassigned> [54321]'}, name='frame1')
ET.parse(p)
plist contains RAW '<unknown>': True
plist contains escaped '<unknown>': False
parses as XML: False -> mismatched tag: line 8, column 43
<string><unknown>::<unassigned> [54321]</string>
Expected behavior
< and > escaped as </> inside <string>, so the document parses.
Additional context
This is live on main today, not new. The pseudo-member has existed since #701 and
EnumField.post_process's docstring already names <unknown>::<unassigned> [28] as its rendered
form, so any capture carrying a genuinely unassigned option code already produces an unparseable
plist. It has gone unnoticed only because no fixture in the corpus hits that path, so
tests/integration/test_output_formats.py::PlistReportTests::test_plist_report_is_well_formed_xml_with_one_key_per_frame
has never seen one.
#771 makes it reachable on essentially every capture, because it routes unassigned ports through
the same pseudo-member and ephemeral ports are ubiquitous. Four CI legs on that PR fail on this single
test — one defect, not four.
The writer is dictdumper's (dictdumper.PLIST), so the escaping is not ours to fix in-tree. Options,
none chosen: fix upstream and pin a new version; escape per-format in make_dumper's hook rather than
in render_enum, since < is legal in the json/text outputs; or rename the pseudo-member to avoid
angle brackets, which changes an established dump spelling. Blocking #771 — see the decision request
there.
Describe the bug
dictdumper's PLIST writer does not escape XML special characters in string values, so any dumpedvalue containing
<or>produces malformed XML.pcapkit's own nameless pseudo-member renders as<unknown>::<unassigned> [N](pcapkit/dumpkit/common.py:168,render_enum), so aplistdumpcontaining one is unparseable.
Reproduction — three lines, no
pcapkitinvolved:Expected behavior
<and>escaped as</>inside<string>, so the document parses.Additional context
This is live on
maintoday, not new. The pseudo-member has existed since #701 andEnumField.post_process's docstring already names<unknown>::<unassigned> [28]as its renderedform, so any capture carrying a genuinely unassigned option code already produces an unparseable
plist. It has gone unnoticed only because no fixture in the corpus hits that path, so
tests/integration/test_output_formats.py::PlistReportTests::test_plist_report_is_well_formed_xml_with_one_key_per_framehas never seen one.
#771 makes it reachable on essentially every capture, because it routes unassigned ports through
the same pseudo-member and ephemeral ports are ubiquitous. Four CI legs on that PR fail on this single
test — one defect, not four.
The writer is
dictdumper's (dictdumper.PLIST), so the escaping is not ours to fix in-tree. Options,none chosen: fix upstream and pin a new version; escape per-format in
make_dumper's hook rather thanin
render_enum, since<is legal in thejson/textoutputs; or rename the pseudo-member to avoidangle brackets, which changes an established dump spelling. Blocking #771 — see the decision request
there.