From ba889cbfa7ac4331d4eb7116d0a0d920a1dd86ee Mon Sep 17 00:00:00 2001 From: Ilia Alshanetsky Date: Tue, 2 Jun 2026 17:01:17 -0400 Subject: [PATCH] Add WebP support to ext/exif WebP stores EXIF metadata in a RIFF "EXIF" chunk whose payload is a raw TIFF block, the same data JPEG carries after its APP1 marker. Walk the RIFF chunks, locate the EXIF chunk, and hand its payload to the existing exif_process_TIFF_in_JPEG() parser, so no third-party library is needed. The chunk is accepted regardless of the VP8X extended-format flag, and an optional leading "Exif\0\0" marker is skipped when an encoder emits one. Closes GH-19904 --- ext/exif/exif.c | 60 +++++++++++++++++++++++++- ext/exif/tests/gh19904.phpt | 82 ++++++++++++++++++++++++++++++++++++ ext/exif/tests/gh19904.webp | Bin 0 -> 526 bytes 3 files changed, 141 insertions(+), 1 deletion(-) create mode 100644 ext/exif/tests/gh19904.phpt create mode 100644 ext/exif/tests/gh19904.webp diff --git a/ext/exif/exif.c b/ext/exif/exif.c index 91be074a0be3..f19ce72d75da 100644 --- a/ext/exif/exif.c +++ b/ext/exif/exif.c @@ -69,7 +69,7 @@ PHP_MINFO_FUNCTION(exif) php_info_print_table_start(); php_info_print_table_row(2, "EXIF Support", "enabled"); php_info_print_table_row(2, "Supported EXIF Version", "0220"); - php_info_print_table_row(2, "Supported filetypes", "JPEG, TIFF"); + php_info_print_table_row(2, "Supported filetypes", "JPEG, TIFF, HEIF, WebP"); if (USE_MBSTRING) { php_info_print_table_row(2, "Multibyte decoding support using mbstring", "enabled"); @@ -4445,6 +4445,53 @@ static bool exif_scan_HEIF_header(image_info_type *ImageInfo, unsigned char *buf return ret; } +static bool exif_scan_WEBP_header(image_info_type *ImageInfo, size_t riff_size) +{ + static const uchar ExifHeader[] = {0x45, 0x78, 0x69, 0x66, 0x00, 0x00}; + unsigned char chunk_header[8]; + size_t offset = 12; + size_t riff_end = riff_size <= ImageInfo->FileSize - 8 ? riff_size + 8 : ImageInfo->FileSize; + + while (offset + 8 <= riff_end) { + if ((php_stream_seek(ImageInfo->infile, offset, SEEK_SET) < 0) || + (exif_read_from_stream_file_looped(ImageInfo->infile, (char*)chunk_header, 8) != 8)) { + return false; + } + + size_t chunk_size = php_ifd_get32u(chunk_header + 4, 0); + size_t payload_offset = offset + 8; + + if (chunk_size > riff_end - payload_offset) { + return false; + } + + if (!memcmp(chunk_header, "EXIF", 4)) { + char *data; + size_t skip = 0; + bool ret = false; + + if (chunk_size < 8) { + return false; + } + + data = emalloc(chunk_size); + if (exif_read_from_stream_file_looped(ImageInfo->infile, data, chunk_size) == chunk_size) { + if (chunk_size >= sizeof(ExifHeader) + 8 && !memcmp(data, ExifHeader, sizeof(ExifHeader))) { + skip = sizeof(ExifHeader); + } + exif_process_TIFF_in_JPEG(ImageInfo, data + skip, chunk_size - skip, payload_offset + skip); + ret = true; + } + efree(data); + return ret; + } + + offset = payload_offset + chunk_size + (chunk_size & 1); + } + + return false; +} + /* {{{ exif_scan_FILE_header * Parse the marker stream until SOS or EOI is seen; */ static bool exif_scan_FILE_header(image_info_type *ImageInfo) @@ -4521,6 +4568,17 @@ static bool exif_scan_FILE_header(image_info_type *ImageInfo) exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "Invalid HEIF file"); return false; } + } else if ((ImageInfo->FileSize >= 16) && + (!memcmp(file_header, "RIFF", 4)) && + (exif_read_from_stream_file_looped(ImageInfo->infile, (char*)(file_header + 8), 4) == 4) && + (!memcmp(file_header + 8, "WEBP", 4))) { + if (exif_scan_WEBP_header(ImageInfo, php_ifd_get32u(file_header + 4, 0))) { + ImageInfo->FileType = IMAGE_FILETYPE_WEBP; + return true; + } else { + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "Invalid WebP file"); + return false; + } } else { exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "File not supported"); return false; diff --git a/ext/exif/tests/gh19904.phpt b/ext/exif/tests/gh19904.phpt new file mode 100644 index 000000000000..cc13d88dd98b --- /dev/null +++ b/ext/exif/tests/gh19904.phpt @@ -0,0 +1,82 @@ +--TEST-- +GH-19904 (exif_read_data() reads EXIF metadata from WebP images) +--EXTENSIONS-- +exif +--INI-- +output_handler= +zlib.output_compression=0 +--FILE-- + +--EXPECTF-- +array(26) { + ["FileName"]=> + string(12) "gh19904.webp" + ["FileDateTime"]=> + int(%d) + ["FileSize"]=> + int(526) + ["FileType"]=> + int(18) + ["MimeType"]=> + string(10) "image/webp" + ["SectionsFound"]=> + string(24) "ANY_TAG, IFD0, EXIF, GPS" + ["COMPUTED"]=> + array(4) { + ["IsColor"]=> + int(0) + ["ByteOrderMotorola"]=> + int(0) + ["UserComment"]=> + string(17) "Created with GIMP" + ["UserCommentEncoding"]=> + string(9) "UNDEFINED" + } + ["ImageWidth"]=> + int(100) + ["ImageLength"]=> + int(100) + ["BitsPerSample"]=> + array(3) { + [0]=> + int(8) + [1]=> + int(8) + [2]=> + int(8) + } + ["ImageDescription"]=> + string(17) "Created with GIMP" + ["XResolution"]=> + string(5) "300/1" + ["YResolution"]=> + string(5) "300/1" + ["ResolutionUnit"]=> + int(2) + ["Software"]=> + string(10) "GIMP 3.0.4" + ["DateTime"]=> + string(19) "2025:09:21 15:30:30" + ["Exif_IFD_Pointer"]=> + int(250) + ["GPS_IFD_Pointer"]=> + int(430) + ["DateTimeOriginal"]=> + string(19) "2025:09:21 15:29:27" + ["DateTimeDigitized"]=> + string(19) "2025:09:21 15:29:27" + ["OffsetTime"]=> + string(6) "+02:00" + ["OffsetTimeOriginal"]=> + string(6) "+02:00" + ["OffsetTimeDigitized"]=> + string(6) "+02:00" + ["UserComment"]=> + string(25) "%sCreated with GIMP" + ["ColorSpace"]=> + int(1) + ["GPSAltitude"]=> + string(5) "0/100" +} diff --git a/ext/exif/tests/gh19904.webp b/ext/exif/tests/gh19904.webp new file mode 100644 index 0000000000000000000000000000000000000000..12dbe96a845cc2c1426dc1dd493af0009c5c343d GIT binary patch literal 526 zcmWIYbaP{4Vqge&bqWXzu!!JdU|`??;$$!aiui~FdHTr*BpBEO{(YLh|9>Kb(4qf- zgIxXxcQUv}c)FcnWMJ_0)B@|`VPIfnVE_s+qyRA^5}S#UnSmK7HV=sT7?~J^fbOGDZFfNTw*dXPClr!g1;)pG;sV?b;KWQzdlOF*324zcSOkgeJQVXp&f zVx9m|^9(2s0vurIT$GwvlA5AWo>`Ki;O^-gz@P(E3$g=7g9H_f^$he(fc6*|nOYfG zS{WHC7@Arc8vqfSm=RFYoPix~TL{o77O2e`K(+vo%?>oZ2FMnKvRi;`At<{C$Zng& z&L9a?GX=B;E`ZpEO-$Rs$jZQgfdQR{8pD9;Z$<_-21uwMfLR7q42m%T DXuwIu literal 0 HcmV?d00001