From ba9650d697b9433061c0396d08d111f433cae43a Mon Sep 17 00:00:00 2001 From: Jakub Zelenka Date: Sun, 27 Aug 2023 15:41:09 +0100 Subject: [PATCH] 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 --- NEWS | 4 ++ UPGRADING | 3 + ext/standard/tests/file/bug52335.phpt | 67 +++++++++++++++++++ ext/standard/tests/file/gh9441.phpt | 20 ++++++ .../tests/file/stream_rfc2397_007.phpt | 8 +-- main/streams/memory.c | 39 +++++------ 6 files changed, 118 insertions(+), 23 deletions(-) create mode 100644 ext/standard/tests/file/bug52335.phpt create mode 100644 ext/standard/tests/file/gh9441.phpt diff --git a/NEWS b/NEWS index 84bbf47ee39..ac76176680c 100644 --- a/NEWS +++ b/NEWS @@ -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: diff --git a/UPGRADING b/UPGRADING index 20ff98b3095..eec2b433219 100644 --- a/UPGRADING +++ b/UPGRADING @@ -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 diff --git a/ext/standard/tests/file/bug52335.phpt b/ext/standard/tests/file/bug52335.phpt new file mode 100644 index 00000000000..8a7cbb53fcf --- /dev/null +++ b/ext/standard/tests/file/bug52335.phpt @@ -0,0 +1,67 @@ +--TEST-- +Bug #52335 (fseek() on memory stream behavior different then file) +--FILE-- + +--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" diff --git a/ext/standard/tests/file/gh9441.phpt b/ext/standard/tests/file/gh9441.phpt new file mode 100644 index 00000000000..0c32e2b1c69 --- /dev/null +++ b/ext/standard/tests/file/gh9441.phpt @@ -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-- + +--EXPECT-- +int(0) +int(10) +string(7) "9&b=ZYX" +string(17) "a=123456789&b=ZYX" diff --git a/ext/standard/tests/file/stream_rfc2397_007.phpt b/ext/standard/tests/file/stream_rfc2397_007.phpt index 56d91621575..dcbe5beeb3d 100644 --- a/ext/standard/tests/file/stream_rfc2397_007.phpt +++ b/ext/standard/tests/file/stream_rfc2397_007.phpt @@ -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) diff --git a/main/streams/memory.c b/main/streams/memory.c index 444f9637617..62d36906635 100644 --- a/main/streams/memory.c +++ b/main/streams/memory.c @@ -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;