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 000000000000..12dbe96a845c Binary files /dev/null and b/ext/exif/tests/gh19904.webp differ