Repository navigation
Fix detection of images shorter than 80 bytes - #9553
JayOfTheKeyboard wants to merge 1 commit into
Conversation
|
The To check, I ran the meson suite locally on Fedora 44 with clang 22, using the macOS job's options ( The two runs match. That's Linux only, so it doesn't explain what geotag is doing on the macOS runner. |
isXmpType reads up to 80 bytes. On a shorter file it returned false without seeking back, leaving the stream at EOF, so every detector registered after XMP (gif, psd, tga, bmp, jp2, the video formats, bmff) failed too. A 43-byte GIF or a 58-byte BMP was reported as "unknown image type". Restore the position on a short read, as isEpsType already does. Do the same when an empty sidecar (just the XMP packet header) is matched and advance is false.
51f559e to
103bb8a
Compare
|
Rebased onto main to pick up d2dad86 ("Avoid sharing the geotag test image with the bugfix suite"). That commit fixes the With the macOS job's options on Linux, |
Bug
Small image files are reported as an unknown type:
A 58-byte 1x1 BMP fails the same way. Padding the same GIF with 40 zero bytes makes it work (
MIME type : image/gif).isXmpType(src/xmpsidecar.cpp) reads 55 bytes and then up to 80. When the file is shorter it returnsfalsewithout seeking back, so the stream is left at EOF.ImageFactory::getTypeandImageFactory::openpass the sameBasicIoto each detector in turn, and every detector registered after XMP (gif, psd, tga, bmp, jp2, the video formats, bmff) then reads nothing and returnsfalseas well. Both early returns are hit: the GIF fails the first read, the BMP the second.Fix
Save
tell()on entry and seek back to it before both earlyreturn falses.isEpsTypealready does exactly this (const size_t restore = iIo.tell();...iIo.seek(restore, BasicIo::beg);).The early
return truefor an empty sidecar (only the XMP packet header, which is whatImageFactory::create(ImageType::xmp, ...)writes) had the same problem: it left the stream at EOF even withadvance == false. It now seeks back too whenadvanceis false, matching what the end of the function does.I put the fix in the detector rather than in the
ImageFactoryloops because theadvance == falsecontract (leave the position where it was) belongs to eachis*Typefunction, and they are also called directly throughImageFactory::checkTypeand from the image classes'readMetadata.One side effect:
XmpSidecar::readMetadataon a non-XMP file shorter than 80 bytes now throwskerNotAnImage("This does not look like a XMP image") rather thankerFailedToReadImageData, because EOF is no longer set when it checks. That is the same error a longer non-XMP file already gets.Not changed
The other fixed-length detectors (
isCr2Type16 bytes,isCrwType14,isRw2Type24, and so on) have the same no-rewind early return. With this fix, files of 24 bytes and up are detected; below that,isPngType's entry check throws "Failed to read input data". A valid GIF or BMP needs at least 26 bytes of headers, so I left those alone. If you would rather handle all of them at once by seeking back to the start in theImageFactoryloops, I can change it to that.Two older things in
isXmpTypeI noticed but left for separate PRs, since neither is needed for this fix:strncmpcompares the header against bytes ofbufthat were never read. MemorySanitizer reports it for a 30-byte file holding the start of the packet header.len - startbytes, so the stream ends up 3 bytes past where it started.Like
isEpsType, the new seeks don't check the return value.Testing
Three tests in
unitTests/test_ImageFactory.cpp. The real 43-byte GIF and 58-byte BMP are byte arrays in the test, so there are no new files undertest/data.detectsImagesShorterThanAnXmpPacketHeaderInMemorychecksgetTypeand opens each image from memory to check the pixel size.detectsImagesShorterThanAnXmpPacketHeaderInFilesdoes the same throughFileIo, which is whatexiv2 file.gifuses. It writes the bytes tostd::filesystem::temp_directory_path()and removes them afterwards.isXmpTypeKeepsPositionForEmptySidecarcreates an empty sidecar withImageFactory::createand checks thatisXmpType(io, false)returns true withtell()still 0 and EOF not set.All three fail without the fix. Removing one seek at a time turns a different check red each time: the empty-sidecar seek fails the third test, the first short-read seek fails the GIF checks in the first two, and the second fails the BMP checks. With the fix, all of them pass.
exiv2 t.gifandexiv2 t.bmpprintMIME type : image/gif/image/x-ms-bmpand the right image size.Full
cteston Linux (GCC, Release, XMP on, curl off): all 373 unit tests and the bugfix, regression, lens, tiff and version tests pass.bashTestshas two failures,io_testandstdin_test, which fail identically onmainwithout this change on my machine (stdin_testdiffers in compressed iCCP bytes, from the system zlib-ng; inio_test,iotestis killed on itshttp://127.0.0.1case). clang-format 20 is clean on both touched files.Prepared with Claude Code.