ILIAS  release_5-2 Revision v5.2.25-18-g3f80b828510
class.ilFileDelivery.php
Go to the documentation of this file.
1<?php
2require_once('./Services/Utilities/classes/class.ilMimeTypeUtil.php');
3require_once('./Services/Utilities/classes/class.ilUtil.php'); // This include is needed since WAC can use ilFileDelivery without Initialisation
4require_once('./Services/Context/classes/class.ilContext.php');
5require_once('./Services/Http/classes/class.ilHTTPS.php');
6require_once('./Services/WebAccessChecker/classes/class.ilHTTP.php');
7require_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;
47 protected $mime_type = '';
51 protected $path_to_file = '';
55 protected $download_file_name = '';
63 protected $send_mime_type = true;
67 protected $exit_after = 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);
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);
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
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();
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;
191 $this->deliverXSendfile();
192 break;
194 $this->deliverXAccelRedirect();
195 break;
197 $this->deliverPHPChunked();
198 break;
200 $this->deliverVirtual();
201 break;
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()) {
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() {
407 }
408
409
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() {
487 }
488
489
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() {
599 }
600
601
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()) {
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}
date( 'd-M-Y', $objPHPExcel->getProperties() ->getCreated())
$size
Definition: RandomTest.php:79
An exception for terminatinating execution or to throw for unit testing.
static getType()
Get context type.
Class ilFileDelivery.
setDeliveryType($delivery_type)
setDownloadFileName($download_file_name)
setDeleteFile($delete_file)
setUrlencodeFilename($urlencode_filename)
setHashFilename($hash_filename)
static deliverFileAttached($path_to_file, $download_file_name=null, $mime_type=null, $delete_file=false)
static setDEBUG($DEBUG)
static returnASCIIFileName($original_name)
__construct($path_to_file)
static streamVideoInline($path_to_file, $download_file_name=null)
setSendMimeType($send_mime_type)
setExitAfter($exit_after)
deliverVirtual()
@description not supported
static deliverFileInline($path_to_file, $download_file_name=null)
setPathToFile($path_to_file)
setConvertFileNameToAsci($convert_file_name_to_asci)
sendFileUnbufferedUsingHeaders(\Closure $closure)
setHasContext($has_context)
setShowLastModified($show_last_modified)
setDisposition($disposition)
static status($status)
INIFile Parser.
static lookupMimeType($path_to_file, $fallback=self::APPLICATION__OCTET_STREAM, $a_external=false)
static getInstance()
static getASCIIFilename($a_filename)
convert utf8 to ascii filename
static getInstance()
$info
Definition: example_052.php:80
if(!file_exists("$old.txt")) if( $old===$new) if(file_exists("$new.txt")) $file
if((!isset($_SERVER['DOCUMENT_ROOT'])) OR(empty($_SERVER['DOCUMENT_ROOT']))) $_SERVER['DOCUMENT_ROOT']
global $DIC