Fix bug #52335 (fseek() on memory stream behavior different then file)

This changes memory stream to allow seeking past end which makes it the
same as seeking on files. It means the position is allowed to be higher
than the string length. The size only increases if data is appended to
the past position. The space between the previous string and position
is filled with zero bytes.

Fixes GH-9441
Closes GH-12058
This commit is contained in:
Jakub Zelenka 2023-08-27 15:41:09 +01:00
parent ea10e79bae
commit ba9650d697
No known key found for this signature in database
GPG Key ID: 1C0779DC5C0A9DE4
6 changed files with 118 additions and 23 deletions

4
NEWS
View File

@ -20,6 +20,10 @@ PHP NEWS
. Fixed GH-11982 (str_getcsv returns null byte for unterminated enclosure).
(Jakub Zelenka)
- Streams:
. Fixed bug #52335 (fseek() on memory stream behavior different than file).
(Jakub Zelenka)
17 Aug 2023, PHP 8.3.0beta3
- Core:

View File

@ -648,6 +648,9 @@ PHP 8.3 UPGRADE NOTES
- Streams:
. Blocking fread() on socket connection returns immediately if there are
any buffered data instead of waiting for more data.
. Memory stream no longer fails if seek offset is past the end. Instead
the memory is increase on the next write and date between the old end and
offset is filled with zero bytes in the same way how it works for files.
========================================
14. Performance Improvements

View File

@ -0,0 +1,67 @@
--TEST--
Bug #52335 (fseek() on memory stream behavior different then file)
--FILE--
<?php
echo "Read mode\n";
$fpr = fopen("php://memory", "r");
var_dump(fseek($fpr, 20));
var_dump(feof($fpr));
var_dump(ftell($fpr));
var_dump(feof($fpr));
var_dump(fread($fpr, 2));
var_dump(feof($fpr));
var_dump(fseek($fpr, 24));
var_dump(feof($fpr));
var_dump(ftell($fpr));
fclose($fpr);
echo "Read write mode\n";
$fprw = fopen("php://memory", "r+");
var_dump(fwrite($fprw, "data"));
var_dump(fseek($fprw, 20, SEEK_END));
var_dump(feof($fprw));
var_dump(ftell($fprw));
var_dump(feof($fprw));
var_dump(fread($fprw, 2));
var_dump(feof($fprw));
var_dump(fseek($fprw, 20));
var_dump(fwrite($fprw, " and more data"));
var_dump(feof($fprw));
var_dump(ftell($fprw));
var_dump(fread($fprw, 10));
var_dump(fseek($fprw, 16, SEEK_CUR));
var_dump(ftell($fprw));
var_dump(fseek($fprw, 0));
var_dump(bin2hex(stream_get_contents($fprw)));
fclose($fprw);
?>
--EXPECT--
Read mode
int(0)
bool(false)
int(20)
bool(false)
string(0) ""
bool(true)
int(0)
bool(false)
int(24)
Read write mode
int(4)
int(0)
bool(false)
int(24)
bool(false)
string(0) ""
bool(true)
int(0)
int(14)
bool(false)
int(34)
string(0) ""
int(0)
int(50)
int(0)
string(68) "646174610000000000000000000000000000000020616e64206d6f72652064617461"

View File

@ -0,0 +1,20 @@
--TEST--
Bug GH-9441 (fseek does not work with php://input when data is not preread)
--INI--
enable_post_data_reading=0
--POST_RAW--
Content-Type: application/unknown
a=123456789&b=ZYX
--FILE--
<?php
$input = fopen("php://input", "r");
var_dump(fseek($input, 10));
var_dump(ftell($input));
var_dump(fread($input, 10));
var_dump(file_get_contents("php://input"));
?>
--EXPECT--
int(0)
int(10)
string(7) "9&b=ZYX"
string(17) "a=123456789&b=ZYX"

View File

@ -125,8 +125,8 @@ int(0)
int(3)
bool(false)
===S:10,C===
int(-1)
bool(false)
int(0)
int(13)
bool(false)
===S:-1,E===
int(0)
@ -137,6 +137,6 @@ int(0)
int(6)
bool(false)
===S:1,E===
int(-1)
bool(false)
int(0)
int(7)
bool(false)

View File

@ -49,11 +49,17 @@ static ssize_t php_stream_memory_write(php_stream *stream, const char *buf, size
if (ms->mode & TEMP_STREAM_READONLY) {
return (ssize_t) -1;
} else if (ms->mode & TEMP_STREAM_APPEND) {
ms->fpos = ZSTR_LEN(ms->data);
}
if (ms->fpos + count > ZSTR_LEN(ms->data)) {
size_t data_len = ZSTR_LEN(ms->data);
if (ms->mode & TEMP_STREAM_APPEND) {
ms->fpos = data_len;
}
if (ms->fpos + count > data_len) {
ms->data = zend_string_realloc(ms->data, ms->fpos + count, 0);
if (ms->fpos > data_len) {
/* zero the bytes added due to seek past end position */
memset(ZSTR_VAL(ms->data) + data_len, 0, ms->fpos - data_len);
}
} else {
ms->data = zend_string_separate(ms->data, 0);
}
@ -73,7 +79,7 @@ static ssize_t php_stream_memory_read(php_stream *stream, char *buf, size_t coun
php_stream_memory_data *ms = (php_stream_memory_data*)stream->abstract;
assert(ms != NULL);
if (ms->fpos == ZSTR_LEN(ms->data)) {
if (ms->fpos >= ZSTR_LEN(ms->data)) {
stream->eof = 1;
count = 0;
} else {
@ -132,20 +138,14 @@ static int php_stream_memory_seek(php_stream *stream, zend_off_t offset, int whe
return 0;
}
} else {
if (ms->fpos + (size_t)(offset) > ZSTR_LEN(ms->data)) {
ms->fpos = ZSTR_LEN(ms->data);
*newoffs = -1;
return -1;
} else {
ms->fpos = ms->fpos + offset;
*newoffs = ms->fpos;
stream->eof = 0;
return 0;
}
stream->eof = 0;
ms->fpos = ms->fpos + offset;
*newoffs = ms->fpos;
return 0;
}
case SEEK_SET:
if (ZSTR_LEN(ms->data) < (size_t)(offset)) {
ms->fpos = ZSTR_LEN(ms->data);
if (offset < 0) {
ms->fpos = 0;
*newoffs = -1;
return -1;
} else {
@ -156,9 +156,10 @@ static int php_stream_memory_seek(php_stream *stream, zend_off_t offset, int whe
}
case SEEK_END:
if (offset > 0) {
ms->fpos = ZSTR_LEN(ms->data);
*newoffs = -1;
return -1;
ms->fpos = ZSTR_LEN(ms->data) + offset;
*newoffs = ms->fpos;
stream->eof = 0;
return 0;
} else if (ZSTR_LEN(ms->data) < (size_t)(-offset)) {
ms->fpos = 0;
*newoffs = -1;