mirror of
https://github.com/kamilwylegala/cakephp2-php8.git
synced 2025-02-07 12:36:25 +00:00
Fix downloading Ranges in files.
Correctly handle ranges that don't terminate at the end of the file. Also reject invalid ranges as described in RFC-2616. Thanks to Kim Biesbjerg for the initial patch. Fixes #3914
This commit is contained in:
parent
b3273e9cc2
commit
494fd05de6
2 changed files with 160 additions and 22 deletions
|
@ -345,6 +345,13 @@ class CakeResponse {
|
||||||
*/
|
*/
|
||||||
protected $_file = null;
|
protected $_file = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* File range. Used for requesting ranges of files.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $_fileRange = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The charset the response body is encoded with
|
* The charset the response body is encoded with
|
||||||
*
|
*
|
||||||
|
@ -413,8 +420,8 @@ class CakeResponse {
|
||||||
$this->_sendHeader($header, $value);
|
$this->_sendHeader($header, $value);
|
||||||
}
|
}
|
||||||
if ($this->_file) {
|
if ($this->_file) {
|
||||||
$this->_sendFile($this->_file);
|
$this->_sendFile($this->_file, $this->_fileRange);
|
||||||
$this->_file = null;
|
$this->_file = $this->_fileRange = null;
|
||||||
} else {
|
} else {
|
||||||
$this->_sendContent($this->_body);
|
$this->_sendContent($this->_body);
|
||||||
}
|
}
|
||||||
|
@ -1268,19 +1275,7 @@ class CakeResponse {
|
||||||
|
|
||||||
$httpRange = env('HTTP_RANGE');
|
$httpRange = env('HTTP_RANGE');
|
||||||
if (isset($httpRange)) {
|
if (isset($httpRange)) {
|
||||||
list(, $range) = explode('=', $httpRange);
|
$this->_fileRange($file, $httpRange);
|
||||||
|
|
||||||
$size = $fileSize - 1;
|
|
||||||
$length = $fileSize - $range;
|
|
||||||
|
|
||||||
$this->header(array(
|
|
||||||
'Content-Length' => $length,
|
|
||||||
'Content-Range' => 'bytes ' . $range . $size . '/' . $fileSize
|
|
||||||
));
|
|
||||||
|
|
||||||
$this->statusCode(206);
|
|
||||||
$file->open('rb', true);
|
|
||||||
$file->offset($range);
|
|
||||||
} else {
|
} else {
|
||||||
$this->header('Content-Length', $fileSize);
|
$this->header('Content-Length', $fileSize);
|
||||||
}
|
}
|
||||||
|
@ -1291,22 +1286,73 @@ class CakeResponse {
|
||||||
$this->_file = $file;
|
$this->_file = $file;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply a file range to a file and set the end offset.
|
||||||
|
*
|
||||||
|
* If an invalid range is requested a 416 Status code will be used
|
||||||
|
* in the response.
|
||||||
|
*
|
||||||
|
* @param File $file The file to set a range on.
|
||||||
|
* @param string $httpRange The range to use.
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
protected function _fileRange($file, $httpRange) {
|
||||||
|
list(, $range) = explode('=', $httpRange);
|
||||||
|
list($start, $end) = explode('-', $range);
|
||||||
|
|
||||||
|
$fileSize = $file->size();
|
||||||
|
$lastByte = $fileSize - 1;
|
||||||
|
if ($start > $end || $end > $lastByte || $start > $lastByte) {
|
||||||
|
$this->statusCode(416);
|
||||||
|
$this->header(array(
|
||||||
|
'Content-Range' => 'bytes 0-' . $lastByte . '/' . $fileSize
|
||||||
|
));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->header(array(
|
||||||
|
'Content-Length' => $end - $start + 1,
|
||||||
|
'Content-Range' => 'bytes ' . $start . '-' . $end . '/' . $fileSize
|
||||||
|
));
|
||||||
|
|
||||||
|
$this->statusCode(206);
|
||||||
|
$this->_fileRange = array($start, $end);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reads out a file, and echos the content to the client.
|
* Reads out a file, and echos the content to the client.
|
||||||
*
|
*
|
||||||
* @param File $file File object
|
* @param File $file File object
|
||||||
|
* @param array $range The range to read out of the file.
|
||||||
* @return boolean True is whole file is echoed successfully or false if client connection is lost in between
|
* @return boolean True is whole file is echoed successfully or false if client connection is lost in between
|
||||||
*/
|
*/
|
||||||
protected function _sendFile($file) {
|
protected function _sendFile($file, $range) {
|
||||||
$compress = $this->outputCompressed();
|
$compress = $this->outputCompressed();
|
||||||
$file->open('rb');
|
$file->open('rb');
|
||||||
|
|
||||||
|
$end = $start = false;
|
||||||
|
if ($range) {
|
||||||
|
list($start, $end) = $range;
|
||||||
|
}
|
||||||
|
if ($start !== false) {
|
||||||
|
$file->offset($start);
|
||||||
|
}
|
||||||
|
|
||||||
|
$bufferSize = 8192;
|
||||||
|
set_time_limit(0);
|
||||||
while (!feof($file->handle)) {
|
while (!feof($file->handle)) {
|
||||||
if (!$this->_isActive()) {
|
if (!$this->_isActive()) {
|
||||||
$file->close();
|
$file->close();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
set_time_limit(0);
|
$offset = $file->offset();
|
||||||
echo fread($file->handle, 8192);
|
if ($end && $offset >= $end) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if ($end && $offset + $bufferSize >= $end) {
|
||||||
|
$bufferSize = $end - $offset;
|
||||||
|
}
|
||||||
|
echo fread($file->handle, $bufferSize);
|
||||||
if (!$compress) {
|
if (!$compress) {
|
||||||
$this->_flushBuffer();
|
$this->_flushBuffer();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,5 @@
|
||||||
<?php
|
<?php
|
||||||
/**
|
/**
|
||||||
* CakeResponse Test case file.
|
|
||||||
*
|
|
||||||
* PHP 5
|
|
||||||
*
|
|
||||||
* CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
|
* CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
|
||||||
* Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
|
* Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
|
||||||
*
|
*
|
||||||
|
@ -1389,4 +1385,100 @@ class CakeResponseTest extends CakeTestCase {
|
||||||
$response->file(CAKE . 'Test' . DS . 'test_app' . DS . 'Vendor' . DS . 'img' . DS . 'test_2.JPG');
|
$response->file(CAKE . 'Test' . DS . 'test_app' . DS . 'Vendor' . DS . 'img' . DS . 'test_2.JPG');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test fetching ranges from a file.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function testFileRange() {
|
||||||
|
$_SERVER['HTTP_RANGE'] = 'bytes=8-25';
|
||||||
|
$response = $this->getMock('CakeResponse', array(
|
||||||
|
'header',
|
||||||
|
'type',
|
||||||
|
'_sendHeader',
|
||||||
|
'_setContentType',
|
||||||
|
'_isActive',
|
||||||
|
'_clearBuffer',
|
||||||
|
'_flushBuffer'
|
||||||
|
));
|
||||||
|
|
||||||
|
$response->expects($this->exactly(1))
|
||||||
|
->method('type')
|
||||||
|
->with('css')
|
||||||
|
->will($this->returnArgument(0));
|
||||||
|
|
||||||
|
$response->expects($this->at(1))
|
||||||
|
->method('header')
|
||||||
|
->with('Content-Disposition', 'attachment; filename="test_asset.css"');
|
||||||
|
|
||||||
|
$response->expects($this->at(2))
|
||||||
|
->method('header')
|
||||||
|
->with('Accept-Ranges', 'bytes');
|
||||||
|
|
||||||
|
$response->expects($this->at(3))
|
||||||
|
->method('header')
|
||||||
|
->with(array(
|
||||||
|
'Content-Length' => 18,
|
||||||
|
'Content-Range' => 'bytes 8-25/38',
|
||||||
|
));
|
||||||
|
|
||||||
|
$response->expects($this->once())->method('_clearBuffer');
|
||||||
|
|
||||||
|
$response->expects($this->any())
|
||||||
|
->method('_isActive')
|
||||||
|
->will($this->returnValue(true));
|
||||||
|
|
||||||
|
$response->file(
|
||||||
|
CAKE . 'Test' . DS . 'test_app' . DS . 'Vendor' . DS . 'css' . DS . 'test_asset.css',
|
||||||
|
array('download' => true)
|
||||||
|
);
|
||||||
|
|
||||||
|
ob_start();
|
||||||
|
$result = $response->send();
|
||||||
|
$output = ob_get_clean();
|
||||||
|
$this->assertEquals(206, $response->statusCode());
|
||||||
|
$this->assertEquals("is the test asset", $output);
|
||||||
|
$this->assertTrue($result !== false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test invalid file ranges.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function testFileRangeInvalid() {
|
||||||
|
$_SERVER['HTTP_RANGE'] = 'bytes=30-2';
|
||||||
|
$response = $this->getMock('CakeResponse', array(
|
||||||
|
'header',
|
||||||
|
'type',
|
||||||
|
'_sendHeader',
|
||||||
|
'_setContentType',
|
||||||
|
'_isActive',
|
||||||
|
'_clearBuffer',
|
||||||
|
'_flushBuffer'
|
||||||
|
));
|
||||||
|
|
||||||
|
$response->expects($this->at(1))
|
||||||
|
->method('header')
|
||||||
|
->with('Content-Disposition', 'attachment; filename="test_asset.css"');
|
||||||
|
|
||||||
|
$response->expects($this->at(2))
|
||||||
|
->method('header')
|
||||||
|
->with('Accept-Ranges', 'bytes');
|
||||||
|
|
||||||
|
$response->expects($this->at(3))
|
||||||
|
->method('header')
|
||||||
|
->with(array(
|
||||||
|
'Content-Range' => 'bytes 0-37/38',
|
||||||
|
));
|
||||||
|
|
||||||
|
$response->file(
|
||||||
|
CAKE . 'Test' . DS . 'test_app' . DS . 'Vendor' . DS . 'css' . DS . 'test_asset.css',
|
||||||
|
array('download' => true)
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertEquals(416, $response->statusCode());
|
||||||
|
$result = $response->send();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue