Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement #71517: Implement SVG support for getimagesize() and friends #16670

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions UPGRADING
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,15 @@ PHP 8.5 UPGRADE NOTES
- DOM:
. Added Dom\Element::$outerHTML.

- Standard:
. getimagesize() now supports SVG images when ext-libxml is also loaded.
Similarly, image_type_to_extension() and image_type_to_extension()
now also handle IMAGETYPE_SVG.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe add a note about the new array element width_unit and height_unit (for the doc people).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Of course

. The array returned by getimagesize() now has two additional entries:
"width_unit" and "height_unit" to indicate in which units the dimensions
are expressed. These units are px by default. They are not necessarily
the same (just to give one example: one may be cm and the other may be px).

- XSL:
. The $namespace argument of XSLTProcessor::getParameter(),
XSLTProcessor::setParameter() and XSLTProcessor::removeParameter()
Expand Down Expand Up @@ -261,6 +270,9 @@ PHP 8.5 UPGRADE NOTES
. TCP_BBR_ALGORITHM (FreeBSD only).
. AF_PACKET (Linux only).

- Standard:
. IMAGETYPE_SVG when libxml is loaded.

========================================
11. Changes to INI File Handling
========================================
Expand Down
6 changes: 6 additions & 0 deletions UPGRADING.INTERNALS
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,12 @@ PHP 8.5 INTERNALS UPGRADE NOTES
is still valid. This is useful when a GC cycle is collected and the
database object can be destroyed prior to destroying the statement.

- ext/standard:
. The functionality of getimagesize(), image_type_to_mime_type(),
and image_type_to_extension() is now extensible using the internal APIs
php_image_register_handler() and php_image_unregister_handler() in
php_image.h.

