PngExifReader.php: add internal debugging error codes

This commit is contained in:
Zankaria 2025-03-22 00:09:06 +01:00
parent 2772784b4f
commit fc967c70f3

View file

@ -1,5 +1,5 @@
<?php <?php
namespace Vichan\Data\Driver; namespace Vichan\Data\Driver\Metadata;
class PngExifReader implements ExifReader { class PngExifReader implements ExifReader {
@ -9,11 +9,23 @@ class PngExifReader implements ExifReader {
// Exif data type identifier. // Exif data type identifier.
private const TYPE_SHORT = 0x3; private const TYPE_SHORT = 0x3;
// Errors, mostly for debugging.
private const ERR_BAD_EXIF = -1;
private const ERR_BAD_ENDIAN = -2;
private const ERR_BAD_MARK = -2;
private const ERR_BAD_OFFSET_TO_IFD = -3;
private const ERR_BAD_OFFSET_TO_DIR_ENTRY = -4;
private const ERR_BAD_ORIENTATION_NOT_FOUND = -5;
private const ERR_NOT_A_PNG = -6;
private const ERR_EOF = -7;
private const ERR_COULD_NOT_OPEN = -8;
private static function tryReadExifChunk(string $chunk_blob): ?int {
private static function tryReadExifChunk(string $chunk_blob): int {
// Ensure the chunk starts with "Exif" (EXIF header). // Ensure the chunk starts with "Exif" (EXIF header).
if (\substr($chunk_blob, 0, 6) !== "Exif\0\0") { $a = \substr($chunk_blob, 0, 6);
return null; if ($a !== "Exif\0\0") {
return self::ERR_BAD_EXIF;
} }
// Remove the "Exif\0\0" header. // Remove the "Exif\0\0" header.
@ -27,18 +39,18 @@ class PngExifReader implements ExifReader {
} elseif ($byte_order === 'MM') { } elseif ($byte_order === 'MM') {
$little_endian = false; $little_endian = false;
} else { } else {
return null; return self::ERR_BAD_ENDIAN;
} }
// Verify tag mark. // Verify tag mark.
$tag_mark = \substr($tiff_data, 2, 4); $tag_mark = \substr($tiff_data, 2, 4);
if ($little_endian) { if ($little_endian) {
if ($tag_mark !== "*\0") { if ($tag_mark !== "*\0") {
return null; return self::ERR_BAD_MARK;
} }
} else { } else {
if ($tag_mark !== "\0*") { if ($tag_mark !== "\0*") {
return null; return self::ERR_BAD_MARK;
} }
} }
@ -48,7 +60,7 @@ class PngExifReader implements ExifReader {
// Offset to the first IFD. // Offset to the first IFD.
$current_offset = \unpack($unpack_fmt, \substr($tiff_data, 4, 2))[1]; $current_offset = \unpack($unpack_fmt, \substr($tiff_data, 4, 2))[1];
if ($current_offset > \strlen($chunk_blob)) { if ($current_offset > \strlen($chunk_blob)) {
return null; return self::ERR_BAD_OFFSET_TO_IFD;
} }
while ($current_offset > 0) { while ($current_offset > 0) {
@ -57,7 +69,7 @@ class PngExifReader implements ExifReader {
$current_offset += 2; $current_offset += 2;
if ($current_offset + $num_entries * 12 > \strlen($chunk_blob)) { if ($current_offset + $num_entries * 12 > \strlen($chunk_blob)) {
return null; return self::ERR_BAD_OFFSET_TO_DIR_ENTRY;
} }
for ($i = 0; $i < $num_entries; $i++) { for ($i = 0; $i < $num_entries; $i++) {
@ -84,18 +96,16 @@ class PngExifReader implements ExifReader {
$current_offset = \unpack($unpack_fmt, \substr($tiff_data, $current_offset + ($num_entries * 12), 4))[1]; $current_offset = \unpack($unpack_fmt, \substr($tiff_data, $current_offset + ($num_entries * 12), 4))[1];
} }
return null; return self::ERR_BAD_ORIENTATION_NOT_FOUND;
} }
// Mostly adapted from https://stackoverflow.com/a/2190438 // Mostly adapted from https://stackoverflow.com/a/2190438
private static function tryReadPngChunks(mixed $fd): ?int { private static function tryReadPngChunks(mixed $fd): int {
$text_chunks = [];
// Read the magic bytes and verify. // Read the magic bytes and verify.
$header = \fread($fd, 8); $header = \fread($fd, 8);
if ($header != "\x89PNG\x0d\x0a\x1a\x0a") { if ($header != "\x89PNG\x0d\x0a\x1a\x0a") {
return null; return self::ERR_NOT_A_PNG;
} }
// Loop through the PNG's chunks. Byte 0-3 is length, Byte 4-7 is type. // Loop through the PNG's chunks. Byte 0-3 is length, Byte 4-7 is type.
@ -105,17 +115,12 @@ class PngExifReader implements ExifReader {
// Extract length and type from binary data. // Extract length and type from binary data.
$chunk = @\unpack('Nsize/a4type', $chunkHeader); $chunk = @\unpack('Nsize/a4type', $chunkHeader);
// Store position into internal array.
if (!isset($text_chunks[$chunk['type']])) {
$text_chunks[$chunk['type']] = [];
}
if ($chunk['type'] === 'tEXt') { if ($chunk['type'] === 'tEXt') {
if ($chunk['size'] > 0 && $chunk['size'] < self::MAX_CHUNK_SIZE) { if ($chunk['size'] > 0 && $chunk['size'] < self::MAX_CHUNK_SIZE) {
$size = $chunk['size']; $size = $chunk['size'];
$chunk_blob = \fread($fd, $size); $chunk_blob = \fread($fd, $size);
$ret = self::tryReadExifChunk($chunk_blob); $ret = self::tryReadExifChunk($chunk_blob);
if ($ret !== null) { if ($ret < 1) {
return $ret; return $ret;
} }
} }
@ -128,18 +133,18 @@ class PngExifReader implements ExifReader {
$chunkHeader = \fread($fd, 8); $chunkHeader = \fread($fd, 8);
} }
return null; return self::ERR_EOF;
} }
public function getOrientation(string $file): ?int { public function getOrientation(string $file): ?int {
// Open the file. // Open the file.
$fd = \fopen($file, 'r'); $fd = \fopen($file, 'r');
if ($fd === false) { if ($fd === false) {
return null; return self::ERR_COULD_NOT_OPEN;
} }
$ret = self::tryReadPngChunks($fd); return self::tryReadPngChunks($fd);
\fclose($fd); \fclose($fd);
if ($ret === null || $ret > 9) { if ($ret < 1 || $ret > 9) {
return null; return null;
} else { } else {
return $ret; return $ret;