diff --git a/Dockerfile b/Dockerfile index 77ffdfd..86f3a3a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,6 +4,10 @@ FROM ${DOCKER_YII2_PHP_IMAGE} # Project source-code WORKDIR /project ADD composer.* /project/ +# Apply testing patches +ADD tests/phpunit_mock_objects.patch /project/tests/phpunit_mock_objects.patch +ADD tests/phpunit_getopt.patch /project/tests/phpunit_getopt.patch +# Install packgaes RUN /usr/local/bin/composer install --prefer-dist ADD ./ /project ENV PATH /project/vendor/bin:${PATH} diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index 3f50c0d..55bba34 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -4,6 +4,7 @@ Yii Framework 2 Change Log 2.0.39 under development ------------------------ +- Bug #18290: Fix response with non-seekable streams (schmunk42) - Bug #16418: Fixed `yii\data\Pagination::getLinks()` to return links to the first and the last pages regardless of the current page (ptz-nerf, bizley) - Bug #18297: Replace usage of deprecated `ReflectionParameter::isArray()` method in PHP8 (baletskyi) - Bug #18308: Fixed `\yii\base\Model::getErrorSummary()` reverse order (DrDeath72) diff --git a/framework/web/Response.php b/framework/web/Response.php index 045c5d5..f5720bb 100644 --- a/framework/web/Response.php +++ b/framework/web/Response.php @@ -441,7 +441,12 @@ class Response extends \yii\base\Response if (is_array($this->stream)) { list($handle, $begin, $end) = $this->stream; - fseek($handle, $begin); + + // only seek if stream is seekable + if ($this->isSeekable($handle)) { + fseek($handle, $begin); + } + while (!feof($handle) && ($pos = ftell($handle)) <= $end) { if ($pos + $chunkSize > $end) { $chunkSize = $end - $pos + 1; @@ -583,8 +588,12 @@ class Response extends \yii\base\Response if (isset($options['fileSize'])) { $fileSize = $options['fileSize']; } else { - fseek($handle, 0, SEEK_END); - $fileSize = ftell($handle); + if ($this->isSeekable($handle)) { + fseek($handle, 0, SEEK_END); + $fileSize = ftell($handle); + } else { + $fileSize = 0; + } } $range = $this->getHttpRange($fileSize); @@ -1089,4 +1098,20 @@ class Response extends \yii\base\Response } } } + + /** + * Checks if a stream is seekable + * + * @param $handle + * @return bool + */ + private function isSeekable($handle) + { + if (!is_resource($handle)) { + return true; + } + + $metaData = stream_get_meta_data($handle); + return isset($metaData['seekable']) && $metaData['seekable'] === true; + } } diff --git a/tests/framework/web/ResponseTest.php b/tests/framework/web/ResponseTest.php index 50b1226..35ddf94 100644 --- a/tests/framework/web/ResponseTest.php +++ b/tests/framework/web/ResponseTest.php @@ -191,6 +191,20 @@ class ResponseTest extends \yiiunit\TestCase $this->assertEquals($statusCode, $this->response->getStatusCode()); } + /** + * @see https://github.com/yiisoft/yii2/pull/18290 + */ + public function testNonSeekableStream() + { + $stream = fopen('php://output', 'r+'); + ob_start(); + $this->response + ->sendStreamAsFile($stream, 'test-stream') + ->send(); + ob_get_clean(); + static::assertEquals(200, $this->response->statusCode); + } + public function dataProviderSetStatusCodeByException() { $data = [