Skip to content

Instantly share code, notes, and snippets.

@stealth35
Created December 13, 2011 14:04
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save stealth35/1472230 to your computer and use it in GitHub Desktop.
Save stealth35/1472230 to your computer and use it in GitHub Desktop.
BinaryFileResponse with X-SendFile and Date Range
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* 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 <jordan.alliot@gmail.com>
* @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