ILIAS  release_5-4 Revision v5.4.26-12-gabc799a52e6
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';
35  private static $delivery_type_static = null;
43  private $mime_type = '';
47  private $path_to_file = '';
51  private $download_file_name = '';
55  private $disposition = self::DISP_ATTACHMENT;
59  private $send_mime_type = true;
63  private $exit_after = true;
71  private $etag = '';
75  private $show_last_modified = true;
79  private $has_context = true;
83  private $cache = false;
87  private $hash_filename = false;
91  private $delete_file = false;
95  private static $DEBUG = false;
99  private $httpService;
104 
105 
110  public function __construct($path_to_file, GlobalHttpState $httpState)
111  {
112  assert(is_string($path_to_file));
113  $this->httpService = $httpState;
114  if ($path_to_file == self::DIRECT_PHP_OUTPUT) {
115  $this->setPathToFile(self::DIRECT_PHP_OUTPUT);
116  } else {
118  $this->detemineDeliveryType();
119  $this->determineMimeType();
120  $this->determineDownloadFileName();
121  }
122  $this->setHasContext(\ilContext::getType() !== null);
123  $this->fileDeliveryTypeFactory = new FileDeliveryTypeFactory($httpState);
124  }
125 
126 
127  public function stream()
128  {
129  if (!$this->delivery()->supportsStreaming()) {
131  }
132  $this->deliver();
133  }
134 
135 
136  private function delivery()
137  {
138  return $this->fileDeliveryTypeFactory->getInstance($this->getDeliveryType());
139  }
140 
141 
142  public function deliver()
143  {
144  $response = $this->httpService->response()->withHeader('X-ILIAS-FileDelivery-Method', $this->getDeliveryType());
145  if (!$this->delivery()->doesFileExists($this->path_to_file)) {
146  $response = $this->httpService->response()->withStatus(404);
147  $this->httpService->saveResponse($response);
148  $this->httpService->sendResponse();
149  $this->close();
150  }
151  $this->httpService->saveResponse($response);
152 
153  $this->clearBuffer();
154  $this->checkCache();
155  $this->setGeneralHeaders();
156  $this->delivery()->prepare($this->getPathToFile());
157  $this->delivery()->deliver($this->getPathToFile(), $this->isDeleteFile());
158  if ($this->isDeleteFile()) {
159  $this->delivery()->handleFileDeletion($this->getPathToFile());
160  }
161  if ($this->isExitAfter()) {
162  $this->close();
163  }
164  }
165 
166 
167  public function setGeneralHeaders()
168  {
169  $this->checkExisting();
170  if ($this->isSendMimeType()) {
171  $response = $this->httpService->response()->withHeader(ResponseHeader::CONTENT_TYPE, $this->getMimeType());
172  $this->httpService->saveResponse($response);
173  }
174  if ($this->isConvertFileNameToAsci()) {
175  $this->cleanDownloadFileName();
176  }
177  if ($this->hasHashFilename()) {
178  $this->setDownloadFileName(md5($this->getDownloadFileName()));
179  }
180  $this->setDispositionHeaders();
181  $response = $this->httpService->response()->withHeader(ResponseHeader::ACCEPT_RANGES, 'bytes');
182  $this->httpService->saveResponse($response);
183  if ($this->getDeliveryType() == DeliveryMethod::PHP
184  && $this->getPathToFile() != self::DIRECT_PHP_OUTPUT
185  ) {
186  $response = $this->httpService->response()->withHeader(ResponseHeader::CONTENT_LENGTH, (string) filesize($this->getPathToFile()));
187  $this->httpService->saveResponse($response);
188  }
189  $response = $this->httpService->response()->withHeader(ResponseHeader::CONNECTION, "close");
190  $this->httpService->saveResponse($response);
191  }
192 
193 
194  public function setCachingHeaders()
195  {
196  $response = $this->httpService->response()->withHeader(ResponseHeader::CACHE_CONTROL, 'must-revalidate, post-check=0, pre-check=0')->withHeader(ResponseHeader::PRAGMA, 'public');
197 
198  $this->httpService->saveResponse($response);
199  $this->sendEtagHeader();
200  $this->sendLastModified();
201  }
202 
203 
204  public function generateEtag()
205  {
206  $this->setEtag(md5(filemtime($this->getPathToFile()) . filesize($this->getPathToFile())));
207  }
208 
209 
210  public function close()
211  {
212  exit;
213  }
214 
215 
219  private function determineMimeType()
220  {
222  if ($info) {
223  $this->setMimeType($info);
224 
225  return true;
226  }
227  $finfo = finfo_open(FILEINFO_MIME_TYPE);
228  $info = finfo_file($finfo, $this->getPathToFile());
229  finfo_close($finfo);
230  if ($info) {
231  $this->setMimeType($info);
232 
233  return true;
234  }
235 
236  return false;
237  }
238 
239 
243  private function determineDownloadFileName()
244  {
245  if (!$this->getDownloadFileName()) {
246  $download_file_name = basename($this->getPathToFile());
248  }
249  }
250 
251 
255  private function detemineDeliveryType()
256  {
257  if (self::$delivery_type_static) {
258  \ilWACLog::getInstance()->write('used cached delivery type');
259  $this->setDeliveryType(self::$delivery_type_static);
260 
261  return true;
262  }
263 
264  if (function_exists('apache_get_modules')
265  && in_array('mod_xsendfile', apache_get_modules())
266  ) {
268  }
269 
270  if (is_file('./Services/FileDelivery/classes/override.php')) {
271  $override_delivery_type = false;
272  require_once('./Services/FileDelivery/classes/override.php');
273  if ($override_delivery_type) {
274  $this->setDeliveryType($override_delivery_type);
275  }
276  }
277 
278  require_once('./Services/Environment/classes/class.ilRuntime.php');
279  $ilRuntime = \ilRuntime::getInstance();
280  if ((!$ilRuntime->isFPM() && !$ilRuntime->isHHVM())
282  ) {
284  }
285 
287  && strpos($this->getPathToFile(), './data') !== 0
288  ) {
290  }
291 
292  self::$delivery_type_static = $this->getDeliveryType();
293 
294  return true;
295  }
296 
297 
301  public function getDeliveryType()
302  {
303  return $this->delivery_type;
304  }
305 
306 
311  {
312  $this->delivery_type = $delivery_type;
313  }
314 
315 
319  public function getMimeType()
320  {
321  return $this->mime_type;
322  }
323 
324 
328  public function setMimeType($mime_type)
329  {
330  $this->mime_type = $mime_type;
331  }
332 
333 
337  public function getPathToFile()
338  {
339  return $this->path_to_file;
340  }
341 
342 
346  public function setPathToFile($path_to_file)
347  {
348  $this->path_to_file = $path_to_file;
349  }
350 
351 
355  public function getDownloadFileName()
356  {
358  }
359 
360 
365  {
366  $this->download_file_name = $download_file_name;
367  }
368 
369 
373  public function getDisposition()
374  {
375  return $this->disposition;
376  }
377 
378 
382  public function setDisposition($disposition)
383  {
384  $this->disposition = $disposition;
385  }
386 
387 
391  public function isSendMimeType()
392  {
393  return $this->send_mime_type;
394  }
395 
396 
401  {
402  $this->send_mime_type = $send_mime_type;
403  }
404 
405 
409  public function isExitAfter()
410  {
411  return $this->exit_after;
412  }
413 
414 
418  public function setExitAfter($exit_after)
419  {
420  $this->exit_after = $exit_after;
421  }
422 
423 
427  public function isConvertFileNameToAsci()
428  {
430  }
431 
432 
437  {
438  $this->convert_file_name_to_asci = $convert_file_name_to_asci;
439  }
440 
441 
445  public function getEtag()
446  {
447  return $this->etag;
448  }
449 
450 
454  public function setEtag($etag)
455  {
456  $this->etag = $etag;
457  }
458 
459 
463  public function getShowLastModified()
464  {
466  }
467 
468 
473  {
474  $this->show_last_modified = $show_last_modified;
475  }
476 
477 
481  public function isHasContext()
482  {
483  return $this->has_context;
484  }
485 
486 
490  public function setHasContext($has_context)
491  {
492  $this->has_context = $has_context;
493  }
494 
495 
499  public function hasCache()
500  {
501  return $this->cache;
502  }
503 
504 
508  public function setCache($cache)
509  {
510  $this->cache = $cache;
511  }
512 
513 
517  public function hasHashFilename()
518  {
519  return $this->hash_filename;
520  }
521 
522 
527  {
528  $this->hash_filename = $hash_filename;
529  }
530 
531 
532  private function sendEtagHeader()
533  {
534  if ($this->getEtag()) {
535  $response = $this->httpService->response()->withHeader('ETag', $this->getEtag());
536  $this->httpService->saveResponse($response);
537  }
538  }
539 
540 
541  private function sendLastModified()
542  {
543  if ($this->getShowLastModified()) {
544  $response = $this->httpService->response()->withHeader(
545  'Last-Modified',
546  date("D, j M Y H:i:s", filemtime($this->getPathToFile()))
547  . " GMT"
548  );
549  $this->httpService->saveResponse($response);
550  }
551  }
552 
553  // /**
554  // * @return bool
555  // */
556  // private function isNonModified() {
557  // if (self::$DEBUG) {
558  // return false;
559  // }
560  //
561  // if (!isset($_SERVER['HTTP_IF_NONE_MATCH']) || !isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) {
562  // return false;
563  // }
564  //
565  // $http_if_none_match = $_SERVER['HTTP_IF_NONE_MATCH'];
566  // $http_if_modified_since = $_SERVER['HTTP_IF_MODIFIED_SINCE'];
567  //
568  // switch (true) {
569  // case ($http_if_none_match != $this->getEtag()):
570  // return false;
571  // case (@strtotime($http_if_modified_since) <= filemtime($this->getPathToFile())):
572  // return false;
573  // }
574  //
575  // return true;
576  // }
577 
581  public static function isDEBUG()
582  {
583  return (bool) self::$DEBUG;
584  }
585 
586 
590  public static function setDEBUG($DEBUG)
591  {
592  assert(is_bool($DEBUG));
593  self::$DEBUG = $DEBUG;
594  }
595 
596 
600  public function checkCache()
601  {
602  if ($this->hasCache()) {
603  $this->generateEtag();
604  $this->sendEtagHeader();
605  $this->setShowLastModified(true);
606  $this->setCachingHeaders();
607  }
608  }
609 
610 
614  public function clearBuffer()
615  {
616  $ob_get_contents = ob_get_contents();
617  if ($ob_get_contents) {
618  // \ilWACLog::getInstance()->write(__CLASS__ . ' had output before file delivery: '
619  // . $ob_get_contents);
620  }
621  ob_end_clean(); // fixed 0016469, 0016467, 0016468
622  }
623 
624 
628  private function checkExisting()
629  {
630  if ($this->getPathToFile() != self::DIRECT_PHP_OUTPUT
631  && !file_exists($this->getPathToFile())
632  ) {
633  $this->close();
634  }
635  }
636 
637 
643  private function cleanDownloadFileName()
644  {
645  $download_file_name = self::returnASCIIFileName($this->getDownloadFileName());
647  }
648 
649 
657  public static function returnASCIIFileName($original_filename)
658  {
659  // The filename must be converted to ASCII, as of RFC 2183,
660  // section 2.3.
661 
673 
676 
677  // #15914 - try to fix german umlauts
678  $umlauts = array(
679  "Ä" => "Ae",
680  "Ö" => "Oe",
681  "Ü" => "Ue",
682  "ä" => "ae",
683  "ö" => "oe",
684  "ü" => "ue",
685  "ß" => "ss",
686  );
687  foreach ($umlauts as $src => $tgt) {
688  $original_filename = str_replace($src, $tgt, $original_filename);
689  }
690 
691  $ascii_filename = htmlentities($original_filename, ENT_NOQUOTES, 'UTF-8');
692  $ascii_filename = preg_replace('/\&(.)[^;]*;/', '\\1', $ascii_filename);
693  $ascii_filename = preg_replace('/[\x7f-\xff]/', '_', $ascii_filename);
694 
695  // OS do not allow the following characters in filenames: \/:*?"<>|
696  $ascii_filename = preg_replace('/[:\x5c\/\*\?\"<>\|]/', '_', $ascii_filename);
697 
698  return (string) $ascii_filename;
699  // return iconv("UTF-8", "ASCII//TRANSLIT", $original_name); // proposal
700  }
701 
702 
706  public function isDeleteFile()
707  {
708  return (bool) $this->delete_file;
709  }
710 
711 
717  public function setDeleteFile($delete_file)
718  {
719  assert(is_bool($delete_file));
720  $this->delete_file = $delete_file;
721  }
722 
723 
724  private function setDispositionHeaders()
725  {
726  $response = $this->httpService->response();
727  $response = $response->withHeader(
729  $this->getDisposition()
730  . '; filename="'
731  . $this->getDownloadFileName()
732  . '"'
733  );
734  $response = $response->withHeader('Content-Description', $this->getDownloadFileName());
735  $this->httpService->saveResponse($response);
736  }
737 }
Interface GlobalHttpState.
static getInstance()
static setDEBUG($DEBUG)
Definition: Delivery.php:590
setConvertFileNameToAsci($convert_file_name_to_asci)
Definition: Delivery.php:436
__construct($path_to_file, GlobalHttpState $httpState)
Definition: Delivery.php:110
setDownloadFileName($download_file_name)
Definition: Delivery.php:364
static lookupMimeType($path_to_file, $fallback=self::APPLICATION__OCTET_STREAM, $a_external=null)
static returnASCIIFileName($original_filename)
Converts a UTF-8 filename to ASCII.
Definition: Delivery.php:657
setPathToFile($path_to_file)
Definition: Delivery.php:346
setSendMimeType($send_mime_type)
Definition: Delivery.php:400
setDeleteFile($delete_file)
Definition: Delivery.php:717
cleanDownloadFileName()
Converts the filename to ASCII.
Definition: Delivery.php:643
setShowLastModified($show_last_modified)
Definition: Delivery.php:472
$ascii_filename
Definition: metadata.php:311
exit
Definition: backend.php:16
setHasContext($has_context)
Definition: Delivery.php:490
setHashFilename($hash_filename)
Definition: Delivery.php:526
static getType()
Get context type.
$info
Definition: index.php:5
setDisposition($disposition)
Definition: Delivery.php:382
$response
setDeliveryType($delivery_type)
Definition: Delivery.php:310