|
|
|
@ -352,6 +352,95 @@ class Response extends \yii\base\Response
|
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public function sendStream($handle, $options = array()) |
|
|
|
|
{ |
|
|
|
|
fseek($handle, 0, SEEK_END); |
|
|
|
|
$fileSize = ftell($handle); |
|
|
|
|
|
|
|
|
|
$contentStart = 0; |
|
|
|
|
$contentEnd = $fileSize - 1; |
|
|
|
|
|
|
|
|
|
$headers = $this->getHeaders(); |
|
|
|
|
|
|
|
|
|
if (isset($_SERVER['HTTP_RANGE'])) { |
|
|
|
|
// client sent us a multibyte range, can not hold this one for now |
|
|
|
|
if (strpos($_SERVER['HTTP_RANGE'], ',') !== false) { |
|
|
|
|
$headers->set('Content-Range', "bytes $contentStart-$contentEnd/$fileSize"); |
|
|
|
|
throw new HttpException(416, Yii::t('yii', 'Requested range not satisfiable')); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
$range = str_replace('bytes=', '', $_SERVER['HTTP_RANGE']); |
|
|
|
|
|
|
|
|
|
// range requests starts from "-", so it means that data must be dumped the end point. |
|
|
|
|
if ($range[0] === '-') { |
|
|
|
|
$contentStart = $fileSize - substr($range, 1); |
|
|
|
|
} else { |
|
|
|
|
$range = explode('-', $range); |
|
|
|
|
$contentStart = $range[0]; |
|
|
|
|
|
|
|
|
|
// check if the last-byte-pos presents in header |
|
|
|
|
if ((isset($range[1]) && is_numeric($range[1]))) { |
|
|
|
|
$contentEnd = $range[1]; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/* Check the range and make sure it's treated according to the specs. |
|
|
|
|
* http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html |
|
|
|
|
*/ |
|
|
|
|
// End bytes can not be larger than $end. |
|
|
|
|
$contentEnd = $contentEnd > $fileSize ? $fileSize - 1 : $contentEnd; |
|
|
|
|
|
|
|
|
|
// Validate the requested range and return an error if it's not correct. |
|
|
|
|
if ($contentStart > $contentEnd || $contentStart > $fileSize - 1 || $contentStart < 0) { |
|
|
|
|
$headers->set('Content-Range', "bytes $contentStart-$contentEnd/$fileSize"); |
|
|
|
|
throw new HttpException(416, Yii::t('yii', 'Requested range not satisfiable')); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
$this->setStatusCode(206); |
|
|
|
|
$headers->set('Content-Range', "bytes $contentStart-$contentEnd/$fileSize"); |
|
|
|
|
} else { |
|
|
|
|
$this->setStatusCode(200); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (isset($options['mimeType'])) { |
|
|
|
|
$headers->set('Content-Type', $options['mimeType']); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
$length = $contentEnd - $contentStart + 1; |
|
|
|
|
$disposition = empty($options['disposition']) ? 'attachment' : $options['disposition']; |
|
|
|
|
if (!isset($options['saveName'])) { |
|
|
|
|
$options['saveName'] = 'data'; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
$headers->set('Pragma', 'public') |
|
|
|
|
->set('Expires', '0') |
|
|
|
|
->set('Cache-Control', 'must-revalidate, post-check=0, pre-check=0') |
|
|
|
|
->set('Content-Disposition', "$disposition; filename=\"{$options['saveName']}\"") |
|
|
|
|
->set('Content-Length', $length) |
|
|
|
|
->set('Content-Transfer-Encoding', 'binary'); |
|
|
|
|
|
|
|
|
|
if (isset($options['headers'])) { |
|
|
|
|
foreach ($options['headers'] as $header => $value) { |
|
|
|
|
$headers->add($header, $value); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fseek($handle, $contentStart); |
|
|
|
|
|
|
|
|
|
set_time_limit(0); // Reset time limit for big files |
|
|
|
|
|
|
|
|
|
$chunkSize = 8 * 1024 * 1024; // 8MB per chunk |
|
|
|
|
while (!feof($handle) && ($fPointer = ftell($handle)) <= $contentEnd) { |
|
|
|
|
if ($fPointer + $chunkSize > $contentEnd) { |
|
|
|
|
$chunkSize = $contentEnd - $fPointer + 1; |
|
|
|
|
} |
|
|
|
|
echo fread($handle, $chunkSize); |
|
|
|
|
flush(); // Free up memory. Otherwise large files will trigger PHP's memory limit. |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fclose($handle); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Sends existing file to a browser as a download using x-sendfile. |
|
|
|
|
* |
|
|
|
@ -404,52 +493,39 @@ class Response extends \yii\base\Response
|
|
|
|
|
* @param string $filePath file name with full path |
|
|
|
|
* @param array $options additional options: |
|
|
|
|
* |
|
|
|
|
* - saveName: file name shown to the user, if not set real file name will be used |
|
|
|
|
* - mimeType: mime type of the file, if not set it will be guessed automatically based on the file name, |
|
|
|
|
* if set to null no content-type header will be sent. |
|
|
|
|
* - xHeader: appropriate x-sendfile header, defaults to "X-Sendfile" |
|
|
|
|
* - terminate: whether to terminate the current application after calling this method, defaults to true |
|
|
|
|
* - forceDownload: specifies whether the file will be downloaded or shown inline, defaults to true |
|
|
|
|
* - addHeaders: an array of additional http headers in header-value pairs |
|
|
|
|
* - saveName: file name shown to the user. If not set, the name will be determined from `$filePath`. |
|
|
|
|
* - mimeType: MIME type of the file. If not set, it will be determined based on the file name. |
|
|
|
|
* - xHeader: appropriate x-sendfile header, defaults to "X-Sendfile". |
|
|
|
|
* - disposition: either "attachment" or "inline". This specifies whether the file will be downloaded |
|
|
|
|
* or shown inline. Defaults to "attachment". |
|
|
|
|
* - headers: an array of additional http headers in name-value pairs. |
|
|
|
|
*/ |
|
|
|
|
public function xSendFile($filePath, $options = array()) |
|
|
|
|
{ |
|
|
|
|
if (!isset($options['forceDownload']) || $options['forceDownload']) { |
|
|
|
|
$disposition = 'attachment'; |
|
|
|
|
} else { |
|
|
|
|
$disposition = 'inline'; |
|
|
|
|
} |
|
|
|
|
$headers = $this->getHeaders(); |
|
|
|
|
|
|
|
|
|
if (!isset($options['saveName'])) { |
|
|
|
|
$options['saveName'] = basename($filePath); |
|
|
|
|
} |
|
|
|
|
$headers->set(empty($options['xHeader']) ? 'X-Sendfile' : $options['xHeader'], $filePath); |
|
|
|
|
|
|
|
|
|
if (!isset($options['mimeType'])) { |
|
|
|
|
if (($options['mimeType'] = FileHelper::getMimeTypeByExtension($filePath)) === null) { |
|
|
|
|
$options['mimeType'] = 'text/plain'; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (!isset($options['xHeader'])) { |
|
|
|
|
$options['xHeader'] = 'X-Sendfile'; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
$headers = $this->getHeaders(); |
|
|
|
|
|
|
|
|
|
if ($options['mimeType'] !== null) { |
|
|
|
|
$headers->set('Content-Type', $options['mimeType']); |
|
|
|
|
|
|
|
|
|
$disposition = empty($options['disposition']) ? 'attachment' : $options['disposition']; |
|
|
|
|
if (!isset($options['saveName'])) { |
|
|
|
|
$options['saveName'] = basename($filePath); |
|
|
|
|
} |
|
|
|
|
$headers->set('Content-Disposition', "$disposition; filename=\"{$options['saveName']}\""); |
|
|
|
|
if (isset($options['addHeaders'])) { |
|
|
|
|
foreach ($options['addHeaders'] as $header => $value) { |
|
|
|
|
$headers->set($header, $value); |
|
|
|
|
|
|
|
|
|
if (isset($options['headers'])) { |
|
|
|
|
foreach ($options['headers'] as $header => $value) { |
|
|
|
|
$headers->add($header, $value); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
$headers->set(trim($options['xHeader']), $filePath); |
|
|
|
|
|
|
|
|
|
if (!isset($options['terminate']) || $options['terminate']) { |
|
|
|
|
Yii::$app->end(); |
|
|
|
|
} |
|
|
|
|
$this->send(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|