1: <?php
2:
3: 4: 5: 6: 7: 8: 9: 10:
11:
12: namespace Symfony\Component\HttpFoundation;
13:
14: use Symfony\Component\HttpFoundation\File\File;
15: use Symfony\Component\HttpFoundation\File\Exception\FileException;
16:
17: 18: 19: 20: 21: 22: 23: 24: 25:
26: class BinaryFileResponse extends Response
27: {
28: protected static $trustXSendfileTypeHeader = false;
29:
30: protected $file;
31: protected $offset;
32: protected $maxlen;
33: protected $deleteFileAfterSend = false;
34:
35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45:
46: public function __construct($file, $status = 200, $headers = array(), $public = true, $contentDisposition = null, $autoEtag = false, $autoLastModified = true)
47: {
48: parent::__construct(null, $status, $headers);
49:
50: $this->setFile($file, $contentDisposition, $autoEtag, $autoLastModified);
51:
52: if ($public) {
53: $this->setPublic();
54: }
55: }
56:
57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67:
68: public static function create($file = null, $status = 200, $headers = array(), $public = true, $contentDisposition = null, $autoEtag = false, $autoLastModified = true)
69: {
70: return new static($file, $status, $headers, $public, $contentDisposition, $autoEtag, $autoLastModified);
71: }
72:
73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84:
85: public function setFile($file, $contentDisposition = null, $autoEtag = false, $autoLastModified = true)
86: {
87: if (!$file instanceof File) {
88: if ($file instanceof \SplFileInfo) {
89: $file = new File($file->getPathname());
90: } else {
91: $file = new File((string) $file);
92: }
93: }
94:
95: if (!$file->isReadable()) {
96: throw new FileException('File must be readable.');
97: }
98:
99: $this->file = $file;
100:
101: if ($autoEtag) {
102: $this->setAutoEtag();
103: }
104:
105: if ($autoLastModified) {
106: $this->setAutoLastModified();
107: }
108:
109: if ($contentDisposition) {
110: $this->setContentDisposition($contentDisposition);
111: }
112:
113: return $this;
114: }
115:
116: 117: 118: 119: 120:
121: public function getFile()
122: {
123: return $this->file;
124: }
125:
126: 127: 128:
129: public function setAutoLastModified()
130: {
131: $this->setLastModified(\DateTime::createFromFormat('U', $this->file->getMTime()));
132:
133: return $this;
134: }
135:
136: 137: 138:
139: public function setAutoEtag()
140: {
141: $this->setEtag(sha1_file($this->file->getPathname()));
142:
143: return $this;
144: }
145:
146: 147: 148: 149: 150: 151: 152: 153: 154:
155: public function setContentDisposition($disposition, $filename = '', $filenameFallback = '')
156: {
157: if ($filename === '') {
158: $filename = $this->file->getFilename();
159: }
160:
161: $dispositionHeader = $this->headers->makeDisposition($disposition, $filename, $filenameFallback);
162: $this->headers->set('Content-Disposition', $dispositionHeader);
163:
164: return $this;
165: }
166:
167: 168: 169:
170: public function prepare(Request $request)
171: {
172: $this->headers->set('Content-Length', $this->file->getSize());
173:
174: if (!$this->headers->has('Accept-Ranges')) {
175:
176: $this->headers->set('Accept-Ranges', $request->isMethodSafe() ? 'bytes' : 'none');
177: }
178:
179: if (!$this->headers->has('Content-Type')) {
180: $this->headers->set('Content-Type', $this->file->getMimeType() ?: 'application/octet-stream');
181: }
182:
183: if ('HTTP/1.0' != $request->server->get('SERVER_PROTOCOL')) {
184: $this->setProtocolVersion('1.1');
185: }
186:
187: $this->ensureIEOverSSLCompatibility($request);
188:
189: $this->offset = 0;
190: $this->maxlen = -1;
191:
192: if (self::$trustXSendfileTypeHeader && $request->headers->has('X-Sendfile-Type')) {
193:
194: $type = $request->headers->get('X-Sendfile-Type');
195: $path = $this->file->getRealPath();
196: if (strtolower($type) == 'x-accel-redirect') {
197:
198: foreach (explode(',', $request->headers->get('X-Accel-Mapping', '')) as $mapping) {
199: $mapping = explode('=', $mapping, 2);
200:
201: if (2 == count($mapping)) {
202: $location = trim($mapping[0]);
203: $pathPrefix = trim($mapping[1]);
204:
205: if (substr($path, 0, strlen($pathPrefix)) == $pathPrefix) {
206: $path = $location.substr($path, strlen($pathPrefix));
207: break;
208: }
209: }
210: }
211: }
212: $this->headers->set($type, $path);
213: $this->maxlen = 0;
214: } elseif ($request->headers->has('Range')) {
215:
216: if (!$request->headers->has('If-Range') || $this->getEtag() == $request->headers->get('If-Range')) {
217: $range = $request->headers->get('Range');
218: $fileSize = $this->file->getSize();
219:
220: list($start, $end) = explode('-', substr($range, 6), 2) + array(0);
221:
222: $end = ('' === $end) ? $fileSize - 1 : (int) $end;
223:
224: if ('' === $start) {
225: $start = $fileSize - $end;
226: $end = $fileSize - 1;
227: } else {
228: $start = (int) $start;
229: }
230:
231: if ($start <= $end) {
232: if ($start < 0 || $end > $fileSize - 1) {
233: $this->setStatusCode(416);
234: } elseif ($start !== 0 || $end !== $fileSize - 1) {
235: $this->maxlen = $end < $fileSize ? $end - $start + 1 : -1;
236: $this->offset = $start;
237:
238: $this->setStatusCode(206);
239: $this->headers->set('Content-Range', sprintf('bytes %s-%s/%s', $start, $end, $fileSize));
240: $this->headers->set('Content-Length', $end - $start + 1);
241: }
242: }
243: }
244: }
245:
246: return $this;
247: }
248:
249: 250: 251:
252: public function sendContent()
253: {
254: if (!$this->isSuccessful()) {
255: parent::sendContent();
256:
257: return;
258: }
259:
260: if (0 === $this->maxlen) {
261: return;
262: }
263:
264: $out = fopen('php://output', 'wb');
265: $file = fopen($this->file->getPathname(), 'rb');
266:
267: stream_copy_to_stream($file, $out, $this->maxlen, $this->offset);
268:
269: fclose($out);
270: fclose($file);
271:
272: if ($this->deleteFileAfterSend) {
273: unlink($this->file->getPathname());
274: }
275: }
276:
277: 278: 279: 280: 281:
282: public function setContent($content)
283: {
284: if (null !== $content) {
285: throw new \LogicException('The content cannot be set on a BinaryFileResponse instance.');
286: }
287: }
288:
289: 290: 291: 292: 293:
294: public function getContent()
295: {
296: return false;
297: }
298:
299: 300: 301:
302: public static function trustXSendfileTypeHeader()
303: {
304: self::$trustXSendfileTypeHeader = true;
305: }
306:
307: 308: 309: 310: 311: 312: 313:
314: public function deleteFileAfterSend($shouldDelete)
315: {
316: $this->deleteFileAfterSend = $shouldDelete;
317:
318: return $this;
319: }
320: }
321: