ILIAS  trunk Revision v11.0_alpha-2638-g80c1d007f79
Delivery.php
Go to the documentation of this file.
1 <?php
2 
19 declare(strict_types=1);
20 
22 
29 
39 final 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 = '';
62  private string $disposition = self::DISP_ATTACHMENT;
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;
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())
253  ) {
255  }
256 
257  if ($this->getDeliveryType() === DeliveryMethod::XACCEL
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  {
269  return $this->delivery_type;
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  {
329  return $this->send_mime_type;
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 
357  public function setConvertFileNameToAsci(bool $convert_file_name_to_asci): void
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  {
413  return $this->hash_filename;
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  {
501  $download_file_name = self::returnASCIIFileName($this->getDownloadFileName());
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 }
static returnASCIIFileName(string $original_filename)
Converts a UTF-8 filename to ASCII.
Definition: Delivery.php:513
setConvertFileNameToAsci(bool $convert_file_name_to_asci)
Definition: Delivery.php:357
setDeleteFile(bool $delete_file)
Definition: Delivery.php:548
setMimeType(string $mime_type)
Definition: Delivery.php:285
FileDeliveryTypeFactory $factory
Definition: Delivery.php:73
static string $delivery_type_static
Definition: Delivery.php:57
static getInstance()
setHasContext(bool $has_context)
Definition: Delivery.php:393
factory()
$response
Definition: xapitoken.php:93
$http
Definition: deliver.php:30
static lookupMimeType(string $path_to_file, string $fallback=self::APPLICATION__OCTET_STREAM, bool $a_external=false)
Definition: MimeType.php:544
while($session_entry=$r->fetchRow(ilDBConstants::FETCHMODE_ASSOC)) return null
setDisposition(string $disposition)
Definition: Delivery.php:321
static http()
Fetches the global http state from ILIAS.
static setDEBUG(bool $DEBUG)
Definition: Delivery.php:450
cleanDownloadFileName()
Converts the filename to ASCII.
Definition: Delivery.php:499
setExitAfter(bool $exit_after)
Definition: Delivery.php:345
setDownloadFileName(string $download_file_name)
Definition: Delivery.php:309
setDeliveryType(string $delivery_type)
Definition: Delivery.php:273
Class Delivery.
Definition: Delivery.php:39
setHashFilename(bool $hash_filename)
Definition: Delivery.php:417
$info
Definition: entry_point.php:21
static getType()
Get context type.
__construct($input, private Services $http)
Definition: Delivery.php:81
setShowLastModified(bool $show_last_modified)
Definition: Delivery.php:381
The base interface for all filesystem streams.
Definition: FileStream.php:31
setPathToFile(string $path_to_file)
Definition: Delivery.php:297
setSendMimeType(bool $send_mime_type)
Definition: Delivery.php:333