========================
4. OpCode changes
========================
Expand Down
2 changes: 1 addition & 1 deletion ext/libxml/config.w32
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ if (PHP_LIBXML == "yes") {
if (GREP_HEADER("libxml/xmlversion.h", "#define\\s+LIBXML_VERSION\\s+(\\d+)", PHP_PHP_BUILD + "\\include\\libxml2") &&
+RegExp.$1 >= 20904) {

EXTENSION("libxml", "libxml.c mime_sniff.c", false /* never shared */, "/DZEND_ENABLE_STATIC_TSRMLS_CACHE=1");
EXTENSION("libxml", "libxml.c mime_sniff.c image_svg.c", false /* never shared */, "/DZEND_ENABLE_STATIC_TSRMLS_CACHE=1");
AC_DEFINE("HAVE_LIBXML", 1, "Define to 1 if the PHP extension 'libxml' is available.");
ADD_FLAG("CFLAGS_LIBXML", "/D LIBXML_STATIC /D LIBXML_STATIC_FOR_DLL /D HAVE_WIN32_THREADS ");
if (!PHP_LIBXML_SHARED) {
Expand Down
2 changes: 1 addition & 1 deletion ext/libxml/config0.m4
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ if test "$PHP_LIBXML" != "no"; then
AC_DEFINE([HAVE_LIBXML], [1],
[Define to 1 if the PHP extension 'libxml' is available.])
PHP_NEW_EXTENSION([libxml],
[libxml.c mime_sniff.c],
[libxml.c mime_sniff.c image_svg.c],
[$ext_shared],,
[-DZEND_ENABLE_STATIC_TSRMLS_CACHE=1])
PHP_INSTALL_HEADERS([ext/libxml], [php_libxml.h])
Expand Down
178 changes: 178 additions & 0 deletions ext/libxml/image_svg.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
/*
+----------------------------------------------------------------------+
| Copyright (c) The PHP Group |
+----------------------------------------------------------------------+
| This source file is subject to version 3.01 of the PHP license, |
| that is bundled with this package in the file LICENSE, and is |
| available through the world-wide-web at the following url: |
| https://www.php.net/license/3_01.txt |
| If you did not receive a copy of the PHP license and are unable to |
| obtain it through the world-wide-web, please send a note to |
| license@php.net so we can mail you a copy immediately. |
+----------------------------------------------------------------------+
| Authors: Niels Dossche <nielsdos@php.net> |
+----------------------------------------------------------------------+
*/

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "php.h"
#include "image_svg.h"
#include "php_libxml.h"

#include "ext/standard/php_image.h"

#include <libxml/xmlreader.h>

#ifdef HAVE_LIBXML

static int svg_image_type_id;

static int php_libxml_svg_stream_read(void *context, char *buffer, int len)
{
return php_stream_read(context, buffer, len);
}

/* Sanity check that the input only contains characters valid for a dimension (numbers with units, e.g. 5cm).
* This also protects the user against injecting XSS.
* Only accept [0-9]+[a-zA-Z]* */
static bool php_libxml_parse_dimension(const xmlChar *input, const xmlChar **unit_position)
{
if (!(*input >= '0' && *input <= '9')) {
return false;
}

input++;

while (*input) {
if (!(*input >= '0' && *input <= '9')) {
if ((*input >= 'a' && *input <= 'z') || (*input >= 'A' && *input <= 'Z')) {
break;
}
return false;
}
input++;
}

*unit_position = input;

while (*input) {
if (!((*input >= 'a' && *input <= 'z') || (*input >= 'A' && *input <= 'Z'))) {
return false;
}
input++;
}

return true;
}

zend_result php_libxml_svg_image_handle(php_stream *stream, struct php_gfxinfo **result)
{
if (php_stream_rewind(stream)) {
return FAILURE;
}

/* Early check before doing more expensive work */
if (php_stream_getc(stream) != '<') {
return FAILURE;
}

if (php_stream_rewind(stream)) {
return FAILURE;
}

PHP_LIBXML_SANITIZE_GLOBALS(reader_for_stream);
xmlTextReaderPtr reader = xmlReaderForIO(
php_libxml_svg_stream_read,
NULL,
stream,
NULL,
NULL,
XML_PARSE_NOWARNING | XML_PARSE_NOERROR | XML_PARSE_NONET
);
PHP_LIBXML_RESTORE_GLOBALS(reader_for_stream);

if (!reader) {
return FAILURE;
}

bool is_svg = false;
while (xmlTextReaderRead(reader) == 1) {
if (xmlTextReaderNodeType(reader) == XML_READER_TYPE_ELEMENT) {
/* Root must be an svg element */
const xmlChar *name = xmlTextReaderConstLocalName(reader);
if (!name || strcasecmp((const char *) name, "svg") != 0) {
break;
}

xmlChar *width = xmlTextReaderGetAttribute(reader, BAD_CAST "width");
xmlChar *height = xmlTextReaderGetAttribute(reader, BAD_CAST "height");
const xmlChar *width_unit_position, *height_unit_position;
if (!width || !height
|| !php_libxml_parse_dimension(width, &width_unit_position)
|| !php_libxml_parse_dimension(height, &height_unit_position)) {
xmlFree(width);
xmlFree(height);
break;
}

is_svg = true;
if (result) {
*result = ecalloc(1, sizeof(**result));
(*result)->width = ZEND_STRTOL((const char *) width, NULL, 10);
(*result)->height = ZEND_STRTOL((const char *) height, NULL, 10);
if (*width_unit_position) {
(*result)->width_unit = zend_string_init((const char*) width_unit_position,
xmlStrlen(width_unit_position), false);
}
if (*height_unit_position) {
(*result)->height_unit = zend_string_init((const char*) height_unit_position,
xmlStrlen(height_unit_position), false);
}
}

xmlFree(width);
xmlFree(height);
break;
}
}

xmlFreeTextReader(reader);

return is_svg ? SUCCESS : FAILURE;
}

zend_result php_libxml_svg_image_identify(php_stream *stream)
{
return php_libxml_svg_image_handle(stream, NULL);
}

struct php_gfxinfo *php_libxml_svg_image_get_info(php_stream *stream)
{
struct php_gfxinfo *result = NULL;
zend_result status = php_libxml_svg_image_handle(stream, &result);
ZEND_ASSERT((status == SUCCESS) == (result != NULL));
return result;
}

static const struct php_image_handler svg_image_handler = {
"image/svg+xml",
".svg",
PHP_IMAGE_CONST_NAME("SVG"),
php_libxml_svg_image_identify,
php_libxml_svg_image_get_info,
};

void php_libxml_register_image_svg_handler(void)
{
svg_image_type_id = php_image_register_handler(&svg_image_handler);
}

zend_result php_libxml_unregister_image_svg_handler(void)
{
return php_image_unregister_handler(svg_image_type_id);
}

#endif
25 changes: 25 additions & 0 deletions ext/libxml/image_svg.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
+----------------------------------------------------------------------+
| Copyright (c) The PHP Group |
+----------------------------------------------------------------------+
| This source file is subject to version 3.01 of the PHP license, |
| that is bundled with this package in the file LICENSE, and is |
| available through the world-wide-web at the following url: |
| https://www.php.net/license/3_01.txt |
| If you did not receive a copy of the PHP license and are unable to |
| obtain it through the world-wide-web, please send a note to |
| license@php.net so we can mail you a copy immediately. |
+----------------------------------------------------------------------+
| Authors: Niels Dossche <nielsdos@php.net> |
+----------------------------------------------------------------------+
*/

#ifndef LIBXML_IMAGE_SVG
#define LIBXML_IMAGE_SVG

#include "zend.h"

void php_libxml_register_image_svg_handler(void);
zend_result php_libxml_unregister_image_svg_handler(void);

#endif
13 changes: 11 additions & 2 deletions ext/libxml/libxml.c
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
#endif

#include "php_libxml.h"
#include "image_svg.h"

#define PHP_LIBXML_LOADED_VERSION ((char *)xmlParserVersion)

Expand Down Expand Up @@ -88,8 +89,14 @@ static zend_long php_libxml_default_dump_doc_to_file(const char *filename, xmlDo

/* }}} */

static const zend_module_dep libxml_deps[] = {
ZEND_MOD_REQUIRED("standard")
ZEND_MOD_END
};

zend_module_entry libxml_module_entry = {
STANDARD_MODULE_HEADER,
STANDARD_MODULE_HEADER_EX, NULL,
libxml_deps,
"libxml", /* extension name */
ext_functions, /* extension function list */
PHP_MINIT(libxml), /* extension-wide startup function */
Expand Down Expand Up @@ -972,6 +979,8 @@ static PHP_MINIT_FUNCTION(libxml)
xmlOutputBufferCreateFilenameDefault(php_libxml_output_buffer_create_filename);
}

php_libxml_register_image_svg_handler();

return SUCCESS;
}

Expand Down Expand Up @@ -1013,7 +1022,7 @@ static PHP_MSHUTDOWN_FUNCTION(libxml)
}
php_libxml_shutdown();

return SUCCESS;
return php_libxml_unregister_image_svg_handler();
}

static zend_result php_libxml_post_deactivate(void)
Expand Down
Loading