ILIAS  release_7 Revision v7.30-3-g800a261c036
All Data Structures Namespaces Files Functions Variables Modules Pages
Delivery.php
Go to the documentation of this file.
1 <?php
2 
4 
5 require_once('./Services/Utilities/classes/class.ilMimeTypeUtil.php');
6 require_once('./Services/Utilities/classes/class.ilUtil.php'); // This include is needed since WAC can use ilFileDelivery without Initialisation
7 require_once('./Services/Context/classes/class.ilContext.php');
8 require_once('./Services/Http/classes/class.ilHTTPS.php');
9 require_once('./Services/FileDelivery/classes/FileDeliveryTypes/FileDeliveryTypeFactory.php');
10 require_once './Services/FileDelivery/classes/FileDeliveryTypes/DeliveryMethod.php';
11 
17 
27 final class Delivery
28 {
29  const DIRECT_PHP_OUTPUT = 'php://output';
30  const DISP_ATTACHMENT = 'attachment';
31  const DISP_INLINE = 'inline';
32  const EXPIRES_IN = '+5 days';
36  private static $delivery_type_static = null;
44  private $mime_type = '';
48  private $path_to_file = '';
52  private $download_file_name = '';
56  private $disposition = self::DISP_ATTACHMENT;
60  private $send_mime_type = true;
64  private $exit_after = true;
72  private $etag = '';
76  private $show_last_modified = true;
80  private $has_context = true;
84  private $cache = false;
88  private $hash_filename = false;
92  private $delete_file = false;
96  private static $DEBUG = false;
100  private $httpService;
105 
106 
111  public function __construct($path_to_file, GlobalHttpState $httpState)
112  {
113  assert(is_string($path_to_file));
114  $this->httpService = $httpState;
115  if ($path_to_file == self::DIRECT_PHP_OUTPUT) {
116  $this->setPathToFile(self::DIRECT_PHP_OUTPUT);
117  } else {
119  $this->detemineDeliveryType();
120  $this->determineMimeType();
121  $this->determineDownloadFileName();
122  }
123  $this->setHasContext(\ilContext::getType() !== null);
124  $this->fileDeliveryTypeFactory = new FileDeliveryTypeFactory($httpState);
125  }
126 
127 
128  public function stream()
129  {
130  if (!$this->delivery()->supportsStreaming()) {
132  }
133  $this->deliver();
134  }
135 
136 
137  private function delivery()
138  {
139  return $this->fileDeliveryTypeFactory->getInstance($this->getDeliveryType());
140  }
141 
142 
143  public function deliver()
144  {
145  $response = $this->httpService->response()->withHeader('X-ILIAS-FileDelivery-Method', $this->getDeliveryType());
146  if (!$this->delivery()->doesFileExists($this->path_to_file)) {
147  $response = $this->httpService->response()->withStatus(404);
148  $this->httpService->saveResponse($response);
149  $this->httpService->sendResponse();
150  $this->close();
151  }
152  $this->httpService->saveResponse($response);
153 
154  $this->clearBuffer();
155  $this->checkCache();
156  $this->setGeneralHeaders();
157  $this->delivery()->prepare($this->getPathToFile());
158  $this->delivery()->deliver($this->getPathToFile(), $this->isDeleteFile());
159  if ($this->isDeleteFile()) {
160  $this->delivery()->handleFileDeletion($this->getPathToFile());
161  }
162  if ($this->isExitAfter()) {
163  $this->close();
164  }
165  }
166 
167 
168  public function setGeneralHeaders()
169  {
170  $this->checkExisting();
171  if ($this->isSendMimeType()) {
172  $response = $this->httpService->response()->withHeader(ResponseHeader::CONTENT_TYPE, $this->getMimeType());
173  $this->httpService->saveResponse($response);
174  }
175  if ($this->isConvertFileNameToAsci()) {
176  $this->cleanDownloadFileName();
177  }
178  if ($this->hasHashFilename()) {
179  $this->setDownloadFileName(md5($this->getDownloadFileName()));
180  }
181  $this->setDispositionHeaders();
182  $response = $this->httpService->response()->withHeader(ResponseHeader::ACCEPT_RANGES, 'bytes');
183  $this->httpService->saveResponse($response);
184  if ($this->getDeliveryType() == DeliveryMethod::PHP
185  && $this->getPathToFile() != self::DIRECT_PHP_OUTPUT
186  ) {
187  $response = $this->httpService->response()->withHeader(ResponseHeader::CONTENT_LENGTH, (string) filesize($this->getPathToFile()));
188  $this->httpService->saveResponse($response);
189  }
190  $response = $this->httpService->response()->withHeader(ResponseHeader::CONNECTION, "close");
191  $this->httpService->saveResponse($response);
192  }
193 
194 
195  public function setCachingHeaders()
196  {
197  $response = $this->httpService->response()->withHeader(ResponseHeader::CACHE_CONTROL, 'must-revalidate, post-check=0, pre-check=0')->withHeader(ResponseHeader::PRAGMA, 'public');
198 
199  $this->httpService->saveResponse($response->withHeader(ResponseHeader::EXPIRES, date("D, j M Y H:i:s", strtotime(self::EXPIRES_IN)) . " GMT"));
200  $this->sendEtagHeader();
201  $this->sendLastModified();
202  }
203 
204 
205  public function generateEtag()
206  {
207  $this->setEtag(md5(filemtime($this->getPathToFile()) . filesize($this->getPathToFile())));
208  }
209 
210 
211  public function close()
212  {
213  exit;
214  }
215 
216 
220  private function determineMimeType()
221  {
223  if ($info) {
224  $this->setMimeType($info);
225 
226  return true;
227  }
228  $finfo = finfo_open(FILEINFO_MIME_TYPE);
229  $info = finfo_file($finfo, $this->getPathToFile());
230  finfo_close($finfo);
231  if ($info) {
232  $this->setMimeType($info);
233 
234  return true;
235  }
236 
237  return false;
238  }
239 
240 
244  private function determineDownloadFileName()
245  {
246  if (!$this->getDownloadFileName()) {
247  $download_file_name = basename($this->getPathToFile());
249  }
250  }
251 
252 
256  private function detemineDeliveryType()
257  {
258  if (self::$delivery_type_static) {
259  \ilWACLog::getInstance()->write('used cached delivery type');
260  $this->setDeliveryType(self::$delivery_type_static);
261 
262  return true;
263  }
264 
265  if (function_exists('apache_get_modules')
266  && in_array('mod_xsendfile', apache_get_modules())
267  ) {
269  }
270 
271  if (is_file('./Services/FileDelivery/classes/override.php')) {
272  $override_delivery_type = false;
273  require_once('./Services/FileDelivery/classes/override.php');
274  if ($override_delivery_type) {
275  $this->setDeliveryType($override_delivery_type);
276  }
277  }
278 
279  require_once('./Services/Environment/classes/class.ilRuntime.php');
280  $ilRuntime = \ilRuntime::getInstance();
281  if ((!$ilRuntime->isFPM() && !$ilRuntime->isHHVM())
283  ) {
285  }
286 
288  && strpos($this->getPathToFile(), './data') !== 0
289  ) {
291  }
292 
293  self::$delivery_type_static = $this->getDeliveryType();
294 
295  return true;
296  }
297 
298 
302  public function getDeliveryType()
303  {
304  return $this->delivery_type;
305  }
306 
307 
312  {
313  $this->delivery_type = $delivery_type;
314  }
315 
316 
320  public function getMimeType()
321  {
322  return $this->mime_type;
323  }
324 
325 
329  public function setMimeType($mime_type)
330  {
331  $this->mime_type = $mime_type;
332  }
333 
334 
338  public function getPathToFile()
339  {
340  return $this->path_to_file;
341  }
342 
343 
347  public function setPathToFile($path_to_file)
348  {
349  $this->path_to_file = $path_to_file;
350  }
351 
352 
356  public function getDownloadFileName()
357  {
359  }
360 
361 
366  {
367  $this->download_file_name = $download_file_name;
368  }
369 
370 
374  public function getDisposition()
375  {
376  return $this->disposition;
377  }
378 
379 
383  public function setDisposition($disposition)
384  {
385  $this->disposition = $disposition;
386  }
387 
388 
392  public function isSendMimeType()
393  {
394  return $this->send_mime_type;
395  }
396 
397 
402  {
403  $this->send_mime_type = $send_mime_type;
404  }
405 
406 
410  public function isExitAfter()
411  {
412  return $this->exit_after;
413  }
414 
415 
419  public function setExitAfter($exit_after)
420  {
421  $this->exit_after = $exit_after;
422  }
423 
424 
428  public function isConvertFileNameToAsci()
429  {
431  }
432 
433 
438  {
439  $this->convert_file_name_to_asci = $convert_file_name_to_asci;
440  }
441 
442 
446  public function getEtag()
447  {
448  return $this->etag;
449  }
450 
451 
455  public function setEtag($etag)
456  {
457  $this->etag = $etag;
458  }
459 
460 
464  public function getShowLastModified()
465  {
467  }
468 
469 
474  {
475  $this->show_last_modified = $show_last_modified;
476  }
477 
478 
482  public function isHasContext()
483  {
484  return $this->has_context;
485  }
486 
487 
491  public function setHasContext($has_context)
492  {
493  $this->has_context = $has_context;
494  }
495 
496 
500  public function hasCache()
501  {
502  return $this->cache;
503  }
504 
505 
509  public function setCache($cache)
510  {
511  $this->cache = $cache;
512  }
513 
514 
518  public function hasHashFilename()
519  {
520  return $this->hash_filename;
521  }
522 
523 
528  {
529  $this->hash_filename = $hash_filename;
530  }
531 
532 
533  private function sendEtagHeader()
534  {
535  if ($this->getEtag()) {
536  $response = $this->httpService->response()->withHeader('ETag', $this->getEtag());
537  $this->httpService->saveResponse($response);
538  }
539  }
540 
541 
542  private function sendLastModified()
543  {
544  if ($this->getShowLastModified()) {
545  $response = $this->httpService->response()->withHeader(
546  'Last-Modified',
547  date("D, j M Y H:i:s", filemtime($this->getPathToFile()))
548  . " GMT"
549  );
550  $this->httpService->saveResponse($response);
551  }
552  }
553 
554  // /**
555  // * @return bool
556  // */
557  // private function isNonModified() {
558  // if (self::$DEBUG) {
559  // return false;
560  // }
561  //
562  // if (!isset($_SERVER['HTTP_IF_NONE_MATCH']) || !isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) {
563  // return false;
564  // }
565  //
566  // $http_if_none_match = $_SERVER['HTTP_IF_NONE_MATCH'];
567  // $http_if_modified_since = $_SERVER['HTTP_IF_MODIFIED_SINCE'];
568  //
569  // switch (true) {
570  // case ($http_if_none_match != $this->getEtag()):
571  // return false;
572  // case (@strtotime($http_if_modified_since) <= filemtime($this->getPathToFile())):
573  // return false;
574  // }
575  //
576  // return true;
577  // }
578 
582  public static function isDEBUG()
583  {
584  return (bool) self::$DEBUG;
585  }
586 
587 
591  public static function setDEBUG($DEBUG)
592  {
593  assert(is_bool($DEBUG));
594  self::$DEBUG = $DEBUG;
595  }
596 
597 
601  public function checkCache()
602  {
603  if ($this->hasCache()) {
604  $this->generateEtag();
605  $this->sendEtagHeader();
606  $this->setShowLastModified(true);
607  $this->setCachingHeaders();
608  }
609  }
610 
611 
615  public function clearBuffer()
616  {
617  $ob_get_contents = ob_get_contents();
618  if ($ob_get_contents) {
619  // \ilWACLog::getInstance()->write(__CLASS__ . ' had output before file delivery: '
620  // . $ob_get_contents);
621  }
622  ob_end_clean(); // fixed 0016469, 0016467, 0016468
623  }
624 
625 
629  private function checkExisting()
630  {
631  if ($this->getPathToFile() != self::DIRECT_PHP_OUTPUT
632  && !file_exists($this->getPathToFile())
633  ) {
634  $this->close();
635  }
636  }
637 
638 
644  private function cleanDownloadFileName()
645  {
646  $download_file_name = self::returnASCIIFileName($this->getDownloadFileName());
648  }
649 
650 
658  public static function returnASCIIFileName($original_filename)
659  {
660  // The filename must be converted to ASCII, as of RFC 2183,
661  // section 2.3.
662 
674 
677 
678  // #15914 - try to fix german umlauts
679  $umlauts = array(
680  "Ä" => "Ae",
681  "Ö" => "Oe",
682  "Ü" => "Ue",
683  "ä" => "ae",
684  "ö" => "oe",
685  "ü" => "ue",
686  "ß" => "ss",
687  );
688  foreach ($umlauts as $src => $tgt) {
689  $original_filename = str_replace($src, $tgt, $original_filename);
690  }
691 
692  $ascii_filename = htmlentities($original_filename, ENT_NOQUOTES, 'UTF-8');
693  $ascii_filename = preg_replace('/\&(.)[^;]*;/', '\\1', $ascii_filename);
694  $ascii_filename = preg_replace('/[\x7f-\xff]/', '_', $ascii_filename);
695 
696  // OS do not allow the following characters in filenames: \/:*?"<>|
697  $ascii_filename = preg_replace('/[:\x5c\/\*\?\"<>\|]/', '_', $ascii_filename);
698 
699  return (string) $ascii_filename;
700  // return iconv("UTF-8", "ASCII//TRANSLIT", $original_name); // proposal
701  }
702 
703 
707  public function isDeleteFile()
708  {
709  return (bool) $this->delete_file;
710  }
711 
712 
718  public function setDeleteFile($delete_file)
719  {
720  assert(is_bool($delete_file));
721  $this->delete_file = $delete_file;
722  }
723 
724 
725  private function setDispositionHeaders()
726  {
727  $response = $this->httpService->response();
728  $response = $response->withHeader(
730  $this->getDisposition()
731  . '; filename="'
732  . $this->getDownloadFileName()
733  . '"'
734  );
735  $response = $response->withHeader('Content-Description', $this->getDownloadFileName());
736  $this->httpService->saveResponse($response);
737  }
738 }
Interface GlobalHttpState.
exit
Definition: login.php:29
static getInstance()
static setDEBUG($DEBUG)
Definition: Delivery.php:591
setConvertFileNameToAsci($convert_file_name_to_asci)
Definition: Delivery.php:437
__construct($path_to_file, GlobalHttpState $httpState)
Definition: Delivery.php:111
setDownloadFileName($download_file_name)
Definition: Delivery.php:365
static lookupMimeType($path_to_file, $fallback=self::APPLICATION__OCTET_STREAM, $a_external=null)
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
static returnASCIIFileName($original_filename)
Converts a UTF-8 filename to ASCII.
Definition: Delivery.php:658
$ascii_filename
Definition: metadata.php:361
setPathToFile($path_to_file)
Definition: Delivery.php:347
setSendMimeType($send_mime_type)
Definition: Delivery.php:401
setDeleteFile($delete_file)
Definition: Delivery.php:718
cleanDownloadFileName()
Converts the filename to ASCII.
Definition: Delivery.php:644
setShowLastModified($show_last_modified)
Definition: Delivery.php:473
setHasContext($has_context)
Definition: Delivery.php:491
setHashFilename($hash_filename)
Definition: Delivery.php:527
static getType()
Get context type.
setDisposition($disposition)
Definition: Delivery.php:383
$response
setDeliveryType($delivery_type)
Definition: Delivery.php:311