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.
This commit is contained in:
Ben Morss 2021-07-05 17:19:51 +02:00 committed by Christoph M. Becker
parent a5360e80c2
commit cee33bab16
No known key found for this signature in database
GPG key ID: D66C9593118BCCB6
13 changed files with 208 additions and 38 deletions

View file

@ -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"
@ -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)());
@ -1469,33 +1471,45 @@ 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 {
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, data, 0);
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);
@ -1504,7 +1518,7 @@ static int _php_image_type (char data[12])
io_ctx->gd_free(io_ctx);
}
}
}
return -1;
}
/* }}} */
@ -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:

View file

@ -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);

View file

@ -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 */

View file

@ -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.

After

Width:  |  Height:  |  Size: 6.7 KiB

View file

@ -88,6 +88,7 @@ PHP_MINIT_FUNCTION(imagetypes)
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_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;
@ -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)
@ -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 */
@ -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) {
@ -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;
}
/* }}} */
@ -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;

View file

@ -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;
@ -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 */

View file

@ -23,7 +23,7 @@ GetImageSize()
var_dump($result);
?>
--EXPECT--
array(16) {
array(17) {
["test-1pix.bmp"]=>
array(6) {
[0]=>
@ -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]=>

View file

@ -23,7 +23,8 @@ image_type_to_extension()
"IMAGETYPE_WBMP" => IMAGETYPE_WBMP,
"IMAGETYPE_JPEG2000" => IMAGETYPE_JPEG2000,
"IMAGETYPE_XBM" => IMAGETYPE_XBM,
"IMAGETYPE_WEBP" => IMAGETYPE_WEBP
"IMAGETYPE_WEBP" => IMAGETYPE_WEBP,
"IMAGETYPE_AVIF" => IMAGETYPE_AVIF,
);
foreach($constants as $name => $constant) {
printf("Constant: %s\n\tWith dot: %s\n\tWithout dot: %s\n", $name, image_type_to_extension($constant), image_type_to_extension($constant, false));
@ -85,6 +86,9 @@ Constant: IMAGETYPE_XBM
Constant: IMAGETYPE_WEBP
With dot: .webp
Without dot: webp
Constant: IMAGETYPE_AVIF
With dot: .avif
Without dot: avif
bool(false)
bool(false)
Done

View file

@ -24,13 +24,15 @@ image_type_to_mime_type()
var_dump($result);
?>
--EXPECT--
array(16) {
array(17) {
["test-1pix.bmp"]=>
string(9) "image/bmp"
["test12pix.webp"]=>
string(10) "image/webp"
["test1bpix.bmp"]=>
string(9) "image/bmp"
["test1pix.avif"]=>
string(10) "image/avif"
["test1pix.bmp"]=>
string(9) "image/bmp"
["test1pix.jp2"]=>

View file

@ -72,4 +72,7 @@ string\(24\) "image\/vnd.microsoft.icon"
string\(10\) "image\/webp"
-- Iteration 19 --
string\(10\) "image\/avif"
-- Iteration 20 --
string\(24\) "application\/octet-stream"

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB