diff --git a/ext/bz2/bz2_filter.c b/ext/bz2/bz2_filter.c index e1b24f6319f2..09c49fa76687 100644 --- a/ext/bz2/bz2_filter.c +++ b/ext/bz2/bz2_filter.c @@ -381,9 +381,14 @@ static const php_stream_filter_ops php_bz2_compress_ops = { 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); @@ -476,7 +481,7 @@ static php_stream_filter *php_bz2_filter_create(const char *filtername, zval *fi return NULL; } - return php_stream_filter_alloc(fops, data, persistent, PSFS_SEEKABLE_START); + return php_stream_filter_alloc(fops, data, persistent, PSFS_SEEKABLE_START, write_seekable); } const php_stream_filter_factory php_bz2_filter_factory = { diff --git a/ext/bz2/tests/bz2_filter_seek_compress.phpt b/ext/bz2/tests/bz2_filter_seek_compress.phpt index 0656b244484d..557021c9fd0d 100644 --- a/ext/bz2/tests/bz2_filter_seek_compress.phpt +++ b/ext/bz2/tests/bz2_filter_seek_compress.phpt @@ -1,55 +1,51 @@ --TEST-- -bzip2.compress filter with seek to start +bzip2.compress write filter is not reset on seek --EXTENSIONS-- bz2 --FILE-- $size1 ? "YES" : "NO") . "\n"; - +/* Seek to middle also succeeds */ $result = fseek($fp, 50, SEEK_SET); echo "Seek to middle: " . ($result === 0 ? "SUCCESS" : "FAILURE") . "\n"; fclose($fp); +/* Verify the compressed output is still valid */ $fp = fopen($file, 'r'); stream_filter_append($fp, 'bzip2.decompress', STREAM_FILTER_READ); $content = stream_get_contents($fp); fclose($fp); -echo "Decompressed content matches text2: " . ($content === $text2 ? "YES" : "NO") . "\n"; +echo "Decompressed content matches: " . ($content === $text ? "YES" : "NO") . "\n"; ?> --CLEAN-- --EXPECTF-- -Size after first write: 40 +Size after write: %d Seek to start: SUCCESS -Size after second write: 98 -Second write is larger: YES - -Warning: fseek(): Stream filter bzip2.compress is seekable only to start position in %s on line %d -Seek to middle: FAILURE -Decompressed content matches text2: YES +Seek to middle: SUCCESS +Decompressed content matches: YES diff --git a/ext/bz2/tests/bz2_filter_write_seek_modes.phpt b/ext/bz2/tests/bz2_filter_write_seek_modes.phpt new file mode 100644 index 000000000000..b3b4fa39eb16 --- /dev/null +++ b/ext/bz2/tests/bz2_filter_write_seek_modes.phpt @@ -0,0 +1,54 @@ +--TEST-- +bzip2.compress write filter: write_seek_mode parameter +--EXTENSIONS-- +bz2 +--FILE-- + 'reset']); +fwrite($fp, $text1); +ftruncate($fp, 0); +var_dump(fseek($fp, 0, SEEK_SET) === 0); +fwrite($fp, $text2); +fclose($fp); + +$fp = fopen($file, 'r'); +stream_filter_append($fp, 'bzip2.decompress', STREAM_FILTER_READ); +$decoded = stream_get_contents($fp); +fclose($fp); +var_dump($decoded === $text2); + +/* "strict" */ +$fp = fopen($file, 'w+'); +stream_filter_append($fp, 'bzip2.compress', STREAM_FILTER_WRITE, + ['write_seek_mode' => 'strict']); +fwrite($fp, $text1); +var_dump(@fseek($fp, 0, SEEK_SET) === -1); +fclose($fp); + +/* Invalid mode: ValueError */ +$fp = fopen($file, 'w+'); +stream_filter_append($fp, 'bzip2.compress', STREAM_FILTER_WRITE, + ['write_seek_mode' => 'nope']); +fclose($fp); + +?> +--CLEAN-- + +--EXPECTF-- +bool(true) +bool(true) +bool(true) + +Warning: stream_filter_append(): "write_seek_mode" filter parameter must be one of "preserve", "reset", or "strict" in %s + +Warning: stream_filter_append(): Unable to create or locate filter "bzip2.compress" in %s diff --git a/ext/iconv/iconv.c b/ext/iconv/iconv.c index 5dfc5d9a1906..d9b54388a8f8 100644 --- a/ext/iconv/iconv.c +++ b/ext/iconv/iconv.c @@ -2633,9 +2633,14 @@ static const php_stream_filter_ops php_iconv_stream_filter_ops = { static php_stream_filter *php_iconv_stream_filter_factory_create(const char *name, zval *params, bool persistent) { php_iconv_stream_filter *inst; + php_stream_filter_seekable_t write_seekable; const char *from_charset = NULL, *to_charset = NULL; size_t from_charset_len, to_charset_len; + if (php_stream_filter_parse_write_seek_mode(params, &write_seekable) == FAILURE) { + return NULL; + } + if ((from_charset = strchr(name, '.')) == NULL) { return NULL; } @@ -2663,7 +2668,7 @@ static php_stream_filter *php_iconv_stream_filter_factory_create(const char *nam } return php_stream_filter_alloc(&php_iconv_stream_filter_ops, inst, persistent, - PSFS_SEEKABLE_START); + PSFS_SEEKABLE_START, write_seekable); } /* }}} */ diff --git a/ext/standard/filters.c b/ext/standard/filters.c index 90dd471cf848..9a0b8b28e497 100644 --- a/ext/standard/filters.c +++ b/ext/standard/filters.c @@ -63,7 +63,8 @@ static const php_stream_filter_ops strfilter_rot13_ops = { static php_stream_filter *strfilter_rot13_create(const char *filtername, zval *filterparams, bool persistent) { - return php_stream_filter_alloc(&strfilter_rot13_ops, NULL, persistent, PSFS_SEEKABLE_ALWAYS); + return php_stream_filter_alloc(&strfilter_rot13_ops, NULL, persistent, + PSFS_SEEKABLE_ALWAYS, PSFS_SEEKABLE_ALWAYS); } static const php_stream_filter_factory strfilter_rot13_factory = { @@ -147,12 +148,14 @@ static const php_stream_filter_ops strfilter_tolower_ops = { static php_stream_filter *strfilter_toupper_create(const char *filtername, zval *filterparams, bool persistent) { - return php_stream_filter_alloc(&strfilter_toupper_ops, NULL, persistent, PSFS_SEEKABLE_ALWAYS); + return php_stream_filter_alloc(&strfilter_toupper_ops, NULL, persistent, + PSFS_SEEKABLE_ALWAYS, PSFS_SEEKABLE_ALWAYS); } static php_stream_filter *strfilter_tolower_create(const char *filtername, zval *filterparams, bool persistent) { - return php_stream_filter_alloc(&strfilter_tolower_ops, NULL, persistent, PSFS_SEEKABLE_ALWAYS); + return php_stream_filter_alloc(&strfilter_tolower_ops, NULL, persistent, + PSFS_SEEKABLE_ALWAYS, PSFS_SEEKABLE_ALWAYS); } static const php_stream_filter_factory strfilter_toupper_factory = { @@ -1634,7 +1637,7 @@ static const php_stream_filter_ops strfilter_convert_ops = { static php_stream_filter *strfilter_convert_create(const char *filtername, zval *filterparams, bool persistent) { php_convert_filter *inst; - + php_stream_filter_seekable_t write_seekable; const char *dot; int conv_mode = 0; @@ -1648,6 +1651,10 @@ static php_stream_filter *strfilter_convert_create(const char *filtername, zval } ++dot; + if (php_stream_filter_parse_write_seek_mode(filterparams, &write_seekable) == FAILURE) { + return NULL; + } + inst = pemalloc(sizeof(php_convert_filter), persistent); if (strcasecmp(dot, "base64-encode") == 0) { @@ -1667,7 +1674,7 @@ static php_stream_filter *strfilter_convert_create(const char *filtername, zval return NULL; } - return php_stream_filter_alloc(&strfilter_convert_ops, inst, persistent, PSFS_SEEKABLE_START); + return php_stream_filter_alloc(&strfilter_convert_ops, inst, persistent, PSFS_SEEKABLE_START, write_seekable); } static const php_stream_filter_factory strfilter_convert_factory = { @@ -1761,7 +1768,7 @@ static php_stream_filter *consumed_filter_create(const char *filtername, zval *f data->offset = ~0; fops = &consumed_filter_ops; - return php_stream_filter_alloc(fops, data, persistent, PSFS_SEEKABLE_START); + return php_stream_filter_alloc(fops, data, persistent, PSFS_SEEKABLE_START, PSFS_SEEKABLE_ALWAYS); } static const php_stream_filter_factory consumed_filter_factory = { @@ -1992,7 +1999,7 @@ static php_stream_filter *chunked_filter_create(const char *filtername, zval *fi data->persistent = persistent; fops = &chunked_filter_ops; - return php_stream_filter_alloc(fops, data, persistent, PSFS_SEEKABLE_START); + return php_stream_filter_alloc(fops, data, persistent, PSFS_SEEKABLE_START, PSFS_SEEKABLE_ALWAYS); } static const php_stream_filter_factory chunked_filter_factory = { diff --git a/ext/standard/tests/filters/chunked_002.phpt b/ext/standard/tests/filters/chunked_002.phpt new file mode 100644 index 000000000000..7114e278e3bd --- /dev/null +++ b/ext/standard/tests/filters/chunked_002.phpt @@ -0,0 +1,58 @@ +--TEST-- +Dechunk write filter state must survive stream seek +--FILE-- + +--EXPECT-- +string(5) "Hello" +string(12) "Hello, World" +string(12) "Hello, World" +string(5) "Hello" +int(5) diff --git a/ext/standard/tests/filters/convert_filter_write_seek_modes.phpt b/ext/standard/tests/filters/convert_filter_write_seek_modes.phpt new file mode 100644 index 000000000000..0a2d4114bf3c --- /dev/null +++ b/ext/standard/tests/filters/convert_filter_write_seek_modes.phpt @@ -0,0 +1,61 @@ +--TEST-- +convert.* write filter: write_seek_mode parameter +--FILE-- + 'preserve']); + fwrite($fp, 'Hello'); + var_dump(fseek($fp, 0, SEEK_SET) === 0); + var_dump(fseek($fp, 100, SEEK_SET) === 0); + fclose($fp); + + /* reset: seeks succeed, callback dispatched */ + $fp = fopen('php://memory', 'w+'); + stream_filter_append($fp, $name, STREAM_FILTER_WRITE, + ['write_seek_mode' => 'reset']); + fwrite($fp, 'Hello'); + var_dump(fseek($fp, 0, SEEK_SET) === 0); + fclose($fp); + + /* strict: seek fails */ + $fp = fopen('php://memory', 'w+'); + stream_filter_append($fp, $name, STREAM_FILTER_WRITE, + ['write_seek_mode' => 'strict']); + fwrite($fp, 'Hello'); + var_dump(@fseek($fp, 0, SEEK_SET) === -1); + fclose($fp); + + /* invalid: ValueError */ + $fp = fopen('php://memory', 'w+'); + stream_filter_append($fp, $name, STREAM_FILTER_WRITE, + ['write_seek_mode' => 42]); + if ($fp) { + fclose($fp); + } +} +?> +--EXPECTF-- +bool(true) +bool(true) +bool(true) +bool(true) + +Warning: stream_filter_append(): "write_seek_mode" filter parameter must be one of "preserve", "reset", or "strict" in %s + +Warning: stream_filter_append(): Unable to create or locate filter "convert.base64-encode" in %s +bool(true) +bool(true) +bool(true) +bool(true) + +Warning: stream_filter_append(): "write_seek_mode" filter parameter must be one of "preserve", "reset", or "strict" in %s + +Warning: stream_filter_append(): Unable to create or locate filter "convert.quoted-printable-encode" in %s diff --git a/ext/standard/tests/filters/php_user_filter_04.phpt b/ext/standard/tests/filters/php_user_filter_04.phpt index 72f874f21ad4..6dc62779aa90 100644 --- a/ext/standard/tests/filters/php_user_filter_04.phpt +++ b/ext/standard/tests/filters/php_user_filter_04.phpt @@ -25,4 +25,4 @@ class InvalidSeekFilter extends php_user_filter ?> --EXPECTF-- -Fatal error: Declaration of InvalidSeekFilter::seek($offset): bool must be compatible with php_user_filter::seek(int $offset, int $whence): bool in %s on line %d +Fatal error: Declaration of InvalidSeekFilter::seek($offset): bool must be compatible with php_user_filter::seek(int $offset, int $whence, int $chain): bool in %s on line %d diff --git a/ext/standard/tests/filters/user_filter_seek_01.phpt b/ext/standard/tests/filters/user_filter_seek_01.phpt index 31ec95ca6aa6..6023ced1d0ee 100644 --- a/ext/standard/tests/filters/user_filter_seek_01.phpt +++ b/ext/standard/tests/filters/user_filter_seek_01.phpt @@ -39,7 +39,7 @@ class RotateFilter extends php_user_filter public function onClose(): void {} - public function seek(int $offset, int $whence): bool + public function seek(int $offset, int $whence, int $chain): bool { // Stateless filter - always seekable to any position return true; diff --git a/ext/standard/tests/filters/user_filter_seek_02.phpt b/ext/standard/tests/filters/user_filter_seek_02.phpt index 39f4c3c66243..f728e75cdca9 100644 --- a/ext/standard/tests/filters/user_filter_seek_02.phpt +++ b/ext/standard/tests/filters/user_filter_seek_02.phpt @@ -28,7 +28,7 @@ class CountingFilter extends php_user_filter public function onClose(): void {} - public function seek(int $offset, int $whence): bool + public function seek(int $offset, int $whence, int $chain): bool { if ($offset === 0 && $whence === SEEK_SET) { $this->count = 0; diff --git a/ext/standard/tests/filters/user_filter_seek_03.phpt b/ext/standard/tests/filters/user_filter_seek_03.phpt new file mode 100644 index 000000000000..bc814e8ed160 --- /dev/null +++ b/ext/standard/tests/filters/user_filter_seek_03.phpt @@ -0,0 +1,67 @@ +--TEST-- +php_user_filter::seek receives chain identifier (read vs write) +--FILE-- +datalen; + stream_bucket_append($out, $bucket); + } + return PSFS_PASS_ON; + } + + public function onCreate(): bool + { + return true; + } + + public function onClose(): void {} + + public function seek(int $offset, int $whence, int $chain): bool + { + $name = match ($chain) { + STREAM_FILTER_READ => 'read', + STREAM_FILTER_WRITE => 'write', + default => 'other', + }; + self::$log[] = "$name:$offset:$whence"; + return true; + } +} + +stream_filter_register('test.tracking', 'TrackingFilter'); + +$file = __DIR__ . '/user_filter_seek_03.txt'; +file_put_contents($file, "abcdefghij"); + +/* Read chain: seek-to-start should dispatch with STREAM_FILTER_READ */ +TrackingFilter::$log = []; +$fp = fopen($file, 'r'); +stream_filter_append($fp, 'test.tracking', STREAM_FILTER_READ); +fread($fp, 4); +fseek($fp, 0, SEEK_SET); +fclose($fp); +echo "Read chain log: " . implode(',', TrackingFilter::$log) . "\n"; + +/* Write chain: any seek should dispatch with STREAM_FILTER_WRITE */ +TrackingFilter::$log = []; +$fp = fopen($file, 'w+'); +stream_filter_append($fp, 'test.tracking', STREAM_FILTER_WRITE); +fwrite($fp, "xyz"); +fseek($fp, 0, SEEK_SET); +fseek($fp, 5, SEEK_SET); +fclose($fp); +echo "Write chain log: " . implode(',', TrackingFilter::$log) . "\n"; + +unlink($file); + +?> +--EXPECT-- +Read chain log: read:0:0 +Write chain log: write:0:0,write:5:0 diff --git a/ext/standard/user_filters.c b/ext/standard/user_filters.c index 816a798c4b00..ae4132733bec 100644 --- a/ext/standard/user_filters.c +++ b/ext/standard/user_filters.c @@ -48,8 +48,9 @@ PHP_METHOD(php_user_filter, filter) PHP_METHOD(php_user_filter, seek) { - zend_long offset, whence; - if (zend_parse_parameters(ZEND_NUM_ARGS(), "ll", &offset, &whence) == FAILURE) { + zend_long offset, whence, chain; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "lll", &offset, &whence, &chain) == FAILURE) { RETURN_THROWS(); } @@ -263,7 +264,7 @@ static zend_result userfilter_seek( { zval *obj = &thisfilter->abstract; zval retval; - zval args[2]; + zval args[3]; /* the userfilter object probably doesn't exist anymore */ if (CG(unclean_shutdown)) { @@ -289,8 +290,9 @@ static zend_result userfilter_seek( /* Setup calling arguments */ ZVAL_LONG(&args[0], offset); ZVAL_LONG(&args[1], whence); + ZVAL_LONG(&args[2], php_stream_filter_get_chain_type(stream, thisfilter)); - zend_call_known_function(seek_method, Z_OBJ_P(obj), Z_OBJCE_P(obj), &retval, 2, args, NULL); + zend_call_known_function(seek_method, Z_OBJ_P(obj), Z_OBJCE_P(obj), &retval, 3, args, NULL); zend_result ret = FAILURE; if (Z_TYPE(retval) != IS_UNDEF) { @@ -383,7 +385,8 @@ static php_stream_filter *user_filter_factory_create(const char *filtername, return NULL; } - filter = php_stream_filter_alloc(&userfilter_ops, NULL, false, PSFS_SEEKABLE_CHECK); + filter = php_stream_filter_alloc(&userfilter_ops, NULL, false, + PSFS_SEEKABLE_CHECK, PSFS_SEEKABLE_CHECK); /* filtername */ add_property_string(&obj, "filtername", filtername); diff --git a/ext/standard/user_filters.stub.php b/ext/standard/user_filters.stub.php index 475ec58e79e7..8696845b78c8 100644 --- a/ext/standard/user_filters.stub.php +++ b/ext/standard/user_filters.stub.php @@ -50,7 +50,7 @@ class php_user_filter public function filter($in, $out, &$consumed, bool $closing): int {} /** @tentative-return-type */ - public function seek(int $offset, int $whence): bool {} + public function seek(int $offset, int $whence, int $chain): bool {} /** @tentative-return-type */ public function onCreate(): bool {} diff --git a/ext/standard/user_filters_arginfo.h b/ext/standard/user_filters_arginfo.h index d530a5c00066..f5b8a7dfa472 100644 --- a/ext/standard/user_filters_arginfo.h +++ b/ext/standard/user_filters_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit user_filters.stub.php instead. - * Stub hash: 593afbcb51bb35207b93ac7556b92ac845043116 */ + * Stub hash: 01be6d52377ecd1940c14e3d508df28a70456c58 */ ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_php_user_filter_filter, 0, 4, IS_LONG, 0) ZEND_ARG_INFO(0, in) @@ -8,9 +8,10 @@ ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_php_user_filter_ ZEND_ARG_TYPE_INFO(0, closing, _IS_BOOL, 0) ZEND_END_ARG_INFO() -ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_php_user_filter_seek, 0, 2, _IS_BOOL, 0) +ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_php_user_filter_seek, 0, 3, _IS_BOOL, 0) ZEND_ARG_TYPE_INFO(0, offset, IS_LONG, 0) ZEND_ARG_TYPE_INFO(0, whence, IS_LONG, 0) + ZEND_ARG_TYPE_INFO(0, chain, IS_LONG, 0) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_php_user_filter_onCreate, 0, 0, _IS_BOOL, 0) diff --git a/ext/zlib/tests/zlib_filter_seek_deflate.phpt b/ext/zlib/tests/zlib_filter_seek_deflate.phpt index 6acee8e4e8c8..743f1be566fd 100644 --- a/ext/zlib/tests/zlib_filter_seek_deflate.phpt +++ b/ext/zlib/tests/zlib_filter_seek_deflate.phpt @@ -1,55 +1,52 @@ --TEST-- -zlib.deflate filter with seek to start +zlib.deflate write filter is not reset on seek --EXTENSIONS-- zlib --FILE-- $size1 ? "YES" : "NO") . "\n"; - +/* Seek to middle also succeeds */ $result = fseek($fp, 50, SEEK_SET); echo "Seek to middle: " . ($result === 0 ? "SUCCESS" : "FAILURE") . "\n"; fclose($fp); +/* Verify the compressed output is still valid */ $fp = fopen($file, 'r'); stream_filter_append($fp, 'zlib.inflate', STREAM_FILTER_READ); $content = stream_get_contents($fp); fclose($fp); -echo "Decompressed content matches text2: " . ($content === $text2 ? "YES" : "NO") . "\n"; +echo "Decompressed content matches: " . ($content === $text ? "YES" : "NO") . "\n"; ?> --CLEAN-- --EXPECTF-- -Size after first write: %d +Size after write: %d Seek to start: SUCCESS -Size after second write: %d -Second write is larger: YES - -Warning: fseek(): Stream filter zlib.deflate is seekable only to start position in %s on line %d -Seek to middle: FAILURE -Decompressed content matches text2: YES +Seek to middle: SUCCESS +Decompressed content matches: YES diff --git a/ext/zlib/tests/zlib_filter_write_seek_modes.phpt b/ext/zlib/tests/zlib_filter_write_seek_modes.phpt new file mode 100644 index 000000000000..39824a7283cf --- /dev/null +++ b/ext/zlib/tests/zlib_filter_write_seek_modes.phpt @@ -0,0 +1,78 @@ +--TEST-- +zlib.deflate write filter: write_seek_mode parameter +--EXTENSIONS-- +zlib +--FILE-- + 'reset']); +fwrite($fp, $text1); +ftruncate($fp, 0); +var_dump(fseek($fp, 0, SEEK_SET) === 0); +fwrite($fp, $text2); +fclose($fp); + +$fp = fopen($file, 'r'); +stream_filter_append($fp, 'zlib.inflate', STREAM_FILTER_READ); +$decoded = stream_get_contents($fp); +fclose($fp); +var_dump($decoded === $text2); + +/* "reset": only seekable to start */ +$fp = fopen($file, 'w+'); +stream_filter_append($fp, 'zlib.deflate', STREAM_FILTER_WRITE, + ['write_seek_mode' => 'reset']); +fwrite($fp, $text1); +var_dump(fseek($fp, 5, SEEK_SET) === 0); +fclose($fp); + +/* "preserve": same as default; seeks accepted, state preserved */ +$fp = fopen($file, 'w+'); +stream_filter_append($fp, 'zlib.deflate', STREAM_FILTER_WRITE, + ['write_seek_mode' => 'preserve']); +fwrite($fp, $text1); +var_dump(fseek($fp, 0, SEEK_SET) === 0); +var_dump(fseek($fp, 50, SEEK_SET) === 0); +fclose($fp); + +/* "strict": every write-chain seek fails */ +$fp = fopen($file, 'w+'); +stream_filter_append($fp, 'zlib.deflate', STREAM_FILTER_WRITE, + ['write_seek_mode' => 'strict']); +fwrite($fp, $text1); +var_dump(@fseek($fp, 0, SEEK_SET) === -1); +var_dump(@fseek($fp, 50, SEEK_SET) === -1); +fclose($fp); + +/* Invalid mode: ValueError */ +$fp = fopen($file, 'w+'); +stream_filter_append($fp, 'zlib.deflate', STREAM_FILTER_WRITE, + ['write_seek_mode' => 'rewind']); +fclose($fp); + +?> +--CLEAN-- + +--EXPECTF-- +bool(true) +bool(true) + +Warning: fseek(): Stream filter zlib.deflate is seekable only to start position in %s +bool(false) +bool(true) +bool(true) +bool(true) +bool(true) + +Warning: stream_filter_append(): "write_seek_mode" filter parameter must be one of "preserve", "reset", or "strict" in %s + +Warning: stream_filter_append(): Unable to create or locate filter "zlib.deflate" in %s diff --git a/ext/zlib/zlib_filter.c b/ext/zlib/zlib_filter.c index b6393feb9083..2d0e4fbb7fa4 100644 --- a/ext/zlib/zlib_filter.c +++ b/ext/zlib/zlib_filter.c @@ -357,9 +357,14 @@ static const php_stream_filter_ops php_zlib_deflate_ops = { static php_stream_filter *php_zlib_filter_create(const char *filtername, zval *filterparams, bool persistent) { const php_stream_filter_ops *fops = NULL; + php_stream_filter_seekable_t write_seekable; php_zlib_filter_data *data; int status; + if (php_stream_filter_parse_write_seek_mode(filterparams, &write_seekable) == FAILURE) { + return NULL; + } + /* Create this filter */ data = pecalloc(1, sizeof(php_zlib_filter_data), persistent); if (!data) { @@ -498,7 +503,7 @@ static php_stream_filter *php_zlib_filter_create(const char *filtername, zval *f return NULL; } - return php_stream_filter_alloc(fops, data, persistent, PSFS_SEEKABLE_START); + return php_stream_filter_alloc(fops, data, persistent, PSFS_SEEKABLE_START, write_seekable); } const php_stream_filter_factory php_zlib_filter_factory = { diff --git a/main/streams/filter.c b/main/streams/filter.c index 9ec144195693..3a19f5ce918a 100644 --- a/main/streams/filter.c +++ b/main/streams/filter.c @@ -259,7 +259,8 @@ PHPAPI php_stream_filter *php_stream_filter_create(const char *filtername, zval } PHPAPI php_stream_filter *_php_stream_filter_alloc(const php_stream_filter_ops *fops, - void *abstract, bool persistent, php_stream_filter_seekable_t seekable STREAMS_DC) + void *abstract, bool persistent, php_stream_filter_seekable_t read_seekable, + php_stream_filter_seekable_t write_seekable STREAMS_DC) { php_stream_filter *filter; @@ -267,13 +268,60 @@ PHPAPI php_stream_filter *_php_stream_filter_alloc(const php_stream_filter_ops * memset(filter, 0, sizeof(php_stream_filter)); filter->fops = fops; - filter->seekable = seekable; + filter->read_seekable = read_seekable; + filter->write_seekable = write_seekable; Z_PTR(filter->abstract) = abstract; filter->is_persistent = persistent; return filter; } +PHPAPI zend_result php_stream_filter_parse_write_seek_mode( + zval *filterparams, + php_stream_filter_seekable_t *write_seekable) +{ + *write_seekable = PSFS_SEEKABLE_ALWAYS; + + if (filterparams == NULL) { + return SUCCESS; + } + if (Z_TYPE_P(filterparams) != IS_ARRAY && Z_TYPE_P(filterparams) != IS_OBJECT) { + return SUCCESS; + } + + zval *tmp = zend_hash_str_find_ind(HASH_OF(filterparams), + "write_seek_mode", sizeof("write_seek_mode") - 1); + if (tmp == NULL) { + return SUCCESS; + } + + zend_string *tmp_str; + zend_string *str = zval_get_tmp_string(tmp, &tmp_str); + zend_result result = SUCCESS; + + if (zend_string_equals_literal(str, "preserve")) { + *write_seekable = PSFS_SEEKABLE_ALWAYS; + } else if (zend_string_equals_literal(str, "reset")) { + *write_seekable = PSFS_SEEKABLE_START; + } else if (zend_string_equals_literal(str, "strict")) { + *write_seekable = PSFS_SEEKABLE_NEVER; + } else { + php_error_docref(NULL, E_WARNING, + "\"write_seek_mode\" filter parameter must be one of " + "\"preserve\", \"reset\", or \"strict\""); + result = FAILURE; + } + + zend_tmp_string_release(tmp_str); + return result; +} + +PHPAPI int php_stream_filter_get_chain_type(php_stream *stream, php_stream_filter *filter) +{ + return filter->chain == &stream->readfilters ? + PHP_STREAM_FILTER_READ : PHP_STREAM_FILTER_WRITE; +} + PHPAPI void php_stream_filter_free(php_stream_filter *filter) { if (filter->fops->dtor) diff --git a/main/streams/php_stream_filter_api.h b/main/streams/php_stream_filter_api.h index a61bc48815f5..111127a8ad1a 100644 --- a/main/streams/php_stream_filter_api.h +++ b/main/streams/php_stream_filter_api.h @@ -118,7 +118,8 @@ struct _php_stream_filter { zval abstract; /* for use by filter implementation */ php_stream_filter *next; php_stream_filter *prev; - php_stream_filter_seekable_t seekable; + php_stream_filter_seekable_t read_seekable; + php_stream_filter_seekable_t write_seekable; bool is_persistent; /* link into stream and chain */ @@ -141,13 +142,17 @@ PHPAPI zend_result _php_stream_filter_flush(php_stream_filter *filter, bool fini PHPAPI php_stream_filter *php_stream_filter_remove(php_stream_filter *filter, bool call_dtor); PHPAPI void php_stream_filter_free(php_stream_filter *filter); PHPAPI php_stream_filter *_php_stream_filter_alloc(const php_stream_filter_ops *fops, - void *abstract, bool persistent, php_stream_filter_seekable_t seekable STREAMS_DC); + void *abstract, bool persistent, php_stream_filter_seekable_t read_seekable, + php_stream_filter_seekable_t write_seekable STREAMS_DC); +PHPAPI zend_result php_stream_filter_parse_write_seek_mode(zval *filterparams, + php_stream_filter_seekable_t *write_seekable); +PHPAPI int php_stream_filter_get_chain_type(php_stream *stream, php_stream_filter *filter); END_EXTERN_C() -#define php_stream_filter_alloc(fops, thisptr, persistent, seekable) \ - _php_stream_filter_alloc((fops), (thisptr), (persistent), (seekable) STREAMS_CC) -#define php_stream_filter_alloc_rel(fops, thisptr, persistent, seekable) \ - _php_stream_filter_alloc((fops), (thisptr), (persistent), (seekable) STREAMS_REL_CC) +#define php_stream_filter_alloc(fops, thisptr, persistent, rseek, wseek) \ + _php_stream_filter_alloc((fops), (thisptr), (persistent), (rseek), (wseek) STREAMS_CC) +#define php_stream_filter_alloc_rel(fops, thisptr, persistent, rseek, wseek) \ + _php_stream_filter_alloc((fops), (thisptr), (persistent), (rseek), (wseek) STREAMS_REL_CC) #define php_stream_filter_prepend(chain, filter) _php_stream_filter_prepend((chain), (filter)) #define php_stream_filter_append(chain, filter) _php_stream_filter_append((chain), (filter)) #define php_stream_filter_flush(filter, finish) _php_stream_filter_flush((filter), (finish)) diff --git a/main/streams/streams.c b/main/streams/streams.c index 31d1eda16790..5d9eb1523dcc 100644 --- a/main/streams/streams.c +++ b/main/streams/streams.c @@ -1360,14 +1360,16 @@ PHPAPI zend_off_t _php_stream_tell(const php_stream *stream) return stream->position; } -static bool php_stream_are_filters_seekable(php_stream_filter *filter, bool is_start_seeking) +static bool php_stream_are_filters_seekable(php_stream_filter *filter, bool is_start_seeking, int chain_type) { while (filter) { - if (filter->seekable == PSFS_SEEKABLE_NEVER) { + php_stream_filter_seekable_t seekable = (chain_type == PHP_STREAM_FILTER_READ) ? + filter->read_seekable : filter->write_seekable; + if (seekable == PSFS_SEEKABLE_NEVER) { php_error_docref(NULL, E_WARNING, "Stream filter %s is never seekable", filter->fops->label); return false; } - if (!is_start_seeking && filter->seekable == PSFS_SEEKABLE_START) { + if (!is_start_seeking && seekable == PSFS_SEEKABLE_START) { php_error_docref(NULL, E_WARNING, "Stream filter %s is seekable only to start position", filter->fops->label); return false; } @@ -1377,11 +1379,13 @@ static bool php_stream_are_filters_seekable(php_stream_filter *filter, bool is_s } static zend_result php_stream_filters_seek(php_stream *stream, php_stream_filter *filter, - bool is_start_seeking, zend_off_t offset, int whence) + bool is_start_seeking, zend_off_t offset, int whence, int chain_type) { while (filter) { - if (((filter->seekable == PSFS_SEEKABLE_START && is_start_seeking) || - filter->seekable == PSFS_SEEKABLE_CHECK) && + php_stream_filter_seekable_t seekable = (chain_type == PHP_STREAM_FILTER_READ) ? + filter->read_seekable : filter->write_seekable; + if (((seekable == PSFS_SEEKABLE_START && is_start_seeking) || + seekable == PSFS_SEEKABLE_CHECK) && filter->fops->seek(stream, filter, offset, whence) == FAILURE) { php_error_docref(NULL, E_WARNING, "Stream filter seeking for %s failed", filter->fops->label); return FAILURE; @@ -1394,10 +1398,12 @@ static zend_result php_stream_filters_seek(php_stream *stream, php_stream_filter static zend_result php_stream_filters_seek_all(php_stream *stream, bool is_start_seeking, zend_off_t offset, int whence) { - if (php_stream_filters_seek(stream, stream->writefilters.head, is_start_seeking, offset, whence) == FAILURE) { + if (php_stream_filters_seek(stream, stream->writefilters.head, is_start_seeking, + offset, whence, PHP_STREAM_FILTER_WRITE) == FAILURE) { return FAILURE; } - if (php_stream_filters_seek(stream, stream->readfilters.head, is_start_seeking, offset, whence) == FAILURE) { + if (php_stream_filters_seek(stream, stream->readfilters.head, is_start_seeking, + offset, whence, PHP_STREAM_FILTER_READ) == FAILURE) { return FAILURE; } @@ -1422,11 +1428,13 @@ PHPAPI int _php_stream_seek(php_stream *stream, zend_off_t offset, int whence) if (stream->writefilters.head) { _php_stream_flush(stream, 0); - if (!php_stream_are_filters_seekable(stream->writefilters.head, is_start_seeking)) { + if (!php_stream_are_filters_seekable(stream->writefilters.head, is_start_seeking, + PHP_STREAM_FILTER_WRITE)) { return -1; } } - if (stream->readfilters.head && !php_stream_are_filters_seekable(stream->readfilters.head, is_start_seeking)) { + if (stream->readfilters.head && !php_stream_are_filters_seekable( + stream->readfilters.head, is_start_seeking, PHP_STREAM_FILTER_READ)) { return -1; }