mirror of
https://github.com/kamilwylegala/cakephp2-php8.git
synced 2025-01-31 09:06:17 +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;
|
||||
|
||||
/**
|
||||
* File range. Used for requesting ranges of files.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $_fileRange = null;
|
||||
|
||||
/**
|
||||
* The charset the response body is encoded with
|
||||
*
|
||||
|
@ -413,8 +420,8 @@ class CakeResponse {
|
|||
$this->_sendHeader($header, $value);
|
||||
}
|
||||
if ($this->_file) {
|
||||
$this->_sendFile($this->_file);
|
||||
$this->_file = null;
|
||||
$this->_sendFile($this->_file, $this->_fileRange);
|
||||
$this->_file = $this->_fileRange = null;
|
||||
} else {
|
||||
$this->_sendContent($this->_body);
|
||||
}
|
||||
|
@ -1268,19 +1275,7 @@ class CakeResponse {
|
|||
|
||||
$httpRange = env('HTTP_RANGE');
|
||||
if (isset($httpRange)) {
|
||||
list(, $range) = explode('=', $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);
|
||||
$this->_fileRange($file, $httpRange);
|
||||
} else {
|
||||
$this->header('Content-Length', $fileSize);
|
||||
}
|
||||
|
@ -1291,22 +1286,73 @@ class CakeResponse {
|
|||
$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.
|
||||
*
|
||||
* @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
|
||||
*/
|
||||
protected function _sendFile($file) {
|
||||
protected function _sendFile($file, $range) {
|
||||
$compress = $this->outputCompressed();
|
||||
$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)) {
|
||||
if (!$this->_isActive()) {
|
||||
$file->close();
|
||||
return false;
|
||||
}
|
||||
set_time_limit(0);
|
||||
echo fread($file->handle, 8192);
|
||||
$offset = $file->offset();
|
||||
if ($end && $offset >= $end) {
|
||||
break;
|
||||
}
|
||||
if ($end && $offset + $bufferSize >= $end) {
|
||||
$bufferSize = $end - $offset;
|
||||
}
|
||||
echo fread($file->handle, $bufferSize);
|
||||
if (!$compress) {
|
||||
$this->_flushBuffer();
|
||||
}
|
||||
|
|
|
@ -1,9 +1,5 @@
|
|||
<?php
|
||||
/**
|
||||
* CakeResponse Test case file.
|
||||
*
|
||||
* PHP 5
|
||||
*
|
||||
* CakePHP(tm) : Rapid Development Framework (http://cakephp.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');
|
||||
}
|
||||
|
||||
/**
|
||||
* 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