Skip to content

Commit cee33ba

Browse files
morssssscmb69
authored andcommitted
AVIF support for getimagesize() and imagecreatefromstring()
Thanks to Joe Drago for help with the AVIF detection code. Co-authored-by: Nikita Popov <nikita.ppv@googlemail.com> Co-authored-by: Christoph M. Becker <cmbecker69@gmx.de> Closes GH-7091.
1 parent a5360e8 commit cee33ba

13 files changed

+208
-38
lines changed

ext/gd/gd.c

Lines changed: 37 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,10 @@
3131
#include <math.h>
3232
#include "SAPI.h"
3333
#include "php_gd.h"
34+
#include "ext/standard/php_image.h"
3435
#include "ext/standard/info.h"
3536
#include "php_open_temporary_file.h"
37+
#include "php_memory_streams.h"
3638
#include "zend_object_handlers.h"
3739
#include "zend_interfaces.h"
3840

@@ -145,7 +147,7 @@ static void _php_image_output(INTERNAL_FUNCTION_PARAMETERS, int image_type, char
145147
static gdIOCtx *create_stream_context_from_zval(zval *to_zval);
146148
static gdIOCtx *create_stream_context(php_stream *stream, int close_stream);
147149
static gdIOCtx *create_output_context(void);
148-
static int _php_image_type(char data[12]);
150+
static int _php_image_type(zend_string *data);
149151

150152
/* output streaming (formerly gd_ctx.c) */
151153
static void _php_image_output_ctx(INTERNAL_FUNCTION_PARAMETERS, int image_type, char *tn, void (*func_p)());
@@ -1469,42 +1471,54 @@ static int _php_ctx_getmbi(gdIOCtx *ctx)
14691471
}
14701472
/* }}} */
14711473

1472-
/* {{{ _php_image_type */
1474+
/* {{{ _php_image_type
1475+
* Based on ext/standard/image.c
1476+
*/
14731477
static const char php_sig_gd2[3] = {'g', 'd', '2'};
14741478

