ILIAS  release_10 Revision v10.1-43-ga1241a92c2f
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 (function_exists('apache_get_version') && strpos(apache_get_version(), '2.4.') !== false) {
228 // $this->setDeliveryType(DeliveryMethod::XSENDFILE);
229 // }
230 
231  if (is_file('./components/ILIAS/FileDelivery/classes/override.php')) {
232  $override_delivery_type = false;
234  require_once('./components/ILIAS/FileDelivery/classes/override.php');
235  if ($override_delivery_type) {
236  $this->setDeliveryType($override_delivery_type);
237  }
238  }
239  $ilRuntime = \ilRuntime::getInstance();
240  if ((!$ilRuntime->isFPM() && !$ilRuntime->isHHVM())
242  ) {
244  }
245 
246  if ($this->getDeliveryType() === DeliveryMethod::XACCEL
247  && strpos($this->getPathToFile(), './public/data') !== 0
248  ) {
250  }
251 
252  self::$delivery_type_static = $this->getDeliveryType();
253  }
254 
255 
256  public function getDeliveryType(): string
257  {
258  return $this->delivery_type;
259  }
260 
261 
262  public function setDeliveryType(string $delivery_type): void
263  {
264  $this->delivery_type = $delivery_type;
265  }
266 
267 
268  public function getMimeType(): string
269  {
270  return $this->mime_type;
271  }
272 
273 
274  public function setMimeType(string $mime_type): void
275  {
276  $this->mime_type = $mime_type;
277  }
278 
279 
280  public function getPathToFile(): string
281  {
282  return $this->path_to_file;
283  }
284 
285 
286  public function setPathToFile(string $path_to_file): void
287  {
288  $this->path_to_file = $path_to_file;
289  }
290 
291 
292  public function getDownloadFileName(): string
293  {
295  }
296 
297 
298  public function setDownloadFileName(string $download_file_name): void
299  {
300  $this->download_file_name = $download_file_name;
301  }
302 
303 
304  public function getDisposition(): string
305  {
306  return $this->disposition;
307  }
308 
309 
310  public function setDisposition(string $disposition): void
311  {
312  $this->disposition = $disposition;
313  }
314 
315 
316  public function isSendMimeType(): bool
317  {
318  return $this->send_mime_type;
319  }
320 
321 
322  public function setSendMimeType(bool $send_mime_type): void
323  {
324  $this->send_mime_type = $send_mime_type;
325  }
326 
327 
328  public function isExitAfter(): bool
329  {
330  return $this->exit_after;
331  }
332 
333 
334  public function setExitAfter(bool $exit_after): void
335  {
336  $this->exit_after = $exit_after;
337  }
338 
339 
340  public function isConvertFileNameToAsci(): bool
341  {
343  }
344 
345 
346  public function setConvertFileNameToAsci(bool $convert_file_name_to_asci): void
347  {
348  $this->convert_file_name_to_asci = $convert_file_name_to_asci;
349  }
350 
351 
352  public function getEtag(): string
353  {
354  return $this->etag;
355  }
356 
357 
358  public function setEtag(string $etag): void
359  {
360  $this->etag = $etag;
361  }
362 
363 
364  public function getShowLastModified(): bool
365  {
367  }
368 
369 
370  public function setShowLastModified(bool $show_last_modified): void
371  {
372  $this->show_last_modified = $show_last_modified;
373  }
374 
375 
376  public function isHasContext(): bool
377  {
378  return $this->has_context;
379  }
380 
381 
382  public function setHasContext(bool $has_context): void
383  {
384  $this->has_context = $has_context;
385  }
386 
387 
388  public function hasCache(): bool
389  {
390  return $this->cache;
391  }
392 
393 
394  public function setCache(bool $cache): void
395  {
396  $this->cache = $cache;
397  }
398 
399 
400  public function hasHashFilename(): bool
401  {
402  return $this->hash_filename;
403  }
404 
405 
406  public function setHashFilename(bool $hash_filename): void
407  {
408  $this->hash_filename = $hash_filename;
409  }
410 
411 
412  private function sendEtagHeader(): void
413  {
414  if ($this->getEtag()) {
415  $response = $this->http->response()->withHeader('ETag', $this->getEtag());
416  $this->http->saveResponse($response);
417  }
418  }
419 
420 
421  private function sendLastModified(): void
422  {
423  if ($this->getShowLastModified()) {
424  $response = $this->http->response()->withHeader(
425  'Last-Modified',
426  date("D, j M Y H:i:s", filemtime($this->getPathToFile()))
427  . " GMT"
428  );
429  $this->http->saveResponse($response);
430  }
431  }
432 
433  public static function isDEBUG(): bool
434  {
435  return self::$DEBUG;
436  }
437 
438 
439  public static function setDEBUG(bool $DEBUG): void
440  {
441  self::$DEBUG = $DEBUG;
442  }
443 
444 
445  public function checkCache(): void
446  {
447  if ($this->hasCache()) {
448  $this->generateEtag();
449  $this->sendEtagHeader();
450  $this->setShowLastModified(true);
451  $this->setCachingHeaders();
452  }
453  }
454 
455 
459  public function clearBuffer(): bool
460  {
461  try {
462  $ob_get_contents = ob_get_contents();
463  if ($ob_get_contents) {
464  // \ilWACLog::getInstance()->write(__CLASS__ . ' had output before file delivery: '
465  // . $ob_get_contents);
466  }
467  ob_end_clean(); // fixed 0016469, 0016467, 0016468
468  return true;
469  } catch (\Throwable $t) {
470  return false;
471  }
472  }
473 
474 
475  private function checkExisting(): void
476  {
477  if ($this->getPathToFile() !== self::DIRECT_PHP_OUTPUT
478  && !file_exists($this->getPathToFile())
479  ) {
480  $this->close();
481  }
482  }
483 
484 
488  private function cleanDownloadFileName(): void
489  {
490  $download_file_name = self::returnASCIIFileName($this->getDownloadFileName());
491  $this->setDownloadFileName($download_file_name);
492  }
493 
494 
502  public static function returnASCIIFileName(string $original_filename): string
503  {
504  $umlaut_mapping = [
505  "Ä" => "Ae",
506  "Ö" => "Oe",
507  "Ü" => "Ue",
508  "ä" => "ae",
509  "ö" => "oe",
510  "ü" => "ue",
511  "ß" => "ss"
512  ];
513  foreach ($umlaut_mapping as $src => $tgt) {
514  $original_filename = str_replace($src, $tgt, $original_filename);
515  }
516 
517  $ascii_filename = htmlentities($original_filename, ENT_NOQUOTES, 'UTF-8');
518  $ascii_filename = preg_replace('/\&(.)[^;]*;/', '\\1', $ascii_filename);
519  $ascii_filename = preg_replace('/[\x7f-\xff]/', '_', $ascii_filename);
520 
521  // OS do not allow the following characters in filenames: \/:*?"<>|
522  $ascii_filename = preg_replace(
523  '/[:\x5c\/\*\?\"<>\|]/',
524  '_',
525  $ascii_filename
526  );
527  return $ascii_filename;
528  }
529 
530 
531  public function isDeleteFile(): bool
532  {
533  return $this->delete_file;
534  }
535 
536 
537  public function setDeleteFile(bool $delete_file): void
538  {
539  $this->delete_file = $delete_file;
540  }
541 
542 
543  private function setDispositionHeaders(): void
544  {
545  $response = $this->http->response();
546  $response = $response->withHeader(
548  $this->getDisposition()
549  . '; filename="'
550  . $this->getDownloadFileName()
551  . '"'
552  );
553  $response = $response->withHeader('Content-Description', $this->getDownloadFileName());
554  $this->http->saveResponse($response);
555  }
556 }
static returnASCIIFileName(string $original_filename)
Converts a UTF-8 filename to ASCII.
Definition: Delivery.php:502
setConvertFileNameToAsci(bool $convert_file_name_to_asci)
Definition: Delivery.php:346
setDeleteFile(bool $delete_file)
Definition: Delivery.php:537
setMimeType(string $mime_type)
Definition: Delivery.php:274
FileDeliveryTypeFactory $factory
Definition: Delivery.php:61
static string $delivery_type_static
Definition: Delivery.php:44
static getInstance()
setHasContext(bool $has_context)
Definition: Delivery.php:382
__construct($input, Services $http)
Definition: Delivery.php:69
Interface Observer Contains several chained tasks and infos about them.
$response
Definition: xapitoken.php:90
setDisposition(string $disposition)
Definition: Delivery.php:310
static http()
Fetches the global http state from ILIAS.
static setDEBUG(bool $DEBUG)
Definition: Delivery.php:439
cleanDownloadFileName()
Converts the filename to ASCII.
Definition: Delivery.php:488
setExitAfter(bool $exit_after)
Definition: Delivery.php:334
setDownloadFileName(string $download_file_name)
Definition: Delivery.php:298
setDeliveryType(string $delivery_type)
Definition: Delivery.php:262
Class Delivery.
Definition: Delivery.php:38
setHashFilename(bool $hash_filename)
Definition: Delivery.php:406
static getType()
Get context type.
setShowLastModified(bool $show_last_modified)
Definition: Delivery.php:370
The base interface for all filesystem streams.
Definition: FileStream.php:31
setPathToFile(string $path_to_file)
Definition: Delivery.php:286
setSendMimeType(bool $send_mime_type)
Definition: Delivery.php:322