fileinfo: Remove php_fileinfo struct (#18398)

* fileinfo: Remove `php_fileinfo` struct

This is just a needless layer of indirection and requires an additional
allocation.

* fileinfo: Remove options field from `finfo_object`

This was only required to restore the original options when options are given
for `finfo_file()` or `finfo_buffer()`. This can more reliably be achieved
using `magic_getflags()` and is therefore redundant.

* fileinfo: Preserve error for uninitialized `finfo` objects
This commit is contained in:
Tim Düsterhus 2025-04-23 12:04:10 +02:00 committed by GitHub
parent acd4b81cc9
commit 7e15a07fea
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 91 additions and 55 deletions

View file

@ -36,17 +36,11 @@
#include "fopen_wrappers.h" /* needed for is_url */ #include "fopen_wrappers.h" /* needed for is_url */
#include "Zend/zend_exceptions.h" #include "Zend/zend_exceptions.h"
/* {{{ macros and type definitions */
typedef struct _php_fileinfo {
zend_long options;
struct magic_set *magic;
} php_fileinfo;
static zend_object_handlers finfo_object_handlers; static zend_object_handlers finfo_object_handlers;
zend_class_entry *finfo_class_entry; zend_class_entry *finfo_class_entry;
typedef struct _finfo_object { typedef struct _finfo_object {
php_fileinfo *ptr; struct magic_set *magic;
zend_object zo; zend_object zo;
} finfo_object; } finfo_object;
@ -56,26 +50,12 @@ static inline finfo_object *php_finfo_fetch_object(zend_object *obj) {
#define Z_FINFO_P(zv) php_finfo_fetch_object(Z_OBJ_P((zv))) #define Z_FINFO_P(zv) php_finfo_fetch_object(Z_OBJ_P((zv)))
#define FILEINFO_FROM_OBJECT(finfo, object) \
{ \
finfo_object *obj = Z_FINFO_P(object); \
finfo = obj->ptr; \
if (!finfo) { \
zend_throw_error(NULL, "Invalid finfo object"); \
RETURN_THROWS(); \
} \
}
/* {{{ finfo_objects_free */ /* {{{ finfo_objects_free */
static void finfo_objects_free(zend_object *object) static void finfo_objects_free(zend_object *object)
{ {
finfo_object *intern = php_finfo_fetch_object(object); finfo_object *intern = php_finfo_fetch_object(object);
if (intern->ptr) { magic_close(intern->magic);
magic_close(intern->ptr->magic);
efree(intern->ptr);
}
zend_object_std_dtor(&intern->zo); zend_object_std_dtor(&intern->zo);
} }
/* }}} */ /* }}} */
@ -153,7 +133,6 @@ PHP_FUNCTION(finfo_open)
zend_long options = MAGIC_NONE; zend_long options = MAGIC_NONE;
char *file = NULL; char *file = NULL;
size_t file_len = 0; size_t file_len = 0;
php_fileinfo *finfo;
zval *object = getThis(); zval *object = getThis();
char resolved_path[MAXPATHLEN]; char resolved_path[MAXPATHLEN];
zend_error_handling zeh; zend_error_handling zeh;
@ -163,15 +142,10 @@ PHP_FUNCTION(finfo_open)
} }
if (object) { if (object) {
finfo_object *finfo_obj = Z_FINFO_P(object);
zend_replace_error_handling(EH_THROW, NULL, &zeh); zend_replace_error_handling(EH_THROW, NULL, &zeh);
if (finfo_obj->ptr) { magic_close(Z_FINFO_P(object)->magic);
magic_close(finfo_obj->ptr->magic); Z_FINFO_P(object)->magic = NULL;
efree(finfo_obj->ptr);
finfo_obj->ptr = NULL;
}
} }
if (file_len == 0) { if (file_len == 0) {
@ -199,13 +173,9 @@ PHP_FUNCTION(finfo_open)
file = resolved_path; file = resolved_path;
} }
finfo = emalloc(sizeof(php_fileinfo)); struct magic_set *magic = magic_open(options);
finfo->options = options; if (magic == NULL) {
finfo->magic = magic_open(options);
if (finfo->magic == NULL) {
efree(finfo);
php_error_docref(NULL, E_WARNING, "Invalid mode '" ZEND_LONG_FMT "'.", options); php_error_docref(NULL, E_WARNING, "Invalid mode '" ZEND_LONG_FMT "'.", options);
if (object) { if (object) {
zend_restore_error_handling(&zeh); zend_restore_error_handling(&zeh);
@ -216,10 +186,9 @@ PHP_FUNCTION(finfo_open)
RETURN_FALSE; RETURN_FALSE;
} }
if (magic_load(finfo->magic, file) == -1) { if (magic_load(magic, file) == -1) {
php_error_docref(NULL, E_WARNING, "Failed to load magic database at \"%s\"", file); php_error_docref(NULL, E_WARNING, "Failed to load magic database at \"%s\"", file);
magic_close(finfo->magic); magic_close(magic);
efree(finfo);
if (object) { if (object) {
zend_restore_error_handling(&zeh); zend_restore_error_handling(&zeh);
if (!EG(exception)) { if (!EG(exception)) {
@ -230,14 +199,13 @@ PHP_FUNCTION(finfo_open)
} }
if (object) { if (object) {
finfo_object *obj;
zend_restore_error_handling(&zeh); zend_restore_error_handling(&zeh);
obj = Z_FINFO_P(object); finfo_object *obj = Z_FINFO_P(object);
obj->ptr = finfo; obj->magic = magic;
} else { } else {
zend_object *zobj = finfo_objects_new(finfo_class_entry); zend_object *zobj = finfo_objects_new(finfo_class_entry);
finfo_object *obj = php_finfo_fetch_object(zobj); finfo_object *obj = php_finfo_fetch_object(zobj);
obj->ptr = finfo; obj->magic = magic;
RETURN_OBJ(zobj); RETURN_OBJ(zobj);
} }
} }
@ -260,18 +228,20 @@ PHP_FUNCTION(finfo_close)
PHP_FUNCTION(finfo_set_flags) PHP_FUNCTION(finfo_set_flags)
{ {
zend_long options; zend_long options;
php_fileinfo *finfo;
zval *self; zval *self;
if (zend_parse_method_parameters(ZEND_NUM_ARGS(), getThis(), "Ol", &self, finfo_class_entry, &options) == FAILURE) { if (zend_parse_method_parameters(ZEND_NUM_ARGS(), getThis(), "Ol", &self, finfo_class_entry, &options) == FAILURE) {
RETURN_THROWS(); RETURN_THROWS();
} }
FILEINFO_FROM_OBJECT(finfo, self);
if (!Z_FINFO_P(self)->magic) {
zend_throw_error(NULL, "Invalid finfo object");
RETURN_THROWS();
}
/* We do not check the return value as it can only ever fail if options contains MAGIC_PRESERVE_ATIME /* We do not check the return value as it can only ever fail if options contains MAGIC_PRESERVE_ATIME
* and the system neither has utime(3) nor utimes(2). Something incredibly unlikely. */ * and the system neither has utime(3) nor utimes(2). Something incredibly unlikely. */
magic_setflags(finfo->magic, options); magic_setflags(Z_FINFO_P(self)->magic, options);
finfo->options = options;
RETURN_TRUE; RETURN_TRUE;
} }
@ -331,13 +301,17 @@ PHP_FUNCTION(finfo_file)
zend_string *path = NULL; zend_string *path = NULL;
zend_long options = 0; zend_long options = 0;
zval *zcontext = NULL; zval *zcontext = NULL;
php_fileinfo *finfo = NULL;
if (zend_parse_method_parameters(ZEND_NUM_ARGS(), getThis(), "OP|lr!", &self, finfo_class_entry, &path, &options, &zcontext) == FAILURE) { if (zend_parse_method_parameters(ZEND_NUM_ARGS(), getThis(), "OP|lr!", &self, finfo_class_entry, &path, &options, &zcontext) == FAILURE) {
RETURN_THROWS(); RETURN_THROWS();
} }
FILEINFO_FROM_OBJECT(finfo, self);
struct magic_set *magic = finfo->magic; if (!Z_FINFO_P(self)->magic) {
zend_throw_error(NULL, "Invalid finfo object");
RETURN_THROWS();
}
struct magic_set *magic = Z_FINFO_P(self)->magic;
if (UNEXPECTED(ZSTR_LEN(path) == 0)) { if (UNEXPECTED(ZSTR_LEN(path) == 0)) {
zend_argument_must_not_be_empty_error(2); zend_argument_must_not_be_empty_error(2);
@ -349,6 +323,7 @@ PHP_FUNCTION(finfo_file)
} }
/* Set options for the current file/buffer. */ /* Set options for the current file/buffer. */
int old_options = magic_getflags(magic);
if (options) { if (options) {
/* We do not check the return value as it can only ever fail if options contains MAGIC_PRESERVE_ATIME /* We do not check the return value as it can only ever fail if options contains MAGIC_PRESERVE_ATIME
* and the system neither has utime(3) nor utimes(2). Something incredibly unlikely. */ * and the system neither has utime(3) nor utimes(2). Something incredibly unlikely. */
@ -356,9 +331,10 @@ PHP_FUNCTION(finfo_file)
} }
const char *ret_val = php_fileinfo_from_path(magic, path, context); const char *ret_val = php_fileinfo_from_path(magic, path, context);
/* Restore options */ /* Restore options */
if (options) { if (options) {
magic_setflags(magic, finfo->options); magic_setflags(magic, old_options);
} }
if (UNEXPECTED(ret_val == NULL)) { if (UNEXPECTED(ret_val == NULL)) {
@ -375,16 +351,23 @@ PHP_FUNCTION(finfo_buffer)
zend_string *buffer = NULL; zend_string *buffer = NULL;
zend_long options = 0; zend_long options = 0;
zval *dummy_context = NULL; zval *dummy_context = NULL;
php_fileinfo *finfo = NULL;
if (zend_parse_method_parameters(ZEND_NUM_ARGS(), getThis(), "OS|lr!", &self, finfo_class_entry, &buffer, &options, &dummy_context) == FAILURE) { if (zend_parse_method_parameters(ZEND_NUM_ARGS(), getThis(), "OS|lr!", &self, finfo_class_entry, &buffer, &options, &dummy_context) == FAILURE) {
RETURN_THROWS(); RETURN_THROWS();
} }
FILEINFO_FROM_OBJECT(finfo, self);
struct magic_set *magic = finfo->magic; if (!Z_FINFO_P(self)->magic) {
zend_throw_error(NULL, "Invalid finfo object");
RETURN_THROWS();
}
struct magic_set *magic = Z_FINFO_P(self)->magic;
/* Set options for the current file/buffer. */ /* Set options for the current file/buffer. */
int old_options = magic_getflags(magic);
if (options) { if (options) {
/* We do not check the return value as it can only ever fail if options contains MAGIC_PRESERVE_ATIME
* and the system neither has utime(3) nor utimes(2). Something incredibly unlikely. */
magic_setflags(magic, options); magic_setflags(magic, options);
} }
@ -392,7 +375,7 @@ PHP_FUNCTION(finfo_buffer)
/* Restore options */ /* Restore options */
if (options) { if (options) {
magic_setflags(magic, finfo->options); magic_setflags(magic, old_options);
} }
if (UNEXPECTED(ret_val == NULL)) { if (UNEXPECTED(ret_val == NULL)) {

View file

@ -0,0 +1,53 @@
--TEST--
Fileinfo uninitialized object
--EXTENSIONS--
fileinfo
--FILE--
<?php
$finfo = (new ReflectionClass('finfo'))->newInstanceWithoutConstructor();
try {
var_dump(finfo_set_flags($finfo, FILEINFO_NONE));
} catch (Error $e) {
echo $e::class, ": ", $e->getMessage(), PHP_EOL;
}
try {
var_dump($finfo->set_flags(FILEINFO_NONE));
} catch (Error $e) {
echo $e::class, ": ", $e->getMessage(), PHP_EOL;
}
try {
var_dump(finfo_file($finfo, __FILE__));
} catch (Error $e) {
echo $e::class, ": ", $e->getMessage(), PHP_EOL;
}
try {
var_dump($finfo->file(__FILE__));
} catch (Error $e) {
echo $e::class, ": ", $e->getMessage(), PHP_EOL;
}
try {
var_dump(finfo_buffer($finfo, file_get_contents(__FILE__)));
} catch (Error $e) {
echo $e::class, ": ", $e->getMessage(), PHP_EOL;
}
try {
var_dump($finfo->file(file_get_contents(__FILE__)));
} catch (Error $e) {
echo $e::class, ": ", $e->getMessage(), PHP_EOL;
}
?>
--EXPECT--
Error: Invalid finfo object
Error: Invalid finfo object
Error: Invalid finfo object
Error: Invalid finfo object
Error: Invalid finfo object
Error: Invalid finfo object