ILIAS  release_5-1 Revision 5.0.0-5477-g43f3e3fab5f
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');
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 
15 
16  const DELIVERY_METHOD_NONE = 'cache';
17  const DELIVERY_METHOD_XSENDFILE = 'mod_xsendfile';
18  const DELIVERY_METHOD_XACCEL = 'x-accel-redirect';
19  const DELIVERY_METHOD_PHP = 'php';
20  const DELIVERY_METHOD_PHP_CHUNKED = 'php_chunked';
21  const DELIVERY_METHOD_VIRTUAL = 'virtual';
22  const DISP_ATTACHMENT = 'attachment';
23  const DISP_INLINE = 'inline';
24  const VIRTUAL_DATA = 'virtual-data';
25  const SECURED_DATA = 'secured-data';
26  const DATA = 'data';
30  protected static $self_streaming_methods = array(
31  self::DELIVERY_METHOD_XSENDFILE,
32  self::DELIVERY_METHOD_XACCEL,
33  );
37  protected static $delivery_type_static = null;
41  protected $delivery_type = self::DELIVERY_METHOD_PHP;
45  protected $mime_type = '';
49  protected $path_to_file = '';
53  protected $download_file_name = '';
57  protected $disposition = self::DISP_ATTACHMENT;
61  protected $send_mime_type = true;
65  protected $exit_after = true;
69  protected $convert_file_name_to_asci = true;
73  protected $etag = '';
77  protected $show_last_modified = true;
81  protected $has_context = true;
85  protected $cache = false;
89  protected $hash_filename = false;
93  protected $delete_file = false;
97  protected $urlencode_filename = false;
101  protected static $DEBUG = false;
102 
103 
108  public static function deliverFileAttached($path_to_file, $download_file_name = null, $mime_type = null) {
109  $obj = new self($path_to_file);
110  if ($download_file_name) {
111  $obj->setDownloadFileName($download_file_name);
112  }
113  if ($mime_type) {
114  $obj->setMimeType($mime_type);
115  }
116  $obj->setDisposition(self::DISP_ATTACHMENT);
117  $obj->deliver();
118  }
119 
120 
125  public static function streamVideoInline($path_to_file, $download_file_name = null) {
126  $obj = new self($path_to_file);
127  if ($download_file_name) {
128  $obj->setDownloadFileName($download_file_name);
129  }
130  $obj->setDisposition(self::DISP_INLINE);
131  $obj->stream();
132  }
133 
134 
139  public static function deliverFileInline($path_to_file, $download_file_name = null) {
140  $obj = new self($path_to_file);
141 
142  if ($download_file_name) {
143  $obj->setDownloadFileName($download_file_name);
144  }
145  $obj->setDisposition(self::DISP_INLINE);
146  $obj->deliver();
147  }
148 
149 
153  public function __construct($path_to_file) {
154  $parts = parse_url($path_to_file);
155  $this->setPathToFile(($parts['path']));
156  $this->detemineDeliveryType();
157  $this->determineMimeType();
158  $this->determineDownloadFileName();
159  $this->setHasContext(ilContext::getType() !== null);
160  }
161 
162 
163  public function stream() {
164  if (!in_array($this->getDeliveryType(), self::$self_streaming_methods)) {
165  $this->setDeliveryType(self::DELIVERY_METHOD_PHP_CHUNKED);
166  }
167  $this->deliver();
168  }
169 
170 
171  public function deliver() {
172  $this->cleanDownloadFileName();
173  $this->clearBuffer();
174  $this->checkCache();
175  $this->setGeneralHeaders();
176  switch ($this->getDeliveryType()) {
177  default:
178  $this->deliverPHP();
179  break;
180  case self::DELIVERY_METHOD_XSENDFILE:
181  $this->deliverXSendfile();
182  break;
183  case self::DELIVERY_METHOD_XACCEL:
184  $this->deliverXAccelRedirect();
185  break;
186  case self::DELIVERY_METHOD_PHP_CHUNKED:
187  $this->deliverPHPChunked();
188  break;
189  case self::DELIVERY_METHOD_VIRTUAL:
190  $this->deliverVirtual();
191  break;
192  case self::DELIVERY_METHOD_NONE;
193  break;
194  }
195  if ($this->isExitAfter()) {
196  $this->close();
197  }
198  }
199 
200 
204  public function deliverVirtual() {
205  $path_to_file = $this->getPathToFile();
206  $this->clearHeaders();
207  header('Content-type:');
208  if (strpos($path_to_file, './' . self::DATA . '/') === 0 && is_dir('./' . self::VIRTUAL_DATA)) {
209  $path_to_file = str_replace('./' . self::DATA . '/', '/' . self::VIRTUAL_DATA . '/', $path_to_file);
210  }
211  virtual($path_to_file);
212  }
213 
214 
215  protected function deliverXSendfile() {
216  $realpath = realpath($this->getPathToFile());
217  $closure = function () use ($realpath) {
218  header('X-Sendfile: ' . $realpath);
219  };
220  if ($this->isDeleteFile()) {
221  $this->sendFileUnbufferedUsingHeaders($closure);
222  } else {
223  $closure();
224  }
225  }
226 
227 
228  protected function deliverXAccelRedirect() {
229  $this->clearHeaders();
230  $path_to_file = $this->getPathToFile();
231 
232  if (strpos($path_to_file, './' . self::DATA . '/') === 0) {
233  $path_to_file = str_replace('./' . self::DATA . '/', '/' . self::SECURED_DATA . '/', $path_to_file);
234  }
235 
236  $closure = function () use ($path_to_file) {
237  header('Content-type:');
238  header('X-Accel-Redirect: ' . ($path_to_file));
239  };
240  if ($this->isDeleteFile()) {
241  $this->sendFileUnbufferedUsingHeaders($closure);
242  } else {
243  $closure();
244  }
245  }
246 
247 
248  protected function deliverPHP() {
249  set_time_limit(0);
250  $file = fopen(($this->getPathToFile()), "rb");
251 
252  fpassthru($file);
253  }
254 
255 
256  protected function clearHeaders() {
257  header_remove();
258  }
259 
260 
261  public function setGeneralHeaders() {
262  header("X-ILIAS-FileDelivery-Method: " . $this->getDeliveryType());
263  $this->checkExisting();
264  if ($this->isSendMimeType()) {
265  header("Content-type: " . $this->getMimeType());
266  }
268  if ($this->isConvertFileNameToAsci()) {
269  $download_file_name = self::returnASCIIFileName($download_file_name);
270  }
271  if ($this->hasHashFilename()) {
273  }
274  header('Content-Disposition: ' . $this->getDisposition() . '; filename="' . $download_file_name . '"');
275  header('Content-Description: ' . $download_file_name);
276  header('Accept-Ranges: bytes');
277  if ($this->getDeliveryType() == self::DELIVERY_METHOD_PHP) {
278  header("Content-Length: " . (string)filesize($this->getPathToFile()));
279  }
280  header("Connection: close");
281  header("X-ILIAS-FileDelivery: " . $this->getDeliveryType());
282  }
283 
284 
285  public function setCachingHeaders() {
286  header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
287  header('Pragma: public');
288  $this->sendEtagHeader();
289  $this->sendLastModified();
290  }
291 
292 
293  public function generateEtag() {
294  $this->setEtag(md5(filemtime($this->getPathToFile()) . filesize($this->getPathToFile())));
295  }
296 
297 
298  protected function close() {
299  exit;
300  }
301 
302 
306  protected function determineMimeType() {
308  if ($info) {
309  $this->setMimeType($info);
310 
311  return true;
312  }
313  $finfo = finfo_open(FILEINFO_MIME_TYPE);
314  $info = finfo_file($finfo, $this->getPathToFile());
315  finfo_close($finfo);
316  if ($info) {
317  $this->setMimeType($info);
318 
319  return true;
320  }
321 
322  return false;
323  }
324 
325 
329  protected function determineDownloadFileName() {
330  if (!$this->getDownloadFileName()) {
331  $download_file_name = basename($this->getPathToFile());
333  }
334  }
335 
336 
340  protected function detemineDeliveryType() {
341  if (self::$delivery_type_static) {
342  ilWACLog::getInstance()->write('used cached delivery type');
343  $this->setDeliveryType(self::$delivery_type_static);
344 
345  return true;
346  }
347 
348  if (function_exists('apache_get_modules') && in_array('mod_xsendfile', apache_get_modules())) {
349  $this->setDeliveryType(self::DELIVERY_METHOD_XSENDFILE);
350  }
351 
352  if (is_file('./Services/FileDelivery/classes/override.php')) {
353  $override_delivery_type = false;
354  require_once('./Services/FileDelivery/classes/override.php');
355  if ($override_delivery_type) {
356  $this->setDeliveryType($override_delivery_type);
357  }
358  }
359 
360  require_once('./Services/Environment/classes/class.ilRuntime.php');
361  $ilRuntime = ilRuntime::getInstance();
362  if ((!$ilRuntime->isFPM() && !$ilRuntime->isHHVM()) && $this->getDeliveryType() == self::DELIVERY_METHOD_XACCEL) {
363  $this->setDeliveryType(self::DELIVERY_METHOD_PHP);
364  }
365 
366  if ($this->getDeliveryType() == self::DELIVERY_METHOD_XACCEL && strpos($this->getPathToFile(), './data') !== 0) {
367  $this->setDeliveryType(self::DELIVERY_METHOD_PHP);
368  }
369 
370  self::$delivery_type_static = $this->getDeliveryType();
371 
372  return true;
373  }
374 
375 
379  public function getDeliveryType() {
380  return $this->delivery_type;
381  }
382 
383 
387  public function setDeliveryType($delivery_type) {
388  $this->delivery_type = $delivery_type;
389  }
390 
391 
395  public function getMimeType() {
396  return $this->mime_type;
397  }
398 
399 
403  public function setMimeType($mime_type) {
404  $this->mime_type = $mime_type;
405  }
406 
407 
411  public function getPathToFile() {
412  return $this->path_to_file;
413  }
414 
415 
419  public function setPathToFile($path_to_file) {
420  $this->path_to_file = $path_to_file;
421  }
422 
423 
427  public function getDownloadFileName() {
429  }
430 
431 
436  $this->download_file_name = $download_file_name;
437  }
438 
439 
443  public function getDisposition() {
444  return $this->disposition;
445  }
446 
447 
451  public function setDisposition($disposition) {
452  $this->disposition = $disposition;
453  }
454 
455 
459  public function isSendMimeType() {
460  return $this->send_mime_type;
461  }
462 
463 
467  public function setSendMimeType($send_mime_type) {
468  $this->send_mime_type = $send_mime_type;
469  }
470 
471 
475  public function isExitAfter() {
476  return $this->exit_after;
477  }
478 
479 
483  public function setExitAfter($exit_after) {
484  $this->exit_after = $exit_after;
485  }
486 
487 
491  public function isConvertFileNameToAsci() {
493  }
494 
495 
500  $this->convert_file_name_to_asci = $convert_file_name_to_asci;
501  }
502 
503 
507  public function getEtag() {
508  return $this->etag;
509  }
510 
511 
515  public function setEtag($etag) {
516  $this->etag = $etag;
517  }
518 
519 
523  public function getShowLastModified() {
525  }
526 
527 
532  $this->show_last_modified = $show_last_modified;
533  }
534 
535 
539  public function isHasContext() {
540  return $this->has_context;
541  }
542 
543 
547  public function setHasContext($has_context) {
548  $this->has_context = $has_context;
549  }
550 
551 
555  public function hasCache() {
556  return $this->cache;
557  }
558 
559 
563  public function setCache($cache) {
564  $this->cache = $cache;
565  }
566 
567 
571  public function hasHashFilename() {
572  return $this->hash_filename;
573  }
574 
575 
579  public function setHashFilename($hash_filename) {
580  $this->hash_filename = $hash_filename;
581  }
582 
583 
584  protected function deliverPHPChunked() {
585  $file = $this->getPathToFile();
586  $fp = @fopen($file, 'rb');
587 
588  $size = filesize($file); // File size
589  $length = $size; // Content length
590  $start = 0; // Start byte
591  $end = $size - 1; // End byte
592  // Now that we've gotten so far without errors we send the accept range header
593  /* At the moment we only support single ranges.
594  * Multiple ranges requires some more work to ensure it works correctly
595  * and comply with the spesifications: http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html#sec19.2
596  *
597  * Multirange support annouces itself with:
598  * header('Accept-Ranges: bytes');
599  *
600  * Multirange content must be sent with multipart/byteranges mediatype,
601  * (mediatype = mimetype)
602  * as well as a boundry header to indicate the various chunks of data.
603  */
604  header("Accept-Ranges: 0-$length");
605  // header('Accept-Ranges: bytes');
606  // multipart/byteranges
607  // http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html#sec19.2
608  if (isset($_SERVER['HTTP_RANGE'])) {
609  $c_start = $start;
610  $c_end = $end;
611 
612  // Extract the range string
613  list(, $range) = explode('=', $_SERVER['HTTP_RANGE'], 2);
614  // Make sure the client hasn't sent us a multibyte range
615  if (strpos($range, ',') !== false) {
616  // (?) Shoud this be issued here, or should the first
617  // range be used? Or should the header be ignored and
618  // we output the whole content?
619  ilHTTP::status(416);
620  header("Content-Range: bytes $start-$end/$size");
621  // (?) Echo some info to the client?
622  $this->close();
623  } // fim do if
624  // If the range starts with an '-' we start from the beginning
625  // If not, we forward the file pointer
626  // And make sure to get the end byte if spesified
627  if ($range{0} == '-') {
628  // The n-number of the last bytes is requested
629  $c_start = $size - substr($range, 1);
630  } else {
631  $range = explode('-', $range);
632  $c_start = $range[0];
633  $c_end = (isset($range[1]) && is_numeric($range[1])) ? $range[1] : $size;
634  } // fim do if
635  /* Check the range and make sure it's treated according to the specs.
636  * http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
637  */
638  // End bytes can not be larger than $end.
639  $c_end = ($c_end > $end) ? $end : $c_end;
640  // Validate the requested range and return an error if it's not correct.
641  if ($c_start > $c_end || $c_start > $size - 1 || $c_end >= $size) {
642  ilHTTP::status(416);
643  header("Content-Range: bytes $start-$end/$size");
644  // (?) Echo some info to the client?
645  $this->close();
646  } // fim do if
647 
648  $start = $c_start;
649  $end = $c_end;
650  $length = $end - $start + 1; // Calculate new content length
651  fseek($fp, $start);
652  ilHTTP::status(206);
653  } // fim do if
654 
655  // Notify the client the byte range we'll be outputting
656  header("Content-Range: bytes $start-$end/$size");
657  header("Content-Length: $length");
658 
659  // Start buffered download
660  $buffer = 1024 * 8;
661  while (!feof($fp) && ($p = ftell($fp)) <= $end) {
662  if ($p + $buffer > $end) {
663  // In case we're only outputtin a chunk, make sure we don't
664  // read past the length
665  $buffer = $end - $p + 1;
666  } // fim do if
667 
668  set_time_limit(0); // Reset time limit for big files
669  echo fread($fp, $buffer);
670  flush(); // Free up memory. Otherwise large files will trigger PHP's memory limit.
671  } // fim do while
672 
673  fclose($fp);
674  }
675 
676 
677  protected function sendEtagHeader() {
678  if ($this->getEtag()) {
679  header('ETag: ' . $this->getEtag() . '');
680  }
681  }
682 
683 
684  protected function sendLastModified() {
685  if ($this->getShowLastModified()) {
686  header('Last-Modified: ' . date("D, j M Y H:i:s", filemtime($this->getPathToFile())) . " GMT");
687  }
688  }
689 
690 
694  protected function isNonModified() {
695  if (self::$DEBUG) {
696  return false;
697  }
698 
699  if (!isset($_SERVER['HTTP_IF_NONE_MATCH']) || !isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) {
700  return false;
701  }
702 
703  $http_if_none_match = $_SERVER['HTTP_IF_NONE_MATCH'];
704  $http_if_modified_since = $_SERVER['HTTP_IF_MODIFIED_SINCE'];
705 
706  switch (true) {
707  case ($http_if_none_match != $this->getEtag()):
708  return false;
709  case (@strtotime($http_if_modified_since) <= filemtime($this->getPathToFile())):
710  return false;
711  }
712 
713  return true;
714  }
715 
716 
720  public static function isDEBUG() {
721  return self::$DEBUG;
722  }
723 
724 
728  public static function setDEBUG($DEBUG) {
729  self::$DEBUG = $DEBUG;
730  }
731 
732 
733  protected function checkCache() {
734  if ($this->hasCache()) {
735  $this->generateEtag();
736  $this->sendEtagHeader();
737  $this->setShowLastModified(true);
738  $this->setCachingHeaders();
739  if ($this->isNonModified()) {
740  //ilHTTP::status(304);
741  //$this->close();
742  }
743  }
744  }
745 
746 
747  protected function clearBuffer() {
748  $ob_get_contents = ob_get_contents();
749  if ($ob_get_contents) {
750  ilWACLog::getInstance()->write(__CLASS__ . ' had output before file delivery: ' . $ob_get_contents);
751  }
752  ob_end_clean(); // fixed 0016469, 0016467, 0016468
753  }
754 
755 
756  protected function checkExisting() {
757  if (!file_exists($this->getPathToFile())) {
758  ilHTTP::status(404);
759  $this->close();
760  }
761  }
762 
763 
764  public function cleanDownloadFileName() {
765  global $ilClientIniFile;
770  if ($ilClientIniFile instanceof ilIniFile && $ilClientIniFile->readVariable('file_access', 'disable_ascii')) {
771  $this->setConvertFileNameToAsci(false);
772  $this->setUrlencodeFilename(false);
773  }
775  if ($this->isConvertFileNameToAsci()) {
776  $download_file_name = self::returnASCIIFileName($download_file_name);
777  }
778  if ($this->isUrlencodeFilename()) {
780  }
782  }
783 
784 
790  public static function returnASCIIFileName($original_name) {
791  return ilUtil::getASCIIFilename($original_name);
792  // return iconv("UTF-8", "ASCII//TRANSLIT", $original_name); // proposal
793  }
794 
795 
799  public function isDeleteFile() {
800  return $this->delete_file;
801  }
802 
803 
807  public function setDeleteFile($delete_file) {
808  $this->delete_file = $delete_file;
809  }
810 
811 
815  public function isUrlencodeFilename() {
817  }
818 
819 
824  $this->urlencode_filename = $urlencode_filename;
825  }
826 
827 
832  protected function sendFileUnbufferedUsingHeaders(\Closure $closure) {
833  ignore_user_abort(true);
834  set_time_limit(0);
835  ob_start();
836 
837  $closure();
838 
839  ob_flush();
840  ob_end_flush();
841  flush();
842  }
843 }
print $file
$size
Definition: RandomTest.php:79
exit
Definition: login.php:54
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)
static deliverFileAttached($path_to_file, $download_file_name=null, $mime_type=null)
setUrlencodeFilename($urlencode_filename)
$info
Definition: example_052.php:80
setDownloadFileName($download_file_name)
setHashFilename($hash_filename)
setDeliveryType($delivery_type)
static returnASCIIFileName($original_name)
setDisposition($disposition)
setExitAfter($exit_after)
setConvertFileNameToAsci($convert_file_name_to_asci)
Class ilFileDelivery.
sendFileUnbufferedUsingHeaders(\Closure $closure)
static getType()
Get context type.
setShowLastModified($show_last_modified)
INIFile Parser.
static streamVideoInline($path_to_file, $download_file_name=null)