diff --git a/NEWS b/NEWS index 191a241cb246..6f230a679f15 100644 --- a/NEWS +++ b/NEWS @@ -122,6 +122,20 @@ PHP NEWS . Fixed bug GH-23899 (Assertion failure when a cancel callback returns an invalid type during shutdown). (Weilin Du) +- PDO_SQLite: + . Fixed bug GH-23905 (Failed seek on PDO SQLite blob streams sets the stream + position to -1). (Marc Bennewitz) + +- SQLite3: + . Fixed bug GH-23905 (Failed seek on SQLite3 blob streams sets the stream + position to -1). (Marc Bennewitz) + +- Streams: + . Fixed bug GH-23905 (Failed seek on php://memory and php://temp streams + sets the stream position to -1). (Marc Bennewitz) + . Fixed a failed seek discarding the read buffer of buffered streams. + (Marc Bennewitz) + 24 Sep 2026, PHP 8.6.0RC2 - Core: diff --git a/UPGRADING b/UPGRADING index 0e4a5288d680..1e9c80d3ef2a 100644 --- a/UPGRADING +++ b/UPGRADING @@ -120,6 +120,13 @@ PHP 8.6 UPGRADE NOTES execution error occurs (e.g. malformed UTF-8 input with the /u modifier). This is consistent with other preg_* functions. +- PDO_SQLite: + . A failed fseek() on a blob stream opened by Pdo\Sqlite::openBlob() now + leaves the stream position unchanged. Previously, ftell() returned false + afterwards and the stream was moved to the start or the end of the blob. + As a result, an fwrite() after a failed seek past the end now writes at + the unchanged position instead of failing. + - PGSQL: . pg_fetch_object() now reports the ValueError for a non-empty $constructor_args on a class without a constructor on the @@ -246,6 +253,13 @@ PHP 8.6 UPGRADE NOTES . DirectoryIterator::key() now returns int|string, and DirectoryIterator::current() returns string|SplFileInfo|static. +- SQLite3: + . A failed fseek() on a blob stream opened by SQLite3::openBlob() now leaves + the stream position unchanged. Previously, ftell() returned false + afterwards and the stream was moved to the start or the end of the blob. + As a result, an fwrite() after a failed seek past the end now writes at + the unchanged position instead of failing. + - Standard: . array_intersect() with at least two arrays now converts values to strings while scanning its inputs instead of during sort comparisons. This can @@ -320,6 +334,15 @@ PHP 8.6 UPGRADE NOTES when a repeater precedes it, as in "s1position < -(size_t)offset) { - sqlite3_stream->position = 0; - *newoffs = -1; return -1; } else { sqlite3_stream->position = sqlite3_stream->position + offset; @@ -215,8 +213,6 @@ static int php_pdosqlite3_stream_seek(php_stream *stream, zend_off_t offset, int } } else { if (sqlite3_stream->position + (size_t)(offset) > sqlite3_stream->size) { - sqlite3_stream->position = sqlite3_stream->size; - *newoffs = -1; return -1; } else { sqlite3_stream->position = sqlite3_stream->position + offset; @@ -226,9 +222,7 @@ static int php_pdosqlite3_stream_seek(php_stream *stream, zend_off_t offset, int } } case SEEK_SET: - if (sqlite3_stream->size < (size_t)(offset)) { - sqlite3_stream->position = sqlite3_stream->size; - *newoffs = -1; + if (offset < 0 || (size_t)offset > sqlite3_stream->size) { return -1; } else { sqlite3_stream->position = offset; @@ -238,12 +232,8 @@ static int php_pdosqlite3_stream_seek(php_stream *stream, zend_off_t offset, int } case SEEK_END: if (offset > 0) { - sqlite3_stream->position = sqlite3_stream->size; - *newoffs = -1; return -1; } else if (sqlite3_stream->size < -(size_t)offset) { - sqlite3_stream->position = 0; - *newoffs = -1; return -1; } else { sqlite3_stream->position = sqlite3_stream->size + offset; @@ -252,7 +242,6 @@ static int php_pdosqlite3_stream_seek(php_stream *stream, zend_off_t offset, int return 0; } default: - *newoffs = sqlite3_stream->position; return -1; } } diff --git a/ext/pdo_sqlite/tests/gh23905.phpt b/ext/pdo_sqlite/tests/gh23905.phpt new file mode 100644 index 000000000000..84608b7a2ddb --- /dev/null +++ b/ext/pdo_sqlite/tests/gh23905.phpt @@ -0,0 +1,147 @@ +--TEST-- +GH-23905 (Failed seek on PDO SQLite blob streams sets the stream position to -1) +--EXTENSIONS-- +pdo_sqlite +--FILE-- +exec('CREATE TABLE test (id INTEGER PRIMARY KEY, data BLOB)'); +$db->exec("INSERT INTO test (id, data) VALUES (1, x'68656c6c6f20776f726c64')"); // "hello world" + +foreach (['read-only' => Pdo\Sqlite::OPEN_READONLY, 'read-write' => Pdo\Sqlite::OPEN_READWRITE] as $mode => $flags) { + echo $mode, "\n"; + $stream = $db->openBlob('test', 'data', 1, 'main', $flags); + + echo "-- SEEK_SET --\n"; + fseek($stream, 6); + var_dump(fseek($stream, -1, SEEK_SET), ftell($stream)); + var_dump(fseek($stream, -6, SEEK_CUR), ftell($stream), fread($stream, 5)); + var_dump(fseek($stream, -1, SEEK_SET), ftell($stream)); + var_dump(fseek($stream, 12, SEEK_SET), feof($stream), ftell($stream)); + var_dump(fread($stream, 10), feof($stream)); + + echo "-- SEEK_CUR --\n"; + fseek($stream, 6); + var_dump(fseek($stream, -7, SEEK_CUR), ftell($stream)); + var_dump(fseek($stream, -6, SEEK_CUR), ftell($stream), fread($stream, 5)); + var_dump(fseek($stream, -6, SEEK_CUR), ftell($stream)); + var_dump(fseek($stream, 7, SEEK_CUR), feof($stream), ftell($stream)); + var_dump(fread($stream, 10), feof($stream)); + + echo "-- SEEK_END --\n"; + fseek($stream, 6); + var_dump(fseek($stream, -12, SEEK_END), ftell($stream)); + var_dump(fseek($stream, -6, SEEK_CUR), ftell($stream), fread($stream, 5)); + var_dump(fseek($stream, -12, SEEK_END), ftell($stream)); + var_dump(fseek($stream, 1, SEEK_END), feof($stream), ftell($stream)); + var_dump(fread($stream, 10), feof($stream)); + + if ($flags === Pdo\Sqlite::OPEN_READWRITE) { + // A blob cannot grow, so a write after a failed seek past the end + // writes at the unchanged position + echo "-- write after failed seek --\n"; + fseek($stream, 6); + var_dump(fseek($stream, 12, SEEK_SET), fwrite($stream, 'W'), ftell($stream)); + fseek($stream, 0); + var_dump(fread($stream, 5)); + var_dump(fseek($stream, 1, SEEK_END), fwrite($stream, '!'), ftell($stream)); + var_dump(fseek($stream, 0), fread($stream, 15)); + } + + fclose($stream); + echo "\n"; +} +?> +--EXPECT-- +read-only +-- SEEK_SET -- +int(-1) +int(6) +int(0) +int(0) +string(5) "hello" +int(-1) +int(5) +int(-1) +bool(false) +int(5) +string(6) " world" +bool(true) +-- SEEK_CUR -- +int(-1) +int(6) +int(0) +int(0) +string(5) "hello" +int(-1) +int(5) +int(-1) +bool(false) +int(5) +string(6) " world" +bool(true) +-- SEEK_END -- +int(-1) +int(6) +int(0) +int(0) +string(5) "hello" +int(-1) +int(5) +int(-1) +bool(false) +int(5) +string(6) " world" +bool(true) + +read-write +-- SEEK_SET -- +int(-1) +int(6) +int(0) +int(0) +string(5) "hello" +int(-1) +int(5) +int(-1) +bool(false) +int(5) +string(6) " world" +bool(true) +-- SEEK_CUR -- +int(-1) +int(6) +int(0) +int(0) +string(5) "hello" +int(-1) +int(5) +int(-1) +bool(false) +int(5) +string(6) " world" +bool(true) +-- SEEK_END -- +int(-1) +int(6) +int(0) +int(0) +string(5) "hello" +int(-1) +int(5) +int(-1) +bool(false) +int(5) +string(6) " world" +bool(true) +-- write after failed seek -- +int(-1) +int(1) +int(7) +string(5) "hello" +int(-1) +int(1) +int(6) +int(0) +string(11) "hello!World" diff --git a/ext/sqlite3/sqlite3.c b/ext/sqlite3/sqlite3.c index 4698fe5cd096..b2d47378e1f7 100644 --- a/ext/sqlite3/sqlite3.c +++ b/ext/sqlite3/sqlite3.c @@ -1201,8 +1201,6 @@ static int php_sqlite3_stream_seek(php_stream *stream, zend_off_t offset, int wh case SEEK_CUR: if (offset < 0) { if (sqlite3_stream->position < -(size_t)offset) { - sqlite3_stream->position = 0; - *newoffs = -1; return -1; } else { sqlite3_stream->position = sqlite3_stream->position + offset; @@ -1213,8 +1211,6 @@ static int php_sqlite3_stream_seek(php_stream *stream, zend_off_t offset, int wh } } else { if (sqlite3_stream->position + (size_t)(offset) > sqlite3_stream->size) { - sqlite3_stream->position = sqlite3_stream->size; - *newoffs = -1; return -1; } else { sqlite3_stream->position = sqlite3_stream->position + offset; @@ -1225,9 +1221,7 @@ static int php_sqlite3_stream_seek(php_stream *stream, zend_off_t offset, int wh } } case SEEK_SET: - if (sqlite3_stream->size < (size_t)(offset)) { - sqlite3_stream->position = sqlite3_stream->size; - *newoffs = -1; + if (offset < 0 || (size_t)offset > sqlite3_stream->size) { return -1; } else { sqlite3_stream->position = offset; @@ -1238,12 +1232,8 @@ static int php_sqlite3_stream_seek(php_stream *stream, zend_off_t offset, int wh } case SEEK_END: if (offset > 0) { - sqlite3_stream->position = sqlite3_stream->size; - *newoffs = -1; return -1; } else if (sqlite3_stream->size < -(size_t)offset) { - sqlite3_stream->position = 0; - *newoffs = -1; return -1; } else { sqlite3_stream->position = sqlite3_stream->size + offset; @@ -1253,7 +1243,6 @@ static int php_sqlite3_stream_seek(php_stream *stream, zend_off_t offset, int wh return 0; } default: - *newoffs = sqlite3_stream->position; return -1; } } diff --git a/ext/sqlite3/tests/gh23905.phpt b/ext/sqlite3/tests/gh23905.phpt new file mode 100644 index 000000000000..957683f248c9 --- /dev/null +++ b/ext/sqlite3/tests/gh23905.phpt @@ -0,0 +1,149 @@ +--TEST-- +GH-23905 (Failed seek on SQLite3 blob streams sets the stream position to -1) +--EXTENSIONS-- +sqlite3 +--FILE-- +exec('CREATE TABLE test (id INTEGER PRIMARY KEY, data BLOB)'); +$db->exec("INSERT INTO test (id, data) VALUES (1, x'68656c6c6f20776f726c64')"); // "hello world" + +foreach (['read-only' => SQLITE3_OPEN_READONLY, 'read-write' => SQLITE3_OPEN_READWRITE] as $mode => $flags) { + echo $mode, "\n"; + $stream = $db->openBlob('test', 'data', 1, 'main', $flags); + + echo "-- SEEK_SET --\n"; + fseek($stream, 6); + var_dump(fseek($stream, -1, SEEK_SET), ftell($stream)); + var_dump(fseek($stream, -6, SEEK_CUR), ftell($stream), fread($stream, 5)); + var_dump(fseek($stream, -1, SEEK_SET), ftell($stream)); + var_dump(fseek($stream, 12, SEEK_SET), feof($stream), ftell($stream)); + var_dump(fread($stream, 10), feof($stream)); + + echo "-- SEEK_CUR --\n"; + fseek($stream, 6); + var_dump(fseek($stream, -7, SEEK_CUR), ftell($stream)); + var_dump(fseek($stream, -6, SEEK_CUR), ftell($stream), fread($stream, 5)); + var_dump(fseek($stream, -6, SEEK_CUR), ftell($stream)); + var_dump(fseek($stream, 7, SEEK_CUR), feof($stream), ftell($stream)); + var_dump(fread($stream, 10), feof($stream)); + + echo "-- SEEK_END --\n"; + fseek($stream, 6); + var_dump(fseek($stream, -12, SEEK_END), ftell($stream)); + var_dump(fseek($stream, -6, SEEK_CUR), ftell($stream), fread($stream, 5)); + var_dump(fseek($stream, -12, SEEK_END), ftell($stream)); + var_dump(fseek($stream, 1, SEEK_END), feof($stream), ftell($stream)); + var_dump(fread($stream, 10), feof($stream)); + + if ($flags === SQLITE3_OPEN_READWRITE) { + // A blob cannot grow, so a write after a failed seek past the end + // writes at the unchanged position + echo "-- write after failed seek --\n"; + fseek($stream, 6); + var_dump(fseek($stream, 12, SEEK_SET), fwrite($stream, 'W'), ftell($stream)); + fseek($stream, 0); + var_dump(fread($stream, 5)); + var_dump(fseek($stream, 1, SEEK_END), fwrite($stream, '!'), ftell($stream)); + var_dump(fseek($stream, 0), fread($stream, 15)); + } + + fclose($stream); + echo "\n"; +} + +$db->close(); +?> +--EXPECT-- +read-only +-- SEEK_SET -- +int(-1) +int(6) +int(0) +int(0) +string(5) "hello" +int(-1) +int(5) +int(-1) +bool(false) +int(5) +string(6) " world" +bool(true) +-- SEEK_CUR -- +int(-1) +int(6) +int(0) +int(0) +string(5) "hello" +int(-1) +int(5) +int(-1) +bool(false) +int(5) +string(6) " world" +bool(true) +-- SEEK_END -- +int(-1) +int(6) +int(0) +int(0) +string(5) "hello" +int(-1) +int(5) +int(-1) +bool(false) +int(5) +string(6) " world" +bool(true) + +read-write +-- SEEK_SET -- +int(-1) +int(6) +int(0) +int(0) +string(5) "hello" +int(-1) +int(5) +int(-1) +bool(false) +int(5) +string(6) " world" +bool(true) +-- SEEK_CUR -- +int(-1) +int(6) +int(0) +int(0) +string(5) "hello" +int(-1) +int(5) +int(-1) +bool(false) +int(5) +string(6) " world" +bool(true) +-- SEEK_END -- +int(-1) +int(6) +int(0) +int(0) +string(5) "hello" +int(-1) +int(5) +int(-1) +bool(false) +int(5) +string(6) " world" +bool(true) +-- write after failed seek -- +int(-1) +int(1) +int(7) +string(5) "hello" +int(-1) +int(1) +int(6) +int(0) +string(11) "hello!World" diff --git a/ext/standard/tests/streams/gh23905.phpt b/ext/standard/tests/streams/gh23905.phpt new file mode 100644 index 000000000000..85f938ab362c --- /dev/null +++ b/ext/standard/tests/streams/gh23905.phpt @@ -0,0 +1,295 @@ +--TEST-- +GH-23905 (Failed seek on php://memory and php://temp streams sets the stream position to -1) +--FILE-- + +--EXPECT-- +php://memory +-- SEEK_SET -- +int(-1) +int(6) +int(0) +int(0) +string(5) "hello" +int(0) +bool(false) +int(15) +int(0) +int(0) +string(11) "hello world" +bool(true) +-- SEEK_CUR -- +int(-1) +int(6) +int(0) +int(0) +string(5) "hello" +int(0) +bool(false) +int(15) +int(0) +int(0) +string(11) "hello world" +bool(true) +-- SEEK_END -- +int(-1) +int(6) +int(0) +int(0) +string(5) "hello" +int(0) +bool(false) +int(15) +int(0) +int(0) +string(11) "hello world" +bool(true) + + +php://filter/string.rot13/resource=php://memory +-- SEEK_SET -- +int(-1) +int(6) +int(0) +int(0) +string(5) "hello" +int(0) +bool(false) +int(15) +int(0) +int(0) +string(11) "hello world" +bool(true) +-- SEEK_CUR -- +int(-1) +int(6) +int(0) +int(0) +string(5) "hello" +int(0) +bool(false) +int(15) +int(0) +int(0) +string(11) "hello world" +bool(true) +-- SEEK_END -- +int(-1) +int(6) +int(0) +int(0) +string(5) "hello" +int(0) +bool(false) +int(15) +int(0) +int(0) +string(11) "hello world" +bool(true) + + +php://temp +-- SEEK_SET -- +int(-1) +int(6) +int(0) +int(0) +string(5) "hello" +int(0) +bool(false) +int(15) +int(0) +int(0) +string(11) "hello world" +bool(true) +-- SEEK_CUR -- +int(-1) +int(6) +int(0) +int(0) +string(5) "hello" +int(0) +bool(false) +int(15) +int(0) +int(0) +string(11) "hello world" +bool(true) +-- SEEK_END -- +int(-1) +int(6) +int(0) +int(0) +string(5) "hello" +int(0) +bool(false) +int(15) +int(0) +int(0) +string(11) "hello world" +bool(true) + + +php://temp/maxmemory:0 +-- SEEK_SET -- +int(-1) +int(6) +int(0) +int(0) +string(5) "hello" +int(0) +bool(false) +int(15) +int(0) +int(0) +string(11) "hello world" +bool(true) +-- SEEK_CUR -- +int(-1) +int(6) +int(0) +int(0) +string(5) "hello" +int(0) +bool(false) +int(15) +int(0) +int(0) +string(11) "hello world" +bool(true) +-- SEEK_END -- +int(-1) +int(6) +int(0) +int(0) +string(5) "hello" +int(0) +bool(false) +int(15) +int(0) +int(0) +string(11) "hello world" +bool(true) + + +php://filter/string.rot13/resource=php://temp +-- SEEK_SET -- +int(-1) +int(6) +int(0) +int(0) +string(5) "hello" +int(0) +bool(false) +int(15) +int(0) +int(0) +string(11) "hello world" +bool(true) +-- SEEK_CUR -- +int(-1) +int(6) +int(0) +int(0) +string(5) "hello" +int(0) +bool(false) +int(15) +int(0) +int(0) +string(11) "hello world" +bool(true) +-- SEEK_END -- +int(-1) +int(6) +int(0) +int(0) +string(5) "hello" +int(0) +bool(false) +int(15) +int(0) +int(0) +string(11) "hello world" +bool(true) + + +php://filter/string.rot13/resource=php://temp/maxmemory:0 +-- SEEK_SET -- +int(-1) +int(6) +int(0) +int(0) +string(5) "hello" +int(0) +bool(false) +int(15) +int(0) +int(0) +string(11) "hello world" +bool(true) +-- SEEK_CUR -- +int(-1) +int(6) +int(0) +int(0) +string(5) "hello" +int(0) +bool(false) +int(15) +int(0) +int(0) +string(11) "hello world" +bool(true) +-- SEEK_END -- +int(-1) +int(6) +int(0) +int(0) +string(5) "hello" +int(0) +bool(false) +int(15) +int(0) +int(0) +string(11) "hello world" +bool(true) diff --git a/ext/standard/tests/streams/stream_seek_failure_keeps_buffer.phpt b/ext/standard/tests/streams/stream_seek_failure_keeps_buffer.phpt new file mode 100644 index 000000000000..1c9fd6450441 --- /dev/null +++ b/ext/standard/tests/streams/stream_seek_failure_keeps_buffer.phpt @@ -0,0 +1,103 @@ +--TEST-- +A failed seek keeps the stream position and the read buffer +--FILE-- +data, $this->pos, $count); + $this->pos += strlen($ret); + return $ret; + } + + public function stream_eof(): bool { + return $this->pos >= strlen($this->data); + } + + public function stream_tell(): int { + return $this->pos; + } + + public function stream_seek(int $offset, int $whence): bool { + $pos = match ($whence) { + SEEK_SET => $offset, + SEEK_CUR => $this->pos + $offset, + SEEK_END => strlen($this->data) + $offset, + }; + if ($pos < 0 || $pos > strlen($this->data)) { + return false; + } + $this->pos = $pos; + return true; + } +} +stream_wrapper_register('test', TestStream::class); + +$file = __DIR__ . '/stream_seek_failure_keeps_buffer.txt'; +file_put_contents($file, 'hello world'); + +echo "-- plain file --\n"; +$stream = fopen($file, 'r'); +var_dump(fread($stream, 5)); +var_dump(fseek($stream, -6, SEEK_CUR), ftell($stream), feof($stream)); +var_dump(fseek($stream, -12, SEEK_END), ftell($stream), feof($stream)); +var_dump(fread($stream, 10), feof($stream)); +fclose($stream); + +echo "-- plain file, write after failed seek --\n"; +$stream = fopen($file, 'r+'); +var_dump(fread($stream, 5)); +var_dump(fseek($stream, -12, SEEK_END), fwrite($stream, '!'), ftell($stream)); +fclose($stream); +var_dump(file_get_contents($file)); + +echo "-- user stream --\n"; +$stream = fopen('test://', 'r'); +var_dump(fread($stream, 5)); +var_dump(fseek($stream, -6, SEEK_CUR), ftell($stream), feof($stream)); +var_dump(fseek($stream, 12, SEEK_SET), ftell($stream), feof($stream)); +var_dump(fseek($stream, 1, SEEK_END), ftell($stream), feof($stream)); +var_dump(fread($stream, 10), feof($stream)); +fclose($stream); +?> +--CLEAN-- + +--EXPECT-- +-- plain file -- +string(5) "hello" +int(-1) +int(5) +bool(false) +int(-1) +int(5) +bool(false) +string(6) " world" +bool(true) +-- plain file, write after failed seek -- +string(5) "hello" +int(-1) +int(1) +int(6) +string(11) "hello!world" +-- user stream -- +string(5) "hello" +int(-1) +int(5) +bool(false) +int(-1) +int(5) +bool(false) +int(-1) +int(5) +bool(false) +string(6) " world" +bool(true) diff --git a/main/streams/memory.c b/main/streams/memory.c index e76598ed0f46..9a161a96d7ab 100644 --- a/main/streams/memory.c +++ b/main/streams/memory.c @@ -127,55 +127,38 @@ static int php_stream_memory_seek(php_stream *stream, zend_off_t offset, int whe case SEEK_CUR: if (offset < 0) { if (ms->fpos < -(size_t)offset) { - ms->fpos = 0; - *newoffs = -1; return -1; } else { ms->fpos = ms->fpos + offset; *newoffs = ms->fpos; - stream->eof = 0; - stream->fatal_error = 0; return 0; } } else { - stream->eof = 0; - stream->fatal_error = 0; ms->fpos = ms->fpos + offset; *newoffs = ms->fpos; return 0; } case SEEK_SET: if (offset < 0) { - ms->fpos = 0; - *newoffs = -1; return -1; } else { ms->fpos = offset; *newoffs = ms->fpos; - stream->eof = 0; - stream->fatal_error = 0; return 0; } case SEEK_END: if (offset > 0) { ms->fpos = ZSTR_LEN(ms->data) + offset; *newoffs = ms->fpos; - stream->eof = 0; - stream->fatal_error = 0; return 0; } else if (ZSTR_LEN(ms->data) < -(size_t)offset) { - ms->fpos = 0; - *newoffs = -1; return -1; } else { ms->fpos = ZSTR_LEN(ms->data) + offset; *newoffs = ms->fpos; - stream->eof = 0; - stream->fatal_error = 0; return 0; } default: - *newoffs = ms->fpos; return -1; } } @@ -446,10 +429,10 @@ static int php_stream_temp_seek(php_stream *stream, zend_off_t offset, int whenc assert(ts != NULL); - if (!ts->innerstream) { - *newoffs = -1; + if (UNEXPECTED(!ts->innerstream)) { return -1; } + ret = php_stream_seek(ts->innerstream, offset, whence); *newoffs = php_stream_tell(ts->innerstream); stream->eof = ts->innerstream->eof; diff --git a/main/streams/streams.c b/main/streams/streams.c index ae2d4b910627..3d783bd2efea 100644 --- a/main/streams/streams.c +++ b/main/streams/streams.c @@ -1402,17 +1402,20 @@ PHPAPI int php_stream_seek(php_stream *stream, zend_off_t offset, int whence) } ret = stream->ops->seek(stream, offset, whence, &stream->position); - if (((stream->flags & PHP_STREAM_FLAG_NO_SEEK) == 0) || ret == 0) { - if (ret == 0) { - stream->eof = 0; - stream->fatal_error = 0; - } + if (ret == 0) { + stream->eof = 0; + stream->fatal_error = 0; /* invalidate the buffer contents */ stream->readpos = stream->writepos = 0; return php_stream_filters_seek_all(stream, is_start_seeking, offset, whence) == SUCCESS ? ret : -1; } + + if ((stream->flags & PHP_STREAM_FLAG_NO_SEEK) == 0) { + /* the stream did not move, so the position and the buffer contents are still valid */ + return ret; + } /* else the stream has decided that it can't support seeking after all; * fall through to attempt emulation */ } diff --git a/tests/basic/gh20964.phpt b/tests/basic/gh20964.phpt index 2a97164c7924..705e59db3a6f 100644 --- a/tests/basic/gh20964.phpt +++ b/tests/basic/gh20964.phpt @@ -17,6 +17,6 @@ fclose($stream); ?> --EXPECT-- int(-1) -bool(false) +int(0) int(-1) -bool(false) +int(0)