PluginProbe ʕ •ᴥ•ʔ
Matomo Analytics – Powerful, Privacy-First Insights for WordPress / 5.2.0
Matomo Analytics – Powerful, Privacy-First Insights for WordPress v5.2.0
5.11.1 5.11.0 5.10.2 5.10.1 trunk 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.1.0 1.1.1 1.1.2 1.1.3 1.2.0 1.3.0 1.3.1 1.3.2 4.0.0 4.0.1 4.0.2 4.0.3 4.0.4 4.1.0 4.1.1 4.1.2 4.1.3 4.10.0 4.11.0 4.12.0 4.13.0 4.13.2 4.13.3 4.13.4 4.13.5 4.14.0 4.14.1 4.14.2 4.15.0 4.15.1 4.15.2 4.15.3 4.2.0 4.3.0 4.3.1 4.4.1 4.4.2 4.5.0 4.6.0 5.0.1 5.0.2 5.0.3 5.0.4 5.0.5 5.0.6 5.0.7 5.0.8 5.1.0 5.1.1 5.1.2 5.1.3 5.1.4 5.1.5 5.1.6 5.1.7 5.10.0 5.2.0 5.2.1 5.2.2 5.3.0 5.3.1 5.3.2 5.3.3 5.6.0 5.6.1 5.7.0 5.7.1 5.8.0 5.8.1 5.8.2
matomo / app / vendor / prefixed / symfony / http-foundation / BinaryFileResponse.php
matomo / app / vendor / prefixed / symfony / http-foundation Last commit date
Exception 2 years ago File 1 year ago RateLimiter 2 years ago Session 1 year ago Test 1 year ago AcceptHeader.php 1 year ago AcceptHeaderItem.php 2 years ago BinaryFileResponse.php 1 year ago Cookie.php 1 year ago ExpressionRequestMatcher.php 2 years ago FileBag.php 1 year ago HeaderBag.php 1 year ago HeaderUtils.php 1 year ago InputBag.php 2 years ago IpUtils.php 1 year ago JsonResponse.php 1 year ago LICENSE 2 years ago ParameterBag.php 1 year ago README.md 2 years ago RedirectResponse.php 2 years ago Request.php 1 year ago RequestMatcher.php 1 year ago RequestMatcherInterface.php 2 years ago RequestStack.php 2 years ago Response.php 1 year ago ResponseHeaderBag.php 1 year ago ServerBag.php 1 year ago StreamedResponse.php 1 year ago UrlHelper.php 2 years ago
BinaryFileResponse.php
356 lines
1 <?php
2
3 /*
4 * This file is part of the Symfony package.
5 *
6 * (c) Fabien Potencier <fabien@symfony.com>
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11 namespace Matomo\Dependencies\Symfony\Component\HttpFoundation;
12
13 use Matomo\Dependencies\Symfony\Component\HttpFoundation\File\Exception\FileException;
14 use Matomo\Dependencies\Symfony\Component\HttpFoundation\File\File;
15 /**
16 * BinaryFileResponse represents an HTTP response delivering a file.
17 *
18 * @author Niklas Fiekas <niklas.fiekas@tu-clausthal.de>
19 * @author stealth35 <stealth35-php@live.fr>
20 * @author Igor Wiedler <igor@wiedler.ch>
21 * @author Jordan Alliot <jordan.alliot@gmail.com>
22 * @author Sergey Linnik <linniksa@gmail.com>
23 */
24 class BinaryFileResponse extends Response
25 {
26 protected static $trustXSendfileTypeHeader = \false;
27 /**
28 * @var File
29 */
30 protected $file;
31 protected $offset = 0;
32 protected $maxlen = -1;
33 protected $deleteFileAfterSend = \false;
34 protected $chunkSize = 16 * 1024;
35 /**
36 * @param \SplFileInfo|string $file The file to stream
37 * @param int $status The response status code
38 * @param array $headers An array of response headers
39 * @param bool $public Files are public by default
40 * @param string|null $contentDisposition The type of Content-Disposition to set automatically with the filename
41 * @param bool $autoEtag Whether the ETag header should be automatically set
42 * @param bool $autoLastModified Whether the Last-Modified header should be automatically set
43 */
44 public function __construct($file, int $status = 200, array $headers = [], bool $public = \true, ?string $contentDisposition = null, bool $autoEtag = \false, bool $autoLastModified = \true)
45 {
46 parent::__construct(null, $status, $headers);
47 $this->setFile($file, $contentDisposition, $autoEtag, $autoLastModified);
48 if ($public) {
49 $this->setPublic();
50 }
51 }
52 /**
53 * @param \SplFileInfo|string $file The file to stream
54 * @param int $status The response status code
55 * @param array $headers An array of response headers
56 * @param bool $public Files are public by default
57 * @param string|null $contentDisposition The type of Content-Disposition to set automatically with the filename
58 * @param bool $autoEtag Whether the ETag header should be automatically set
59 * @param bool $autoLastModified Whether the Last-Modified header should be automatically set
60 *
61 * @return static
62 *
63 * @deprecated since Symfony 5.2, use __construct() instead.
64 */
65 public static function create($file = null, int $status = 200, array $headers = [], bool $public = \true, ?string $contentDisposition = null, bool $autoEtag = \false, bool $autoLastModified = \true)
66 {
67 trigger_deprecation('symfony/http-foundation', '5.2', 'The "%s()" method is deprecated, use "new %s()" instead.', __METHOD__, static::class);
68 return new static($file, $status, $headers, $public, $contentDisposition, $autoEtag, $autoLastModified);
69 }
70 /**
71 * Sets the file to stream.
72 *
73 * @param \SplFileInfo|string $file The file to stream
74 *
75 * @return $this
76 *
77 * @throws FileException
78 */
79 public function setFile($file, ?string $contentDisposition = null, bool $autoEtag = \false, bool $autoLastModified = \true)
80 {
81 if (!$file instanceof File) {
82 if ($file instanceof \SplFileInfo) {
83 $file = new File($file->getPathname());
84 } else {
85 $file = new File((string) $file);
86 }
87 }
88 if (!$file->isReadable()) {
89 throw new FileException('File must be readable.');
90 }
91 $this->file = $file;
92 if ($autoEtag) {
93 $this->setAutoEtag();
94 }
95 if ($autoLastModified) {
96 $this->setAutoLastModified();
97 }
98 if ($contentDisposition) {
99 $this->setContentDisposition($contentDisposition);
100 }
101 return $this;
102 }
103 /**
104 * Gets the file.
105 *
106 * @return File
107 */
108 public function getFile()
109 {
110 return $this->file;
111 }
112 /**
113 * Sets the response stream chunk size.
114 *
115 * @return $this
116 */
117 public function setChunkSize(int $chunkSize) : self
118 {
119 if ($chunkSize < 1 || $chunkSize > \PHP_INT_MAX) {
120 throw new \LogicException('The chunk size of a BinaryFileResponse cannot be less than 1 or greater than PHP_INT_MAX.');
121 }
122 $this->chunkSize = $chunkSize;
123 return $this;
124 }
125 /**
126 * Automatically sets the Last-Modified header according the file modification date.
127 *
128 * @return $this
129 */
130 public function setAutoLastModified()
131 {
132 $this->setLastModified(\DateTime::createFromFormat('U', $this->file->getMTime()));
133 return $this;
134 }
135 /**
136 * Automatically sets the ETag header according to the checksum of the file.
137 *
138 * @return $this
139 */
140 public function setAutoEtag()
141 {
142 $this->setEtag(base64_encode(hash_file('sha256', $this->file->getPathname(), \true)));
143 return $this;
144 }
145 /**
146 * Sets the Content-Disposition header with the given filename.
147 *
148 * @param string $disposition ResponseHeaderBag::DISPOSITION_INLINE or ResponseHeaderBag::DISPOSITION_ATTACHMENT
149 * @param string $filename Optionally use this UTF-8 encoded filename instead of the real name of the file
150 * @param string $filenameFallback A fallback filename, containing only ASCII characters. Defaults to an automatically encoded filename
151 *
152 * @return $this
153 */
154 public function setContentDisposition(string $disposition, string $filename = '', string $filenameFallback = '')
155 {
156 if ('' === $filename) {
157 $filename = $this->file->getFilename();
158 }
159 if ('' === $filenameFallback && (!preg_match('/^[\\x20-\\x7e]*$/', $filename) || str_contains($filename, '%'))) {
160 $encoding = mb_detect_encoding($filename, null, \true) ?: '8bit';
161 for ($i = 0, $filenameLength = mb_strlen($filename, $encoding); $i < $filenameLength; ++$i) {
162 $char = mb_substr($filename, $i, 1, $encoding);
163 if ('%' === $char || \ord($char) < 32 || \ord($char) > 126) {
164 $filenameFallback .= '_';
165 } else {
166 $filenameFallback .= $char;
167 }
168 }
169 }
170 $dispositionHeader = $this->headers->makeDisposition($disposition, $filename, $filenameFallback);
171 $this->headers->set('Content-Disposition', $dispositionHeader);
172 return $this;
173 }
174 /**
175 * {@inheritdoc}
176 */
177 public function prepare(Request $request)
178 {
179 if ($this->isInformational() || $this->isEmpty()) {
180 parent::prepare($request);
181 $this->maxlen = 0;
182 return $this;
183 }
184 if (!$this->headers->has('Content-Type')) {
185 $this->headers->set('Content-Type', $this->file->getMimeType() ?: 'application/octet-stream');
186 }
187 parent::prepare($request);
188 $this->offset = 0;
189 $this->maxlen = -1;
190 if (\false === ($fileSize = $this->file->getSize())) {
191 return $this;
192 }
193 $this->headers->remove('Transfer-Encoding');
194 $this->headers->set('Content-Length', $fileSize);
195 if (!$this->headers->has('Accept-Ranges')) {
196 // Only accept ranges on safe HTTP methods
197 $this->headers->set('Accept-Ranges', $request->isMethodSafe() ? 'bytes' : 'none');
198 }
199 if (self::$trustXSendfileTypeHeader && $request->headers->has('X-Sendfile-Type')) {
200 // Use X-Sendfile, do not send any content.
201 $type = $request->headers->get('X-Sendfile-Type');
202 $path = $this->file->getRealPath();
203 // Fall back to scheme://path for stream wrapped locations.
204 if (\false === $path) {
205 $path = $this->file->getPathname();
206 }
207 if ('x-accel-redirect' === strtolower($type)) {
208 // Do X-Accel-Mapping substitutions.
209 // @link https://github.com/rack/rack/blob/main/lib/rack/sendfile.rb
210 // @link https://mattbrictson.com/blog/accelerated-rails-downloads
211 if (!$request->headers->has('X-Accel-Mapping')) {
212 throw new \LogicException('The "X-Accel-Mapping" header must be set when "X-Sendfile-Type" is set to "X-Accel-Redirect".');
213 }
214 $parts = HeaderUtils::split($request->headers->get('X-Accel-Mapping'), ',=');
215 foreach ($parts as $part) {
216 [$pathPrefix, $location] = $part;
217 if (substr($path, 0, \strlen($pathPrefix)) === $pathPrefix) {
218 $path = $location . substr($path, \strlen($pathPrefix));
219 // Only set X-Accel-Redirect header if a valid URI can be produced
220 // as nginx does not serve arbitrary file paths.
221 $this->headers->set($type, $path);
222 $this->maxlen = 0;
223 break;
224 }
225 }
226 } else {
227 $this->headers->set($type, $path);
228 $this->maxlen = 0;
229 }
230 } elseif ($request->headers->has('Range') && $request->isMethod('GET')) {
231 // Process the range headers.
232 if (!$request->headers->has('If-Range') || $this->hasValidIfRangeHeader($request->headers->get('If-Range'))) {
233 $range = $request->headers->get('Range');
234 if (str_starts_with($range, 'bytes=')) {
235 [$start, $end] = explode('-', substr($range, 6), 2) + [1 => 0];
236 $end = '' === $end ? $fileSize - 1 : (int) $end;
237 if ('' === $start) {
238 $start = $fileSize - $end;
239 $end = $fileSize - 1;
240 } else {
241 $start = (int) $start;
242 }
243 if ($start <= $end) {
244 $end = min($end, $fileSize - 1);
245 if ($start < 0 || $start > $end) {
246 $this->setStatusCode(416);
247 $this->headers->set('Content-Range', sprintf('bytes */%s', $fileSize));
248 } elseif ($end - $start < $fileSize - 1) {
249 $this->maxlen = $end < $fileSize ? $end - $start + 1 : -1;
250 $this->offset = $start;
251 $this->setStatusCode(206);
252 $this->headers->set('Content-Range', sprintf('bytes %s-%s/%s', $start, $end, $fileSize));
253 $this->headers->set('Content-Length', $end - $start + 1);
254 }
255 }
256 }
257 }
258 }
259 if ($request->isMethod('HEAD')) {
260 $this->maxlen = 0;
261 }
262 return $this;
263 }
264 private function hasValidIfRangeHeader(?string $header) : bool
265 {
266 if ($this->getEtag() === $header) {
267 return \true;
268 }
269 if (null === ($lastModified = $this->getLastModified())) {
270 return \false;
271 }
272 return $lastModified->format('D, d M Y H:i:s') . ' GMT' === $header;
273 }
274 /**
275 * {@inheritdoc}
276 */
277 public function sendContent()
278 {
279 try {
280 if (!$this->isSuccessful()) {
281 return parent::sendContent();
282 }
283 if (0 === $this->maxlen) {
284 return $this;
285 }
286 $out = fopen('php://output', 'w');
287 $file = fopen($this->file->getPathname(), 'r');
288 ignore_user_abort(\true);
289 if (0 !== $this->offset) {
290 fseek($file, $this->offset);
291 }
292 $length = $this->maxlen;
293 while ($length && !feof($file)) {
294 $read = $length > $this->chunkSize || 0 > $length ? $this->chunkSize : $length;
295 if (\false === ($data = fread($file, $read))) {
296 break;
297 }
298 while ('' !== $data) {
299 $read = fwrite($out, $data);
300 if (\false === $read || connection_aborted()) {
301 break 2;
302 }
303 if (0 < $length) {
304 $length -= $read;
305 }
306 $data = substr($data, $read);
307 }
308 }
309 fclose($out);
310 fclose($file);
311 } finally {
312 if ($this->deleteFileAfterSend && is_file($this->file->getPathname())) {
313 unlink($this->file->getPathname());
314 }
315 }
316 return $this;
317 }
318 /**
319 * {@inheritdoc}
320 *
321 * @throws \LogicException when the content is not null
322 */
323 public function setContent(?string $content)
324 {
325 if (null !== $content) {
326 throw new \LogicException('The content cannot be set on a BinaryFileResponse instance.');
327 }
328 return $this;
329 }
330 /**
331 * {@inheritdoc}
332 */
333 public function getContent()
334 {
335 return \false;
336 }
337 /**
338 * Trust X-Sendfile-Type header.
339 */
340 public static function trustXSendfileTypeHeader()
341 {
342 self::$trustXSendfileTypeHeader = \true;
343 }
344 /**
345 * If this is set to true, the file will be unlinked after the request is sent
346 * Note: If the X-Sendfile header is used, the deleteFileAfterSend setting will not be used.
347 *
348 * @return $this
349 */
350 public function deleteFileAfterSend(bool $shouldDelete = \true)
351 {
352 $this->deleteFileAfterSend = $shouldDelete;
353 return $this;
354 }
355 }
356