1475-
static int _php_image_type (char data[12])
1479+
static int _php_image_type(zend_string *data)
14761480
{
1477-
/* Based on ext/standard/image.c */
1478-
1479-
if (data == NULL) {
1481+
if (ZSTR_LEN(data) < 12) {
1482+
/* Handle this the same way as an unknown image type. */
14801483
return -1;
14811484
}
14821485

1483-
if (!memcmp(data, php_sig_gd2, sizeof(php_sig_gd2))) {
1486+
if (!memcmp(ZSTR_VAL(data), php_sig_gd2, sizeof(php_sig_gd2))) {
14841487
return PHP_GDIMG_TYPE_GD2;
1485-
} else if (!memcmp(data, php_sig_jpg, sizeof(php_sig_jpg))) {
1488+
} else if (!memcmp(ZSTR_VAL(data), php_sig_jpg, sizeof(php_sig_jpg))) {
14861489
return PHP_GDIMG_TYPE_JPG;
1487-
} else if (!memcmp(data, php_sig_png, sizeof(php_sig_png))) {
1490+
} else if (!memcmp(ZSTR_VAL(data), php_sig_png, sizeof(php_sig_png))) {
14881491
return PHP_GDIMG_TYPE_PNG;
1489-
} else if (!memcmp(data, php_sig_gif, sizeof(php_sig_gif))) {
1492+
} else if (!memcmp(ZSTR_VAL(data), php_sig_gif, sizeof(php_sig_gif))) {
14901493
return PHP_GDIMG_TYPE_GIF;
1491-
} else if (!memcmp(data, php_sig_bmp, sizeof(php_sig_bmp))) {
1494+
} else if (!memcmp(ZSTR_VAL(data), php_sig_bmp, sizeof(php_sig_bmp))) {
14921495
return PHP_GDIMG_TYPE_BMP;
1493-
} else if(!memcmp(data, php_sig_riff, sizeof(php_sig_riff)) && !memcmp(data + sizeof(php_sig_riff) + sizeof(uint32_t), php_sig_webp, sizeof(php_sig_webp))) {
1496+
} else if(!memcmp(ZSTR_VAL(data), php_sig_riff, sizeof(php_sig_riff)) && !memcmp(ZSTR_VAL(data) + sizeof(php_sig_riff) + sizeof(uint32_t), php_sig_webp, sizeof(php_sig_webp))) {
14941497
return PHP_GDIMG_TYPE_WEBP;
14951498
}
1496-
else {
1497-
gdIOCtx *io_ctx;
1498-
io_ctx = gdNewDynamicCtxEx(8, data, 0);
1499-
if (io_ctx) {
1500-
if (_php_ctx_getmbi(io_ctx) == 0 && _php_ctx_getmbi(io_ctx) >= 0) {
1501-
io_ctx->gd_free(io_ctx);
1502-
return PHP_GDIMG_TYPE_WBM;
1503-
} else {
1504-
io_ctx->gd_free(io_ctx);
1505-
}
1499+
1500+
php_stream *image_stream = php_stream_memory_open(TEMP_STREAM_READONLY, data);
1501+
1502+
if (image_stream != NULL) {
1503+
bool is_avif = php_is_image_avif(image_stream);
1504+
php_stream_close(image_stream);
1505+
1506+
if (is_avif) {
1507+
return PHP_GDIMG_TYPE_AVIF;
15061508
}
15071509
}
1510+
1511+
gdIOCtx *io_ctx;
1512+
io_ctx = gdNewDynamicCtxEx(8, ZSTR_VAL(data), 0);
1513+
if (io_ctx) {
1514+
if (_php_ctx_getmbi(io_ctx) == 0 && _php_ctx_getmbi(io_ctx) >= 0) {
1515+
io_ctx->gd_free(io_ctx);
1516+
return PHP_GDIMG_TYPE_WBM;
1517+
} else {
1518+
io_ctx->gd_free(io_ctx);
1519+
}
1520+
}
1521+
15081522
return -1;
15091523
}
15101524
/* }}} */
@@ -1540,21 +1554,12 @@ PHP_FUNCTION(imagecreatefromstring)
15401554
zend_string *data;
15411555
gdImagePtr im;
15421556
int imtype;
1543-
char sig[12];
15441557

15451558
if (zend_parse_parameters(ZEND_NUM_ARGS(), "S", &data) == FAILURE) {
15461559
RETURN_THROWS();
15471560
}
15481561

1549-
if (ZSTR_LEN(data) < sizeof(sig)) {
1550-
/* Handle this the same way as an unknown image type. */
1551-
php_error_docref(NULL, E_WARNING, "Data is not in a recognized format");
1552-
RETURN_FALSE;
1553-
}
1554-
1555-
memcpy(sig, ZSTR_VAL(data), sizeof(sig));
1556-
1557-
imtype = _php_image_type(sig);
1562+
imtype = _php_image_type(data);
15581563

15591564
switch (imtype) {
15601565
case PHP_GDIMG_TYPE_JPG:

ext/gd/libgd/gd.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -628,7 +628,6 @@ gdImagePtr gdImageCreateFromGif(FILE *fd);
628628
gdImagePtr gdImageCreateFromGifCtx(gdIOCtxPtr in);
629629
gdImagePtr gdImageCreateFromGifSource(gdSourcePtr in);
630630

631-
//TODO: we may not need all of these
632631
void gdImageAvif(gdImagePtr im, FILE *outfile);
633632
void gdImageAvifEx(gdImagePtr im, FILE *outfile, int quality, int speed);
634633
void *gdImageAvifPtr(gdImagePtr im, int *size);

ext/gd/php_gd.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@
1818
#ifndef PHP_GD_H
1919
#define PHP_GD_H
2020

21+
#include "zend_string.h"
22+
#include "php_streams.h"
23+
2124
#if defined(HAVE_LIBGD) || defined(HAVE_GD_BUNDLED)
2225

2326
/* open_basedir and safe_mode checks */
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
--TEST--
2+
imagecreatefromstring() - AVIF format
3+
--EXTENSIONS--
4+
gd
5+
--SKIPIF--
6+
<?php
7+
if (!(imagetypes() & IMG_AVIF)) {
8+
die('skip AVIF support required');
9+
}
10+
?>
11+
--FILE--
12+
<?php
13+
echo "Reading image whose major brand is 'avif':\n";
14+
$im = imagecreatefromstring(file_get_contents(__DIR__ . '/imagecreatefromstring_major_brand.avif'));
15+
var_dump(imagesx($im));
16+
var_dump(imagesy($im));
17+
18+
echo "Reading image with a compatible brand that's 'avif':\n";
19+
$im = imagecreatefromstring(file_get_contents(__DIR__ . '/imagecreatefromstring_compatible_brand.avif'));
20+
var_dump(imagesx($im));
21+
var_dump(imagesy($im));
22+
?>
23+
--EXPECT--
24+
Reading image whose major brand is 'avif':
25+
int(250)
26+
int(375)
27+
Reading image with a compatible brand that's 'avif':
28+
int(480)
29+
int(270)
Binary file not shown.
Binary file not shown.

ext/standard/image.c

Lines changed: 111 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,8 @@ PHP_MINIT_FUNCTION(imagetypes)
8787
REGISTER_LONG_CONSTANT("IMAGETYPE_JPEG2000",IMAGE_FILETYPE_JPC, CONST_CS | CONST_PERSISTENT); /* keep alias */
8888
REGISTER_LONG_CONSTANT("IMAGETYPE_XBM", IMAGE_FILETYPE_XBM, CONST_CS | CONST_PERSISTENT);
8989
REGISTER_LONG_CONSTANT("IMAGETYPE_ICO", IMAGE_FILETYPE_ICO, CONST_CS | CONST_PERSISTENT);
90-
REGISTER_LONG_CONSTANT("IMAGETYPE_WEBP", IMAGE_FILETYPE_WEBP, CONST_CS | CONST_PERSISTENT);
90+
REGISTER_LONG_CONSTANT("IMAGETYPE_WEBP", IMAGE_FILETYPE_WEBP, CONST_CS | CONST_PERSISTENT);
91+
REGISTER_LONG_CONSTANT("IMAGETYPE_AVIF", IMAGE_FILETYPE_AVIF, CONST_CS | CONST_PERSISTENT);
9192
REGISTER_LONG_CONSTANT("IMAGETYPE_UNKNOWN", IMAGE_FILETYPE_UNKNOWN, CONST_CS | CONST_PERSISTENT);
9293
REGISTER_LONG_CONSTANT("IMAGETYPE_COUNT", IMAGE_FILETYPE_COUNT, CONST_CS | CONST_PERSISTENT);
9394
return SUCCESS;
@@ -1148,6 +1149,99 @@ static struct gfxinfo *php_handle_webp(php_stream * stream)
11481149
}
11491150
/* }}} */
11501151

1152+
/* {{{ php_handle_avif
1153+
* There's no simple way to get this information - so, for now, this is unsupported.
1154+
* Simply return 0 for everything.
1155+
*/
1156+
static struct gfxinfo *php_handle_avif(php_stream * stream) {
1157+
return ecalloc(1, sizeof(struct gfxinfo));
1158+
}
1159+
/* }}} */
1160+
1161+
/* {{{ php_ntohl
1162+
* Convert a big-endian network uint32 to host order -
1163+
* which may be either little-endian or big-endian.
1164+
* Thanks to Rob Pike via Joe Drago:
1165+
* https://commandcenter.blogspot.nl/2012/04/byte-order-fallacy.html
1166+
*/
1167+
static uint32_t php_ntohl(uint32_t val) {
1168+
uint8_t data[4];
1169+
1170+
memcpy(&data, &val, sizeof(data));
1171+
return ((uint32_t)data[3] << 0) |
1172+
((uint32_t)data[2] << 8) |
1173+
((uint32_t)data[1] << 16) |
1174+
((uint32_t)data[0] << 24);
1175+
}
1176+
/* }}} */
1177+
1178+
/* {{{ php_is_image_avif
1179+
* detect whether an image is of type AVIF
1180+
*
1181+
* An AVIF image will start off a header "box".
1182+
* This starts with with a four-byte integer containing the number of bytes in the filetype box.
1183+
* This must be followed by the string "ftyp".
1184+
* Next comes a four-byte string indicating the "major brand".
1185+
* If that's "avif" or "avis", this is an AVIF image.
1186+
* Next, there's a four-byte "minor version" field, which we can ignore.
1187+
* Next comes an array of four-byte strings containing "compatible brands".
1188+
* These extend to the end of the box.
1189+
* If any of the compatible brands is "avif" or "avis", then this is an AVIF image.
1190+
* Otherwise, well, it's not.
1191+
* For more, see https://mpeg.chiariglione.org/standards/mpeg-4/iso-base-media-file-format/text-isoiec-14496-12-5th-edition
1192+
*/
1193+
bool php_is_image_avif(php_stream * stream) {
1194+
uint32_t header_size_reversed, header_size, i;
1195+
char box_type[4], brand[4];
1196+
1197+
ZEND_ASSERT(stream != NULL);
1198+
1199+
if (php_stream_read(stream, (char *) &header_size_reversed, 4) != 4) {
1200+
return 0;
1201+
}
1202+
1203+
header_size = php_ntohl(header_size_reversed);
1204+
1205+
/* If the box type isn't "ftyp", it can't be an AVIF image. */
1206+
if (php_stream_read(stream, box_type, 4) != 4) {
1207+
return 0;
1208+
}
1209+
1210+
if (memcmp(box_type, "ftyp", 4)) {
1211+
return 0;
1212+
}
1213+
1214+
/* If the major brand is "avif" or "avis", it's an AVIF image. */
1215+
if (php_stream_read(stream, brand, 4) != 4) {
1216+
return 0;
1217+
}
1218+
1219+
if (!memcmp(brand, "avif", 4) || !memcmp(brand, "avis", 4)) {
1220+
return 1;
1221+
}
1222+
1223+
/* Skip the next four bytes, which are the "minor version". */
1224+
if (php_stream_read(stream, brand, 4) != 4) {
1225+
return 0;
1226+
}
1227+
1228+
/* Look for "avif" or "avis" in any member of compatible_brands[], to the end of the header.
1229+
Note we've already read four groups of four bytes. */
1230+
1231+
for (i = 16; i < header_size; i += 4) {
1232+
if (php_stream_read(stream, brand, 4) != 4) {
1233+
return 0;
1234+
}
1235+
1236+
if (!memcmp(brand, "avif", 4) || !memcmp(brand, "avis", 4)) {
1237+
return 1;
1238+
}
1239+
}
1240+
1241+
return 0;
1242+
}
1243+
/* }}} */
1244+
11511245
/* {{{ php_image_type_to_mime_type
11521246
* Convert internal image_type to mime type */
11531247
PHPAPI char * php_image_type_to_mime_type(int image_type)
@@ -1183,6 +1277,8 @@ PHPAPI char * php_image_type_to_mime_type(int image_type)
11831277
return "image/vnd.microsoft.icon";
11841278
case IMAGE_FILETYPE_WEBP:
11851279
return "image/webp";
1280+
case IMAGE_FILETYPE_AVIF:
1281+
return "image/avif";
11861282
default:
11871283
case IMAGE_FILETYPE_UNKNOWN:
11881284
return "application/octet-stream"; /* suppose binary format */
@@ -1265,6 +1361,9 @@ PHP_FUNCTION(image_type_to_extension)
12651361
case IMAGE_FILETYPE_WEBP:
12661362
imgext = ".webp";
12671363
break;
1364+
case IMAGE_FILETYPE_AVIF:
1365+
imgext = ".avif";
1366+
break;
12681367
}
12691368

12701369
if (imgext) {
@@ -1277,7 +1376,7 @@ PHP_FUNCTION(image_type_to_extension)
12771376

12781377
/* {{{ php_imagetype
12791378
detect filetype from first bytes */
1280-
PHPAPI int php_getimagetype(php_stream * stream, const char *input, char *filetype)
1379+
PHPAPI int php_getimagetype(php_stream *stream, const char *input, char *filetype)
12811380
{
12821381
char tmp[12];
12831382
int twelve_bytes_read;
@@ -1349,17 +1448,24 @@ PHPAPI int php_getimagetype(php_stream * stream, const char *input, char *filety
13491448
return IMAGE_FILETYPE_JP2;
13501449
}
13511450

1451+
if (!php_stream_rewind(stream) && php_is_image_avif(stream)) {
1452+
return IMAGE_FILETYPE_AVIF;
1453+
}
1454+
13521455
/* AFTER ALL ABOVE FAILED */
13531456
if (php_get_wbmp(stream, NULL, 1)) {
13541457
return IMAGE_FILETYPE_WBMP;
13551458
}
1459+
13561460
if (!twelve_bytes_read) {
13571461
php_error_docref(NULL, E_NOTICE, "Error reading from %s!", input);
13581462
return IMAGE_FILETYPE_UNKNOWN;
13591463
}
1464+
13601465
if (php_get_xbm(stream, NULL)) {
13611466
return IMAGE_FILETYPE_XBM;
13621467
}
1468+
13631469
return IMAGE_FILETYPE_UNKNOWN;
13641470
}
13651471
/* }}} */
@@ -1431,6 +1537,9 @@ static void php_getimagesize_from_stream(php_stream *stream, char *input, zval *
14311537
case IMAGE_FILETYPE_WEBP:
14321538
result = php_handle_webp(stream);
14331539
break;
1540+
case IMAGE_FILETYPE_AVIF:
1541+
result = php_handle_avif(stream);
1542+
break;
14341543
default:
14351544
case IMAGE_FILETYPE_UNKNOWN:
14361545
break;

ext/standard/php_image.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ typedef enum
4343
IMAGE_FILETYPE_XBM,
4444
IMAGE_FILETYPE_ICO,
4545
IMAGE_FILETYPE_WEBP,
46+
IMAGE_FILETYPE_AVIF,
4647
/* WHEN EXTENDING: PLEASE ALSO REGISTER IN image.c:PHP_MINIT_FUNCTION(imagetypes) */
4748
IMAGE_FILETYPE_COUNT
4849
} image_filetype;
@@ -54,4 +55,6 @@ PHPAPI int php_getimagetype(php_stream *stream, const char *input, char *filetyp
5455

5556
PHPAPI char * php_image_type_to_mime_type(int image_type);
5657

58+
PHPAPI bool php_is_image_avif(php_stream *stream);
59+
5760
#endif /* PHP_IMAGE_H */

ext/standard/tests/image/getimagesize.phpt

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ GetImageSize()
2323
var_dump($result);
2424
?>
2525
--EXPECT--
26-
array(16) {
26+
array(17) {
2727
["test-1pix.bmp"]=>
2828
array(6) {
2929
[0]=>
@@ -69,6 +69,19 @@ array(16) {
6969
["mime"]=>
7070
string(9) "image/bmp"
7171
}
72+
["test1pix.avif"]=>
73+
array(5) {
74+
[0]=>
75+
int(0)
76+
[1]=>
77+
int(0)
78+
[2]=>
79+
int(19)
80+
[3]=>
81+
string(20) "width="0" height="0""
82+
["mime"]=>
83+
string(10) "image/avif"
84+
}
7285
["test1pix.bmp"]=>
7386
array(6) {
7487
[0]=>

0 commit comments

Comments
 (0)