2015-08-17 17:00:26 -07:00
< ? 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 delivering a file .
*
* @ author Niklas Fiekas < niklas . fiekas @ tu - clausthal . de >
* @ author stealth35 < stealth35 - php @ live . fr >
* @ author Igor Wiedler < igor @ wiedler . ch >
* @ author Jordan Alliot < jordan . alliot @ gmail . com >
* @ author Sergey Linnik < linniksa @ gmail . com >
*/
class BinaryFileResponse extends Response
{
protected static $trustXSendfileTypeHeader = false ;
protected $file ;
protected $offset ;
protected $maxlen ;
protected $deleteFileAfterSend = false ;
/**
* Constructor .
*
* @ param \SplFileInfo | string $file The file to stream
* @ param int $status The response status code
* @ param array $headers An array of response headers
* @ param bool $public Files are public by default
* @ param null | string $contentDisposition The type of Content - Disposition to set automatically with the filename
* @ param bool $autoEtag Whether the ETag header should be automatically set
* @ param bool $autoLastModified Whether the Last - Modified header should be automatically set
*/
public function __construct ( $file , $status = 200 , $headers = array (), $public = true , $contentDisposition = null , $autoEtag = false , $autoLastModified = true )
{
parent :: __construct ( null , $status , $headers );
$this -> setFile ( $file , $contentDisposition , $autoEtag , $autoLastModified );
if ( $public ) {
$this -> setPublic ();
}
}
/**
* @ param \SplFileInfo | string $file The file to stream
* @ param int $status The response status code
* @ param array $headers An array of response headers
* @ param bool $public Files are public by default
* @ param null | string $contentDisposition The type of Content - Disposition to set automatically with the filename
* @ param bool $autoEtag Whether the ETag header should be automatically set
* @ param bool $autoLastModified Whether the Last - Modified header should be automatically set
*
* @ return BinaryFileResponse The created response
*/
public static function create ( $file = null , $status = 200 , $headers = array (), $public = true , $contentDisposition = null , $autoEtag = false , $autoLastModified = true )
{
return new static ( $file , $status , $headers , $public , $contentDisposition , $autoEtag , $autoLastModified );
}
/**
* Sets the file to stream .
*
* @ param \SplFileInfo | string $file The file to stream
* @ param string $contentDisposition
* @ param bool $autoEtag
* @ param bool $autoLastModified
*
* @ return BinaryFileResponse
*
* @ throws FileException
*/
public function setFile ( $file , $contentDisposition = null , $autoEtag = false , $autoLastModified = true )
{
if ( ! $file instanceof File ) {
if ( $file instanceof \SplFileInfo ) {
$file = new File ( $file -> getPathname ());
} else {
$file = new File (( string ) $file );
}
}
if ( ! $file -> isReadable ()) {
throw new FileException ( 'File must be readable.' );
}
$this -> file = $file ;
if ( $autoEtag ) {
$this -> setAutoEtag ();
}
if ( $autoLastModified ) {
$this -> setAutoLastModified ();
}
if ( $contentDisposition ) {
$this -> setContentDisposition ( $contentDisposition );
}
return $this ;
}
/**
* Gets the file .
*
* @ return File The file to stream
*/
public function getFile ()
{
return $this -> file ;
}
/**
* Automatically sets the Last - Modified header according the file modification date .
*/
public function setAutoLastModified ()
{
$this -> setLastModified ( \DateTime :: createFromFormat ( 'U' , $this -> file -> getMTime ()));
return $this ;
}
/**
* Automatically sets the ETag header according to the checksum of the file .
*/
public function setAutoEtag ()
{
$this -> setEtag ( sha1_file ( $this -> file -> getPathname ()));
return $this ;
}
/**
* Sets the Content - Disposition header with the given filename .
*
* @ param string $disposition ResponseHeaderBag :: DISPOSITION_INLINE or ResponseHeaderBag :: DISPOSITION_ATTACHMENT
* @ param string $filename Optionally use this filename instead of the real name of the file
* @ param string $filenameFallback A fallback filename , containing only ASCII characters . Defaults to an automatically encoded filename
*
* @ return BinaryFileResponse
*/
public function setContentDisposition ( $disposition , $filename = '' , $filenameFallback = '' )
{
if ( $filename === '' ) {
$filename = $this -> file -> getFilename ();
}
$dispositionHeader = $this -> headers -> makeDisposition ( $disposition , $filename , $filenameFallback );
$this -> headers -> set ( 'Content-Disposition' , $dispositionHeader );
return $this ;
}
/**
* { @ inheritdoc }
*/
public function prepare ( Request $request )
{
$this -> headers -> set ( 'Content-Length' , $this -> file -> getSize ());
if ( ! $this -> headers -> has ( 'Accept-Ranges' )) {
// Only accept ranges on safe HTTP methods
$this -> headers -> set ( 'Accept-Ranges' , $request -> isMethodSafe () ? 'bytes' : 'none' );
}
if ( ! $this -> headers -> has ( 'Content-Type' )) {
$this -> headers -> set ( 'Content-Type' , $this -> file -> getMimeType () ? : 'application/octet-stream' );
}
if ( 'HTTP/1.0' != $request -> server -> get ( 'SERVER_PROTOCOL' )) {
$this -> setProtocolVersion ( '1.1' );
}
$this -> ensureIEOverSSLCompatibility ( $request );
$this -> offset = 0 ;
$this -> maxlen = - 1 ;
if ( self :: $trustXSendfileTypeHeader && $request -> headers -> has ( 'X-Sendfile-Type' )) {
// Use X-Sendfile, do not send any content.
$type = $request -> headers -> get ( 'X-Sendfile-Type' );
$path = $this -> file -> getRealPath ();
if ( strtolower ( $type ) == 'x-accel-redirect' ) {
// Do X-Accel-Mapping substitutions.
// @link http://wiki.nginx.org/X-accel#X-Accel-Redirect
foreach ( explode ( ',' , $request -> headers -> get ( 'X-Accel-Mapping' , '' )) as $mapping ) {
$mapping = explode ( '=' , $mapping , 2 );
if ( 2 == count ( $mapping )) {
$pathPrefix = trim ( $mapping [ 0 ]);
$location = trim ( $mapping [ 1 ]);
if ( substr ( $path , 0 , strlen ( $pathPrefix )) == $pathPrefix ) {
$path = $location . substr ( $path , strlen ( $pathPrefix ));
break ;
}
}
}
}
$this -> headers -> set ( $type , $path );
$this -> maxlen = 0 ;
} elseif ( $request -> headers -> has ( 'Range' )) {
// Process the range headers.
if ( ! $request -> headers -> has ( 'If-Range' ) || $this -> getEtag () == $request -> headers -> get ( 'If-Range' )) {
$range = $request -> headers -> get ( 'Range' );
$fileSize = $this -> file -> getSize ();
list ( $start , $end ) = explode ( '-' , substr ( $range , 6 ), 2 ) + array ( 0 );
$end = ( '' === $end ) ? $fileSize - 1 : ( int ) $end ;
if ( '' === $start ) {
$start = $fileSize - $end ;
$end = $fileSize - 1 ;
} else {
$start = ( int ) $start ;
}
if ( $start <= $end ) {
if ( $start < 0 || $end > $fileSize - 1 ) {
$this -> setStatusCode ( 416 );
} elseif ( $start !== 0 || $end !== $fileSize - 1 ) {
$this -> maxlen = $end < $fileSize ? $end - $start + 1 : - 1 ;
$this -> offset = $start ;
$this -> setStatusCode ( 206 );
$this -> headers -> set ( 'Content-Range' , sprintf ( 'bytes %s-%s/%s' , $start , $end , $fileSize ));
$this -> headers -> set ( 'Content-Length' , $end - $start + 1 );
}
}
}
}
return $this ;
}
/**
* Sends the file .
*/
public function sendContent ()
{
if ( ! $this -> isSuccessful ()) {
parent :: sendContent ();
return ;
}
if ( 0 === $this -> maxlen ) {
return ;
}
$out = fopen ( 'php://output' , 'wb' );
$file = fopen ( $this -> file -> getPathname (), 'rb' );
stream_copy_to_stream ( $file , $out , $this -> maxlen , $this -> offset );
fclose ( $out );
fclose ( $file );
if ( $this -> deleteFileAfterSend ) {
unlink ( $this -> file -> getPathname ());
}
}
/**
* { @ inheritdoc }
*
* @ throws \LogicException when the content is not null
*/
public function setContent ( $content )
{
if ( null !== $content ) {
throw new \LogicException ( 'The content cannot be set on a BinaryFileResponse instance.' );
}
}
/**
* { @ inheritdoc }
*
* @ return false
*/
public function getContent ()
{
return false ;
}
/**
* Trust X - Sendfile - Type header .
*/
public static function trustXSendfileTypeHeader ()
{
self :: $trustXSendfileTypeHeader = true ;
}
/**
* If this is set to true , the file will be unlinked after the request is send
* Note : If the X - Sendfile header is used , the deleteFileAfterSend setting will not be used .
2015-10-08 11:40:12 -07:00
*
2015-08-17 17:00:26 -07:00
* @ param bool $shouldDelete
*
* @ return BinaryFileResponse
*/
public function deleteFileAfterSend ( $shouldDelete )
{
$this -> deleteFileAfterSend = $shouldDelete ;
return $this ;
}
}