ILIAS  release_5-2 Revision v5.2.25-18-g3f80b828510
class.ilFileDelivery.php
Go to the documentation of this file.
1 <?php
2 require_once('./Services/Utilities/classes/class.ilMimeTypeUtil.php');
3 require_once('./Services/Utilities/classes/class.ilUtil.php'); // This include is needed since WAC can use ilFileDelivery without Initialisation
4 require_once('./Services/Context/classes/class.ilContext.php');
5 require_once('./Services/Http/classes/class.ilHTTPS.php');
6 require_once('./Services/WebAccessChecker/classes/class.ilHTTP.php');
7 require_once('./Services/WebAccessChecker/classes/class.ilWACLog.php');
8 
16 
17  const DIRECT_PHP_OUTPUT = 'php://output';
18  const DELIVERY_METHOD_NONE = 'cache';
19  const DELIVERY_METHOD_XSENDFILE = 'mod_xsendfile';
20  const DELIVERY_METHOD_XACCEL = 'x-accel-redirect';
21  const DELIVERY_METHOD_PHP = 'php';
22  const DELIVERY_METHOD_PHP_CHUNKED = 'php_chunked';
23  const DELIVERY_METHOD_VIRTUAL = 'virtual';
24  const DISP_ATTACHMENT = 'attachment';
25  const DISP_INLINE = 'inline';
26  const VIRTUAL_DATA = 'virtual-data';
27  const SECURED_DATA = 'secured-data';
28  const DATA = 'data';
32  protected static $self_streaming_methods = array(
33  self::DELIVERY_METHOD_XSENDFILE,
34  self::DELIVERY_METHOD_XACCEL,
35  );
39  protected static $delivery_type_static = null;
43  protected $delivery_type = self::DELIVERY_METHOD_PHP;
47  protected $mime_type = '';
51  protected $path_to_file = '';
55  protected $download_file_name = '';
59  protected $disposition = self::DISP_ATTACHMENT;
63  protected $send_mime_type = true;
67  protected $exit_after = true;
71  protected $convert_file_name_to_asci = true;
75  protected $etag = '';
79  protected $show_last_modified = true;
83  protected $has_context = true;
87  protected $cache = false;
91  protected $hash_filename = false;
95  protected $delete_file = false;
99  protected $urlencode_filename = false;
103  protected static $DEBUG = false;
104 
105 
112  public static function deliverFileAttached($path_to_file, $download_file_name = null, $mime_type = null, $delete_file = false) {
113  $obj = new self($path_to_file);
114  if ($download_file_name) {
115  $obj->setDownloadFileName($download_file_name);
116  }
117  if ($mime_type) {
118  $obj->setMimeType($mime_type);
119  }
120  $obj->setDisposition(self::DISP_ATTACHMENT);
121  $obj->setDeleteFile($delete_file);
122  $obj->deliver();
123  }
124 
125 
130  public static function streamVideoInline($path_to_file, $download_file_name = null) {
131  $obj = new self($path_to_file);
132  if ($download_file_name) {
133  $obj->setDownloadFileName($download_file_name);
134  }
135  $obj->setDisposition(self::DISP_INLINE);
136  $obj->stream();
137  }
138 
139 
144  public static function deliverFileInline($path_to_file, $download_file_name = null) {
145  $obj = new self($path_to_file);
146 
147  if ($download_file_name) {
148  $obj->setDownloadFileName($download_file_name);
149  }
150  $obj->setDisposition(self::DISP_INLINE);
151  $obj->deliver();
152  }
153 
154 
158  public function __construct($path_to_file) {
159  if ($path_to_file == self::DIRECT_PHP_OUTPUT) {
160  $this->setPathToFile(self::DIRECT_PHP_OUTPUT);
161  } else {
162  $path_to_file = explode("?", $path_to_file); // removing everything behind ?
165  $this->detemineDeliveryType();
166  $this->determineMimeType();
167  $this->determineDownloadFileName();
168  }
169  $this->setHasContext(ilContext::getType() !== null);
170  }
171 
172 
173  public function stream() {
174  if (!in_array($this->getDeliveryType(), self::$self_streaming_methods)) {
175  $this->setDeliveryType(self::DELIVERY_METHOD_PHP_CHUNKED);
176  }
177  $this->deliver();
178  }
179 
180 
181  public function deliver() {
182  $this->cleanDownloadFileName();
183  $this->clearBuffer();
184  $this->checkCache();
185  $this->setGeneralHeaders();
186  switch ($this->getDeliveryType()) {
187  default:
188  $this->deliverPHP();
189  break;
190  case self::DELIVERY_METHOD_XSENDFILE:
191  $this->deliverXSendfile();
192  break;
193  case self::DELIVERY_METHOD_XACCEL:
194  $this->deliverXAccelRedirect();
195  break;
196  case self::DELIVERY_METHOD_PHP_CHUNKED:
197  $this->deliverPHPChunked();
198  break;
199  case self::DELIVERY_METHOD_VIRTUAL:
200  $this->deliverVirtual();
201  break;
202  case self::DELIVERY_METHOD_NONE;
203  break;
204  }
205  if ($this->isDeleteFile()) {
206  unlink($this->getPathToFile());
207  }
208  if ($this->isExitAfter()) {
209  $this->close();
210  }
211  }
212 
213 
217  public function deliverVirtual() {
218  $path_to_file = $this->getPathToFile();
219  $this->clearHeaders();
220  header('Content-type:');
221  if (strpos($path_to_file, './' . self::DATA . '/') === 0
222  && is_dir('./' . self::VIRTUAL_DATA)
223  ) {
224  $path_to_file = str_replace('./' . self::DATA . '/', '/' . self::VIRTUAL_DATA
225  . '/', $path_to_file);
226  }
227  virtual($path_to_file);
228  }
229 
230 
231  protected function deliverXSendfile() {
232  $realpath = realpath($this->getPathToFile());
233  $closure = function () use ($realpath) {
234  header('X-Sendfile: ' . $realpath);
235  };
236  if ($this->isDeleteFile()) {
237  $this->sendFileUnbufferedUsingHeaders($closure);
238  } else {
239  $closure();
240  }
241  }
242 
243 
244  protected function deliverXAccelRedirect() {
245  $this->clearHeaders();
246  $path_to_file = $this->getPathToFile();
247 
248  if (strpos($path_to_file, './' . self::DATA . '/') === 0) {
249  $path_to_file = str_replace('./' . self::DATA . '/', '/' . self::SECURED_DATA
250  . '/', $path_to_file);
251  }
252 
253  $closure = function () use ($path_to_file) {
254  header('Content-type:');
255  header('X-Accel-Redirect: ' . ($path_to_file));
256  };
257  if ($this->isDeleteFile()) {
258  $this->sendFileUnbufferedUsingHeaders($closure);
259  } else {
260  $closure();
261  }
262  }
263 
264 
265  protected function deliverPHP() {
266  set_time_limit(0);
267  $file = fopen(($this->getPathToFile()), "rb");
268 
269  fpassthru($file);
270  }
271 
272 
273  protected function clearHeaders() {
274  header_remove();
275  }
276 
277 
278  public function setGeneralHeaders() {
279  header("X-ILIAS-FileDelivery-Method: " . $this->getDeliveryType());
280  $this->checkExisting();
281  if ($this->isSendMimeType()) {
282  header("Content-type: " . $this->getMimeType());
283  }
285  if ($this->isConvertFileNameToAsci()) {
286  $download_file_name = self::returnASCIIFileName($download_file_name);
287  }
288  if ($this->hasHashFilename()) {
290  }
291  header('Content-Disposition: ' . $this->getDisposition() . '; filename="'
292  . $download_file_name . '"');
293  header('Content-Description: ' . $download_file_name);
294  header('Accept-Ranges: bytes');
295  if ($this->getDeliveryType() == self::DELIVERY_METHOD_PHP
296  && $this->getPathToFile() != self::DIRECT_PHP_OUTPUT
297  ) {
298  header("Content-Length: " . (string)filesize($this->getPathToFile()));
299  }
300  header("Connection: close");
301  header("X-ILIAS-FileDelivery: " . $this->getDeliveryType());
302  }
303 
304 
305  public function setCachingHeaders() {
306  header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
307  header('Pragma: public');
308  $this->sendEtagHeader();
309  $this->sendLastModified();
310  }
311 
312 
313  public function generateEtag() {
314  $this->setEtag(md5(filemtime($this->getPathToFile()) . filesize($this->getPathToFile())));
315  }
316 
317 
318  public function close() {
319  exit;
320  }
321 
322 
326  protected function determineMimeType() {
328  if ($info) {
329  $this->setMimeType($info);
330 
331  return true;
332  }
333  $finfo = finfo_open(FILEINFO_MIME_TYPE);
334  $info = finfo_file($finfo, $this->getPathToFile());
335  finfo_close($finfo);
336  if ($info) {
337  $this->setMimeType($info);
338 
339  return true;
340  }
341 
342  return false;
343  }
344 
345 
349  protected function determineDownloadFileName() {
350  if (!$this->getDownloadFileName()) {
351  $download_file_name = basename($this->getPathToFile());
353  }
354  }
355 
356 
360  protected function detemineDeliveryType() {
361  if (self::$delivery_type_static) {
362  ilWACLog::getInstance()->write('used cached delivery type');
363  $this->setDeliveryType(self::$delivery_type_static);
364 
365  return true;
366  }
367 
368  if (function_exists('apache_get_modules')
369  && in_array('mod_xsendfile', apache_get_modules())
370  ) {
371  $this->setDeliveryType(self::DELIVERY_METHOD_XSENDFILE);
372  }
373 
374  if (is_file('./Services/FileDelivery/classes/override.php')) {
375  $override_delivery_type = false;
376  require_once('./Services/FileDelivery/classes/override.php');
377  if ($override_delivery_type) {
378  $this->setDeliveryType($override_delivery_type);
379  }
380  }
381 
382  require_once('./Services/Environment/classes/class.ilRuntime.php');
383  $ilRuntime = ilRuntime::getInstance();
384  if ((!$ilRuntime->isFPM() && !$ilRuntime->isHHVM())
385  && $this->getDeliveryType() == self::DELIVERY_METHOD_XACCEL
386  ) {
387  $this->setDeliveryType(self::DELIVERY_METHOD_PHP);
388  }
389 
390  if ($this->getDeliveryType() == self::DELIVERY_METHOD_XACCEL
391  && strpos($this->getPathToFile(), './data') !== 0
392  ) {
393  $this->setDeliveryType(self::DELIVERY_METHOD_PHP);
394  }
395 
396  self::$delivery_type_static = $this->getDeliveryType();
397 
398  return true;
399  }
400 
401 
405  public function getDeliveryType() {
406  return $this->delivery_type;
407  }
408 
409 
413  public function setDeliveryType($delivery_type) {
414  $this->delivery_type = $delivery_type;
415  }
416 
417 
421  public function getMimeType() {
422  return $this->mime_type;
423  }
424 
425 
429  public function setMimeType($mime_type) {
430  $this->mime_type = $mime_type;
431  }
432 
433 
437  public function getPathToFile() {
438  return $this->path_to_file;
439  }
440 
441 
445  public function setPathToFile($path_to_file) {
446  $this->path_to_file = $path_to_file;
447  }
448 
449 
453  public function getDownloadFileName() {
455  }
456 
457 
462  $this->download_file_name = $download_file_name;
463  }
464 
465 
469  public function getDisposition() {
470  return $this->disposition;
471  }
472 
473 
477  public function setDisposition($disposition) {
478  $this->disposition = $disposition;
479  }
480 
481 
485  public function isSendMimeType() {
486  return $this->send_mime_type;
487  }
488 
489 
493  public function setSendMimeType($send_mime_type) {
494  $this->send_mime_type = $send_mime_type;
495  }
496 
497 
501  public function isExitAfter() {
502  return $this->exit_after;
503  }
504 
505 
509  public function setExitAfter($exit_after) {
510  $this->exit_after = $exit_after;
511  }
512 
513 
517  public function isConvertFileNameToAsci() {
519  }
520 
521 
526  $this->convert_file_name_to_asci = $convert_file_name_to_asci;
527  }
528 
529 
533  public function getEtag() {
534  return $this->etag;
535  }
536 
537 
541  public function setEtag($etag) {
542  $this->etag = $etag;
543  }
544 
545 
549  public function getShowLastModified() {
551  }
552 
553 
558  $this->show_last_modified = $show_last_modified;
559  }
560 
561 
565  public function isHasContext() {
566  return $this->has_context;
567  }
568 
569 
573  public function setHasContext($has_context) {
574  $this->has_context = $has_context;
575  }
576 
577 
581  public function hasCache() {
582  return $this->cache;
583  }
584 
585 
589  public function setCache($cache) {
590  $this->cache = $cache;
591  }
592 
593 
597  public function hasHashFilename() {
598  return $this->hash_filename;
599  }
600 
601 
605  public function setHashFilename($hash_filename) {
606  $this->hash_filename = $hash_filename;
607  }
608 
609 
610  protected function deliverPHPChunked() {
611  $file = $this->getPathToFile();
612  $fp = @fopen($file, 'rb');
613 
614  $size = filesize($file); // File size
615  $length = $size; // Content length
616  $start = 0; // Start byte
617  $end = $size - 1; // End byte
618  // Now that we've gotten so far without errors we send the accept range header
619  /* At the moment we only support single ranges.
620  * Multiple ranges requires some more work to ensure it works correctly
621  * and comply with the spesifications: http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html#sec19.2
622  *
623  * Multirange support annouces itself with:
624  * header('Accept-Ranges: bytes');
625  *
626  * Multirange content must be sent with multipart/byteranges mediatype,
627  * (mediatype = mimetype)
628  * as well as a boundry header to indicate the various chunks of data.
629  */
630  header("Accept-Ranges: 0-$length");
631  // header('Accept-Ranges: bytes');
632  // multipart/byteranges
633  // http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html#sec19.2
634  if (isset($_SERVER['HTTP_RANGE'])) {
635  $c_start = $start;
636  $c_end = $end;
637 
638  // Extract the range string
639  list(, $range) = explode('=', $_SERVER['HTTP_RANGE'], 2);
640  // Make sure the client hasn't sent us a multibyte range
641  if (strpos($range, ',') !== false) {
642  // (?) Shoud this be issued here, or should the first
643  // range be used? Or should the header be ignored and
644  // we output the whole content?
645  ilHTTP::status(416);
646  header("Content-Range: bytes $start-$end/$size");
647  // (?) Echo some info to the client?
648  $this->close();
649  } // fim do if
650  // If the range starts with an '-' we start from the beginning
651  // If not, we forward the file pointer
652  // And make sure to get the end byte if spesified
653  if ($range{0} == '-') {
654  // The n-number of the last bytes is requested
655  $c_start = $size - substr($range, 1);
656  } else {
657  $range = explode('-', $range);
658  $c_start = $range[0];
659  $c_end = (isset($range[1]) && is_numeric($range[1])) ? $range[1] : $size;
660  } // fim do if
661  /* Check the range and make sure it's treated according to the specs.
662  * http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
663  */
664  // End bytes can not be larger than $end.
665  $c_end = ($c_end > $end) ? $end : $c_end;
666  // Validate the requested range and return an error if it's not correct.
667  if ($c_start > $c_end || $c_start > $size - 1 || $c_end >= $size) {
668  ilHTTP::status(416);
669  header("Content-Range: bytes $start-$end/$size");
670  // (?) Echo some info to the client?
671  $this->close();
672  } // fim do if
673 
674  $start = $c_start;
675  $end = $c_end;
676  $length = $end - $start + 1; // Calculate new content length
677  fseek($fp, $start);
678  ilHTTP::status(206);
679  } // fim do if
680 
681  // Notify the client the byte range we'll be outputting
682  header("Content-Range: bytes $start-$end/$size");
683  header("Content-Length: $length");
684 
685  // Start buffered download
686  $buffer = 1024 * 8;
687  while (!feof($fp) && ($p = ftell($fp)) <= $end) {
688  if ($p + $buffer > $end) {
689  // In case we're only outputtin a chunk, make sure we don't
690  // read past the length
691  $buffer = $end - $p + 1;
692  } // fim do if
693 
694  set_time_limit(0); // Reset time limit for big files
695  echo fread($fp, $buffer);
696  flush(); // Free up memory. Otherwise large files will trigger PHP's memory limit.
697  } // fim do while
698 
699  fclose($fp);
700  }
701 
702 
703  protected function sendEtagHeader() {
704  if ($this->getEtag()) {
705  header('ETag: ' . $this->getEtag() . '');
706  }
707  }
708 
709 
710  protected function sendLastModified() {
711  if ($this->getShowLastModified()) {
712  header('Last-Modified: ' . date("D, j M Y H:i:s", filemtime($this->getPathToFile()))
713  . " GMT");
714  }
715  }
716 
717 
721  protected function isNonModified() {
722  if (self::$DEBUG) {
723  return false;
724  }
725 
726  if (!isset($_SERVER['HTTP_IF_NONE_MATCH']) || !isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) {
727  return false;
728  }
729 
730  $http_if_none_match = $_SERVER['HTTP_IF_NONE_MATCH'];
731  $http_if_modified_since = $_SERVER['HTTP_IF_MODIFIED_SINCE'];
732 
733  switch (true) {
734  case ($http_if_none_match != $this->getEtag()):
735  return false;
736  case (@strtotime($http_if_modified_since) <= filemtime($this->getPathToFile())):
737  return false;
738  }
739 
740  return true;
741  }
742 
743 
747  public static function isDEBUG() {
748  return self::$DEBUG;
749  }
750 
751 
755  public static function setDEBUG($DEBUG) {
756  self::$DEBUG = $DEBUG;
757  }
758 
759 
760  public function checkCache() {
761  if ($this->hasCache()) {
762  $this->generateEtag();
763  $this->sendEtagHeader();
764  $this->setShowLastModified(true);
765  $this->setCachingHeaders();
766  if ($this->isNonModified()) {
767  //ilHTTP::status(304);
768  //$this->close();
769  }
770  }
771  }
772 
773 
774  public function clearBuffer() {
775  $ob_get_contents = ob_get_contents();
776  if ($ob_get_contents) {
777  ilWACLog::getInstance()->write(__CLASS__ . ' had output before file delivery: '
778  . $ob_get_contents);
779  }
780  ob_end_clean(); // fixed 0016469, 0016467, 0016468
781  }
782 
783 
784  protected function checkExisting() {
785  if ($this->getPathToFile() != self::DIRECT_PHP_OUTPUT
786  && !file_exists($this->getPathToFile())
787  ) {
788  ilHTTP::status(404);
789  $this->close();
790  }
791  }
792 
793 
794  public function cleanDownloadFileName() {
795  global $DIC;
799  $ilClientIniFile = $DIC['ilClientIniFile'];
800 
801  if ($ilClientIniFile instanceof ilIniFile
802  && $ilClientIniFile->readVariable('file_access', 'disable_ascii')
803  ) {
804  $this->setConvertFileNameToAsci(false);
805  $this->setUrlencodeFilename(false);
806  }
808  if ($this->isConvertFileNameToAsci()) {
809  $download_file_name = self::returnASCIIFileName($download_file_name);
810  }
811  if ($this->isUrlencodeFilename()) {
813  }
815  }
816 
817 
823  public static function returnASCIIFileName($original_name) {
824  return ilUtil::getASCIIFilename($original_name);
825  // return iconv("UTF-8", "ASCII//TRANSLIT", $original_name); // proposal
826  }
827 
828 
832  public function isDeleteFile() {
833  return $this->delete_file;
834  }
835 
836 
840  public function setDeleteFile($delete_file) {
841  $this->delete_file = $delete_file;
842  }
843 
844 
848  public function isUrlencodeFilename() {
850  }
851 
852 
857  $this->urlencode_filename = $urlencode_filename;
858  }
859 
860 
865  protected function sendFileUnbufferedUsingHeaders(\Closure $closure) {
866  ignore_user_abort(true);
867  set_time_limit(0);
868  ob_start();
869 
870  $closure();
871 
872  ob_flush();
873  ob_end_flush();
874  flush();
875  }
876 }
$size
Definition: RandomTest.php:79
if((!isset($_SERVER['DOCUMENT_ROOT'])) OR(empty($_SERVER['DOCUMENT_ROOT']))) $_SERVER['DOCUMENT_ROOT']
setHasContext($has_context)
static getInstance()
readVariable($a_group, $a_var_name)
reads a single variable from a group public
setPathToFile($path_to_file)
static lookupMimeType($path_to_file, $fallback=self::APPLICATION__OCTET_STREAM, $a_external=false)
static deliverFileInline($path_to_file, $download_file_name=null)
static setDEBUG($DEBUG)
setSendMimeType($send_mime_type)
static getInstance()
setDeleteFile($delete_file)
deliverVirtual()
not supported
__construct($path_to_file)
static getASCIIFilename($a_filename)
convert utf8 to ascii filename
static status($status)
setUrlencodeFilename($urlencode_filename)
$info
Definition: example_052.php:80
setDownloadFileName($download_file_name)
static deliverFileAttached($path_to_file, $download_file_name=null, $mime_type=null, $delete_file=false)
setHashFilename($hash_filename)
date( 'd-M-Y', $objPHPExcel->getProperties() ->getCreated())
setDeliveryType($delivery_type)
static returnASCIIFileName($original_name)
setDisposition($disposition)
Add a drawing to the header
Definition: 04printing.php:69
Create styles array
The data for the language used.
setExitAfter($exit_after)
setConvertFileNameToAsci($convert_file_name_to_asci)
Class ilFileDelivery.
sendFileUnbufferedUsingHeaders(\Closure $closure)
global $DIC
if(!file_exists("$old.txt")) if($old===$new) if(file_exists("$new.txt")) $file
static getType()
Get context type.
setShowLastModified($show_last_modified)
INIFile Parser.
static streamVideoInline($path_to_file, $download_file_name=null)