Created
December 13, 2011 14:04
-
-
Save stealth35/1472230 to your computer and use it in GitHub Desktop.
BinaryFileResponse with X-SendFile and Date Range
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/* | |
* This file is part of the Symfony package. | |
* | |
* (c) Fabien Potencier <[email protected]> | |
* | |
* For the full copyright and license information, please view the LICENSE | |
* file that was distributed with this source code. | |
*/ | |
namespace Symfony\Component\HttpFoundation; | |
use Symfony\Component\HttpFoundation\File\File; | |
use Symfony\Component\HttpFoundation\File\Exception\FileException; | |
/** | |
* BinaryFileResponse represents an HTTP response representing a binary file. | |
* | |
* @author Jordan Alliot <[email protected]> | |
* @author stealth35 | |
*/ | |
class BinaryFileResponse extends Response | |
{ | |
private $file; | |
private $offset; | |
private $maxlen; | |
/** | |
* Creates a BinaryFileResponse. | |
* | |
* @param SplFileInfo|string $file The file to download | |
* @param integer $status The response status code | |
* @param array $headers An array of response headers | |
* @param boolean $autoValidation Whether ETag and Last-Modified headers | |
* are automatically set or not | |
* @param boolean $public Files are public by default | |
*/ | |
public function __construct($file, $status = 200, $headers = array(), $autoValidation = true, $public = true) | |
{ | |
parent::__construct('', $status, $headers); | |
$this->setFile($file); | |
$this->setProtocolVersion('1.1'); | |
$this->headers->set('Content-Transfer-Encoding', 'binary'); | |
if (true === $autoValidation) { | |
$this->setAutoLastModified(); | |
$this->setAutoEtag(); | |
} | |
if (true === $public) { | |
$this->setPublic(); | |
} | |
$request = Request::createFromGlobals(); | |
if ($request->headers->has('x-sendfile-type')) { | |
$this->processSendFile($request); | |
} else if ($request->headers->has('range')) { | |
$this->processRange($request); | |
} | |
} | |
/** | |
* Sets the file to download. | |
* | |
* Also sets some headers for the download, namely | |
* Content-Disposition, Content-Length and Content-Type. | |
* | |
* @param SplFileInfo|string $file The file to download | |
*/ | |
public function setFile($file) | |
{ | |
if (null === $file) { | |
throw new \InvalidArgumentException('File cannot be null.'); | |
} | |
$file = new File((string) $file, true); | |
if (!$file->isReadable()) { | |
throw new FileException('File must be readable.'); | |
} | |
$this->file = $file; | |
$this->makeDisposition(); | |
$this->headers->set('Content-Length', $file->getSize()); | |
$this->headers->set('Content-Type', $file->getMimeType() ?: 'application/octet-stream'); | |
$this->headers->set('Accept-Ranges', 'bytes'); | |
} | |
/** | |
* Returns the file. | |
* | |
* @return File | |
*/ | |
public function getFile() | |
{ | |
return $this->file; | |
} | |
/** | |
* Sets the Content-Disposition header. | |
* | |
* @param string $disposition Either "inline" or "attachment" (default) | |
* @param string $filename Name of the file (by default the original file name) | |
* May be unicode | |
* @param string $filenameFallback A string containing only ASCII characters that | |
* is semantically equivalent to $filename. If the filename is already ASCII, | |
* it can be omitted, or just copied from $filename | |
*/ | |
public function makeDisposition($disposition = null, $filename = '', $filenameFallback = '') | |
{ | |
$this->headers->set('Content-Disposition', $this->headers->makeDisposition( | |
$disposition ?: ResponseHeaderBag::DISPOSITION_ATTACHMENT, | |
$filename ?: $this->file->getBasename(), | |
$filenameFallback ?: rawurlencode($this->file->getBasename()) | |
)); | |
} | |
/** | |
* Automatically sets the Last-Modified header according to the file modification date. | |
*/ | |
public function setAutoLastModified() | |
{ | |
$this->setLastModified(\DateTime::createFromFormat('U', $this->file->getMTime())); | |
} | |
/** | |
* Automatically sets the ETag header according to the checksum of the file. | |
*/ | |
public function setAutoEtag() | |
{ | |
$this->setEtag(sha1_file($this->file->getPathname())); | |
} | |
/** | |
* Sends the file. | |
*/ | |
public function sendContent() | |
{ | |
if (!$this->isSuccessful()) { | |
parent::sendContent(); | |
return; | |
} | |
$offset = (null !== $this->offset) ? $this->offset : 0; | |
$maxlen = (null !== $this->maxlen) ? $this->maxlen : -1; | |
$out = fopen('php://output', 'wb'); | |
$file = fopen($this->file->getPathname(), 'rb'); | |
stream_copy_to_stream($file, $out, $maxlen, $offset); | |
fclose($out); | |
fclose($file); | |
} | |
private function processSendFile(Request $request) | |
{ | |
$sendfiletype = $request->headers->get('x-sendfile-type'); | |
$this->headers->set($sendfiletype, $this->file->getPathname()); | |
$this->maxlen = 0; | |
} | |
private function processRange(Request $request) | |
{ | |
if ($request->headers->has('if-range') && ($this->getEtag() !== $request->headers->get('if-range'))) { | |
return; | |
} | |
$range = $request->headers->get('range'); | |
list($start, $end) = explode('-', substr($range, 6)); | |
if ('' !== $end) { | |
$this->maxlen = $end - $start; | |
} else { | |
$end = $this->file->getSize() - 1; | |
} | |
$this->offset = $start; | |
$this->setStatusCode(206); | |
$this->headers->set('Content-Range', sprintf('bytes %s-%s/%s', $start, $end, $this->file->getSize())); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment