Skip to content

Commit

Permalink
AVIF support for getimagesize() and imagecreatefromstring()
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
morsssss authored and cmb69 committed Jul 6, 2021
1 parent a5360e8 commit cee33ba
Show file tree
Hide file tree
Showing 13 changed files with 208 additions and 38 deletions.
69 changes: 37 additions & 32 deletions ext/gd/gd.c
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,10 @@
#include <math.h>
#include "SAPI.h"
#include "php_gd.h"
#include "ext/standard/php_image.h"
#include "ext/standard/info.h"
#include "php_open_temporary_file.h"
#include "php_memory_streams.h"
#include "zend_object_handlers.h"
#include "zend_interfaces.h"

Expand Down Expand Up @@ -145,7 +147,7 @@ static void _php_image_output(INTERNAL_FUNCTION_PARAMETERS, int image_type, char
static gdIOCtx *create_stream_context_from_zval(zval *to_zval);
static gdIOCtx *create_stream_context(php_stream *stream, int close_stream);
static gdIOCtx *create_output_context(void);
static int _php_image_type(char data[12]);
static int _php_image_type(zend_string *data);

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

/* {{{ _php_image_type */
/* {{{ _php_image_type
* Based on ext/standard/image.c
*/
static const char php_sig_gd2[3] = {'g', 'd', '2'};

static int _php_image_type (char data[12])
static int _php_image_type(zend_string *data)
{
/* Based on ext/standard/image.c */

if (data == NULL) {
if (ZSTR_LEN(data) < 12) {
/* Handle this the same way as an unknown image type. */
return -1;
}

if (!memcmp(data, php_sig_gd2, sizeof(php_sig_gd2))) {
if (!memcmp(ZSTR_VAL(data), php_sig_gd2, sizeof(php_sig_gd2))) {
return PHP_GDIMG_TYPE_GD2;
} else if (!memcmp(data, php_sig_jpg, sizeof(php_sig_jpg))) {
} else if (!memcmp(ZSTR_VAL(data), php_sig_jpg, sizeof(php_sig_jpg))) {
return PHP_GDIMG_TYPE_JPG;
} else if (!memcmp(data, php_sig_png, sizeof(php_sig_png))) {
} else if (!memcmp(ZSTR_VAL(data), php_sig_png, sizeof(php_sig_png))) {
return PHP_GDIMG_TYPE_PNG;
} else if (!memcmp(data, php_sig_gif, sizeof(php_sig_gif))) {
} else if (!memcmp(ZSTR_VAL(data), php_sig_gif, sizeof(php_sig_gif))) {
return PHP_GDIMG_TYPE_GIF;
} else if (!memcmp(data, php_sig_bmp, sizeof(php_sig_bmp))) {
} else if (!memcmp(ZSTR_VAL(data), php_sig_bmp, sizeof(php_sig_bmp))) {
return PHP_GDIMG_TYPE_BMP;
} 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))) {
} 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))) {
return PHP_GDIMG_TYPE_WEBP;
}
else {
gdIOCtx *io_ctx;
io_ctx = gdNewDynamicCtxEx(8, data, 0);
if (io_ctx) {
if (_php_ctx_getmbi(io_ctx) == 0 && _php_ctx_getmbi(io_ctx) >= 0) {
io_ctx->gd_free(io_ctx);
return PHP_GDIMG_TYPE_WBM;
} else {
io_ctx->gd_free(io_ctx);
}

php_stream *image_stream = php_stream_memory_open(TEMP_STREAM_READONLY, data);

if (image_stream != NULL) {
bool is_avif = php_is_image_avif(image_stream);
php_stream_close(image_stream);

if (is_avif) {
return PHP_GDIMG_TYPE_AVIF;
}
}

gdIOCtx *io_ctx;
io_ctx = gdNewDynamicCtxEx(8, ZSTR_VAL(data), 0);
if (io_ctx) {
if (_php_ctx_getmbi(io_ctx) == 0 && _php_ctx_getmbi(io_ctx) >= 0) {
io_ctx->gd_free(io_ctx);
return PHP_GDIMG_TYPE_WBM;
} else {
io_ctx->gd_free(io_ctx);
}
}

return -1;
}
/* }}} */
Expand Down Expand Up @@ -1540,21 +1554,12 @@ PHP_FUNCTION(imagecreatefromstring)
zend_string *data;
gdImagePtr im;
int imtype;
char sig[12];

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

if (ZSTR_LEN(data) < sizeof(sig)) {
/* Handle this the same way as an unknown image type. */
php_error_docref(NULL, E_WARNING, "Data is not in a recognized format");
RETURN_FALSE;
}

memcpy(sig, ZSTR_VAL(data), sizeof(sig));

imtype = _php_image_type(sig);
imtype = _php_image_type(data);

switch (imtype) {
case PHP_GDIMG_TYPE_JPG:
Expand Down
1 change: 0 additions & 1 deletion ext/gd/libgd/gd.h
Original file line number Diff line number Diff line change
Expand Up @@ -628,7 +628,6 @@ gdImagePtr gdImageCreateFromGif(FILE *fd);
gdImagePtr gdImageCreateFromGifCtx(gdIOCtxPtr in);
gdImagePtr gdImageCreateFromGifSource(gdSourcePtr in);

//TODO: we may not need all of these
void gdImageAvif(gdImagePtr im, FILE *outfile);
void gdImageAvifEx(gdImagePtr im, FILE *outfile, int quality, int speed);
void *gdImageAvifPtr(gdImagePtr im, int *size);
Expand Down
3 changes: 3 additions & 0 deletions ext/gd/php_gd.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@
#ifndef PHP_GD_H
#define PHP_GD_H

#include "zend_string.h"
#include "php_streams.h"

#if defined(HAVE_LIBGD) || defined(HAVE_GD_BUNDLED)

/* open_basedir and safe_mode checks */
Expand Down
29 changes: 29 additions & 0 deletions ext/gd/tests/imagecreatefromstring_avif.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
--TEST--
imagecreatefromstring() - AVIF format
--EXTENSIONS--
gd
--SKIPIF--
<?php
if (!(imagetypes() & IMG_AVIF)) {
die('skip AVIF support required');
}
?>
--FILE--
<?php
echo "Reading image whose major brand is 'avif':\n";
$im = imagecreatefromstring(file_get_contents(__DIR__ . '/imagecreatefromstring_major_brand.avif'));
var_dump(imagesx($im));
var_dump(imagesy($im));

echo "Reading image with a compatible brand that's 'avif':\n";
$im = imagecreatefromstring(file_get_contents(__DIR__ . '/imagecreatefromstring_compatible_brand.avif'));
var_dump(imagesx($im));
var_dump(imagesy($im));
?>
--EXPECT--
Reading image whose major brand is 'avif':
int(250)
int(375)
Reading image with a compatible brand that's 'avif':
int(480)
int(270)
Binary file not shown.
Binary file not shown.
113 changes: 111 additions & 2 deletions ext/standard/image.c
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,8 @@ PHP_MINIT_FUNCTION(imagetypes)
REGISTER_LONG_CONSTANT("IMAGETYPE_JPEG2000",IMAGE_FILETYPE_JPC, CONST_CS | CONST_PERSISTENT); /* keep alias */
REGISTER_LONG_CONSTANT("IMAGETYPE_XBM", IMAGE_FILETYPE_XBM, CONST_CS | CONST_PERSISTENT);
REGISTER_LONG_CONSTANT("IMAGETYPE_ICO", IMAGE_FILETYPE_ICO, CONST_CS | CONST_PERSISTENT);
REGISTER_LONG_CONSTANT("IMAGETYPE_WEBP", IMAGE_FILETYPE_WEBP, CONST_CS | CONST_PERSISTENT);
REGISTER_LONG_CONSTANT("IMAGETYPE_WEBP", IMAGE_FILETYPE_WEBP, CONST_CS | CONST_PERSISTENT);
REGISTER_LONG_CONSTANT("IMAGETYPE_AVIF", IMAGE_FILETYPE_AVIF, CONST_CS | CONST_PERSISTENT);
REGISTER_LONG_CONSTANT("IMAGETYPE_UNKNOWN", IMAGE_FILETYPE_UNKNOWN, CONST_CS | CONST_PERSISTENT);
REGISTER_LONG_CONSTANT("IMAGETYPE_COUNT", IMAGE_FILETYPE_COUNT, CONST_CS | CONST_PERSISTENT);
return SUCCESS;
Expand Down Expand Up @@ -1148,6 +1149,99 @@ static struct gfxinfo *php_handle_webp(php_stream * stream)
}
/* }}} */

/* {{{ php_handle_avif
* There's no simple way to get this information - so, for now, this is unsupported.
* Simply return 0 for everything.
*/
static struct gfxinfo *php_handle_avif(php_stream * stream) {
return ecalloc(1, sizeof(struct gfxinfo));
}
/* }}} */

/* {{{ php_ntohl
* Convert a big-endian network uint32 to host order -
* which may be either little-endian or big-endian.
* Thanks to Rob Pike via Joe Drago:
* https://commandcenter.blogspot.nl/2012/04/byte-order-fallacy.html
*/
static uint32_t php_ntohl(uint32_t val) {
uint8_t data[4];

memcpy(&data, &val, sizeof(data));
return ((uint32_t)data[3] << 0) |
((uint32_t)data[2] << 8) |
((uint32_t)data[1] << 16) |
((uint32_t)data[0] << 24);
}
/* }}} */

/* {{{ php_is_image_avif
* detect whether an image is of type AVIF
*
* An AVIF image will start off a header "box".
* This starts with with a four-byte integer containing the number of bytes in the filetype box.
* This must be followed by the string "ftyp".
* Next comes a four-byte string indicating the "major brand".
* If that's "avif" or "avis", this is an AVIF image.
* Next, there's a four-byte "minor version" field, which we can ignore.
* Next comes an array of four-byte strings containing "compatible brands".
* These extend to the end of the box.
* If any of the compatible brands is "avif" or "avis", then this is an AVIF image.
* Otherwise, well, it's not.
* For more, see https://mpeg.chiariglione.org/standards/mpeg-4/iso-base-media-file-format/text-isoiec-14496-12-5th-edition
*/
bool php_is_image_avif(php_stream * stream) {
uint32_t header_size_reversed, header_size, i;
char box_type[4], brand[4];

ZEND_ASSERT(stream != NULL);

if (php_stream_read(stream, (char *) &header_size_reversed, 4) != 4) {
return 0;
}

header_size = php_ntohl(header_size_reversed);

/* If the box type isn't "ftyp", it can't be an AVIF image. */
if (php_stream_read(stream, box_type, 4) != 4) {
return 0;
}

if (memcmp(box_type, "ftyp", 4)) {
return 0;
}

/* If the major brand is "avif" or "avis", it's an AVIF image. */
if (php_stream_read(stream, brand, 4) != 4) {
return 0;
}

if (!memcmp(brand, "avif", 4) || !memcmp(brand, "avis", 4)) {
return 1;
}

/* Skip the next four bytes, which are the "minor version". */
if (php_stream_read(stream, brand, 4) != 4) {
return 0;
}

/* Look for "avif" or "avis" in any member of compatible_brands[], to the end of the header.
Note we've already read four groups of four bytes. */

for (i = 16; i < header_size; i += 4) {
if (php_stream_read(stream, brand, 4) != 4) {
return 0;
}

if (!memcmp(brand, "avif", 4) || !memcmp(brand, "avis", 4)) {
return 1;
}
}

return 0;
}
/* }}} */

/* {{{ php_image_type_to_mime_type
* Convert internal image_type to mime type */
PHPAPI char * php_image_type_to_mime_type(int image_type)
Expand Down Expand Up @@ -1183,6 +1277,8 @@ PHPAPI char * php_image_type_to_mime_type(int image_type)
return "image/vnd.microsoft.icon";
case IMAGE_FILETYPE_WEBP:
return "image/webp";
case IMAGE_FILETYPE_AVIF:
return "image/avif";
default:
case IMAGE_FILETYPE_UNKNOWN:
return "application/octet-stream"; /* suppose binary format */
Expand Down Expand Up @@ -1265,6 +1361,9 @@ PHP_FUNCTION(image_type_to_extension)
case IMAGE_FILETYPE_WEBP:
imgext = ".webp";
break;
case IMAGE_FILETYPE_AVIF:
imgext = ".avif";
break;
}

