ILIAS  trunk Revision v11.0_alpha-3011-gc6b235a2e85
Delivery.php
Go to the documentation of this file.
1<?php
2
19declare(strict_types=1);
20
22
29
39final class Delivery
40{
44 public const DIRECT_PHP_OUTPUT = 'php://output';
48 public const DISP_ATTACHMENT = 'attachment';
52 public const DISP_INLINE = 'inline';
56 public const EXPIRES_IN = '+5 days';
57 private static ?string $delivery_type_static = null;
59 private string $mime_type = '';
60 private string $path_to_file = '';
61 private string $download_file_name = '';
63 private bool $send_mime_type = true;
64 private bool $exit_after = true;
65 private bool $convert_file_name_to_asci = true;
66 private string $etag = '';
67 private bool $show_last_modified = true;
68 private bool $has_context = true;
69 private bool $cache = false;
70 private bool $hash_filename = false;
71 private bool $delete_file = false;
72 private static bool $DEBUG = false;
74 private ?FileStream $resource = null;
75
76
81 public function __construct($input, private Services $http)
82 {
83 if ($input instanceof FileStream) {
84 $this->resource = $input;
85 $this->path_to_file = $input->getMetadata('uri');
86 } elseif ($input === self::DIRECT_PHP_OUTPUT) {
87 $this->setPathToFile(self::DIRECT_PHP_OUTPUT);
88 } else {
89 $this->setPathToFile($input);
90 }
91
92 $this->detemineDeliveryType();
93 $this->determineMimeType();
95
96 $this->setHasContext(\ilContext::getType() !== null);
97 $this->factory = new FileDeliveryTypeFactory($this->http);
98 }
99
100
101 public function stream(): void
102 {
103 if (!$this->delivery()->supportsStreaming()) {
105 }
106 $this->deliver();
107 }
108
109 private function delivery(): ilFileDeliveryType
110 {
111 if ($this->isDeleteFile()) {
112 return $this->factory->getInstance(DeliveryMethod::PHP);
113 }
114
115 return $this->factory->getInstance($this->getDeliveryType());
116 }
117
118
119 public function deliver(): void
120 {
121 $response = $this->http->response()->withHeader('X-ILIAS-FileDelivery-Method', $this->getDeliveryType());
122 if (
123 !$this->delivery()->doesFileExists($this->path_to_file)
124 && $this->path_to_file !== self::DIRECT_PHP_OUTPUT
125 ) {
126 $response = $this->http->response()->withStatus(404);
127 $this->http->saveResponse($response);
128 $this->http->sendResponse();
129 $this->close();
130 }
131 $this->http->saveResponse($response);
132
133 $this->clearBuffer();
134 $this->checkCache();
135 $this->setGeneralHeaders();
136 $this->delivery()->prepare($this->getPathToFile(), $this->resource);
137 $this->delivery()->deliver($this->getPathToFile(), $this->isDeleteFile());
138 if ($this->isDeleteFile()) {
139 $this->delivery()->handleFileDeletion($this->getPathToFile());
140 }
141 if ($this->isExitAfter()) {
142 $this->close();
143 }
144 }
145
146
147 public function setGeneralHeaders(): void
148 {
149 $this->checkExisting();
150 if ($this->isSendMimeType()) {
151 $response = $this->http->response()->withHeader(ResponseHeader::CONTENT_TYPE, $this->getMimeType());
152 $this->http->saveResponse($response);
153 }
154 if ($this->isConvertFileNameToAsci()) {
155 $this->cleanDownloadFileName();
156 }
157 if ($this->hasHashFilename()) {
158 $this->setDownloadFileName(md5($this->getDownloadFileName()));
159 }
160 $this->setDispositionHeaders();
161 $response = $this->http->response()->withHeader(ResponseHeader::ACCEPT_RANGES, 'bytes');
162 $this->http->saveResponse($response);
163 if ($this->getDeliveryType() === DeliveryMethod::PHP
164 && $this->getPathToFile() !== self::DIRECT_PHP_OUTPUT
165 ) {
166 $response = $this->http->response()->withHeader(ResponseHeader::CONTENT_LENGTH, (string) filesize($this->getPathToFile()));
167 $this->http->saveResponse($response);
168 }
169 $response = $this->http->response()->withHeader(ResponseHeader::CONNECTION, "close");
170 $this->http->saveResponse($response);
171 }
172
173
174 public function setCachingHeaders(): void
175 {
176 $response = $this->http->response()->withHeader(ResponseHeader::CACHE_CONTROL, 'must-revalidate, post-check=0, pre-check=0')->withHeader(ResponseHeader::PRAGMA, 'public');
177
178 $this->http->saveResponse($response->withHeader(ResponseHeader::EXPIRES, date("D, j M Y H:i:s", strtotime(self::EXPIRES_IN)) . " GMT"));
179 $this->sendEtagHeader();
180 $this->sendLastModified();
181 }
182
183
184 public function generateEtag(): void
185 {
186 $this->setEtag(md5(filemtime($this->getPathToFile()) . filesize($this->getPathToFile())));
187 }
188
189
190 public function close(): void
191 {
192 $this->http->close();
193 }
194
195
196 private function determineMimeType(): void
197 {
199 if ($info !== '' && $info !== '0') {
200 $this->setMimeType($info);
201
202 return;
203 }
204 $finfo = finfo_open(FILEINFO_MIME_TYPE);
205 $info = finfo_file($finfo, $this->getPathToFile());
206 finfo_close($finfo);
207 if ($info) {
208 $this->setMimeType($info);
209
210 return;
211 }
212 }
213
214
215 private function determineDownloadFileName(): void
216 {
217 if ($this->getDownloadFileName() === '' || $this->getDownloadFileName() === '0') {
218 $download_file_name = basename($this->getPathToFile());
219 $this->setDownloadFileName($download_file_name);
220 }
221 }
222
223
224 private function detemineDeliveryType(): void
225 {
226 if (self::$delivery_type_static) {
227 $this->setDeliveryType(self::$delivery_type_static);
228
229 return;
230 }
231
232 if (function_exists('apache_get_modules')
233 && in_array('mod_xsendfile', apache_get_modules(), true)
234 ) {
236 }
237
238 // if (function_exists('apache_get_version') && strpos(apache_get_version(), '2.4.') !== false) {
239 // $this->setDeliveryType(DeliveryMethod::XSENDFILE);
240 // }
241
242 if (is_file('./components/ILIAS/FileDelivery/classes/override.php')) {
243 $override_delivery_type = false;
245 require_once('./components/ILIAS/FileDelivery/classes/override.php');
246 if ($override_delivery_type) {
247 $this->setDeliveryType($override_delivery_type);
248 }
249 }
250 $ilRuntime = \ilRuntime::getInstance();
251 if ((!$ilRuntime->isFPM() && !$ilRuntime->isHHVM())
252 && $this->getDeliveryType() === DeliveryMethod::XACCEL
253 ) {
255 }
256
258 && !str_starts_with($this->getPathToFile(), './public/data')
259 ) {
261 }
262
263 self::$delivery_type_static = $this->getDeliveryType();
264 }
265
266
267 public function getDeliveryType(): string
268 {
270 }
271
272
273 public function setDeliveryType(string $delivery_type): void
274 {
275 $this->delivery_type = $delivery_type;
276 }
277
278
279 public function getMimeType(): string
280 {
281 return $this->mime_type;
282 }
283
284
285 public function setMimeType(string $mime_type): void
286 {
287 $this->mime_type = $mime_type;
288 }
289
290
291 public function getPathToFile(): string
292 {
293 return $this->path_to_file;
294 }
295
296
297 public function setPathToFile(string $path_to_file): void
298 {
299 $this->path_to_file = $path_to_file;
300 }
301
302
303 public function getDownloadFileName(): string
304 {
306 }
307
308
309 public function setDownloadFileName(string $download_file_name): void
310 {
311 $this->download_file_name = $download_file_name;
312 }
313
314
315 public function getDisposition(): string
316 {
317 return $this->disposition;
318 }
319
320
321 public function setDisposition(string $disposition): void
322 {
323 $this->disposition = $disposition;
324 }
325
326
327 public function isSendMimeType(): bool
328 {
330 }
331
332
333 public function setSendMimeType(bool $send_mime_type): void
334 {
335 $this->send_mime_type = $send_mime_type;
336 }
337
338
339 public function isExitAfter(): bool
340 {
341 return $this->exit_after;
342 }
343
344
345 public function setExitAfter(bool $exit_after): void
346 {
347 $this->exit_after = $exit_after;
348 }
349
350
351 public function isConvertFileNameToAsci(): bool
352 {
354 }
355
356
358 {
359 $this->convert_file_name_to_asci = $convert_file_name_to_asci;
360 }
361
362
363 public function getEtag(): string
364 {
365 return $this->etag;
366 }
367
368
369 public function setEtag(string $etag): void
370 {
371 $this->etag = $etag;
372 }
373
374
375 public function getShowLastModified(): bool
376 {
378 }
379
380
381 public function setShowLastModified(bool $show_last_modified): void
382 {
383 $this->show_last_modified = $show_last_modified;
384 }
385
386
387 public function isHasContext(): bool
388 {
389 return $this->has_context;
390 }
391
392
393 public function setHasContext(bool $has_context): void
394 {
395 $this->has_context = $has_context;
396 }
397
398
399 public function hasCache(): bool
400 {
401 return $this->cache;
402 }
403
404
405 public function setCache(bool $cache): void
406 {
407 $this->cache = $cache;
408 }
409
410
411 public function hasHashFilename(): bool
412 {
414 }
415
416
417 public function setHashFilename(bool $hash_filename): void
418 {
419 $this->hash_filename = $hash_filename;
420 }
421
422
423 private function sendEtagHeader(): void
424 {
425 if ($this->getEtag() !== '' && $this->getEtag() !== '0') {
426 $response = $this->http->response()->withHeader('ETag', $this->getEtag());
427 $this->http->saveResponse($response);
428 }
429 }
430
431
432 private function sendLastModified(): void
433 {
434 if ($this->getShowLastModified()) {
435 $response = $this->http->response()->withHeader(
436 'Last-Modified',
437 date("D, j M Y H:i:s", filemtime($this->getPathToFile()))
438 . " GMT"
439 );
440 $this->http->saveResponse($response);
441 }
442 }
443
444 public static function isDEBUG(): bool
445 {
446 return self::$DEBUG;
447 }
448
449
450 public static function setDEBUG(bool $DEBUG): void
451 {
452 self::$DEBUG = $DEBUG;
453 }
454
455
456 public function checkCache(): void
457 {
458 if ($this->hasCache()) {
459 $this->generateEtag();
460 $this->sendEtagHeader();
461 $this->setShowLastModified(true);
462 $this->setCachingHeaders();
463 }
464 }
465
466
470 public function clearBuffer(): bool
471 {
472 try {
473 $ob_get_contents = ob_get_contents();
474 if ($ob_get_contents) {
475 // \ilWACLog::getInstance()->write(__CLASS__ . ' had output before file delivery: '
476 // . $ob_get_contents);
477 }
478 ob_end_clean(); // fixed 0016469, 0016467, 0016468
479 return true;
480 } catch (\Throwable) {
481 return false;
482 }
483 }
484
485
486 private function checkExisting(): void
487 {
488 if ($this->getPathToFile() !== self::DIRECT_PHP_OUTPUT
489 && !file_exists($this->getPathToFile())
490 ) {
491 $this->close();
492 }
493 }
494
495
499 private function cleanDownloadFileName(): void
500 {
502 $this->setDownloadFileName($download_file_name);
503 }
504
505
513 public static function returnASCIIFileName(string $original_filename): string
514 {
515 $umlaut_mapping = [
516 "Ä" => "Ae",
517 "Ö" => "Oe",
518 "Ü" => "Ue",
519 "ä" => "ae",
520 "ö" => "oe",
521 "ü" => "ue",
522 "ß" => "ss"
523 ];
524 foreach ($umlaut_mapping as $src => $tgt) {
525 $original_filename = str_replace($src, $tgt, $original_filename);
526 }
527
528 $ascii_filename = htmlentities($original_filename, ENT_NOQUOTES, 'UTF-8');
529 $ascii_filename = preg_replace('/\&(.)[^;]*;/', '\\1', $ascii_filename);
530 $ascii_filename = preg_replace('/[\x7f-\xff]/', '_', (string) $ascii_filename);
531
532 // OS do not allow the following characters in filenames: \/:*?"<>|
533 $ascii_filename = preg_replace(
534 '/[:\x5c\/\*\?\"<>\|]/',
535 '_',
536 (string) $ascii_filename
537 );
538 return $ascii_filename;
539 }
540
541
542 public function isDeleteFile(): bool
543 {
544 return $this->delete_file;
545 }
546
547
548 public function setDeleteFile(bool $delete_file): void
549 {
550 $this->delete_file = $delete_file;
551 }
552
553
554 private function setDispositionHeaders(): void
555 {
556 $response = $this->http->response();
557 $response = $response->withHeader(
559 $this->getDisposition()
560 . '; filename="'
561 . $this->getDownloadFileName()
562 . '"'
563 );
564 $response = $response->withHeader('Content-Description', $this->getDownloadFileName());
565 $this->http->saveResponse($response);
566 }
567}
factory()
static string $delivery_type_static
Definition: Delivery.php:57
__construct($input, private Services $http)
Definition: Delivery.php:81
setSendMimeType(bool $send_mime_type)
Definition: Delivery.php:333
FileDeliveryTypeFactory $factory
Definition: Delivery.php:73
cleanDownloadFileName()
Converts the filename to ASCII.
Definition: Delivery.php:499
setExitAfter(bool $exit_after)
Definition: Delivery.php:345
setConvertFileNameToAsci(bool $convert_file_name_to_asci)
Definition: Delivery.php:357
setShowLastModified(bool $show_last_modified)
Definition: Delivery.php:381
static setDEBUG(bool $DEBUG)
Definition: Delivery.php:450
setMimeType(string $mime_type)
Definition: Delivery.php:285
setDisposition(string $disposition)
Definition: Delivery.php:321
setHasContext(bool $has_context)
Definition: Delivery.php:393
setHashFilename(bool $hash_filename)
Definition: Delivery.php:417
setDeliveryType(string $delivery_type)
Definition: Delivery.php:273
setPathToFile(string $path_to_file)
Definition: Delivery.php:297
static returnASCIIFileName(string $original_filename)
Converts a UTF-8 filename to ASCII.
Definition: Delivery.php:513
setDeleteFile(bool $delete_file)
Definition: Delivery.php:548
setDownloadFileName(string $download_file_name)
Definition: Delivery.php:309
Mime type determination.
Definition: MimeType.php:30
static lookupMimeType(string $path_to_file, string $fallback=self::APPLICATION__OCTET_STREAM, bool $a_external=false)
Definition: MimeType.php:544
Class Services.
Definition: Services.php:38
static getType()
Get context type.
static getInstance()
$http
Definition: deliver.php:30
$info
Definition: entry_point.php:21
The base interface for all filesystem streams.
Definition: FileStream.php:32
Interface ResponseHeader.
static http()
Fetches the global http state from ILIAS.
$response
Definition: xapitoken.php:93