Skip to content
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
82 changes: 80 additions & 2 deletions ext/bz2/bz2_filter.c
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ typedef struct _php_bz2_filter_data {
unsigned int is_flushed : 1; /* only for compression */

int persistent;

/* Configuration for reset - immutable */
int blockSize100k; /* compress only */
int workFactor; /* compress only */
} php_bz2_filter_data;

/* }}} */
Expand Down Expand Up @@ -178,6 +182,38 @@ static php_stream_filter_status_t php_bz2_decompress_filter(
return exit_status;
}

static zend_result php_bz2_decompress_seek(
php_stream *stream,
php_stream_filter *thisfilter,
zend_off_t offset,
int whence
)
{
php_bz2_filter_data *data;

if (!Z_PTR(thisfilter->abstract)) {
return FAILURE;
}

data = (php_bz2_filter_data *)Z_PTR(thisfilter->abstract);

/* End current decompression if running */
if (data->status == PHP_BZ2_RUNNING) {
BZ2_bzDecompressEnd(&(data->strm));
}

/* Reset stream state */
data->strm.next_in = data->inbuf;
data->strm.avail_in = 0;
data->strm.next_out = data->outbuf;
data->strm.avail_out = data->outbuf_len;
data->status = PHP_BZ2_UNINITIALIZED;

/* Note: We don't reinitialize here - it will be done on first use in the filter function */

return SUCCESS;
}

