49 public function __construct(
$file, $status = 200,
$headers = array(), $public =
true, $contentDisposition =
null, $autoEtag =
false, $autoLastModified =
true)
51 parent::__construct(
null, $status,
$headers);
53 $this->
setFile(
$file, $contentDisposition, $autoEtag, $autoLastModified);
71 public static function create(
$file =
null, $status = 200,
$headers = array(), $public =
true, $contentDisposition =
null, $autoEtag =
false, $autoLastModified =
true)
73 return new static(
$file, $status,
$headers, $public, $contentDisposition, $autoEtag, $autoLastModified);
88 public function setFile(
$file, $contentDisposition =
null, $autoEtag =
false, $autoLastModified =
true)
91 if (
$file instanceof \SplFileInfo) {
98 if (!
$file->isReadable()) {
99 throw new FileException(
'File must be readable.');
108 if ($autoLastModified) {
112 if ($contentDisposition) {
134 $this->
setLastModified(\DateTime::createFromFormat(
'U', $this->file->getMTime()));
144 $this->
setEtag(sha1_file($this->file->getPathname()));
160 if ($filename ===
'') {
161 $filename = $this->file->getFilename();
164 if (
'' === $filenameFallback && (!preg_match(
'/^[\x20-\x7e]*$/', $filename) ||
false !== strpos($filename,
'%'))) {
165 $encoding = mb_detect_encoding($filename,
null,
true);
167 for ($i = 0, $filenameLength = mb_strlen($filename, $encoding); $i < $filenameLength; ++$i) {
168 $char = mb_substr($filename, $i, 1, $encoding);
170 if (
'%' === $char || ord($char) < 32 || ord($char) > 126) {
171 $filenameFallback .=
'_';
173 $filenameFallback .= $char;
178 $dispositionHeader = $this->headers->makeDisposition($disposition, $filename, $filenameFallback);
179 $this->headers->set(
'Content-Disposition', $dispositionHeader);
187 public function prepare(Request $request)
189 if (!$this->headers->has(
'Content-Type')) {
190 $this->headers->set(
'Content-Type', $this->file->getMimeType() ?:
'application/octet-stream');
193 if (
'HTTP/1.0' !== $request->server->get(
'SERVER_PROTOCOL')) {
202 if (
false === $fileSize = $this->file->getSize()) {
205 $this->headers->set(
'Content-Length', $fileSize);
207 if (!$this->headers->has(
'Accept-Ranges')) {
209 $this->headers->set(
'Accept-Ranges', $request->isMethodSafe(
false) ?
'bytes' :
'none');
212 if (self::$trustXSendfileTypeHeader && $request->headers->has(
'X-Sendfile-Type')) {
214 $type = $request->headers->get(
'X-Sendfile-Type');
215 $path = $this->file->getRealPath();
217 if (
false === $path) {
218 $path = $this->file->getPathname();
220 if (strtolower($type) ===
'x-accel-redirect') {
223 foreach (explode(
',', $request->headers->get(
'X-Accel-Mapping',
'')) as $mapping) {
224 $mapping = explode(
'=', $mapping, 2);
226 if (2 ===
count($mapping)) {
227 $pathPrefix = trim($mapping[0]);
228 $location = trim($mapping[1]);
230 if (substr($path, 0, strlen($pathPrefix)) === $pathPrefix) {
231 $path = $location.substr($path, strlen($pathPrefix));
237 $this->headers->set($type, $path);
239 } elseif ($request->headers->has(
'Range')) {
241 if (!$request->headers->has(
'If-Range') || $this->hasValidIfRangeHeader($request->headers->get(
'If-Range'))) {
242 $range = $request->headers->get(
'Range');
244 list($start, $end) = explode(
'-', substr($range, 6), 2) + array(0);
246 $end = (
'' === $end) ? $fileSize - 1 : (
int) $end;
249 $start = $fileSize - $end;
250 $end = $fileSize - 1;
252 $start = (int) $start;
255 if ($start <= $end) {
256 if ($start < 0 || $end > $fileSize - 1) {
258 $this->headers->set(
'Content-Range', sprintf(
'bytes */%s', $fileSize));
259 } elseif ($start !== 0 || $end !== $fileSize - 1) {
260 $this->maxlen = $end < $fileSize ? $end - $start + 1 : -1;
261 $this->offset = $start;
264 $this->headers->set(
'Content-Range', sprintf(
'bytes %s-%s/%s', $start, $end, $fileSize));
265 $this->headers->set(
'Content-Length', $end - $start + 1);
274 private function hasValidIfRangeHeader($header)
276 if ($this->
getEtag() === $header) {
284 return $lastModified->format(
'D, d M Y H:i:s').
' GMT' === $header;
295 return parent::sendContent();
298 if (0 === $this->maxlen) {
302 $out = fopen(
'php://output',
'wb');
303 $file = fopen($this->file->getPathname(),
'rb');
305 stream_copy_to_stream(
$file, $out, $this->maxlen, $this->offset);
311 unlink($this->file->getPathname());
325 throw new \LogicException(
'The content cannot be set on a BinaryFileResponse instance.');
344 self::$trustXSendfileTypeHeader =
true;