if (imgext) {
Expand All @@ -1277,7 +1376,7 @@ PHP_FUNCTION(image_type_to_extension)

/* {{{ php_imagetype
detect filetype from first bytes */
PHPAPI int php_getimagetype(php_stream * stream, const char *input, char *filetype)
PHPAPI int php_getimagetype(php_stream *stream, const char *input, char *filetype)
{
char tmp[12];
int twelve_bytes_read;
Expand Down Expand Up @@ -1349,17 +1448,24 @@ PHPAPI int php_getimagetype(php_stream * stream, const char *input, char *filety
return IMAGE_FILETYPE_JP2;
}

if (!php_stream_rewind(stream) && php_is_image_avif(stream)) {
return IMAGE_FILETYPE_AVIF;
}

/* AFTER ALL ABOVE FAILED */
if (php_get_wbmp(stream, NULL, 1)) {
return IMAGE_FILETYPE_WBMP;
}

if (!twelve_bytes_read) {
php_error_docref(NULL, E_NOTICE, "Error reading from %s!", input);
return IMAGE_FILETYPE_UNKNOWN;
}

if (php_get_xbm(stream, NULL)) {
return IMAGE_FILETYPE_XBM;
}

return IMAGE_FILETYPE_UNKNOWN;
}
/* }}} */
Expand Down Expand Up @@ -1431,6 +1537,9 @@ static void php_getimagesize_from_stream(php_stream *stream, char *input, zval *
case IMAGE_FILETYPE_WEBP:
result = php_handle_webp(stream);
break;
case IMAGE_FILETYPE_AVIF:
result = php_handle_avif(stream);
break;
default:
case IMAGE_FILETYPE_UNKNOWN:
break;
Expand Down
3 changes: 3 additions & 0 deletions ext/standard/php_image.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ typedef enum
IMAGE_FILETYPE_XBM,
IMAGE_FILETYPE_ICO,
IMAGE_FILETYPE_WEBP,
IMAGE_FILETYPE_AVIF,
/* WHEN EXTENDING: PLEASE ALSO REGISTER IN image.c:PHP_MINIT_FUNCTION(imagetypes) */
IMAGE_FILETYPE_COUNT
} image_filetype;
Expand All @@ -54,4 +55,6 @@ PHPAPI int php_getimagetype(php_stream *stream, const char *input, char *filetyp

PHPAPI char * php_image_type_to_mime_type(int image_type);

PHPAPI bool php_is_image_avif(php_stream *stream);

#endif /* PHP_IMAGE_H */
15 changes: 14 additions & 1 deletion ext/standard/tests/image/getimagesize.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ GetImageSize()
var_dump($result);
?>
--EXPECT--
array(16) {
array(17) {
["test-1pix.bmp"]=>
array(6) {
[0]=>
Expand Down Expand Up @@ -69,6 +69,19 @@ array(16) {
["mime"]=>
string(9) "image/bmp"
}
["test1pix.avif"]=>
array(5) {
[0]=>
int(0)
[1]=>
int(0)
[2]=>
int(19)
[3]=>
string(20) "width="0" height="0""
["mime"]=>
string(10) "image/avif"
}
["test1pix.bmp"]=>
array(6) {
[0]=>
Expand Down

0 comments on commit cee33ba

Please sign in to comment.