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
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
--TEST--
stream_copy_to_stream() when a user wrapper closes the other stream
--FILE--
<?php
class Wrapper {
public $context;
private int $left = 3;

public function stream_open($path, $mode, $options, &$opened_path): bool {
return true;
}

public function stream_read(int $count): string|false {
self::closeOther();

return $this->left-- > 0 ? 'abc' : '';
}

public function stream_write(string $data): int {
self::closeOther();

return strlen($data);
}

public function stream_eof(): bool {
return $this->left <= 0;
}

private static function closeOther(): void {
if (isset($GLOBALS['close'])) {
$stream = $GLOBALS['close'];
unset($GLOBALS['close']);
var_dump(fclose($stream));
}
}
}

stream_wrapper_register('test', 'Wrapper');

echo "The destination closes the source:\n";
$src = fopen('php://memory', 'w+');
fwrite($src, str_repeat('x', 100000));
rewind($src);
$GLOBALS['close'] = $src;
var_dump(stream_copy_to_stream($src, fopen('test://', 'w')));
var_dump(fclose($src));

echo "The source closes the destination:\n";
$dest = fopen('php://memory', 'w+');
$GLOBALS['close'] = $dest;
var_dump(stream_copy_to_stream(fopen('test://', 'r'), $dest));
var_dump(fclose($dest));
?>
--EXPECTF--
The destination closes the source:

Warning: fclose(): %d is not a valid stream resource in %s on line %d
bool(false)
int(100000)
bool(true)
The source closes the destination:

Warning: fclose(): %d is not a valid stream resource in %s on line %d
bool(false)
int(9)
bool(true)
21 changes: 20 additions & 1 deletion main/streams/streams.c
Original file line number Diff line number Diff line change
Expand Up @@ -1655,7 +1655,7 @@ PHPAPI zend_string *_php_stream_copy_to_mem(php_stream *src, size_t maxlen, int
}

/* Returns SUCCESS/FAILURE and sets *len to the number of bytes moved */
PHPAPI zend_result _php_stream_copy_to_stream_ex(php_stream *src, php_stream *dest, size_t maxlen, size_t *len STREAMS_DC)
static zend_result php_stream_copy_to_stream_impl(php_stream *src, php_stream *dest, size_t maxlen, size_t *len)
{
char buf[CHUNK_SIZE];
size_t haveread = 0;
Expand Down Expand Up @@ -1850,6 +1850,25 @@ PHPAPI zend_result _php_stream_copy_to_stream_ex(php_stream *src, php_stream *de
return SUCCESS;
}

PHPAPI zend_result _php_stream_copy_to_stream_ex(php_stream *src, php_stream *dest, size_t maxlen, size_t *len STREAMS_DC)
{
/* Writing to dest or reading from src may run PHP code (a user wrapper or
* filter) that would otherwise close either stream under the copy. */
uint32_t src_no_fclose = src->flags & PHP_STREAM_FLAG_NO_FCLOSE;
uint32_t dest_no_fclose = dest->flags & PHP_STREAM_FLAG_NO_FCLOSE;
src->flags |= PHP_STREAM_FLAG_NO_FCLOSE;
dest->flags |= PHP_STREAM_FLAG_NO_FCLOSE;

zend_result ret = php_stream_copy_to_stream_impl(src, dest, maxlen, len);

src->flags &= ~PHP_STREAM_FLAG_NO_FCLOSE;
src->flags |= src_no_fclose;
dest->flags &= ~PHP_STREAM_FLAG_NO_FCLOSE;
dest->flags |= dest_no_fclose;

return ret;
}

/* Returns the number of bytes moved.
* Returns 1 when source len is 0.
* Deprecated in favor of php_stream_copy_to_stream_ex() */
Expand Down
Loading