diff -ruN a/ext/exif/exif.c b/ext/exif/exif.c --- a/ext/exif/exif.c 2024-01-16 20:19:32.000000000 +0800 +++ b/ext/exif/exif.c 2024-02-22 10:47:05.653710321 +0800 @@ -1296,6 +1296,18 @@ mn_offset_mode_t offset_mode; } maker_note_type; +#define FOURCC(id) (((uint32_t)(id[0])<<24) | (id[1]<<16) | (id[2]<<8) | (id[3])) + +typedef struct { + uint64_t size; + uint32_t type; +} isobmff_box_type; + +typedef struct { + uint32_t offset; + uint32_t size; +} isobmff_item_pos_type; + /* Some maker notes (e.g. DJI info tag) require custom parsing */ #define REQUIRES_CUSTOM_PARSING NULL @@ -4305,11 +4317,125 @@ return result; } +static int exif_isobmff_parse_box(unsigned char *buf, isobmff_box_type *box) +{ + box->size = php_ifd_get32u(buf, 1); + buf += 4; + box->type = php_ifd_get32u(buf, 1); + if (box->size != 1) { + return 8; + } + buf += 4; + box->size = php_ifd_get64u(buf, 1); + return 16; +} + +static void exif_isobmff_parse_meta(unsigned char *data, unsigned char *end, isobmff_item_pos_type *pos) +{ + isobmff_box_type box, item; + unsigned char *box_offset, *p, *p2; + int header_size, exif_id = -1, version, item_count, i; + + for (box_offset = data + 4; box_offset < end; box_offset += box.size) { + header_size = exif_isobmff_parse_box(box_offset, &box); + if (box.type == FOURCC("iinf")) { + p = box_offset + header_size; + version = p[0]; + p += 4; + if (version < 2) { + item_count = php_ifd_get16u(p, 1); + p += 2; + } else { + item_count = php_ifd_get32u(p, 1); + p += 4; + } + for (i=0; ioffset = php_ifd_get32u(p2 + 8, 1); + pos->size = php_ifd_get32u(p2 + 12, 1); + break; + } + } + break; + } + } +} + +static bool exif_scan_HEIF_header(image_info_type *ImageInfo, unsigned char *buf) +{ + isobmff_box_type box; + isobmff_item_pos_type pos; + unsigned char *data; + off_t offset; + uint64_t limit; + int box_header_size, remain; + bool ret = false; + + pos.size = 0; + for (offset = php_ifd_get32u(buf, 1); ImageInfo->FileSize > offset + 16; offset += box.size) { + if ((php_stream_seek(ImageInfo->infile, offset, SEEK_SET) < 0) || + (exif_read_from_stream_file_looped(ImageInfo->infile, (char*)buf, 16) != 16)) { + break; + } + box_header_size = exif_isobmff_parse_box(buf, &box); + if (box.type == FOURCC("meta")) { + limit = box.size - box_header_size; + data = (unsigned char *)safe_emalloc(1, limit, 0); + remain = 16 - box_header_size; + if (remain) { + memcpy(data, buf + box_header_size, remain); + } + if (exif_read_from_stream_file_looped(ImageInfo->infile, (char*)(data + remain), limit - remain) == limit - remain) { + exif_isobmff_parse_meta(data, data + limit, &pos); + } + efree(data); + if ((pos.size) && + (ImageInfo->FileSize >= pos.offset + pos.size) && + (php_stream_seek(ImageInfo->infile, pos.offset + 2, SEEK_SET) >= 0)) { + limit = pos.size - 2; + data = (unsigned char *)safe_emalloc(1, limit, 0); + if (exif_read_from_stream_file_looped(ImageInfo->infile, (char*)data, limit) == limit) { + exif_process_APP1(ImageInfo, (char*)data, limit, pos.offset + 2); + ret = true; + } + efree(data); + } + break; + } + } + + return ret; +} + /* {{{ exif_scan_FILE_header * Parse the marker stream until SOS or EOI is seen; */ static bool exif_scan_FILE_header(image_info_type *ImageInfo) { - unsigned char file_header[8]; + unsigned char file_header[16]; bool ret = false; ImageInfo->FileType = IMAGE_FILETYPE_UNKNOWN; @@ -4358,6 +4484,16 @@ } else { exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "Invalid TIFF file"); } + } else if ((ImageInfo->FileSize > 12) && + (!memcmp(file_header + 4, "ftyp", 4)) && + (exif_read_from_stream_file_looped(ImageInfo->infile, (char*)(file_header + 8), 4) == 4) && + ((!memcmp(file_header + 8, "heic", 4)) || (!memcmp(file_header + 8, "heix", 4)) || (!memcmp(file_header + 8, "mif1", 4)))) { + if (exif_scan_HEIF_header(ImageInfo, file_header)) { + ImageInfo->FileType = IMAGE_FILETYPE_HEIF; + ret = true; + } else { + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "Invalid HEIF file"); + } } else { exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "File not supported"); return false; diff -ruN a/ext/standard/basic_functions.stub.php b/ext/standard/basic_functions.stub.php --- a/ext/standard/basic_functions.stub.php 2024-01-16 20:19:32.000000000 +0800 +++ b/ext/standard/basic_functions.stub.php 2024-02-22 10:47:05.653710321 +0800 @@ -532,6 +532,11 @@ const IMAGETYPE_AVIF = UNKNOWN; /** * @var int + * @cvalue IMAGE_FILETYPE_HEIF + */ +const IMAGETYPE_HEIF = UNKNOWN; +/** + * @var int * @cvalue IMAGE_FILETYPE_UNKNOWN */ const IMAGETYPE_UNKNOWN = UNKNOWN; diff -ruN a/ext/standard/image.c b/ext/standard/image.c --- a/ext/standard/image.c 2024-01-16 20:19:32.000000000 +0800 +++ b/ext/standard/image.c 2024-02-22 10:47:05.663710399 +0800 @@ -51,6 +51,10 @@ PHPAPI const char php_sig_ico[4] = {(char)0x00, (char)0x00, (char)0x01, (char)0x00}; PHPAPI const char php_sig_riff[4] = {'R', 'I', 'F', 'F'}; PHPAPI const char php_sig_webp[4] = {'W', 'E', 'B', 'P'}; +PHPAPI const char php_sig_ftyp[4] = {'f', 't', 'y', 'p'}; +PHPAPI const char php_sig_mif1[4] = {'m', 'i', 'f', '1'}; +PHPAPI const char php_sig_heic[4] = {'h', 'e', 'i', 'c'}; +PHPAPI const char php_sig_heix[4] = {'h', 'e', 'i', 'x'}; /* REMEMBER TO ADD MIME-TYPE TO FUNCTION php_image_type_to_mime_type */ /* PCX must check first 64bytes and byte 0=0x0a and byte2 < 0x06 */ @@ -1249,6 +1253,8 @@ return "image/webp"; case IMAGE_FILETYPE_AVIF: return "image/avif"; + case IMAGE_FILETYPE_HEIF: + return "image/heif"; default: case IMAGE_FILETYPE_UNKNOWN: return "application/octet-stream"; /* suppose binary format */ @@ -1334,6 +1340,10 @@ case IMAGE_FILETYPE_AVIF: imgext = ".avif"; break; + case IMAGE_FILETYPE_HEIF: + imgext = ".heif"; + break; + break; } if (imgext) { @@ -1418,6 +1428,11 @@ return IMAGE_FILETYPE_JP2; } + if (twelve_bytes_read && !memcmp(filetype + 4, php_sig_ftyp, 4) && + (!memcmp(filetype + 8, php_sig_mif1, 4) || !memcmp(filetype + 8, php_sig_heic, 4) || !memcmp(filetype + 8, php_sig_heix, 4))) { + return IMAGE_FILETYPE_HEIF; + } + if (!php_stream_rewind(stream) && php_is_image_avif(stream)) { return IMAGE_FILETYPE_AVIF; } @@ -1510,6 +1525,10 @@ case IMAGE_FILETYPE_AVIF: result = php_handle_avif(stream); break; + case IMAGE_FILETYPE_HEIF: + php_stream_rewind(stream); + result = php_handle_avif(stream); + break; default: case IMAGE_FILETYPE_UNKNOWN: break; diff -ruN a/ext/standard/php_image.h b/ext/standard/php_image.h --- a/ext/standard/php_image.h 2024-01-16 20:19:32.000000000 +0800 +++ b/ext/standard/php_image.h 2024-02-22 10:47:05.663710399 +0800 @@ -44,6 +44,7 @@ IMAGE_FILETYPE_ICO, IMAGE_FILETYPE_WEBP, IMAGE_FILETYPE_AVIF, + IMAGE_FILETYPE_HEIF, /* WHEN EXTENDING: PLEASE ALSO REGISTER IN basic_function.stub.php */ IMAGE_FILETYPE_COUNT } image_filetype; diff -ruN a/ext/standard/tests/image/getimagesize.phpt b/ext/standard/tests/image/getimagesize.phpt --- a/ext/standard/tests/image/getimagesize.phpt 2024-01-16 20:19:32.000000000 +0800 +++ b/ext/standard/tests/image/getimagesize.phpt 2024-02-22 10:47:05.663710399 +0800 @@ -23,7 +23,7 @@ var_dump($result); ?> --EXPECT-- -array(17) { +array(18) { ["test-1pix.bmp"]=> array(6) { [0]=> @@ -216,6 +216,23 @@ ["mime"]=> string(9) "image/gif" } + ["test4pix.heic"]=> + array(7) { + [0]=> + int(924) + [1]=> + int(941) + [2]=> + int(20) + [3]=> + string(24) "width="924" height="941"" + ["bits"]=> + int(8) + ["channels"]=> + int(3) + ["mime"]=> + string(10) "image/heif" + } ["test4pix.iff"]=> array(6) { [0]=> diff -ruN a/ext/standard/tests/image/image_type_to_mime_type_basic.phpt b/ext/standard/tests/image/image_type_to_mime_type_basic.phpt --- a/ext/standard/tests/image/image_type_to_mime_type_basic.phpt 2024-01-16 20:19:32.000000000 +0800 +++ b/ext/standard/tests/image/image_type_to_mime_type_basic.phpt 2024-02-22 10:47:05.663710399 +0800 @@ -21,6 +21,7 @@ IMAGETYPE_IFF, IMAGETYPE_WBMP, IMAGETYPE_JPEG2000, + IMAGETYPE_HEIF, IMAGETYPE_XBM, IMAGETYPE_WEBP ); @@ -49,6 +50,7 @@ string(9) "image/iff" string(18) "image/vnd.wap.wbmp" string(24) "application/octet-stream" +string(10) "image/heif" string(9) "image/xbm" string(10) "image/webp" diff -ruN a/ext/standard/tests/image/image_type_to_mime_type.phpt b/ext/standard/tests/image/image_type_to_mime_type.phpt --- a/ext/standard/tests/image/image_type_to_mime_type.phpt 2024-01-16 20:19:32.000000000 +0800 +++ b/ext/standard/tests/image/image_type_to_mime_type.phpt 2024-02-22 10:47:05.663710399 +0800 @@ -24,7 +24,7 @@ var_dump($result); ?> --EXPECT-- -array(17) { +array(18) { ["test-1pix.bmp"]=> string(9) "image/bmp" ["test12pix.webp"]=> @@ -49,6 +49,8 @@ string(10) "image/webp" ["test4pix.gif"]=> string(9) "image/gif" + ["test4pix.heic"]=> + string(10) "image/heif" ["test4pix.iff"]=> string(9) "image/iff" ["test4pix.png"]=> diff -ruN a/ext/standard/tests/image/image_type_to_mime_type_variation3.phpt b/ext/standard/tests/image/image_type_to_mime_type_variation3.phpt --- a/ext/standard/tests/image/image_type_to_mime_type_variation3.phpt 2024-01-16 20:19:32.000000000 +0800 +++ b/ext/standard/tests/image/image_type_to_mime_type_variation3.phpt 2024-02-22 10:47:05.663710399 +0800 @@ -75,4 +75,7 @@ string\(10\) "image\/avif" -- Iteration 20 -- +string\(10\) "image\/heif" + +-- Iteration 21 -- string\(24\) "application\/octet-stream"