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