Skip to content

Commit d89f14b

Browse files
committed
Add WebP support to ext/exif
WebP keeps EXIF in a RIFF "EXIF" chunk whose payload is a raw TIFF block, parsed via the existing exif_process_TIFF_in_JPEG() helper. Closes GH-19904 Closes GH-22213
1 parent c701c57 commit d89f14b

4 files changed

Lines changed: 146 additions & 1 deletion

File tree

NEWS

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@ PHP NEWS
4141
values, and textContent returns NULL per the DOM specification.
4242
(jordikroon)
4343

44+
- EXIF:
45+
. Added support for reading EXIF metadata from WebP images (GH-19904).
46+
(iliaal)
47+
4448
- Fileinfo:
4549
. Fixed bug GH-20679 (finfo_file() doesn't work on remote resources).
4650
(ndossche)

ext/exif/exif.c

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ PHP_MINFO_FUNCTION(exif)
6969
php_info_print_table_start();
7070
php_info_print_table_row(2, "EXIF Support", "enabled");
7171
php_info_print_table_row(2, "Supported EXIF Version", "0220");
72-
php_info_print_table_row(2, "Supported filetypes", "JPEG, TIFF");
72+
php_info_print_table_row(2, "Supported filetypes", "JPEG, TIFF, HEIF, WebP");
7373

7474
if (USE_MBSTRING) {
7575
php_info_print_table_row(2, "Multibyte decoding support using mbstring", "enabled");
@@ -4445,6 +4445,54 @@ static bool exif_scan_HEIF_header(image_info_type *ImageInfo, unsigned char *buf
44454445
return ret;
44464446
}
44474447

4448+
static bool exif_scan_WEBP_header(image_info_type *ImageInfo, size_t riff_size)
4449+
{
4450+
/* "Exif\0\0" identifier code */
4451+
static const uchar ExifHeader[] = {0x45, 0x78, 0x69, 0x66, 0x00, 0x00};
4452+
unsigned char chunk_header[8];
4453+
size_t offset = 12;
4454+
size_t riff_end = riff_size <= ImageInfo->FileSize - 8 ? riff_size + 8 : ImageInfo->FileSize;
4455+
4456+
while (offset + 8 <= riff_end) {
4457+
if ((php_stream_seek(ImageInfo->infile, offset, SEEK_SET) < 0) ||
4458+
(exif_read_from_stream_file_looped(ImageInfo->infile, (char*)chunk_header, 8) != 8)) {
4459+
return false;
4460+
}
4461+
4462+
size_t chunk_size = php_ifd_get32u(chunk_header + 4, 0);
4463+
size_t payload_offset = offset + 8;
4464+
4465+
if (chunk_size > riff_end - payload_offset) {
4466+
return false;
4467+
}
4468+
4469+
if (!memcmp(chunk_header, "EXIF", 4)) {
4470+
size_t skip = 0;
4471+
bool ret = false;
4472+
4473+
if (chunk_size < 8) {
4474+
return false;
4475+
}
4476+
4477+
char *data = emalloc(chunk_size);
4478+
if (exif_read_from_stream_file_looped(ImageInfo->infile, data, chunk_size) == chunk_size) {
4479+
if (chunk_size >= sizeof(ExifHeader) + 8 && !memcmp(data, ExifHeader, sizeof(ExifHeader))) {
4480+
skip = sizeof(ExifHeader);
4481+
}
4482+
exif_process_TIFF_in_JPEG(ImageInfo, data + skip, chunk_size - skip, payload_offset + skip);
4483+
ret = true;
4484+
}
4485+
efree(data);
4486+
return ret;
4487+
}
4488+
4489+
/* RIFF chunks are word-aligned: an odd payload is followed by a pad byte. */
4490+
offset = payload_offset + chunk_size + (chunk_size & 1);
4491+
}
4492+
4493+
return false;
4494+
}
4495+
44484496
/* {{{ exif_scan_FILE_header
44494497
* Parse the marker stream until SOS or EOI is seen; */
44504498
static bool exif_scan_FILE_header(image_info_type *ImageInfo)
@@ -4521,6 +4569,17 @@ static bool exif_scan_FILE_header(image_info_type *ImageInfo)
45214569
exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "Invalid HEIF file");
45224570
return false;
45234571
}
4572+
} else if ((ImageInfo->FileSize >= 16) &&
4573+
(!memcmp(file_header, "RIFF", 4)) &&
4574+
(exif_read_from_stream_file_looped(ImageInfo->infile, (char*)(file_header + 8), 4) == 4) &&
4575+
(!memcmp(file_header + 8, "WEBP", 4))) {
4576+
if (exif_scan_WEBP_header(ImageInfo, php_ifd_get32u(file_header + 4, 0))) {
4577+
ImageInfo->FileType = IMAGE_FILETYPE_WEBP;
4578+
return true;
4579+
} else {
4580+
exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "Invalid WebP file");
4581+
return false;
4582+
}
45244583
} else {
45254584
exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "File not supported");
45264585
return false;