static void php_bz2_decompress_dtor(php_stream_filter *thisfilter)
{
if (thisfilter && Z_PTR(thisfilter->abstract)) {
Expand All @@ -193,6 +229,7 @@ static void php_bz2_decompress_dtor(php_stream_filter *thisfilter)

static const php_stream_filter_ops php_bz2_decompress_ops = {
php_bz2_decompress_filter,
php_bz2_decompress_seek,
php_bz2_decompress_dtor,
"bzip2.decompress"
};
Expand Down Expand Up @@ -288,6 +325,42 @@ static php_stream_filter_status_t php_bz2_compress_filter(
return exit_status;
}

static zend_result php_bz2_compress_seek(
php_stream *stream,
php_stream_filter *thisfilter,
zend_off_t offset,
int whence
)
{
php_bz2_filter_data *data;
int status;

if (!Z_PTR(thisfilter->abstract)) {
return FAILURE;
}

data = (php_bz2_filter_data *)Z_PTR(thisfilter->abstract);

/* End current compression */
BZ2_bzCompressEnd(&(data->strm));

/* Reset stream state */
data->strm.next_in = data->inbuf;
data->strm.avail_in = 0;
data->strm.next_out = data->outbuf;
data->strm.avail_out = data->outbuf_len;
data->is_flushed = 1;

/* Reinitialize compression with saved configuration */
status = BZ2_bzCompressInit(&(data->strm), data->blockSize100k, 0, data->workFactor);
if (status != BZ_OK) {
php_error_docref(NULL, E_WARNING, "bzip2.compress: failed to reset compression state");
return FAILURE;
}

return SUCCESS;
}

static void php_bz2_compress_dtor(php_stream_filter *thisfilter)
{
if (Z_PTR(thisfilter->abstract)) {
Expand All @@ -301,6 +374,7 @@ static void php_bz2_compress_dtor(php_stream_filter *thisfilter)

static const php_stream_filter_ops php_bz2_compress_ops = {
php_bz2_compress_filter,
php_bz2_compress_seek,
php_bz2_compress_dtor,
"bzip2.compress"
};
Expand All @@ -309,7 +383,7 @@ 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, uint8_t persistent)
static php_stream_filter *php_bz2_filter_create(const char *filtername, zval *filterparams, bool persistent)
{
const php_stream_filter_ops *fops = NULL;
php_bz2_filter_data *data;
Expand Down Expand Up @@ -388,6 +462,10 @@ static php_stream_filter *php_bz2_filter_create(const char *filtername, zval *fi
}
}

/* Save configuration for reset */
data->blockSize100k = blockSize100k;
data->workFactor = workFactor;

status = BZ2_bzCompressInit(&(data->strm), blockSize100k, 0, workFactor);
data->is_flushed = 1;
fops = &php_bz2_compress_ops;
Expand All @@ -403,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);
return php_stream_filter_alloc(fops, data, persistent, PHP_STREAM_FILTER_SEEKABLE_START);
}

const php_stream_filter_factory php_bz2_filter_factory = {
Expand Down
55 changes: 55 additions & 0 deletions ext/bz2/tests/bz2_filter_seek_compress.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
--TEST--
bzip2.compress filter with seek to start
--EXTENSIONS--
bz2
--FILE--
<?php
$file = __DIR__ . '/bz2_filter_seek_compress.bz2';

$text1 = 'Short text.';
$text2 = 'This is a much longer text that will completely overwrite the previous compressed data in the file.';

$fp = fopen($file, 'w+');
stream_filter_append($fp, 'bzip2.compress', STREAM_FILTER_WRITE);

fwrite($fp, $text1);
fflush($fp);

$size1 = ftell($fp);
echo "Size after first write: $size1\n";

$result = fseek($fp, 0, SEEK_SET);
echo "Seek to start: " . ($result === 0 ? "SUCCESS" : "FAILURE") . "\n";

fwrite($fp, $text2);
fflush($fp);

$size2 = ftell($fp);
echo "Size after second write: $size2\n";
echo "Second write is larger: " . ($size2 > $size1 ? "YES" : "NO") . "\n";

$result = fseek($fp, 50, SEEK_SET);
echo "Seek to middle: " . ($result === 0 ? "SUCCESS" : "FAILURE") . "\n";

fclose($fp);

$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";
?>
--CLEAN--
<?php
@unlink(__DIR__ . '/bz2_filter_seek_compress.bz2');
?>
--EXPECTF--
Size after first write: 40
Seek to start: SUCCESS
Size after second write: 98
Second write is larger: YES

Warning: fseek(): Stream filter bzip2.compress is not seekable in %s on line %d
Seek to middle: FAILURE
Decompressed content matches text2: YES
43 changes: 43 additions & 0 deletions ext/bz2/tests/bz2_filter_seek_decompress.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
--TEST--
bzip2.decompress filter with seek to start
--EXTENSIONS--
bz2
--FILE--
<?php
$file = __DIR__ . '/bz2_filter_seek_decompress.bz2';

$text = 'I am the very model of a modern major general, I\'ve information vegetable, animal, and mineral.';

$fp = fopen($file, 'w');
stream_filter_append($fp, 'bzip2.compress', STREAM_FILTER_WRITE);
fwrite($fp, $text);
fclose($fp);

$fp = fopen($file, 'r');
stream_filter_append($fp, 'bzip2.decompress', STREAM_FILTER_READ);

$partial = fread($fp, 20);
echo "First read (20 bytes): " . $partial . "\n";

$result = fseek($fp, 0, SEEK_SET);
echo "Seek to start: " . ($result === 0 ? "SUCCESS" : "FAILURE") . "\n";

$full = stream_get_contents($fp);
echo "Content after seek matches: " . ($full === $text ? "YES" : "NO") . "\n";

$result = fseek($fp, 50, SEEK_SET);
echo "Seek to middle: " . ($result === 0 ? "SUCCESS" : "FAILURE") . "\n";

fclose($fp);
?>
--CLEAN--
<?php
@unlink(__DIR__ . '/bz2_filter_seek_decompress.bz2');
?>
--EXPECTF--
First read (20 bytes): I am the very model
Seek to start: SUCCESS
Content after seek matches: YES

Warning: fseek(): Stream filter bzip2.decompress is not seekable in %s on line %d
Seek to middle: FAILURE
8 changes: 4 additions & 4 deletions ext/gd/tests/bug79945.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,17 @@ if (!(imagetypes() & IMG_PNG)) {
}
set_error_handler(function($errno, $errstr) {
if (str_contains($errstr, 'Cannot cast a filtered stream on this system')) {
die('skip: fopencookie not support on this system');
die('skip: fopencookie not supported on this system');
}
});
imagecreatefrompng('php://filter/read=convert.base64-encode/resource=' . __DIR__ . '/test.png');
imagecreatefrompng('php://filter/read=string.rot13/resource=' . __DIR__ . '/test.png');
restore_error_handler();
?>
--FILE--
<?php
imagecreatefrompng('php://filter/read=convert.base64-encode/resource=' . __DIR__ . '/test.png');
imagecreatefrompng('php://filter/read=string.rot13/resource=' . __DIR__ . '/test.png');
?>
--CLEAN--
--EXPECTF--

Warning: imagecreatefrompng(): "php://filter/read=convert.base64-encode/resource=%s" is not a valid PNG file in %s on line %d
Warning: imagecreatefrompng(): "php://filter/read=string.rot13/resource=%s" is not a valid PNG file in %s on line %d
33 changes: 30 additions & 3 deletions ext/iconv/iconv.c
Original file line number Diff line number Diff line change
Expand Up @@ -2286,7 +2286,6 @@ PHP_FUNCTION(iconv_get_encoding)

}
/* }}} */

/* {{{ iconv stream filter */
typedef struct _php_iconv_stream_filter {
iconv_t cd;
Expand Down Expand Up @@ -2554,6 +2553,32 @@ static php_stream_filter_status_t php_iconv_stream_filter_do_filter(
}
/* }}} */

/* {{{ php_iconv_stream_filter_seek */
static zend_result php_iconv_stream_filter_seek(
php_stream *stream,
php_stream_filter *filter,
zend_off_t offset,
int whence)
{
php_iconv_stream_filter *self = (php_iconv_stream_filter *)Z_PTR(filter->abstract);

/* Reset stub buffer */
self->stub_len = 0;

/* Reset iconv conversion state by closing and reopening the converter */
iconv_close(self->cd);

if ((iconv_t)-1 == (self->cd = iconv_open(self->to_charset, self->from_charset))) {
php_error_docref(NULL, E_WARNING,
"iconv stream filter (\"%s\"=>\"%s\"): failed to reset conversion state",
self->from_charset, self->to_charset);
return FAILURE;
}

return SUCCESS;
}
/* }}} */

/* {{{ php_iconv_stream_filter_cleanup */
static void php_iconv_stream_filter_cleanup(php_stream_filter *filter)
{
Expand All @@ -2564,12 +2589,13 @@ static void php_iconv_stream_filter_cleanup(php_stream_filter *filter)

static const php_stream_filter_ops php_iconv_stream_filter_ops = {
php_iconv_stream_filter_do_filter,
php_iconv_stream_filter_seek,
php_iconv_stream_filter_cleanup,
"convert.iconv.*"
};

/* {{{ php_iconv_stream_filter_create */
static php_stream_filter *php_iconv_stream_filter_factory_create(const char *name, zval *params, uint8_t persistent)
static php_stream_filter *php_iconv_stream_filter_factory_create(const char *name, zval *params, bool persistent)
{
php_iconv_stream_filter *inst;
const char *from_charset = NULL, *to_charset = NULL;
Expand Down Expand Up @@ -2601,7 +2627,8 @@ static php_stream_filter *php_iconv_stream_filter_factory_create(const char *nam
return NULL;
}

return php_stream_filter_alloc(&php_iconv_stream_filter_ops, inst, persistent);
return php_stream_filter_alloc(&php_iconv_stream_filter_ops, inst, persistent,
PHP_STREAM_FILTER_SEEKABLE_START);
}
/* }}} */

Expand Down
43 changes: 43 additions & 0 deletions ext/iconv/tests/iconv_stream_filter_seek.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
--TEST--
iconv stream filter with seek to start
--EXTENSIONS--
iconv
--FILE--
<?php
$file = __DIR__ . '/iconv_stream_filter_seek.txt';

$text = 'Hello, this is a test for iconv stream filter seeking functionality.';

$fp = fopen($file, 'w');
stream_filter_append($fp, 'convert.iconv.ISO-2022-JP/UTF-8');
fwrite($fp, $text);
fclose($fp);

$fp = fopen($file, 'r');
stream_filter_append($fp, 'convert.iconv.UTF-8/ISO-2022-JP');

$partial = fread($fp, 20);
echo "First read (20 bytes): " . $partial . "\n";

$result = fseek($fp, 0, SEEK_SET);
echo "Seek to start: " . ($result === 0 ? "SUCCESS" : "FAILURE") . "\n";

$full = fread($fp, strlen($text));
echo "Content after seek matches: " . ($full === $text ? "YES" : "NO") . "\n";

$result = fseek($fp, 50, SEEK_SET);
echo "Seek to middle: " . ($result === 0 ? "SUCCESS" : "FAILURE") . "\n";

fclose($fp);
?>
--CLEAN--
<?php
@unlink(__DIR__ . '/iconv_stream_filter_seek.txt');
?>
--EXPECTF--
First read (20 bytes): Hello, this is a tes
Seek to start: SUCCESS
Content after seek matches: YES

Warning: fseek(): Stream filter convert.iconv.* is not seekable in %s on line %d
Seek to middle: FAILURE
Loading