From 667d3a8478796f55a46647d0b1697fd44e8fa762 Mon Sep 17 00:00:00 2001 From: peter279k Date: Fri, 16 Mar 2018 23:44:21 +0800 Subject: [PATCH 1/5] test enhancement --- tests/HTTP/Auth/AWSTest.php | 8 ++++++++ tests/HTTP/FunctionsTest.php | 32 ++++++++++++++++++++++++++++++++ tests/HTTP/SapiTest.php | 28 ++++++++++++++++++++++++++++ 3 files changed, 68 insertions(+) diff --git a/tests/HTTP/Auth/AWSTest.php b/tests/HTTP/Auth/AWSTest.php index f3b36cf..243da4b 100644 --- a/tests/HTTP/Auth/AWSTest.php +++ b/tests/HTTP/Auth/AWSTest.php @@ -42,6 +42,14 @@ public function testNoHeader() $this->assertEquals(AWS::ERR_NOAWSHEADER, $this->auth->errorCode); } + public function testInvalidAuthorizationHeader() + { + $this->request->setMethod('GET'); + $this->request->setHeader('Authorization', 'Invalid Auth Header'); + + $this->assertFalse($this->auth->init(), 'The Invalid AWS authorization header'); + } + public function testIncorrectContentMD5() { $accessKey = 'accessKey'; diff --git a/tests/HTTP/FunctionsTest.php b/tests/HTTP/FunctionsTest.php index f5e2e6e..0078873 100644 --- a/tests/HTTP/FunctionsTest.php +++ b/tests/HTTP/FunctionsTest.php @@ -6,6 +6,30 @@ class FunctionsTest extends \PHPUnit\Framework\TestCase { + /** + * @dataProvider getHeaderValuesDataOnValues2 + */ + public function testGetHeaderValuesOnValues2($result, $values1, $values2) + { + $this->assertEquals($result, getHeaderValues($values1, $values2)); + } + + public function getHeaderValuesDataOnValues2() + { + return [ + [ + ['a', 'b'], + ['a'], + ['b'], + ], + [ + ['a', 'b', 'c', 'd', 'e'], + ['a', 'b', 'c'], + ['d', 'e'], + ], + ]; + } + /** * @dataProvider getHeaderValuesData */ @@ -174,4 +198,12 @@ public function testToHTTPDate() toDate($dt) ); } + + public function testParseMimeTypeOnInvalidMimeType() + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('Not a valid mime-type: invalid_mime_type'); + + parseMimeType('invalid_mime_type'); + } } diff --git a/tests/HTTP/SapiTest.php b/tests/HTTP/SapiTest.php index 3c8e7f5..890ae58 100644 --- a/tests/HTTP/SapiTest.php +++ b/tests/HTTP/SapiTest.php @@ -31,6 +31,34 @@ public function testConstructFromServerArray() $this->assertNull($request->getRawServerValue('FOO')); } + public function testConstructFromServerArrayOnNullUrl() + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('The _SERVER array must have a REQUEST_URI key'); + + $request = Sapi::createFromServerArray([ + 'REQUEST_METHOD' => 'GET', + 'HTTP_USER_AGENT' => 'Evert', + 'CONTENT_TYPE' => 'text/xml', + 'CONTENT_LENGTH' => '400', + 'SERVER_PROTOCOL' => 'HTTP/1.0', + ]); + } + + public function testConstructFromServerArrayOnNullMethod() + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('The _SERVER array must have a REQUEST_METHOD key'); + + $request = Sapi::createFromServerArray([ + 'REQUEST_URI' => '/foo', + 'HTTP_USER_AGENT' => 'Evert', + 'CONTENT_TYPE' => 'text/xml', + 'CONTENT_LENGTH' => '400', + 'SERVER_PROTOCOL' => 'HTTP/1.0', + ]); + } + public function testConstructPHPAuth() { $request = Sapi::createFromServerArray([ From f73fb623d450393d2eedfa5be1b341781b142c2b Mon Sep 17 00:00:00 2001 From: Daniel Kesselberg Date: Wed, 24 May 2023 16:02:12 +0200 Subject: [PATCH 2/5] fix: handle client disconnect properly with ignore_user_abort true ignore_user_abort(true) instruct php to not abort the script on client disconnect. To avoid reading the entire stream and dismissing the data afterward, check between the chunks if the client is still there. Signed-off-by: Daniel Kesselberg --- lib/Sapi.php | 6 +++ tests/HTTP/SapiTest.php | 31 ++++++++++++++ tests/www/connection_aborted.php | 69 ++++++++++++++++++++++++++++++++ 3 files changed, 106 insertions(+) create mode 100644 tests/www/connection_aborted.php diff --git a/lib/Sapi.php b/lib/Sapi.php index f8e8397..55a4c46 100644 --- a/lib/Sapi.php +++ b/lib/Sapi.php @@ -115,6 +115,12 @@ public static function sendResponse(ResponseInterface $response) if ($copied <= 0) { break; } + // Abort on client disconnect. + // With ignore_user_abort(true), the script is not aborted on client disconnect. + // To avoid reading the entire stream and dismissing the data afterward, check between the chunks if the client is still there. + if (1 === ignore_user_abort() && 1 === connection_aborted()) { + break; + } $left -= $copied; } } else { diff --git a/tests/HTTP/SapiTest.php b/tests/HTTP/SapiTest.php index 890ae58..3b0a42b 100644 --- a/tests/HTTP/SapiTest.php +++ b/tests/HTTP/SapiTest.php @@ -287,4 +287,35 @@ public function testSendWorksWithCallbackAsBody() $this->assertEquals('foo', $result); } + + public function testSendConnectionAborted(): void + { + $baseUrl = getenv('BASEURL'); + if (!$baseUrl) { + $this->markTestSkipped('Set an environment value BASEURL to continue'); + } + + $url = rtrim($baseUrl, '/').'/connection_aborted.php'; + $chunk_size = 4 * 1024 * 1024; + $fetch_size = 6 * 1024 * 1024; + + $stream = fopen($url, 'r'); + $size = 0; + + while ($size <= $fetch_size) { + $temp = fread($stream, 8192); + if (false === $temp) { + break; + } + $size += strlen($temp); + } + + fclose($stream); + + sleep(5); + + $bytes_read = file_get_contents(sys_get_temp_dir().'/dummy_stream_read_counter'); + $this->assertEquals($chunk_size * 2, $bytes_read); + $this->assertGreaterThanOrEqual($fetch_size, $bytes_read); + } } diff --git a/tests/www/connection_aborted.php b/tests/www/connection_aborted.php new file mode 100644 index 0000000..0774954 --- /dev/null +++ b/tests/www/connection_aborted.php @@ -0,0 +1,69 @@ +position = 0; + + return true; + } + + public function stream_read(int $count) + { + $this->position += $count; + + return random_bytes($count); + } + + public function stream_tell(): int + { + return $this->position; + } + + public function stream_eof(): bool + { + return $this->position > 25 * 1024 * 1024; + } + + public function stream_close(): void + { + file_put_contents(sys_get_temp_dir().'/dummy_stream_read_counter', $this->position); + } +} + +/* + * The DummyStream wrapper has two functions: + * - Provide dummy data. + * - Count how many bytes have been read. + */ +stream_wrapper_register('dummy', DummyStream::class); + +/* + * Overwrite default connection handling. + * The default behaviour is however for your script to be aborted when the remote client disconnects. + * + * Nextcloud/ownCloud set ignore_user_abort(true) on purpose to work around + * some edge cases where the default behavior would end a script too early. + * + * https://github.com/owncloud/core/issues/22370 + * https://github.com/owncloud/core/pull/26775 + */ +ignore_user_abort(true); + +$body = fopen('dummy://hello', 'r'); + +$response = new HTTP\Response(); +$response->setStatus(200); +$response->addHeader('Content-Length', 25 * 1024 * 1024); +$response->setBody($body); + +HTTP\Sapi::sendResponse($response); From 54c889bd20eba95dfc1870bc158b903773c44eea Mon Sep 17 00:00:00 2001 From: Phil Davis Date: Mon, 26 Jun 2023 14:50:42 +0545 Subject: [PATCH 3/5] Declare return type of stream_read in test --- tests/www/connection_aborted.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/www/connection_aborted.php b/tests/www/connection_aborted.php index 0774954..e9f62ab 100644 --- a/tests/www/connection_aborted.php +++ b/tests/www/connection_aborted.php @@ -17,7 +17,7 @@ public function stream_open(string $path, string $mode, int $options, ?string &$ return true; } - public function stream_read(int $count) + public function stream_read(int $count): string { $this->position += $count; From 9cecf083e2b661d1c552c7cb86cebed06bde24eb Mon Sep 17 00:00:00 2001 From: Phil Davis Date: Mon, 26 Jun 2023 15:50:26 +0545 Subject: [PATCH 4/5] Prepare 5.1.7 --- CHANGELOG.md | 6 ++++++ lib/Version.php | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2dddce4..97b5615 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,12 @@ ChangeLog ========= +5.1.7 (2023-06-26) +------------------ + +* #98 and #176 Add more tests (@peter279k) +* #207 fix: handle client disconnect properly with ignore_user_abort true (@kesselb) + 5.1.6 (2022-07-15) ------------------ diff --git a/lib/Version.php b/lib/Version.php index 47582f2..1c9190c 100644 --- a/lib/Version.php +++ b/lib/Version.php @@ -16,5 +16,5 @@ class Version /** * Full version number. */ - const VERSION = '5.1.6'; + const VERSION = '5.1.7'; } From 10f57abbef6b60902b65a812d09f1639d373a29f Mon Sep 17 00:00:00 2001 From: Phil Davis Date: Mon, 26 Jun 2023 15:56:20 +0545 Subject: [PATCH 5/5] Remove type from property in test, so that it works with older PHP versions --- tests/www/connection_aborted.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/www/connection_aborted.php b/tests/www/connection_aborted.php index e9f62ab..724ad2d 100644 --- a/tests/www/connection_aborted.php +++ b/tests/www/connection_aborted.php @@ -8,7 +8,7 @@ class DummyStream { - private int $position; + private $position; public function stream_open(string $path, string $mode, int $options, ?string &$opened_path): bool {