From 5bed2a8920f89ce07fdecd44ed845ce544947f50 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+ndossche@users.noreply.github.com> Date: Sun, 16 Nov 2025 05:57:00 -0800 Subject: [PATCH 1/4] Avoid string copy in ZipArchive::addFromString() (#20497) Instead of copying the data we increment the refcount of the string. --- UPGRADING | 3 +++ ext/zip/php_zip.c | 11 +++++------ ext/zip/php_zip.h | 2 +- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/UPGRADING b/UPGRADING index 3b0301522d46..1e84b8beee97 100644 --- a/UPGRADING +++ b/UPGRADING @@ -130,3 +130,6 @@ PHP 8.6 UPGRADE NOTES . Improved performance of array_walk(). . Improved performance of intval('+0b...', 2) and intval('0b...', 2). . Improved performance of str_split(). + +- Zip: + . Avoid string copies in ZipArchive::addFromString(). diff --git a/ext/zip/php_zip.c b/ext/zip/php_zip.c index 40c098d8901c..06bd41d602c0 100644 --- a/ext/zip/php_zip.c +++ b/ext/zip/php_zip.c @@ -1014,7 +1014,7 @@ static void php_zip_object_free_storage(zend_object *object) /* {{{ */ if (intern->buffers_cnt>0) { for (i=0; ibuffers_cnt; i++) { - efree(intern->buffers[i]); + zend_string_release(intern->buffers[i]); } efree(intern->buffers); } @@ -1868,17 +1868,16 @@ PHP_METHOD(ZipArchive, addFromString) ze_obj = Z_ZIP_P(self); if (ze_obj->buffers_cnt) { - ze_obj->buffers = (char **)safe_erealloc(ze_obj->buffers, sizeof(char *), (ze_obj->buffers_cnt+1), 0); + ze_obj->buffers = safe_erealloc(ze_obj->buffers, sizeof(*ze_obj->buffers), (ze_obj->buffers_cnt + 1), 0); pos = ze_obj->buffers_cnt++; } else { - ze_obj->buffers = (char **)emalloc(sizeof(char *)); + ze_obj->buffers = emalloc(sizeof(*ze_obj->buffers)); ze_obj->buffers_cnt++; pos = 0; } - ze_obj->buffers[pos] = (char *)safe_emalloc(ZSTR_LEN(buffer), 1, 1); - memcpy(ze_obj->buffers[pos], ZSTR_VAL(buffer), ZSTR_LEN(buffer) + 1); + ze_obj->buffers[pos] = zend_string_copy(buffer); - zs = zip_source_buffer(intern, ze_obj->buffers[pos], ZSTR_LEN(buffer), 0); + zs = zip_source_buffer(intern, ZSTR_VAL(buffer), ZSTR_LEN(buffer), 0); if (zs == NULL) { RETURN_FALSE; diff --git a/ext/zip/php_zip.h b/ext/zip/php_zip.h index 61e7fb8dad1e..5dc7bab25004 100644 --- a/ext/zip/php_zip.h +++ b/ext/zip/php_zip.h @@ -68,7 +68,7 @@ typedef struct _ze_zip_read_rsrc { /* Extends zend object */ typedef struct _ze_zip_object { struct zip *za; - char **buffers; + zend_string **buffers; HashTable *prop_handler; char *filename; int filename_len; From e844700333a3f23f2166afdc5fd955f335d52861 Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Thu, 2 Oct 2025 13:05:42 +0100 Subject: [PATCH 2/4] ext/standard/tests/filter: Add zlib dependency And remove strange function to check if a filter is available --- ext/standard/tests/filters/filter_errors.inc | 11 ----------- .../filters/filter_errors_convert_base64_decode.phpt | 2 -- .../tests/filters/filter_errors_zlib_inflate.phpt | 4 ++-- ext/standard/tests/filters/gh13264.phpt | 4 ++-- 4 files changed, 4 insertions(+), 17 deletions(-) diff --git a/ext/standard/tests/filters/filter_errors.inc b/ext/standard/tests/filters/filter_errors.inc index 18711844f834..e933eea725e2 100644 --- a/ext/standard/tests/filters/filter_errors.inc +++ b/ext/standard/tests/filters/filter_errors.inc @@ -1,16 +1,5 @@ --FILE-- +--EXTENSIONS-- +zlib --FILE-- +--EXTENSIONS-- +zlib --FILE-- Date: Thu, 2 Oct 2025 18:38:39 +0100 Subject: [PATCH 3/4] Move stream filter tests to ext/standard/tests/filters/ folder --- {Zend/tests => ext/standard/tests/filters}/bug21478.phpt | 0 ext/standard/tests/{file => filters}/bug39551.phpt | 0 ext/standard/tests/{streams => filters}/bug77069.phpt | 0 ext/standard/tests/{streams => filters}/bug77080.phpt | 0 {Zend/tests => ext/standard/tests/filters}/bug78406.phpt | 0 ext/standard/tests/{streams => filters}/bug78506.phpt | 0 ext/standard/tests/{streams => filters}/bug79984.phpt | 0 ext/standard/tests/{streams => filters}/gh17650.phpt | 0 .../tests/{streams => filters}/stream_filter_register.phpt | 0 .../tests/{streams => filters}/stream_multi_filters_close.phpt | 0 .../tests/{streams => filters}/user_streams_consumed_bug.phpt | 0 ext/standard/tests/{file => filters}/userfilters.phpt | 0 12 files changed, 0 insertions(+), 0 deletions(-) rename {Zend/tests => ext/standard/tests/filters}/bug21478.phpt (100%) rename ext/standard/tests/{file => filters}/bug39551.phpt (100%) rename ext/standard/tests/{streams => filters}/bug77069.phpt (100%) rename ext/standard/tests/{streams => filters}/bug77080.phpt (100%) rename {Zend/tests => ext/standard/tests/filters}/bug78406.phpt (100%) rename ext/standard/tests/{streams => filters}/bug78506.phpt (100%) rename ext/standard/tests/{streams => filters}/bug79984.phpt (100%) rename ext/standard/tests/{streams => filters}/gh17650.phpt (100%) rename ext/standard/tests/{streams => filters}/stream_filter_register.phpt (100%) rename ext/standard/tests/{streams => filters}/stream_multi_filters_close.phpt (100%) rename ext/standard/tests/{streams => filters}/user_streams_consumed_bug.phpt (100%) rename ext/standard/tests/{file => filters}/userfilters.phpt (100%) diff --git a/Zend/tests/bug21478.phpt b/ext/standard/tests/filters/bug21478.phpt similarity index 100% rename from Zend/tests/bug21478.phpt rename to ext/standard/tests/filters/bug21478.phpt diff --git a/ext/standard/tests/file/bug39551.phpt b/ext/standard/tests/filters/bug39551.phpt similarity index 100% rename from ext/standard/tests/file/bug39551.phpt rename to ext/standard/tests/filters/bug39551.phpt diff --git a/ext/standard/tests/streams/bug77069.phpt b/ext/standard/tests/filters/bug77069.phpt similarity index 100% rename from ext/standard/tests/streams/bug77069.phpt rename to ext/standard/tests/filters/bug77069.phpt diff --git a/ext/standard/tests/streams/bug77080.phpt b/ext/standard/tests/filters/bug77080.phpt similarity index 100% rename from ext/standard/tests/streams/bug77080.phpt rename to ext/standard/tests/filters/bug77080.phpt diff --git a/Zend/tests/bug78406.phpt b/ext/standard/tests/filters/bug78406.phpt similarity index 100% rename from Zend/tests/bug78406.phpt rename to ext/standard/tests/filters/bug78406.phpt diff --git a/ext/standard/tests/streams/bug78506.phpt b/ext/standard/tests/filters/bug78506.phpt similarity index 100% rename from ext/standard/tests/streams/bug78506.phpt rename to ext/standard/tests/filters/bug78506.phpt diff --git a/ext/standard/tests/streams/bug79984.phpt b/ext/standard/tests/filters/bug79984.phpt similarity index 100% rename from ext/standard/tests/streams/bug79984.phpt rename to ext/standard/tests/filters/bug79984.phpt diff --git a/ext/standard/tests/streams/gh17650.phpt b/ext/standard/tests/filters/gh17650.phpt similarity index 100% rename from ext/standard/tests/streams/gh17650.phpt rename to ext/standard/tests/filters/gh17650.phpt diff --git a/ext/standard/tests/streams/stream_filter_register.phpt b/ext/standard/tests/filters/stream_filter_register.phpt similarity index 100% rename from ext/standard/tests/streams/stream_filter_register.phpt rename to ext/standard/tests/filters/stream_filter_register.phpt diff --git a/ext/standard/tests/streams/stream_multi_filters_close.phpt b/ext/standard/tests/filters/stream_multi_filters_close.phpt similarity index 100% rename from ext/standard/tests/streams/stream_multi_filters_close.phpt rename to ext/standard/tests/filters/stream_multi_filters_close.phpt diff --git a/ext/standard/tests/streams/user_streams_consumed_bug.phpt b/ext/standard/tests/filters/user_streams_consumed_bug.phpt similarity index 100% rename from ext/standard/tests/streams/user_streams_consumed_bug.phpt rename to ext/standard/tests/filters/user_streams_consumed_bug.phpt diff --git a/ext/standard/tests/file/userfilters.phpt b/ext/standard/tests/filters/userfilters.phpt similarity index 100% rename from ext/standard/tests/file/userfilters.phpt rename to ext/standard/tests/filters/userfilters.phpt From 9c33091713f8d289c3fc924b8beefd6174b65bb1 Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Thu, 2 Oct 2025 19:30:20 +0100 Subject: [PATCH 4/4] ext/standard: add a bunch of whacky stream filter tests --- ...er_class_coerce_consumed_by_ref_param.phpt | 32 +++++++++++++++++ ...ter_register_class_completely_invalid.phpt | 33 +++++++++++++++++ ...mpletely_invalid_filtername_prop_type.phpt | 28 +++++++++++++++ ...s_completely_invalid_no_dynamic_props.phpt | 28 +++++++++++++++ ...egister_class_private_filtername_prop.phpt | 28 +++++++++++++++ ...ilter_register_class_throwing_onclose.phpt | 30 ++++++++++++++++ ...lter_register_class_throwing_oncreate.phpt | 27 ++++++++++++++ ...am_filter_register_filter_always_feed.phpt | 24 +++++++++++++ ...eam_filter_register_mock_class_filter.phpt | 30 ++++++++++++++++ ...ock_class_filter_incorect_return_type.phpt | 32 +++++++++++++++++ ...register_mock_class_filter_is_private.phpt | 35 +++++++++++++++++++ ...am_filter_register_non_existing_class.phpt | 21 +++++++++++ 12 files changed, 348 insertions(+) create mode 100644 ext/standard/tests/filters/stream_filter_register_class_coerce_consumed_by_ref_param.phpt create mode 100644 ext/standard/tests/filters/stream_filter_register_class_completely_invalid.phpt create mode 100644 ext/standard/tests/filters/stream_filter_register_class_completely_invalid_filtername_prop_type.phpt create mode 100644 ext/standard/tests/filters/stream_filter_register_class_completely_invalid_no_dynamic_props.phpt create mode 100644 ext/standard/tests/filters/stream_filter_register_class_private_filtername_prop.phpt create mode 100644 ext/standard/tests/filters/stream_filter_register_class_throwing_onclose.phpt create mode 100644 ext/standard/tests/filters/stream_filter_register_class_throwing_oncreate.phpt create mode 100644 ext/standard/tests/filters/stream_filter_register_filter_always_feed.phpt create mode 100644 ext/standard/tests/filters/stream_filter_register_mock_class_filter.phpt create mode 100644 ext/standard/tests/filters/stream_filter_register_mock_class_filter_incorect_return_type.phpt create mode 100644 ext/standard/tests/filters/stream_filter_register_mock_class_filter_is_private.phpt create mode 100644 ext/standard/tests/filters/stream_filter_register_non_existing_class.phpt diff --git a/ext/standard/tests/filters/stream_filter_register_class_coerce_consumed_by_ref_param.phpt b/ext/standard/tests/filters/stream_filter_register_class_coerce_consumed_by_ref_param.phpt new file mode 100644 index 000000000000..f5f0ecca7975 --- /dev/null +++ b/ext/standard/tests/filters/stream_filter_register_class_coerce_consumed_by_ref_param.phpt @@ -0,0 +1,32 @@ +--TEST-- +stream_filter_register() with a class that coerces the $consumed parameter of filter method +--XFAIL-- +This leaks memory +--FILE-- + +--EXPECTF-- +bool(true) +resource(4) of type (stream filter) + +Warning: Object of class stdClass could not be converted to int in %s on line %d + +Warning: fwrite(): Unprocessed filter buckets remaining on input brigade in %s on line %d +int(1) + +Warning: Object of class stdClass could not be converted to int in Unknown on line 0 diff --git a/ext/standard/tests/filters/stream_filter_register_class_completely_invalid.phpt b/ext/standard/tests/filters/stream_filter_register_class_completely_invalid.phpt new file mode 100644 index 000000000000..6bd3e7bc1c9d --- /dev/null +++ b/ext/standard/tests/filters/stream_filter_register_class_completely_invalid.phpt @@ -0,0 +1,33 @@ +--TEST-- +stream_filter_register() with a class name that exist but does not extend php_user_filter nor mock anything +--FILE-- + +--EXPECTF-- +bool(true) + +Deprecated: Creation of dynamic property foo::$filtername is deprecated in %s on line %d + +Deprecated: Creation of dynamic property foo::$params is deprecated in %s on line %d +resource(%d) of type (stream filter) + +Warning: fwrite(): Unprocessed filter buckets remaining on input brigade in %s on line %d + +Fatal error: Uncaught Error: Invalid callback foo::filter, class foo does not have a method "filter" in %s:%d +Stack trace: +#0 %s(%d): fwrite(Resource id #%d, 'Hello\n') +#1 {main} + thrown in %s on line %d + +Fatal error: Invalid callback foo::filter, class foo does not have a method "filter" in Unknown on line 0 diff --git a/ext/standard/tests/filters/stream_filter_register_class_completely_invalid_filtername_prop_type.phpt b/ext/standard/tests/filters/stream_filter_register_class_completely_invalid_filtername_prop_type.phpt new file mode 100644 index 000000000000..793c112791a9 --- /dev/null +++ b/ext/standard/tests/filters/stream_filter_register_class_completely_invalid_filtername_prop_type.phpt @@ -0,0 +1,28 @@ +--TEST-- +stream_filter_register() with a class name exist but does not extend php_user_filter and defines a $filtername prop with different type +--FILE-- + +--EXPECTF-- +bool(true) + +Deprecated: Creation of dynamic property foo::$params is deprecated in %s on line %d + +Fatal error: Uncaught TypeError: Cannot assign string to property foo::$filtername of type array in %s:%d +Stack trace: +#0 %s(%d): stream_filter_append(Resource id #2, 'invalid_filter') +#1 {main} + thrown in %s on line %d + +Fatal error: Invalid callback foo::filter, class foo does not have a method "filter" in Unknown on line 0 diff --git a/ext/standard/tests/filters/stream_filter_register_class_completely_invalid_no_dynamic_props.phpt b/ext/standard/tests/filters/stream_filter_register_class_completely_invalid_no_dynamic_props.phpt new file mode 100644 index 000000000000..86994d5c1b66 --- /dev/null +++ b/ext/standard/tests/filters/stream_filter_register_class_completely_invalid_no_dynamic_props.phpt @@ -0,0 +1,28 @@ +--TEST-- +stream_filter_register() with a class name exist but does not extend php_user_filter and cannot have dynamic properties +--FILE-- + +--EXPECTF-- +bool(true) + +Fatal error: Uncaught Error: Cannot create dynamic property SensitiveParameter::$filtername in %s:%d +Stack trace: +#0 %s(%d): stream_filter_append(Resource id #%d, 'invalid_filter') +#1 {main} + +Next Error: Cannot create dynamic property SensitiveParameter::$params in %s:%d +Stack trace: +#0 %s(%d): stream_filter_append(Resource id #%d, 'invalid_filter') +#1 {main} + thrown in %s on line %d + +Fatal error: Invalid callback SensitiveParameter::filter, class SensitiveParameter does not have a method "filter" in Unknown on line 0 diff --git a/ext/standard/tests/filters/stream_filter_register_class_private_filtername_prop.phpt b/ext/standard/tests/filters/stream_filter_register_class_private_filtername_prop.phpt new file mode 100644 index 000000000000..6b9be4bbbb4b --- /dev/null +++ b/ext/standard/tests/filters/stream_filter_register_class_private_filtername_prop.phpt @@ -0,0 +1,28 @@ +--TEST-- +stream_filter_register() with a class name exist but does not extend php_user_filter and defines a private $filtername prop +--FILE-- + +--EXPECTF-- +bool(true) + +Deprecated: Creation of dynamic property foo::$params is deprecated in %s on line %d + +Fatal error: Uncaught Error: Cannot access private property foo::$filtername in %s:%d +Stack trace: +#0 %s(%d): stream_filter_append(Resource id #2, 'invalid_filter') +#1 {main} + thrown in %s on line %d + +Fatal error: Invalid callback foo::filter, class foo does not have a method "filter" in Unknown on line 0 diff --git a/ext/standard/tests/filters/stream_filter_register_class_throwing_onclose.phpt b/ext/standard/tests/filters/stream_filter_register_class_throwing_onclose.phpt new file mode 100644 index 000000000000..76b925cc0483 --- /dev/null +++ b/ext/standard/tests/filters/stream_filter_register_class_throwing_onclose.phpt @@ -0,0 +1,30 @@ +--TEST-- +stream_filter_register() with a class that has a onclose method that throws +--FILE-- + +--EXPECTF-- +bool(true) +resource(4) of type (stream filter) + +Warning: fwrite(): Unprocessed filter buckets remaining on input brigade in %s on line %d +bool(false) + +Fatal error: Uncaught Exception: No in %s:%d +Stack trace: +#0 [internal function]: foo->onclose() +#1 {main} + thrown in %s on line %d diff --git a/ext/standard/tests/filters/stream_filter_register_class_throwing_oncreate.phpt b/ext/standard/tests/filters/stream_filter_register_class_throwing_oncreate.phpt new file mode 100644 index 000000000000..3e429a2cc343 --- /dev/null +++ b/ext/standard/tests/filters/stream_filter_register_class_throwing_oncreate.phpt @@ -0,0 +1,27 @@ +--TEST-- +stream_filter_register() with a class that has a oncreate method that throws +--FILE-- + +--EXPECTF-- +bool(true) + +Fatal error: Uncaught Exception: No in %s:%d +Stack trace: +#0 [internal function]: foo->oncreate() +#1 %s(%d): stream_filter_append(Resource id #2, 'invalid_filter') +#2 {main} + thrown in %s on line %d diff --git a/ext/standard/tests/filters/stream_filter_register_filter_always_feed.phpt b/ext/standard/tests/filters/stream_filter_register_filter_always_feed.phpt new file mode 100644 index 000000000000..7a3d3f889993 --- /dev/null +++ b/ext/standard/tests/filters/stream_filter_register_filter_always_feed.phpt @@ -0,0 +1,24 @@ +--TEST-- +stream_filter_register() with a filter method always returning PSFS_FEED_ME +--FILE-- + +--EXPECTF-- +bool(true) +resource(4) of type (stream filter) + +Warning: fwrite(): Unprocessed filter buckets remaining on input brigade in %s on line %d +int(0) diff --git a/ext/standard/tests/filters/stream_filter_register_mock_class_filter.phpt b/ext/standard/tests/filters/stream_filter_register_mock_class_filter.phpt new file mode 100644 index 000000000000..4d2309a718eb --- /dev/null +++ b/ext/standard/tests/filters/stream_filter_register_mock_class_filter.phpt @@ -0,0 +1,30 @@ +--TEST-- +stream_filter_register() with a class name exist that mocks php_user_filter with a filter method +--XFAIL-- +This leaks memory +--FILE-- + +--EXPECTF-- +bool(true) +resource(4) of type (stream filter) + +Warning: fwrite(): Unprocessed filter buckets remaining on input brigade in %s on line %d +int(0) diff --git a/ext/standard/tests/filters/stream_filter_register_mock_class_filter_incorect_return_type.phpt b/ext/standard/tests/filters/stream_filter_register_mock_class_filter_incorect_return_type.phpt new file mode 100644 index 000000000000..b5ec8da3dde1 --- /dev/null +++ b/ext/standard/tests/filters/stream_filter_register_mock_class_filter_incorect_return_type.phpt @@ -0,0 +1,32 @@ +--TEST-- +stream_filter_register() with a class name exist that mocks php_user_filter with a filter method returning not an int +--FILE-- + +--EXPECTF-- +bool(true) +resource(4) of type (stream filter) + +Warning: Object of class stdClass could not be converted to int in %s on line %d + +Warning: fwrite(): Unprocessed filter buckets remaining on input brigade in %s on line %d +int(0) + +Warning: Object of class stdClass could not be converted to int in Unknown on line 0 diff --git a/ext/standard/tests/filters/stream_filter_register_mock_class_filter_is_private.phpt b/ext/standard/tests/filters/stream_filter_register_mock_class_filter_is_private.phpt new file mode 100644 index 000000000000..6abffe1ff5f8 --- /dev/null +++ b/ext/standard/tests/filters/stream_filter_register_mock_class_filter_is_private.phpt @@ -0,0 +1,35 @@ +--TEST-- +stream_filter_register() with a class name exist that mocks php_user_filter with a private filter method +--FILE-- + +--EXPECTF-- +bool(true) +resource(%d) of type (stream filter) + +Warning: fwrite(): Unprocessed filter buckets remaining on input brigade in %s on line %d + +Fatal error: Uncaught Error: Invalid callback foo::filter, cannot access private method foo::filter() in %s:%d +Stack trace: +#0 %s(%d): fwrite(Resource id #%d, 'Hello\n') +#1 {main} + thrown in %s on line %d + +Fatal error: Invalid callback foo::filter, cannot access private method foo::filter() in Unknown on line 0 diff --git a/ext/standard/tests/filters/stream_filter_register_non_existing_class.phpt b/ext/standard/tests/filters/stream_filter_register_non_existing_class.phpt new file mode 100644 index 000000000000..1c08201cff0c --- /dev/null +++ b/ext/standard/tests/filters/stream_filter_register_non_existing_class.phpt @@ -0,0 +1,21 @@ +--TEST-- +stream_filter_register() with a class name that does not exist +--FILE-- + +--EXPECTF-- +bool(true) + +Warning: stream_filter_append(): User-filter "not_existing_filter" requires class "not_existing", but that class is not defined in %s on line %d + +Warning: stream_filter_append(): Unable to create or locate filter "not_existing_filter" in %s on line %d +Hello +int(6)