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