26 class BinaryFileResponse
extends Response
47 public function __construct(
$file,
int $status = 200, array
$headers = [],
bool $public =
true,
string $contentDisposition =
null,
bool $autoEtag =
false,
bool $autoLastModified =
true)
49 parent::__construct(
null, $status,
$headers);
51 $this->
setFile(
$file, $contentDisposition, $autoEtag, $autoLastModified);
69 public static function create(
$file =
null, $status = 200,
$headers = [], $public =
true, $contentDisposition =
null, $autoEtag =
false, $autoLastModified =
true)
71 return new static(
$file, $status,
$headers, $public, $contentDisposition, $autoEtag, $autoLastModified);
86 public function setFile(
$file, $contentDisposition =
null, $autoEtag =
false, $autoLastModified =
true)
89 if (
$file instanceof \SplFileInfo) {
96 if (!
$file->isReadable()) {
97 throw new FileException(
'File must be readable.');
106 if ($autoLastModified) {
110 if ($contentDisposition) {
132 $this->
setLastModified(\DateTime::createFromFormat(
'U', $this->file->getMTime()));
142 $this->
setEtag(base64_encode(hash_file(
'sha256', $this->file->getPathname(),
true)));
158 if (
'' === $filename) {
159 $filename = $this->file->getFilename();
162 if (
'' === $filenameFallback && (!preg_match(
'/^[\x20-\x7e]*$/', $filename) ||
false !== strpos($filename,
'%'))) {
163 $encoding = mb_detect_encoding($filename,
null,
true) ?:
'8bit';
165 for ($i = 0, $filenameLength = mb_strlen($filename, $encoding); $i < $filenameLength; ++$i) {
166 $char = mb_substr($filename, $i, 1, $encoding);
168 if (
'%' === $char || \ord($char) < 32 || \ord($char) > 126) {
169 $filenameFallback .=
'_';
171 $filenameFallback .= $char;
176 $dispositionHeader = $this->headers->makeDisposition($disposition, $filename, $filenameFallback);
177 $this->headers->set(
'Content-Disposition', $dispositionHeader);
187 if (!$this->headers->has(
'Content-Type')) {
188 $this->headers->set(
'Content-Type', $this->file->getMimeType() ?:
'application/octet-stream');
191 if (
'HTTP/1.0' !== $request->server->get(
'SERVER_PROTOCOL')) {
200 if (
false === $fileSize = $this->file->getSize()) {
203 $this->headers->set(
'Content-Length', $fileSize);
205 if (!$this->headers->has(
'Accept-Ranges')) {
207 $this->headers->set(
'Accept-Ranges', $request->isMethodSafe() ?
'bytes' :
'none');
210 if (self::$trustXSendfileTypeHeader && $request->headers->has(
'X-Sendfile-Type')) {
212 $type = $request->headers->get(
'X-Sendfile-Type');
213 $path = $this->file->getRealPath();
215 if (
false === $path) {
216 $path = $this->file->getPathname();
218 if (
'x-accel-redirect' === strtolower($type)) {
222 foreach ($parts as $part) {
223 list($pathPrefix, $location) = $part;
224 if (substr($path, 0, \strlen($pathPrefix)) === $pathPrefix) {
225 $path = $location.substr($path, \strlen($pathPrefix));
228 $this->headers->set($type, $path);
234 $this->headers->set($type, $path);
237 } elseif ($request->headers->has(
'Range')) {
239 if (!$request->headers->has(
'If-Range') || $this->hasValidIfRangeHeader($request->headers->get(
'If-Range'))) {
240 $range = $request->headers->get(
'Range');
242 list($start, $end) = explode(
'-', substr($range, 6), 2) + [0];
244 $end = (
'' === $end) ? $fileSize - 1 : (
int) $end;
247 $start = $fileSize - $end;
248 $end = $fileSize - 1;
250 $start = (int) $start;
253 if ($start <= $end) {
254 if ($start < 0 || $end > $fileSize - 1) {
256 $this->headers->set(
'Content-Range', sprintf(
'bytes */%s', $fileSize));
257 } elseif (0 !== $start || $end !== $fileSize - 1) {
258 $this->maxlen = $end < $fileSize ? $end - $start + 1 : -1;
259 $this->offset = $start;
262 $this->headers->set(
'Content-Range', sprintf(
'bytes %s-%s/%s', $start, $end, $fileSize));
263 $this->headers->set(
'Content-Length', $end - $start + 1);
272 private function hasValidIfRangeHeader(?
string $header): bool
274 if ($this->
getEtag() === $header) {
282 return $lastModified->format(
'D, d M Y H:i:s').
' GMT' === $header;
293 return parent::sendContent();
296 if (0 === $this->maxlen) {
300 $out = fopen(
'php://output',
'wb');
301 $file = fopen($this->file->getPathname(),
'rb');
303 stream_copy_to_stream(
$file, $out, $this->maxlen, $this->offset);
309 unlink($this->file->getPathname());
323 throw new \LogicException(
'The content cannot be set on a BinaryFileResponse instance.');
342 self::$trustXSendfileTypeHeader =
true;