ext/exif/tests/gh19904.phpt

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
--TEST--
2+
GH-19904 (exif_read_data() reads EXIF metadata from WebP images)
3+
--EXTENSIONS--
4+
exif
5+
--INI--
6+
output_handler=
7+
zlib.output_compression=0
8+
--FILE--
9+
<?php
10+
var_dump(exif_read_data(__DIR__.'/gh19904.webp'));
11+
?>
12+
--EXPECTF--
13+
array(26) {
14+
["FileName"]=>
15+
string(12) "gh19904.webp"
16+
["FileDateTime"]=>
17+
int(%d)
18+
["FileSize"]=>
19+
int(526)
20+
["FileType"]=>
21+
int(18)
22+
["MimeType"]=>
23+
string(10) "image/webp"
24+
["SectionsFound"]=>
25+
string(24) "ANY_TAG, IFD0, EXIF, GPS"
26+
["COMPUTED"]=>
27+
array(4) {
28+
["IsColor"]=>
29+
int(0)
30+
["ByteOrderMotorola"]=>
31+
int(0)
32+
["UserComment"]=>
33+
string(17) "Created with GIMP"
34+
["UserCommentEncoding"]=>
35+
string(9) "UNDEFINED"
36+
}
37+
["ImageWidth"]=>
38+
int(100)
39+
["ImageLength"]=>
40+
int(100)
41+
["BitsPerSample"]=>
42+
array(3) {
43+
[0]=>
44+
int(8)
45+
[1]=>
46+
int(8)
47+
[2]=>
48+
int(8)
49+
}
50+
["ImageDescription"]=>
51+
string(17) "Created with GIMP"
52+
["XResolution"]=>
53+
string(5) "300/1"
54+
["YResolution"]=>
55+
string(5) "300/1"
56+
["ResolutionUnit"]=>
57+
int(2)
58+
["Software"]=>
59+
string(10) "GIMP 3.0.4"
60+
["DateTime"]=>
61+
string(19) "2025:09:21 15:30:30"
62+
["Exif_IFD_Pointer"]=>
63+
int(250)
64+
["GPS_IFD_Pointer"]=>
65+
int(430)
66+
["DateTimeOriginal"]=>
67+
string(19) "2025:09:21 15:29:27"
68+
["DateTimeDigitized"]=>
69+
string(19) "2025:09:21 15:29:27"
70+
["OffsetTime"]=>
71+
string(6) "+02:00"
72+
["OffsetTimeOriginal"]=>
73+
string(6) "+02:00"
74+
["OffsetTimeDigitized"]=>
75+
string(6) "+02:00"
76+
["UserComment"]=>
77+
string(25) "%sCreated with GIMP"
78+
["ColorSpace"]=>
79+
int(1)
80+
["GPSAltitude"]=>
81+
string(5) "0/100"
82+
}

ext/exif/tests/gh19904.webp

526 Bytes
Loading

0 commit comments

Comments
 (0)