Commit 3129b3baab5 for php.net

commit 3129b3baab5ab5052a3aa7f7c5cf3d5151983b4f
Author: Gina Peter Banyard <girgias@php.net>
Date:   Mon Jul 13 17:26:52 2026 +0100

    bz2: refactor filter creation to do parameter validation (#22307)

diff --git a/ext/bz2/bz2_filter.c b/ext/bz2/bz2_filter.c
index ec042a51a3b..845c11865f1 100644
--- a/ext/bz2/bz2_filter.c
+++ b/ext/bz2/bz2_filter.c
@@ -375,22 +375,8 @@ static const php_stream_filter_ops php_bz2_compress_ops = {

 /* }}} */

-/* {{{ bzip2.* common factory */
-
-static php_stream_filter *php_bz2_filter_create(const char *filtername, zval *filterparams, bool persistent)
-{
-	const php_stream_filter_ops *fops = NULL;
-	php_stream_filter_seekable_t write_seekable;
-	php_bz2_filter_data *data;
-	int status = BZ_OK;
-
-	if (php_stream_filter_parse_write_seek_mode(filterparams, &write_seekable) == FAILURE) {
-		return NULL;
-	}
-
-	/* Create this filter */
-	data = pecalloc(1, sizeof(php_bz2_filter_data), persistent);
-
+static php_bz2_filter_data *php_bz2_filter_data_new(bool persistent) {
+	php_bz2_filter_data *data = pecalloc(1, sizeof(php_bz2_filter_data), persistent);
 	/* Circular reference */
 	data->strm.opaque = (void *) data;

@@ -401,86 +387,143 @@ static php_stream_filter *php_bz2_filter_create(const char *filtername, zval *fi
 	data->strm.next_in = data->inbuf = (char *) pemalloc(data->inbuf_len, persistent);
 	data->strm.avail_in = 0;
 	data->strm.next_out = data->outbuf = (char *) pemalloc(data->outbuf_len, persistent);
+	return data;
+}

-	if (strcasecmp(filtername, "bzip2.decompress") == 0) {
-		data->small_footprint = 0;
-		data->expect_concatenated = 0;
-
-		if (filterparams) {
-			zval *tmpzval = NULL;
+static php_stream_filter *php_bz2_decompress_filter_create(zval *filter_params, bool persistent) {
+	php_stream_filter_seekable_t write_seekable = PSFS_SEEKABLE_ALWAYS;
+	bool small_footprint = false;
+	bool expect_concatenated = false;
+
+	if (filter_params) {
+		if (UNEXPECTED(
+			Z_TYPE_P(filter_params) != IS_TRUE
+			&& Z_TYPE_P(filter_params) != IS_FALSE
+			&& Z_TYPE_P(filter_params) != IS_ARRAY
+			&& Z_TYPE_P(filter_params) != IS_OBJECT
+		)) {
+			php_error_docref(NULL, E_WARNING,
+				"Filter parameters for bzip2.decompress filter must be of type array|object|bool, %s given",
+				zend_zval_type_name(filter_params)
+			);
+			return NULL;
+		}

-			if (Z_TYPE_P(filterparams) == IS_ARRAY || Z_TYPE_P(filterparams) == IS_OBJECT) {
-				HashTable *ht = HASH_OF(filterparams);
+		if (Z_TYPE_P(filter_params) == IS_TRUE || Z_TYPE_P(filter_params) == IS_FALSE) {
+			small_footprint = Z_TYPE_P(filter_params) == IS_TRUE;
+		} else {
+			ZEND_ASSERT(Z_TYPE_P(filter_params) == IS_ARRAY || Z_TYPE_P(filter_params) == IS_OBJECT);

-				if ((tmpzval = zend_hash_str_find_ind(ht, "concatenated", sizeof("concatenated")-1))) {
-					data->expect_concatenated = zend_is_true(tmpzval);
-					tmpzval = NULL;
-				}
+			const HashTable *filter_params_ht = HASH_OF(filter_params);
+			/* TODO: convert php_stream_filter_parse_write_seek_mode() to take HashTable */
+			if (php_stream_filter_parse_write_seek_mode(filter_params, &write_seekable) == FAILURE) {
+				return NULL;
+			}

-				tmpzval = zend_hash_str_find_ind(ht, "small", sizeof("small")-1);
-			} else {
-				tmpzval = filterparams;
+			const zval *concatenated = zend_hash_str_find_ind(filter_params_ht, ZEND_STRL("concatenated"));
+			if (concatenated) {
+				expect_concatenated = zend_is_true(concatenated);
 			}

-			if (tmpzval) {
-				data->small_footprint = zend_is_true(tmpzval);
+			const zval *small = zend_hash_str_find_ind(filter_params_ht, ZEND_STRL("small"));
+			if (small) {
+				small_footprint = zend_is_true(small);
 			}
 		}
+	}

-		data->status = PHP_BZ2_UNINITIALIZED;
-		fops = &php_bz2_decompress_ops;
-	} else if (strcasecmp(filtername, "bzip2.compress") == 0) {
-		int blockSize100k = PHP_BZ2_FILTER_DEFAULT_BLOCKSIZE;
-		int workFactor = PHP_BZ2_FILTER_DEFAULT_WORKFACTOR;
-
-		if (filterparams) {
-			zval *tmpzval;
-
-			if (Z_TYPE_P(filterparams) == IS_ARRAY || Z_TYPE_P(filterparams) == IS_OBJECT) {
-				HashTable *ht = HASH_OF(filterparams);
-
-				if ((tmpzval = zend_hash_str_find_ind(ht, "blocks", sizeof("blocks")-1))) {
-					/* How much memory to allocate (1 - 9) x 100kb */
-					zend_long blocks = zval_get_long(tmpzval);
-					if (blocks < 1 || blocks > 9) {
-						php_error_docref(NULL, E_WARNING, "Invalid parameter given for number of blocks to allocate (" ZEND_LONG_FMT ")", blocks);
-					} else {
-						blockSize100k = (int) blocks;
-					}
-				}
+	php_bz2_filter_data *data = php_bz2_filter_data_new(persistent);
+	/* Save configuration for reset */
+	data->small_footprint = small_footprint;
+	data->expect_concatenated = expect_concatenated;
+	data->status = PHP_BZ2_UNINITIALIZED;

-				if ((tmpzval = zend_hash_str_find_ind(ht, "work", sizeof("work")-1))) {
-					/* Work Factor (0 - 250) */
-					zend_long work = zval_get_long(tmpzval);
-					if (work < 0 || work > 250) {
-						php_error_docref(NULL, E_WARNING, "Invalid parameter given for work factor (" ZEND_LONG_FMT ")", work);
-					} else {
-						workFactor = (int) work;
-					}
-				}
-			}
+	return php_stream_filter_alloc(&php_bz2_decompress_ops, data, persistent, PSFS_SEEKABLE_START, write_seekable);
+}
+
+static php_stream_filter *php_bz2_compress_filter_create(zval *filter_params, bool persistent) {
+	php_stream_filter_seekable_t write_seekable = PSFS_SEEKABLE_ALWAYS;
+	int blockSize100k = PHP_BZ2_FILTER_DEFAULT_BLOCKSIZE;
+	int workFactor = PHP_BZ2_FILTER_DEFAULT_WORKFACTOR;
+
+	if (filter_params) {
+		if (UNEXPECTED(Z_TYPE_P(filter_params) != IS_ARRAY && Z_TYPE_P(filter_params) != IS_OBJECT)) {
+			php_error_docref(NULL, E_WARNING,
+				"Filter parameters for bzip2.compress filter must be of type array|object, %s given",
+				zend_zval_type_name(filter_params)
+			);
+			return NULL;
 		}

-		/* Save configuration for reset */
-		data->blockSize100k = blockSize100k;
-		data->workFactor = workFactor;
+		const HashTable *filter_params_ht = HASH_OF(filter_params);
+		/* TODO: convert php_stream_filter_parse_write_seek_mode() to take HashTable */
+		if (php_stream_filter_parse_write_seek_mode(filter_params, &write_seekable) == FAILURE) {
+			return NULL;
+		}

-		status = BZ2_bzCompressInit(&(data->strm), blockSize100k, 0, workFactor);
-		data->is_flushed = 1;
-		fops = &php_bz2_compress_ops;
-	} else {
-		status = BZ_DATA_ERROR;
+		const zval *blocks_zv = zend_hash_str_find_ind(filter_params_ht, ZEND_STRL("blocks"));
+		if (blocks_zv) {
+			ZEND_ASSERT(Z_TYPE_P(blocks_zv) != IS_INDIRECT);
+			bool failed = false;
+			/* How much memory to allocate (1 - 9) x 100kb */
+			zend_long blocks = zval_try_get_long(blocks_zv, &failed);
+			if (UNEXPECTED(failed)) {
+				php_error_docref(NULL, E_WARNING, "Number of blocks parameter must be of type int, %s given", zend_zval_type_name(blocks_zv));
+				return NULL;
+			} else if (blocks < 1 || blocks > 9) {
+				php_error_docref(NULL, E_WARNING, "Number of blocks to allocate must be between 1 and 9, " ZEND_LONG_FMT " given", blocks);
+				return NULL;
+			} else {
+				blockSize100k = (int) blocks;
+			}
+		}
+
+		const zval *work_zv = zend_hash_str_find_ind(filter_params_ht, ZEND_STRL("work"));
+		if (work_zv) {
+			ZEND_ASSERT(Z_TYPE_P(work_zv) != IS_INDIRECT);
+			bool failed = false;
+			/* Work Factor (0 - 250) */
+			zend_long work = zval_try_get_long(work_zv, &failed);
+			if (UNEXPECTED(failed)) {
+				php_error_docref(NULL, E_WARNING, "Work factor parameter must be of type int, %s given", zend_zval_type_name(work_zv));
+				return NULL;
+			} else if (work < 0 || work > 250) {
+				php_error_docref(NULL, E_WARNING, "Work factor must be between 0 and 250, " ZEND_LONG_FMT " given", work);
+				return NULL;
+			} else {
+				workFactor = (int) work;
+			}
+		}
 	}

-	if (status != BZ_OK) {
+	php_bz2_filter_data *data = php_bz2_filter_data_new(persistent);
+	/* Save configuration for reset */
+	data->blockSize100k = blockSize100k;
+	data->workFactor = workFactor;
+
+	int status = BZ2_bzCompressInit(&(data->strm), blockSize100k, 0, workFactor);
+	if (UNEXPECTED(status != BZ_OK)) {
 		/* Unspecified (probably strm) error, let stream-filter error do its own whining */
 		pefree(data->strm.next_in, persistent);
 		pefree(data->strm.next_out, persistent);
 		pefree(data, persistent);
 		return NULL;
 	}
+	data->is_flushed = true;
+
+	return php_stream_filter_alloc(&php_bz2_compress_ops, data, persistent, PSFS_SEEKABLE_START, write_seekable);
+}

-	return php_stream_filter_alloc(fops, data, persistent, PSFS_SEEKABLE_START, write_seekable);
+/* {{{ bzip2.* common factory */
+static php_stream_filter *php_bz2_filter_create(const char *filtername, zval *filterparams, bool persistent)
+{
+	if (strcasecmp(filtername, "bzip2.decompress") == 0) {
+		return php_bz2_decompress_filter_create(filterparams, persistent);
+	} else if (strcasecmp(filtername, "bzip2.compress") == 0) {
+		return php_bz2_compress_filter_create(filterparams, persistent);
+	} else {
+		return NULL;
+	}
 }

 const php_stream_filter_factory php_bz2_filter_factory = {
diff --git a/ext/bz2/tests/bug72447.phpt b/ext/bz2/tests/bug72447.phpt
index 11f3bd9136b..8e2fc2b7980 100644
--- a/ext/bz2/tests/bug72447.phpt
+++ b/ext/bz2/tests/bug72447.phpt
@@ -16,4 +16,6 @@
 unlink('testfile');
 ?>
 --EXPECTF--
-Warning: stream_filter_append(): Invalid parameter given for number of blocks to allocate (0) in %s%ebug72447.php on line %d
+Warning: stream_filter_append(): Number of blocks parameter must be of type int, string given in %s on line %d
+
+Warning: stream_filter_append(): Unable to create or locate filter "bzip2.compress" in %s on line %d
diff --git a/ext/bz2/tests/bz2_filter_compress_errors.phpt b/ext/bz2/tests/bz2_filter_compress_errors.phpt
new file mode 100644
index 00000000000..48f6759d642
--- /dev/null
+++ b/ext/bz2/tests/bz2_filter_compress_errors.phpt
@@ -0,0 +1,67 @@
+--TEST--
+bzip2.compress filter param errors
+--EXTENSIONS--
+bz2
+--FILE--
+<?php
+$fp = fopen('php://stdout', 'w');
+
+$param = 'not an array';
+var_dump(stream_filter_append($fp, 'bzip2.compress', STREAM_FILTER_WRITE, $param));
+
+$param = ['blocks' => 'not an int'];
+var_dump(stream_filter_append($fp, 'bzip2.compress', STREAM_FILTER_WRITE, $param));
+
+$param = ['blocks' => '15'];
+var_dump(stream_filter_append($fp, 'bzip2.compress', STREAM_FILTER_WRITE, $param));
+
+$param = ['blocks' => '0'];
+var_dump(stream_filter_append($fp, 'bzip2.compress', STREAM_FILTER_WRITE, $param));
+
+$param = ['work' => 'not an int'];
+var_dump(stream_filter_append($fp, 'bzip2.compress', STREAM_FILTER_WRITE, $param));
+
+$param = ['work' => '251'];
+var_dump(stream_filter_append($fp, 'bzip2.compress', STREAM_FILTER_WRITE, $param));
+
+$param = ['work' => '-1'];
+var_dump(stream_filter_append($fp, 'bzip2.compress', STREAM_FILTER_WRITE, $param));
+
+fclose($fp);
+
+?>
+--EXPECTF--
+Warning: stream_filter_append(): Filter parameters for bzip2.compress filter must be of type array|object, string given in %s on line %d
+
+Warning: stream_filter_append(): Unable to create or locate filter "bzip2.compress" in %s on line %d
+bool(false)
+
+Warning: stream_filter_append(): Number of blocks parameter must be of type int, string given in %s on line %d
+
+Warning: stream_filter_append(): Unable to create or locate filter "bzip2.compress" in %s on line %d
+bool(false)
+
+Warning: stream_filter_append(): Number of blocks to allocate must be between 1 and 9, 15 given in %s on line %d
+
+Warning: stream_filter_append(): Unable to create or locate filter "bzip2.compress" in %s on line %d
+bool(false)
+
+Warning: stream_filter_append(): Number of blocks to allocate must be between 1 and 9, 0 given in %s on line %d
+
+Warning: stream_filter_append(): Unable to create or locate filter "bzip2.compress" in %s on line %d
+bool(false)
+
+Warning: stream_filter_append(): Work factor parameter must be of type int, string given in %s on line %d
+
+Warning: stream_filter_append(): Unable to create or locate filter "bzip2.compress" in %s on line %d
+bool(false)
+
+Warning: stream_filter_append(): Work factor must be between 0 and 250, 251 given in %s on line %d
+
+Warning: stream_filter_append(): Unable to create or locate filter "bzip2.compress" in %s on line %d
+bool(false)
+
+Warning: stream_filter_append(): Work factor must be between 0 and 250, -1 given in %s on line %d
+
+Warning: stream_filter_append(): Unable to create or locate filter "bzip2.compress" in %s on line %d
+bool(false)
diff --git a/ext/bz2/tests/bz2_filter_decompress_errors.phpt b/ext/bz2/tests/bz2_filter_decompress_errors.phpt
new file mode 100644
index 00000000000..f89029bd50c
--- /dev/null
+++ b/ext/bz2/tests/bz2_filter_decompress_errors.phpt
@@ -0,0 +1,19 @@
+--TEST--
+bzip2.decompress filter param errors
+--EXTENSIONS--
+bz2
+--FILE--
+<?php
+$fp = fopen('php://stdout', 'w');
+
+$param = 'not an array or bool';
+var_dump(stream_filter_append($fp, 'bzip2.decompress', STREAM_FILTER_WRITE, $param));
+
+fclose($fp);
+
+?>
+--EXPECTF--
+Warning: stream_filter_append(): Filter parameters for bzip2.decompress filter must be of type array|object|bool, string given in %s on line %d
+
+Warning: stream_filter_append(): Unable to create or locate filter "bzip2.decompress" in %s on line %d
+bool(false)
diff --git a/ext/bz2/tests/bz2_filter_unknown_errors.phpt b/ext/bz2/tests/bz2_filter_unknown_errors.phpt
new file mode 100644
index 00000000000..7364481185a
--- /dev/null
+++ b/ext/bz2/tests/bz2_filter_unknown_errors.phpt
@@ -0,0 +1,16 @@
+--TEST--
+bzip2 filter error on unknown filter name
+--EXTENSIONS--
+bz2
+--FILE--
+<?php
+$fp = fopen('php://stdout', 'w');
+
+var_dump(stream_filter_append($fp, 'bzip2.i_dont_exist', STREAM_FILTER_WRITE));
+
+fclose($fp);
+
+?>
+--EXPECTF--
+Warning: stream_filter_append(): Unable to create or locate filter "bzip2.i_dont_exist" in %s on line %d
+bool(false)