From c6a3597c0ec2ef6a171cbb77158bc3ffc2dae2a7 Mon Sep 17 00:00:00 2001 From: Kari Laari Date: Tue, 3 Jun 2025 17:21:25 +0300 Subject: [PATCH] Add ImageMetadataBuffer for efficient XMP metadata buffering MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Introduce CSD\Image\Buffer\ImageMetadataBuffer to read just enough of PNG/JPEG/WebP files (from URLs or local paths) to capture embedded XMP metadata - PNG: buffer chunk-by-chunk until an iTXt chunk starting with “XML:com.adobe.xmp\x00” or IEND - JPEG: buffer segment-by-segment until APP1-XMP and first SOF, then append EOI - WebP: buffer RIFF sub-chunks until “XMP ” or “EXIF” chunk is found - Falls back to full file read only if fopen() fails or format is unrecognized --- src/Buffer/ImageMetadataBuffer.php | 336 +++++++++++++++++++++++ tests/Buffer/ImageMetadataBufferTest.php | 170 ++++++++++++ tests/Fixtures/frameright.png | Bin 0 -> 25395 bytes 3 files changed, 506 insertions(+) create mode 100644 src/Buffer/ImageMetadataBuffer.php create mode 100644 tests/Buffer/ImageMetadataBufferTest.php create mode 100644 tests/Fixtures/frameright.png diff --git a/src/Buffer/ImageMetadataBuffer.php b/src/Buffer/ImageMetadataBuffer.php new file mode 100644 index 0000000..217f377 --- /dev/null +++ b/src/Buffer/ImageMetadataBuffer.php @@ -0,0 +1,336 @@ += 12 + && substr($peek, 0, 4) === self::RIFF_SIGNATURE + && substr($peek, 8, 4) === self::WEBP_SIGNATURE + ) { + fclose($stream); + $stream = @fopen($url, 'rb'); + if (! $stream) { + return null; + } + $buf = self::bufferWebpUpToXmp($stream); + fclose($stream); + return $buf; + } + + // Unknown format: just read entire file + fclose($stream); + $stream = @fopen($url, 'rb'); + if (! $stream) { + return null; + } + $all = stream_get_contents($stream); + if ($all === false) { + fclose($stream); + return null; + } + fclose($stream); + return $all === '' ? null : $all; + } + + /** + * Buffer a PNG chunk‐by‐chunk until we fully read an iTXt whose + * data begins with "XML:com.adobe.xmp\x00", and then read through + * the IEND chunk before stopping. + * + * @param resource $stream Opened PNG stream in binary mode + * @return string|null A byte‐buffer containing: signature → iTXt(XMP) → IEND (or null on error) + */ + private static function bufferPngUpToXmp($stream) + { + // 1) Read and verify the 8‐byte PNG signature + $sig = fread($stream, 8); + if ($sig === false || strlen($sig) < 8) { + return null; + } + if ($sig !== self::PNG_SIGNATURE) { + return null; + } + + $buffer = $sig; + $foundXmp = false; + + while (true) { + // 2) Read the next chunk's length+type (8 bytes) + $hdr = fread($stream, 8); + if ($hdr === false || strlen($hdr) < 8) { + // EOF or truncated; return what we have so far + break; + } + $buffer .= $hdr; + + // Parse length (4 bytes BE) and chunk type (4 bytes ASCII) + $u = @unpack('Nlength/a4type', $hdr); + if ($u === false || ! isset($u['length'], $u['type'])) { + // Invalid header, bail + break; + } + $length = (int) $u['length']; + $type = $u['type']; + + // 3) If this is IEND, read its 4‐byte CRC, append, and stop + if ($type === 'IEND') { + $crc = fread($stream, 4); + if ($crc !== false && strlen($crc) === 4) { + $buffer .= $crc; + } + break; + } + + // 4) Otherwise, read payload + 4‐byte CRC + $toRead = $length + 4; + if ($toRead > 0) { + $chunkData = fread($stream, $toRead); + if ($chunkData === false || strlen($chunkData) < $toRead) { + // Truncated payload; append whatever we got and bail + $buffer .= ($chunkData ?: ''); + break; + } + $buffer .= $chunkData; + } else { + $chunkData = ''; + } + + // 5) If this is an iTXt chunk and it begins with the XMP keyword, mark $foundXmp + if ($type === 'iTXt') { + $data = substr($chunkData, 0, $length); + if (strpos($data, self::PNG_ITXT_XMP_KEYWORD) === 0) { + $foundXmp = true; + // Do NOT break yet – we still need to read through IEND + } + } + + // 6) If we've seen XMP‐tagged iTXt, continue looping until we hit IEND above + } + + return $buffer; + } + + /** + * Buffer a JPEG segment‐by‐segment until we have: + * 1) Fully read the APP1-XMP chunk (so $foundXmp = true), + * 2) Fully read the first SOF segment (so $sawSOF = true), + * 3) Then append an EOI marker (0xFFD9) and break. + * + * If we encounter SOS (0xFFDA) or EOI (0xFFD9) before capturing both, + * we break anyway, because no more headers exist. + * + * @param resource $stream + * @return string|null + */ + private static function bufferJpegUpToXmp($stream) + { + // 1) Read SOI (2 bytes). Must be 0xFFD8. + $soi = fread($stream, 2); + if ($soi === false || strlen($soi) < 2 || $soi !== self::JPEG_SOI) { + return null; + } + + $buffer = $soi; + $foundXmp = false; + $sawSOF = false; + + while (true) { + $marker = fread($stream, 2); + if ($marker === false || strlen($marker) < 2) { + // EOF or truncated + break; + } + $buffer .= $marker; + + // If SOS (0xFFDA) or EOI (0xFFD9) appear before we've captured both flags, + // break anyway (no more headers). + if ($marker === self::JPEG_SOS) { + if (! ($foundXmp && $sawSOF)) { + break; + } + // Both flags true, we’ll append EOI and stop. + break; + } + if ($marker === self::JPEG_EOI) { + break; + } + + $lenBytes = fread($stream, 2); + if ($lenBytes === false || strlen($lenBytes) < 2) { + break; + } + $buffer .= $lenBytes; + + $un = @unpack('nsegmentLength', $lenBytes); + if ($un === false || ! isset($un['segmentLength'])) { + break; + } + $segLen = (int) $un['segmentLength']; + $payloadLen = $segLen - 2; + + if ($payloadLen > 0) { + $payload = fread($stream, $payloadLen); + if ($payload === false || strlen($payload) < $payloadLen) { + $buffer .= ($payload ?: ''); + break; + } + $buffer .= $payload; + } else { + $payload = ''; + } + + // If this marker is APP1 (0xFFE1), check for XMP header + if ($marker === self::JPEG_APP1_MARKER) { + $xmpHeader = self::JPEG_APP1_XMP_HEADER; + if (strncmp($payload, $xmpHeader, strlen($xmpHeader)) === 0) { + $foundXmp = true; + } + } + + // Check if this is a SOF marker (0xFFC0,0xFFC1,0xFFC2,…) + $secondByte = ord($marker[1]); + $isSOF = in_array($secondByte, [ + 0xC0, 0xC1, 0xC2, 0xC3, + 0xC5, 0xC6, 0xC7, + 0xC9, 0xCA, 0xCB, + 0xCD, 0xCE, 0xCF, + ], true); + if ($isSOF) { + $sawSOF = true; + } + + // If we now have both APP1-XMP and SOF, stop reading further segments. + if ($foundXmp && $sawSOF) { + break; + } + } + + // We've captured XMP and SOF (if present). + // Append EOI (0xFFD9) so that fromStream() won't unpack an empty marker. + $buffer .= self::JPEG_EOI; + + return $buffer; + } + + /** + * Buffer a WebP RIFF sub‐chunk by sub‐chunk until we find "XMP " or "EXIF", + * then stop. If none, buffer until EOF. + * + * @param resource $stream + * @return string|null + */ + private static function bufferWebpUpToXmp($stream) + { + $riffHdr = fread($stream, 12); + if ($riffHdr === false || strlen($riffHdr) < 12) { + return null; + } + if (substr($riffHdr, 0, 4) !== self::RIFF_SIGNATURE || substr($riffHdr, 8, 4) !== self::WEBP_SIGNATURE) { + return null; + } + + $buffer = $riffHdr; + + while (true) { + $hdr = fread($stream, 8); + if ($hdr === false || strlen($hdr) < 8) { + break; + } + $buffer .= $hdr; + + $type = substr($hdr, 0, 4); + $sizeLE = substr($hdr, 4, 4); + + $un = @unpack('VchunkSize', $sizeLE); + if ($un === false || ! isset($un['chunkSize'])) { + break; + } + $chunkSize = (int) $un['chunkSize']; + + if ($chunkSize > 0) { + $data = fread($stream, $chunkSize); + if ($data === false || strlen($data) < $chunkSize) { + $buffer .= ($data ?: ''); + break; + } + $buffer .= $data; + + if ($chunkSize % 2 !== 0) { + $pad = fread($stream, 1); + if ($pad !== false && strlen($pad) === 1) { + $buffer .= $pad; + } + } + } + + if ($type === 'XMP ' || $type === 'EXIF') { + break; + } + } + + return $buffer; + } +} diff --git a/tests/Buffer/ImageMetadataBufferTest.php b/tests/Buffer/ImageMetadataBufferTest.php new file mode 100644 index 0000000..96f7b3e --- /dev/null +++ b/tests/Buffer/ImageMetadataBufferTest.php @@ -0,0 +1,170 @@ +assertIsString($buf, "bufferUpThroughXmp returned null or non‐string for $filename"); + $this->assertStringContainsString($expectedSubstring, $buf, "Buffer for $filename did not contain expected XMP marker"); + } + + public function providerFormatsWithXmp(): array + { + return [ + // JPEG with embedded XMP + ['frameright.jpg', 'http://ns.adobe.com/xap/1.0/'], + // PNG with an iTXt chunk holding XML:com.adobe.xmp + ['frameright.png', 'XML:com.adobe.xmp'], + // WebP with an XMP chunk + ['meta.webp', 'XMP '], + ]; + } + + /** + * If an image has no XMP at all, bufferUpThroughXmp should still return + * the full file contents (not null), and getimagesizefromstring should work + * later on. We simply assert that it’s non‐null and that its length is + * “reasonably” larger than zero. + * + * @dataProvider providerFormatsWithoutXmp + */ + public function testBufferUpThroughXmp_NoXmpStillReturnsEntireFile(string $filename) + { + $path = __DIR__ . '/../Fixtures/' . $filename; + $buf = ImageMetadataBuffer::bufferUpThroughXmp($path); + + $this->assertIsString($buf, "Expected a buffer, got null for $filename"); + $this->assertGreaterThan(0, strlen($buf), "Empty buffer for $filename"); + } + + public function providerFormatsWithoutXmp(): array + { + return [ + ['nometa.jpg'], + ['nometa.png'], + ['exif.webp'], // a WebP with EXIF but no XMP (still buffer as full file) + ]; + } + + /** + * If fopen() fails (for instance, point at a non‐existent file), we expect null. + */ + public function testBufferUpThroughXmp_NonexistentReturnsNull() + { + $buf = ImageMetadataBuffer::bufferUpThroughXmp('/this/path/does/not/exist.jpg'); + $this->assertNull($buf); + } + + /** + * Now, testing each private helper via reflection (optional): + * - bufferPngUpToXmp + * - bufferJpegUpToXmp + * - bufferWebpUpToXmp + * + * We can invoke them via ReflectionMethod, passing in a local php://temp stream + * that we manually write a minimal header+XMP chunk into. Below is an example for JPEG. + */ + public function testBufferJpegUpToXmp_AppendsEOIAfterXmpAndSOF() + { + // Construct a minimal JPEG header with: + // 0xFFD8 (SOI) + // 0xFFE1 (APP1), length=??, payload="http://ns.adobe.com/xap/1.0/\0" + // 0xFFC0 (SOF0), length=… (we don’t care about real dimensions) + // 0xFFDA (SOS) to simulate “start of scan” + // + // After we feed this to bufferJpegUpToXmp, the returned string must end in 0xFFD9 (EOI). + $fakeXmpPrefix = "http://ns.adobe.com/xap/1.0/\x00"; + $app1Length = pack('n', strlen($fakeXmpPrefix) + 2); + $jpegBytes = "\xFF\xD8"; // SOI + $jpegBytes .= "\xFF\xE1" . $app1Length . $fakeXmpPrefix; + $jpegBytes .= "\xFF\xC0" . "\x00\x04" . "\x00\x01\x00\x01"; // SOF0 with length=4, dummy dims + $jpegBytes .= "\xFF\xDA"; // SOS + // No actual compressed data, so fromStream would break; our buffer helper should append EOI. + + // Write into a php://temp stream + $stream = fopen('php://temp', 'r+'); + fwrite($stream, $jpegBytes); + rewind($stream); + + $rm = new \ReflectionMethod(ImageMetadataBuffer::class, 'bufferJpegUpToXmp'); + $rm->setAccessible(true); + $buffered = $rm->invoke(null, $stream); + fclose($stream); + + // Should contain the XMP header + $this->assertStringContainsString("http://ns.adobe.com/xap/1.0/\x00", $buffered); + // Should end with EOI (\xFF\xD9) + $this->assertStringEndsWith("\xFF\xD9", $buffered); + } + + public function testBufferPngUpToXmp_StopsAtIENDOriTXt() + { + // Build a minimal PNG: + // Signature (0x89 50 4E 47 0D 0A 1A 0A) + // iTXt chunk: length, type='iTXt', data="XML:com.adobe.xmp\0", CRC + // IEND chunk: length=0, type='IEND', CRC + $sig = "\x89PNG\x0D\x0A\x1A\x0A"; + $xmpTxt = "XML:com.adobe.xmp\x00"; + $iTXtLen = pack('N', strlen($xmpTxt)); + $iTXtType = 'iTXt'; + $fakeCRC = "\x00\x00\x00\x00"; + $iTXtChunk = $iTXtLen . $iTXtType . $xmpTxt . $fakeCRC; + $iENDChunk = pack('N', 0) . 'IEND' . $fakeCRC; + + $pngBytes = $sig . $iTXtChunk . $iENDChunk; + + $stream = fopen('php://temp', 'r+'); + fwrite($stream, $pngBytes); + rewind($stream); + + $rm = new \ReflectionMethod(ImageMetadataBuffer::class, 'bufferPngUpToXmp'); + $rm->setAccessible(true); + $buffered = $rm->invoke(null, $stream); + fclose($stream); + + // The returned buffer should begin with the PNG signature + $this->assertSame(substr($buffered, 0, 8), ImageMetadataBuffer::PNG_SIGNATURE); + // It should contain our “XML:com.adobe.xmp” keyword and stop after that + $this->assertStringContainsString("XML:com.adobe.xmp\x00", $buffered); + // It should also contain the IEND chunk + $this->assertStringContainsString('IEND', $buffered); + } + + public function testBufferWebpUpToXmp_StopsWhenXmpTypeIsFound() + { + // Minimal WebP RIFF header: + // 'RIFF' + [4‐byte size] + 'WEBP' + // Then a chunk with type='XMP ' + 4 bytes size + payload '' + // We don’t need to append padding, because our code will break immediately when it sees 'XMP '. + $riff = 'RIFF' . pack('V', 8 + 4 + 6) . 'WEBP'; + $xmpType = 'XMP '; + $xmpData = ''; + $sizeLE = pack('V', strlen($xmpData)); + $webpBytes = $riff . $xmpType . $sizeLE . $xmpData; + $stream = fopen('php://temp', 'r+'); + fwrite($stream, $webpBytes); + rewind($stream); + + $rm = new \ReflectionMethod(ImageMetadataBuffer::class, 'bufferWebpUpToXmp'); + $rm->setAccessible(true); + $buffered = $rm->invoke(null, $stream); + fclose($stream); + + $this->assertStringContainsString('WEBP', $buffered); + $this->assertStringContainsString('XMP ', $buffered); + // As soon as it sees 'XMP ', it should stop reading further sub‐chunks. + } +} diff --git a/tests/Fixtures/frameright.png b/tests/Fixtures/frameright.png new file mode 100644 index 0000000000000000000000000000000000000000..3744a54778d6c9394e0196d7d2c70f2d4c756823 GIT binary patch literal 25395 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 literal 0 HcmV?d00001