Skip to content

Commit ba889cb

Browse files
committed
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
1 parent b5c17e7 commit ba889cb

3 files changed

Lines changed: 141 additions & 1 deletion

File tree

ext/exif/exif.c

Lines changed: 59 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,53 @@ 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+
static const uchar ExifHeader[] = {0x45, 0x78, 0x69, 0x66, 0x00, 0x00};
4451+
unsigned char chunk_header[8];
4452+
size_t offset = 12;
4453+
size_t riff_end = riff_size <= ImageInfo->FileSize - 8 ? riff_size + 8 : ImageInfo->FileSize;
4454+
4455+
while (offset + 8 <= riff_end) {
4456+
if ((php_stream_seek(ImageInfo->infile, offset, SEEK_SET) < 0) ||
4457+
(exif_read_from_stream_file_looped(ImageInfo->infile, (char*)chunk_header, 8) != 8)) {
4458+
return false;
4459+
}
4460+
4461+
size_t chunk_size = php_ifd_get32u(chunk_header + 4, 0);
4462+
size_t payload_offset = offset + 8;
4463+
4464+
if (chunk_size > riff_end - payload_offset) {
4465+
return false;
4466+
}
4467+
4468+
if (!memcmp(chunk_header, "EXIF", 4)) {
4469+
char *data;
4470+
size_t skip = 0;
4471+
bool ret = false;
4472+
4473+
if (chunk_size < 8) {
4474+
return false;
4475+
}
4476+
4477+
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+
offset = payload_offset + chunk_size + (chunk_size & 1);
4490+
}
4491+
4492+
return false;
4493+
}
4494+
44484495
/* {{{ exif_scan_FILE_header
44494496
* Parse the marker stream until SOS or EOI is seen; */
44504497
static bool exif_scan_FILE_header(image_info_type *ImageInfo)
@@ -4521,6 +4568,17 @@ static bool exif_scan_FILE_header(image_info_type *ImageInfo)
45214568
exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "Invalid HEIF file");
45224569
return false;
45234570
}
4571+
} else if ((ImageInfo->FileSize >= 16) &&
4572+
(!memcmp(file_header, "RIFF", 4)) &&
4573+
(exif_read_from_stream_file_looped(ImageInfo->infile, (char*)(file_header + 8), 4) == 4) &&
4574+
(!memcmp(file_header + 8, "WEBP", 4))) {
4575+
if (exif_scan_WEBP_header(ImageInfo, php_ifd_get32u(file_header + 4, 0))) {
4576+
ImageInfo->FileType = IMAGE_FILETYPE_WEBP;
4577+
return true;
4578+
} else {
4579+
exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "Invalid WebP file");
4580+
return false;
4581+
}
45244582
} else {
45254583
exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "File not supported");
45264584
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)