@@ -711,6 +711,8 @@ PHPAPI const int php_tiff_bytes_per_format[] = {0, 1, 1, 2, 4, 8, 1, 1, 2, 4, 8,
711711/* compressed images only */
712712#define TAG_COMP_IMAGEWIDTH 0xA002
713713#define TAG_COMP_IMAGEHEIGHT 0xA003
714+ /* pointer to the Exif sub-IFD */
715+ #define TAG_EXIF_IFD_POINTER 0x8769
714716
715717#define TAG_FMT_BYTE 1
716718#define TAG_FMT_STRING 2
@@ -1171,6 +1173,369 @@ static void php_avif_stream_skip(void* stream, size_t num_bytes) {
11711173}
11721174/* }}} */
11731175
1176+ #define HEIF_META_MAX_SIZE (1 * 1024 * 1024)
1177+ #define HEIF_EXIF_MAX_SIZE (128 * 1024)
1178+
1179+ /* {{{ php_heif_buf_get */
1180+ static uint64_t php_heif_buf_get (const unsigned char * buf , unsigned int size )
1181+ {
1182+ uint64_t value = 0 ;
1183+ while (size -- > 0 ) {
1184+ value = (value << 8 ) | * buf ++ ;
1185+ }
1186+ return value ;
1187+ }
1188+ /* }}} */
1189+
1190+ /* {{{ php_heif_exif_ifd_dims */
1191+ static bool php_heif_exif_ifd_dims (const unsigned char * tiff , size_t tiff_len ,
1192+ int motorola , uint32_t ifd_off , size_t * width , size_t * height ,
1193+ uint32_t * sub_ifd )
1194+ {
1195+ if (ifd_off > tiff_len || tiff_len - ifd_off < 2 ) {
1196+ return false;
1197+ }
1198+ unsigned int num_entries = php_ifd_get16u ((void * ) (tiff + ifd_off ), motorola );
1199+ if ((size_t ) num_entries > (tiff_len - ifd_off - 2 ) / 12 ) {
1200+ return false;
1201+ }
1202+ for (unsigned int i = 0 ; i < num_entries ; i ++ ) {
1203+ const unsigned char * entry = tiff + ifd_off + 2 + i * 12 ;
1204+ unsigned int tag = php_ifd_get16u ((void * ) entry , motorola );
1205+ unsigned int type = php_ifd_get16u ((void * ) (entry + 2 ), motorola );
1206+ size_t value ;
1207+
1208+ switch (type ) {
1209+ case TAG_FMT_BYTE :
1210+ value = entry [8 ];
1211+ break ;
1212+ case TAG_FMT_USHORT :
1213+ value = php_ifd_get16u ((void * ) (entry + 8 ), motorola );
1214+ break ;
1215+ case TAG_FMT_ULONG :
1216+ value = php_ifd_get32u ((void * ) (entry + 8 ), motorola );
1217+ break ;
1218+ default :
1219+ continue ;
1220+ }
1221+ switch (tag ) {
1222+ case TAG_IMAGEWIDTH :
1223+ case TAG_COMP_IMAGEWIDTH :
1224+ * width = value ;
1225+ break ;
1226+ case TAG_IMAGEHEIGHT :
1227+ case TAG_COMP_IMAGEHEIGHT :
1228+ * height = value ;
1229+ break ;
1230+ case TAG_EXIF_IFD_POINTER :
1231+ * sub_ifd = (uint32_t ) value ;
1232+ break ;
1233+ }
1234+ }
1235+ return true;
1236+ }
1237+ /* }}} */
1238+
1239+ /* {{{ php_heif_exif_tiff_dims */
1240+ static bool php_heif_exif_tiff_dims (const unsigned char * tiff , size_t tiff_len ,
1241+ size_t * out_w , size_t * out_h )
1242+ {
1243+ int motorola ;
1244+ if (tiff_len < 8 ) {
1245+ return false;
1246+ }
1247+ if (!memcmp (tiff , "MM" , 2 )) {
1248+ motorola = 1 ;
1249+ } else if (!memcmp (tiff , "II" , 2 )) {
1250+ motorola = 0 ;
1251+ } else {
1252+ return false;
1253+ }
1254+
1255+ uint32_t ifd0 = php_ifd_get32u ((void * ) (tiff + 4 ), motorola );
1256+ size_t w0 = 0 , h0 = 0 ;
1257+ uint32_t sub_ifd = 0 ;
1258+ if (!php_heif_exif_ifd_dims (tiff , tiff_len , motorola , ifd0 , & w0 , & h0 , & sub_ifd )) {
1259+ return false;
1260+ }
1261+
1262+ size_t ws = 0 , hs = 0 ;
1263+ if (sub_ifd != 0 ) {
1264+ uint32_t ignore = 0 ;
1265+ php_heif_exif_ifd_dims (tiff , tiff_len , motorola , sub_ifd , & ws , & hs , & ignore );
1266+ }
1267+
1268+ size_t w = ws ? ws : w0 ;
1269+ size_t h = hs ? hs : h0 ;
1270+ if (w == 0 || h == 0 ) {
1271+ return false;
1272+ }
1273+ * out_w = w ;
1274+ * out_h = h ;
1275+ return true;
1276+ }
1277+ /* }}} */
1278+
1279+ /* {{{ php_heif_find_exif_item */
1280+ static bool php_heif_find_exif_item (const unsigned char * meta , size_t meta_len ,
1281+ uint64_t * file_off , uint64_t * length )
1282+ {
1283+ uint32_t exif_id = 0 ;
1284+ bool have_exif_id = false;
1285+ size_t p = 0 ;
1286+
1287+ const unsigned char * iinf = NULL , * iloc = NULL ;
1288+ size_t iinf_len = 0 , iloc_len = 0 ;
1289+ while (p + 8 <= meta_len ) {
1290+ uint64_t box_size = php_heif_buf_get (meta + p , 4 );
1291+ size_t hdr = 8 ;
1292+ if (box_size == 1 ) {
1293+ if (p + 16 > meta_len ) {
1294+ break ;
1295+ }
1296+ box_size = php_heif_buf_get (meta + p + 8 , 8 );
1297+ hdr = 16 ;
1298+ } else if (box_size == 0 ) {
1299+ box_size = meta_len - p ;
1300+ }
1301+ if (box_size < hdr || box_size > meta_len - p ) {
1302+ break ;
1303+ }
1304+ if (!memcmp (meta + p + 4 , "iinf" , 4 )) {
1305+ iinf = meta + p + hdr ;
1306+ iinf_len = (size_t ) box_size - hdr ;
1307+ } else if (!memcmp (meta + p + 4 , "iloc" , 4 )) {
1308+ iloc = meta + p + hdr ;
1309+ iloc_len = (size_t ) box_size - hdr ;
1310+ }
1311+ p += (size_t ) box_size ;
1312+ }
1313+ if (iinf == NULL || iloc == NULL ) {
1314+ return false;
1315+ }
1316+
1317+ if (iinf_len < 6 ) {
1318+ return false;
1319+ }
1320+ unsigned int iinf_ver = iinf [0 ];
1321+ size_t q = 4 ;
1322+ unsigned int count_bytes = (iinf_ver == 0 ) ? 2 : 4 ;
1323+ if (q + count_bytes > iinf_len ) {
1324+ return false;
1325+ }
1326+ uint32_t entry_count = (uint32_t ) php_heif_buf_get (iinf + q , count_bytes );
1327+ q += count_bytes ;
1328+ for (uint32_t i = 0 ; i < entry_count && q + 8 <= iinf_len ; i ++ ) {
1329+ uint64_t isz = php_heif_buf_get (iinf + q , 4 );
1330+ if (isz < 12 || isz > iinf_len - q ) {
1331+ break ;
1332+ }
1333+ if (!memcmp (iinf + q + 4 , "infe" , 4 )) {
1334+ unsigned int infe_ver = iinf [q + 8 ];
1335+ if (infe_ver == 2 && isz >= 20 ) {
1336+ uint32_t iid = (uint32_t ) php_heif_buf_get (iinf + q + 12 , 2 );
1337+ if (!memcmp (iinf + q + 16 , "Exif" , 4 )) {
1338+ exif_id = iid ;
1339+ have_exif_id = true;
1340+ }
1341+ } else if (infe_ver >= 3 && isz >= 24 ) {
1342+ uint32_t iid = (uint32_t ) php_heif_buf_get (iinf + q + 12 , 4 );
1343+ if (!memcmp (iinf + q + 18 , "Exif" , 4 )) {
1344+ exif_id = iid ;
1345+ have_exif_id = true;
1346+ }
1347+ }
1348+ }
1349+ q += (size_t ) isz ;
1350+ }
1351+ if (!have_exif_id ) {
1352+ return false;
1353+ }
1354+
1355+ if (iloc_len < 8 ) {
1356+ return false;
1357+ }
1358+ unsigned int iloc_ver = iloc [0 ];
1359+ size_t r = 4 ;
1360+ unsigned int offset_size = (iloc [r ] >> 4 ) & 0xf ;
1361+ unsigned int length_size = iloc [r ] & 0xf ;
1362+ unsigned int base_offset_size = (iloc [r + 1 ] >> 4 ) & 0xf ;
1363+ unsigned int index_size = (iloc_ver == 1 || iloc_ver == 2 ) ? (iloc [r + 1 ] & 0xf ) : 0 ;
1364+ r += 2 ;
1365+ unsigned int item_count_bytes = (iloc_ver < 2 ) ? 2 : 4 ;
1366+ if (r + item_count_bytes > iloc_len ) {
1367+ return false;
1368+ }
1369+ uint32_t item_count = (uint32_t ) php_heif_buf_get (iloc + r , item_count_bytes );
1370+ r += item_count_bytes ;
1371+ unsigned int id_bytes = (iloc_ver < 2 ) ? 2 : 4 ;
1372+
1373+ for (uint32_t i = 0 ; i < item_count ; i ++ ) {
1374+ if (r + id_bytes > iloc_len ) {
1375+ return false;
1376+ }
1377+ uint32_t item_id = (uint32_t ) php_heif_buf_get (iloc + r , id_bytes );
1378+ r += id_bytes ;
1379+ unsigned int construction_method = 0 ;
1380+ if (iloc_ver == 1 || iloc_ver == 2 ) {
1381+ if (r + 2 > iloc_len ) {
1382+ return false;
1383+ }
1384+ construction_method = php_heif_buf_get (iloc + r , 2 ) & 0xf ;
1385+ r += 2 ;
1386+ }
1387+ if (r + 2 > iloc_len ) {
1388+ return false;
1389+ }
1390+ r += 2 ;
1391+ if (r + base_offset_size > iloc_len ) {
1392+ return false;
1393+ }
1394+ uint64_t base_offset = php_heif_buf_get (iloc + r , base_offset_size );
1395+ r += base_offset_size ;
1396+ if (r + 2 > iloc_len ) {
1397+ return false;
1398+ }
1399+ uint32_t extent_count = (uint32_t ) php_heif_buf_get (iloc + r , 2 );
1400+ r += 2 ;
1401+ for (uint32_t e = 0 ; e < extent_count ; e ++ ) {
1402+ if (index_size ) {
1403+ if (r + index_size > iloc_len ) {
1404+ return false;
1405+ }
1406+ r += index_size ;
1407+ }
1408+ if (r + offset_size + length_size > iloc_len ) {
1409+ return false;
1410+ }
1411+ uint64_t ext_off = php_heif_buf_get (iloc + r , offset_size );
1412+ r += offset_size ;
1413+ uint64_t ext_len = php_heif_buf_get (iloc + r , length_size );
1414+ r += length_size ;
1415+ if (item_id == exif_id && construction_method == 0 && e == 0 ) {
1416+ if (ext_off > UINT64_MAX - base_offset ) {
1417+ return false;
1418+ }
1419+ * file_off = base_offset + ext_off ;
1420+ * length = ext_len ;
1421+ return true;
1422+ }
1423+ }
1424+ }
1425+ return false;
1426+ }
1427+ /* }}} */
1428+
1429+ /* {{{ php_handle_heif_exif */
1430+ static struct php_gfxinfo * php_handle_heif_exif (php_stream * stream )
1431+ {
1432+ struct php_gfxinfo * result = NULL ;
1433+ unsigned char header [8 ];
1434+
1435+ if (php_stream_rewind (stream )) {
1436+ return NULL ;
1437+ }
1438+
1439+ uint64_t meta_hdr = 0 , meta_size = 0 ;
1440+ for (;;) {
1441+ if (php_stream_read (stream , (char * ) header , 8 ) != 8 ) {
1442+ return NULL ;
1443+ }
1444+ uint64_t box_size = php_heif_buf_get (header , 4 );
1445+ uint64_t hdr = 8 ;
1446+ if (box_size == 1 ) {
1447+ unsigned char ext [8 ];
1448+ if (php_stream_read (stream , (char * ) ext , 8 ) != 8 ) {
1449+ return NULL ;
1450+ }
1451+ box_size = php_heif_buf_get (ext , 8 );
1452+ hdr = 16 ;
1453+ } else if (box_size == 0 ) {
1454+ zend_off_t cur = php_stream_tell (stream );
1455+ if (cur < 0 || php_stream_seek (stream , 0 , SEEK_END )) {
1456+ return NULL ;
1457+ }
1458+ zend_off_t end = php_stream_tell (stream );
1459+ if (end < cur || php_stream_seek (stream , cur , SEEK_SET )) {
1460+ return NULL ;
1461+ }
1462+ box_size = (uint64_t ) (end - cur ) + hdr ;
1463+ }
1464+ if (box_size < hdr ) {
1465+ return NULL ;
1466+ }
1467+ if (!memcmp (header + 4 , "meta" , 4 )) {
1468+ meta_hdr = hdr ;
1469+ meta_size = box_size ;
1470+ break ;
1471+ }
1472+ if (php_stream_seek (stream , (zend_off_t ) (box_size - hdr ), SEEK_CUR )) {
1473+ return NULL ;
1474+ }
1475+ }
1476+
1477+ if (meta_size <= meta_hdr + 4 ) {
1478+ return NULL ;
1479+ }
1480+ uint64_t meta_body_len = meta_size - meta_hdr - 4 ;
1481+ if (meta_body_len == 0 || meta_body_len > HEIF_META_MAX_SIZE ) {
1482+ return NULL ;
1483+ }
1484+ if (php_stream_seek (stream , 4 , SEEK_CUR )) {
1485+ return NULL ;
1486+ }
1487+
1488+ unsigned char * meta = emalloc ((size_t ) meta_body_len );
1489+ if (php_stream_read (stream , (char * ) meta , (size_t ) meta_body_len ) != (ssize_t ) meta_body_len ) {
1490+ efree (meta );
1491+ return NULL ;
1492+ }
1493+
1494+ uint64_t exif_off = 0 , exif_len = 0 ;
1495+ bool found = php_heif_find_exif_item (meta , (size_t ) meta_body_len , & exif_off , & exif_len );
1496+ efree (meta );
1497+ if (!found || exif_len < 4 ) {
1498+ return NULL ;
1499+ }
1500+
1501+ if (php_stream_seek (stream , (zend_off_t ) exif_off , SEEK_SET )) {
1502+ return NULL ;
1503+ }
1504+ unsigned char prefix [4 ];
1505+ if (php_stream_read (stream , (char * ) prefix , 4 ) != 4 ) {
1506+ return NULL ;
1507+ }
1508+ uint64_t tiff_skip = php_heif_buf_get (prefix , 4 );
1509+ if (tiff_skip > exif_len - 4 ) {
1510+ return NULL ;
1511+ }
1512+ uint64_t tiff_len = exif_len - 4 - tiff_skip ;
1513+ if (tiff_len < 8 ) {
1514+ return NULL ;
1515+ }
1516+ if (tiff_len > HEIF_EXIF_MAX_SIZE ) {
1517+ tiff_len = HEIF_EXIF_MAX_SIZE ;
1518+ }
1519+ if (tiff_skip && php_stream_seek (stream , (zend_off_t ) tiff_skip , SEEK_CUR )) {
1520+ return NULL ;
1521+ }
1522+ unsigned char * tiff = emalloc ((size_t ) tiff_len );
1523+ if (php_stream_read (stream , (char * ) tiff , (size_t ) tiff_len ) != (ssize_t ) tiff_len ) {
1524+ efree (tiff );
1525+ return NULL ;
1526+ }
1527+
1528+ size_t w = 0 , h = 0 ;
1529+ if (php_heif_exif_tiff_dims (tiff , (size_t ) tiff_len , & w , & h )) {
1530+ result = ecalloc (1 , sizeof (struct php_gfxinfo ));
1531+ result -> width = (unsigned int ) w ;
1532+ result -> height = (unsigned int ) h ;
1533+ }
1534+ efree (tiff );
1535+ return result ;
1536+ }
1537+ /* }}} */
1538+
11741539/* {{{ php_handle_avif
11751540 * Parse AVIF features
11761541 *
@@ -1192,6 +1557,8 @@ static struct php_gfxinfo *php_handle_avif(php_stream * stream) {
11921557 result -> height = features .height ;
11931558 result -> bits = features .bit_depth ;
11941559 result -> channels = features .num_channels ;
1560+ } else {
1561+ result = php_handle_heif_exif (stream );
11951562 }
11961563 return result ;
11971564}
0 commit comments