fix(detection): anchor the text-format rules, parse Radiance, read DXF's magic once - #657
Merged
Conversation
`text.contains("v ")` accepted a `v ` anywhere in the first 100 bytes, so
`Radiance.hdr` -- whose header opens `#?RADIANCE\noconv mat.rad ...` -- was
dispatched to the OBJ parser by the `v ` that ends `oconv `. It reported
HasNormals/HasTextureCoords for an image file, silently: `File:FileType`
comes from the magic table and went on saying HDR.
Anchor all four rules to the start of a line, the way ExifTool's magic
patterns do:
* OBJ requires `^v `, `^vn ` or `^vt ` with operands, not a loose substring.
* DXF requires SECTION as its own group-code record, not a keyword anywhere
in the header. The `2`/`HEADER` records ExifTool's magic also wants are
deliberately not required -- that would reject ENTITIES-first files this
has always accepted.
* GLTF requires the probe to open a JSON object and `"asset"` to be a real
key; `contains("{") && contains("\"asset\"")` matched any document that
merely mentioned both.
* STL keeps its byte-0 anchor -- it never had this bug -- but now needs
`solid` as a whole word (`solidification` matched the prefix) plus a
corroborating facet/endsolid directive, so prose opening "solid ..." is
no longer handed to the STL parser. That directive is looked for across
the whole probe, since a long solid name pushes it past 100 bytes.
OBJParser::verify_signature carried a second copy of the same substring
rule, which is why the parser accepted the Radiance file rather than
rejecting it; it now calls the detector's predicate.
Verified: `just compare-file .../Radiance.hdr` (pinned ExifTool 13.59) is
unchanged at MISSING 9 / WRONG 0 -- the OBJ tags were EXTRA and never
scored -- while `oxidex -j` no longer emits them. Corpus sweep over
combined-samples via scripts/compare_file.py holds at 134 WRONG, the same
134 lines. `cargo test --workspace`: 4821 passed, 0 failed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two defects the OBJ misroute uncovered, both of the same shape: a format's
identity and its parser dispatch were decided by two different pieces of
code, and the second one was wrong.
Radiance HDR was detected and not parsed. `FileFormat` had no HDR variant,
so a file the magic table had already identified -- `File:FileType: HDR`,
`MIMEType: image/vnd.radiance` -- fell through `detect_format` to the
plain-text fallback and reported TEXT statistics ExifTool reports for no
image. Add the variant, detect the `#?RADIANCE`/`#?RGBE` signature ahead of
the text rules, and transcribe `Radiance.pm`'s ProcessHDR: the `KEY=value`
header split on its last `=` as the greedy regex does, the table's eight
names plus ExifTool's AddTagToTable fallback for unlisted keys, comments,
every Command line rather than the last, and the resolution line's
orientation with rows before columns.
DXF read its magic number from three places. ExifTool's pattern is
`^\s*0\s+\x00?\s*SECTION\s+2\s+HEADER`; `filetype` compiled it for the
identity tags, while `detect_format` tested `starts_with("0\n")` and
DXFParser::verify_signature tested 20 bytes for `0\nSECTION`. Real AutoCAD
writers right-align group codes in three columns and end lines with CRLF,
so ` 0\r\n` files were called DXF by the identity tags and parsed as plain
text. Both hand-written copies now call `filetype::matches_magic("DXF", ..)`,
which is also the only way to keep the two answers from drifting again;
`parse_content` already trimmed group codes, so nothing else had to change.
Verified: `just compare-file .../Radiance.hdr` (pinned ExifTool 13.59) goes
from MISSING 9 / WRONG 0 to MISSING 0 / WRONG 0 -- Software, View, Format,
Exposure, Orientation, ImageWidth, ImageHeight and the two composites that
follow from the dimensions. Corpus sweep over combined-samples via
scripts/compare_file.py holds at 122 WRONG, the same 122 lines (122, not
the 134 measured before this branch rebased -- upstream's empty-UserComment
fix removed 12). `cargo test --workspace`: 4864 passed, 0 failed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
swackhamer
force-pushed
the
claude/keen-easley-cc904b
branch
from
August 10, 2026 14:58
1e86409 to
67c65cd
Compare
This was referenced Aug 10, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
text.contains("v ")accepted avanywhere in the first 100 bytes of afile.
Radiance.hdrin the shared corpus opensand the
vthat endsoconvsatisfied it, so a Radiance HDR image wasdispatched to the OBJ parser, which reported
HasNormals/HasTextureCoordsfor an image file. Nothing surfaced it:
File:FileTypeis decided by the magictable and went on saying HDR, and #651 removed the ungrouped
FileType: OBJthat had been the only visible symptom.
Pulling on that turned up two more of the same shape -- a format's identity and
its parser dispatch decided by two different pieces of code, the second one
wrong.
Anchor the four text rules
ExifTool's magic patterns anchor; these did not.
^v,^vnor^vtwith operands, on a real line.SECTIONas its own group-code record (see below)."asset"to be a realkey.
contains("{") && contains("\"asset\"")accepted any document thatmentioned both, in either order, at any depth.
solidas a whole word (solidificationmatched the prefix) plus acorroborating
facet/endsoliddirective, so prose opening "solid ..." is nolonger handed to the STL parser. That directive is looked for across the whole
probe, since a long solid name pushes it past 100 bytes.
OBJParser::verify_signaturecarried a second copy of the substring rule --which is why the parser accepted the Radiance file instead of rejecting it --
and now calls the detector's predicate.
Parse Radiance instead of detecting it
FileFormathad no HDR variant, so a file the magic table had alreadyidentified fell through to the plain-text fallback and reported TEXT statistics
ExifTool reports for no image.
src/parsers/image/radiance.rstranscribesRadiance.pm'sProcessHDR: theKEY=valueheader split on its last=as the greedy regex does, the table's eight names plus ExifTool's
AddTagToTablefallback for unlisted keys, comments, everyCommandlinerather than the last, and the resolution line's orientation with rows before
columns.
Read DXF's magic number once
ExifTool's pattern is
^\s*0\s+\x00?\s*SECTION\s+2\s+HEADER.filetypecompiled it for the identity tags;
detect_formattestedstarts_with("0\n");DXFParser::verify_signaturetested 20 bytes for0\nSECTION. Real AutoCADwriters right-align group codes in three columns and end lines with CRLF, so
0\r\nfiles were called DXF by the identity tags and parsed as plain text.Both hand-written copies now call
filetype::matches_magic("DXF", ..), which isalso the only thing that keeps the two answers from drifting again.
parse_contentalready trimmed group codes, so nothing else had to change.Verification
Instruments named, since the numbers are claims about them:
just compare-file .../Radiance.hdr(oracle: pinned ExifTool 13.59 viaexiftool_oracle.shared()) -- MISSING 9 / WRONG 0 to MISSING 0 / WRONG 0.Software, View, Format, Exposure, Orientation, ImageWidth, ImageHeight, and
the two composites that follow from the dimensions. The remaining AMBIGUOUS 1
is
Command, four values collapsing onto one bare name.combined-samplesviascripts/compare_file.py--122 WRONG before, 122 after, the same 122 lines by
diff, not merely thesame count. (122 rather than the 134 this branch started against: upstream's
empty-
UserCommentfix removed 12.)cargo test --workspace(full, not filtered) -- 4864 passed, 0 failed,47 binaries, including 25 new tests.
cargo clippy --workspace --all-targets-- no findings in the changed files..obj,.stl,.gltfand both DXFspellings still reach their parsers; prose opening "solid " stops at TXT.
Known gap, not addressed here
The Radiance tags are emitted ungrouped, where ExifTool groups them under
[Radiance]. That matches what every comparable parser here already does --DPX emits its content tags ungrouped against ExifTool's
[File]-- andcompare_file.pycompares by bare name, so it cannot see the difference eitherway. Worth a separate pass across the parsers rather than one format's
exception.
🤖 Generated with Claude Code