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