Fix GH-10031: [Stream] STREAM_NOTIFY_PROGRESS over HTTP emitted irregularly for last chunk of data

It's possible that the server already sent in more data than just the headers.
Since the stream only accepts progress increments after the headers are
processed, the already read data is never added to the process.
We account for this by adjusting the progress counter by the difference of
already read header data and the body.

For the test:
Co-authored-by: aetonsi <18366087+aetonsi@users.noreply.github.com>

Closes GH-10492.
This commit is contained in:
Niels Dossche 2023-02-03 00:00:42 +01:00 committed by nielsdos
parent 05bd1423ee
commit b33fbbfe3d
3 changed files with 61 additions and 0 deletions

2
NEWS
View file

@ -24,6 +24,8 @@ PHP NEWS
source file). (ilutov) source file). (ilutov)
- Streams: - Streams:
. Fixed bug GH-10031 ([Stream] STREAM_NOTIFY_PROGRESS over HTTP emitted
irregularly for last chunk of data). (nielsdos)
. Fixed bug GH-11175 (Stream Socket Timeout). (nielsdos) . Fixed bug GH-11175 (Stream Socket Timeout). (nielsdos)
. Fixed bug GH-11177 (ASAN UndefinedBehaviorSanitizer when timeout = -1 . Fixed bug GH-11177 (ASAN UndefinedBehaviorSanitizer when timeout = -1
passed to stream_socket_accept/stream_socket_client). (nielsdos) passed to stream_socket_accept/stream_socket_client). (nielsdos)

View file

@ -955,6 +955,13 @@ out:
if (transfer_encoding) { if (transfer_encoding) {
php_stream_filter_append(&stream->readfilters, transfer_encoding); php_stream_filter_append(&stream->readfilters, transfer_encoding);
} }
/* It's possible that the server already sent in more data than just the headers.
* We account for this by adjusting the progress counter by the difference of
* already read header data and the body. */
if (stream->writepos > stream->readpos) {
php_stream_notify_progress_increment(context, stream->writepos - stream->readpos, 0);
}
} }
return stream; return stream;

View file

@ -0,0 +1,52 @@
--TEST--
GH-10031 ([Stream] STREAM_NOTIFY_PROGRESS over HTTP emitted irregularly for last chunk of data)
--SKIPIF--
<?php
if (getenv("SKIP_SLOW_TESTS")) die("skip slow test");
?>
--INI--
allow_url_fopen=1
--CONFLICTS--
server
--FILE--
<?php
$serverCode = <<<'CODE'
$fsize = 1000;
$chunksize = 99;
$chunks = floor($fsize / $chunksize); // 10 chunks * 99 bytes
$lastchunksize = $fsize - $chunksize * $chunks; // 1 chunk * 10 bytes
header("Content-Length: " . $fsize);
flush();
for ($chunk = 1; $chunk <= $chunks; $chunk++) {
echo str_repeat('x', $chunksize);
@ob_flush();
usleep(50 * 1000);
}
echo str_repeat('x', $lastchunksize);
CODE;
include __DIR__."/../../../../sapi/cli/tests/php_cli_server.inc";
php_cli_server_start($serverCode, null, []);
$context = stream_context_create(['http' => ['ignore_errors' => true,]]);
$lastBytesTransferred = 0;
stream_context_set_params($context, ['notification' => function ($code, $s, $m, $mc, $bytes_transferred, $bytes_max)
use (&$lastBytesTransferred) {
if ($code === STREAM_NOTIFY_FILE_SIZE_IS) echo "expected filesize=$bytes_max".PHP_EOL;
$lastBytesTransferred = $bytes_transferred;
@ob_flush();
}]);
$get = file_get_contents("http://".PHP_CLI_SERVER_ADDRESS, false, $context);
echo "got filesize=" . strlen($get) . PHP_EOL;
var_dump($lastBytesTransferred);
?>
--EXPECT--
expected filesize=1000
got filesize=1000
int(1000)