Skip to content
Draft
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
2 changes: 1 addition & 1 deletion ext/zlib/tests/gzseek_variation6.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ move 40 bytes
tell=int(40)
move to the end

Warning: gzseek(): SEEK_END is not supported in %s on line %d
Warning: gzseek(): SEEK_END is not supported on this stream in %s on line %d
int(-1)
tell=int(40)
eof=bool(false)
Expand Down
2 changes: 1 addition & 1 deletion ext/zlib/tests/gzseek_variation7.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ unlink($f);
tell=int(23)
move to the end of the file

Warning: gzseek(): SEEK_END is not supported in %s on line %d
Warning: gzseek(): SEEK_END is not supported on this stream in %s on line %d
int(-1)
tell=int(23)
tell=int(47)
Expand Down
46 changes: 46 additions & 0 deletions ext/zlib/tests/zlib_wrapper_level_errors.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
--TEST--
compress.zlib:// wrapper with invalid compression level
--EXTENSIONS--
zlib
--SKIPIF--
<?php
in_array('compress.zlib', stream_get_wrappers()) || die('skip No zlib wrapper');
?>
--FILE--
<?php declare(strict_types=1);

$filename = tempnam(sys_get_temp_dir(), "php-zlib-test-wrapper-level-error");
$thisfile = file_get_contents(__FILE__);

function write_at_level(mixed $level) {
global $filename, $thisfile;

$ctx = stream_context_create();
stream_context_set_option($ctx, 'zlib', 'level', $level);
$fp = fopen("compress.zlib://$filename", 'w', false, $ctx);
for ($i = 0; $i < 10; ++$i) {
fwrite($fp, $thisfile);
}
fclose($fp);
$size = filesize($filename);
unlink($filename);
return $size;
}

$size_string_type = write_at_level("not a number");
var_dump($size_string_type);

$size_array_type = write_at_level(new stdClass());
var_dump($size_array_type);

$size_oob = write_at_level(10000);
var_dump($size_oob);

?>
--EXPECTF--
Warning: fopen(): zlib "level" context option must be of type int, string given in %s on line %d
int(%d)

Warning: fopen(): zlib "level" context option must be of type int, stdClass given in %s on line %d
int(%d)
int(0)
43 changes: 27 additions & 16 deletions ext/zlib/zlib_fopen_wrapper.c
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,13 @@ struct php_gz_stream_data_t {
static void php_gziop_report_errors(php_stream *stream, size_t count, const char *verb)
{
if (!(stream->flags & PHP_STREAM_FLAG_SUPPRESS_ERRORS)) {
struct php_gz_stream_data_t *self = stream->abstract;
const struct php_gz_stream_data_t *self = stream->abstract;
int error = 0;
gzerror(self->gz_file, &error);
if (error == Z_ERRNO) {
php_error_docref(NULL, E_NOTICE, "%s of %zu bytes failed with errno=%d %s", verb, count, errno, strerror(errno));
php_stream_notice(stream, ReadFailed,
"%s of %zu bytes failed with errno=%d %s",
verb, count, errno, strerror(errno));
}
}
}
Expand Down Expand Up @@ -98,12 +100,13 @@ static ssize_t php_gziop_write(php_stream *stream, const char *buf, size_t count

static int php_gziop_seek(php_stream *stream, zend_off_t offset, int whence, zend_off_t *newoffs)
{
struct php_gz_stream_data_t *self = (struct php_gz_stream_data_t *) stream->abstract;
const struct php_gz_stream_data_t *self = (struct php_gz_stream_data_t *) stream->abstract;

assert(self != NULL);
ZEND_ASSERT(self != NULL);

if (whence == SEEK_END) {
php_error_docref(NULL, E_WARNING, "SEEK_END is not supported");
php_stream_wrapper_warn(NULL, PHP_STREAM_CONTEXT(stream), REPORT_ERRORS,
SeekNotSupported, "SEEK_END is not supported on this stream");
return -1;
}

Expand Down Expand Up @@ -171,14 +174,12 @@ const php_stream_ops php_stream_gzio_ops = {
php_stream *php_stream_gzopen(php_stream_wrapper *wrapper, const char *path, const char *mode, int options,
zend_string **opened_path, php_stream_context *context STREAMS_DC)
{
struct php_gz_stream_data_t *self;
php_stream *stream = NULL, *innerstream = NULL;

/* sanity check the stream: it can be either read-only or write-only */
if (strchr(mode, '+')) {
if (options & REPORT_ERRORS) {
php_error_docref(NULL, E_WARNING, "Cannot open a zlib stream for reading and writing at the same time!");
}
php_stream_wrapper_log_warn(wrapper, context, REPORT_ERRORS, ModeNotSupported,
"Cannot open a zlib stream for reading and writing at the same time!");
return NULL;
}

Expand All @@ -194,14 +195,24 @@ php_stream *php_stream_gzopen(php_stream_wrapper *wrapper, const char *path, con
php_socket_t fd;

if (SUCCESS == php_stream_cast(innerstream, PHP_STREAM_AS_FD, (void **) &fd, REPORT_ERRORS)) {
self = emalloc(sizeof(*self));
struct php_gz_stream_data_t *self = emalloc(sizeof(*self));
self->stream = innerstream;
self->gz_file = gzdopen(dup(fd), mode);

if (self->gz_file) {
zval *zlevel = context ? php_stream_context_get_option(context, "zlib", "level") : NULL;
if (zlevel && (Z_OK != gzsetparams(self->gz_file, zval_get_long(zlevel), Z_DEFAULT_STRATEGY))) {
php_error(E_WARNING, "failed setting compression level");
const zval *zlevel = context ? php_stream_context_get_option(context, "zlib", "level") : NULL;

if (zlevel) {
bool failed = true;
const zend_long level = zval_try_get_long(zlevel, &failed);
if (UNEXPECTED(failed)) {
/* TODO: keep options arg instead of REPORT_ERRORS? Fix _php_stream_open_wrapper_ex() to not clear report errors flag? */
php_stream_wrapper_log_warn(wrapper, context, REPORT_ERRORS, InvalidParam,
"zlib \"level\" context option must be of type int, %s given", zend_zval_type_name(zlevel));
} else if (Z_OK != gzsetparams(self->gz_file, level, Z_DEFAULT_STRATEGY)) {
php_stream_wrapper_log_warn(wrapper, context, REPORT_ERRORS, Generic,
"failed setting compression level");
}
Comment on lines +208 to +215

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason those warnings don't appear if we use options is because the REPORT_ERRORS flag is negated when calling the wrapper ops, but also because we do return a string, so the caller just expects no warnings to have occurred.

I'm experimenting with not negating the flag in the first place at the wrapper level.

}

stream = php_stream_alloc_rel(&php_stream_gzio_ops, self, 0, mode);
Expand All @@ -214,9 +225,9 @@ php_stream *php_stream_gzopen(php_stream_wrapper *wrapper, const char *path, con
}

efree(self);
if (options & REPORT_ERRORS) {
php_error_docref(NULL, E_WARNING, "gzopen failed");
}

php_stream_wrapper_log_warn(wrapper, context, options, OpenFailed,
"gzopen failed");
}

php_stream_close(innerstream);
Expand Down
Loading