ILIAS  release_5-1 Revision 5.0.0-5477-g43f3e3fab5f
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');
4require_once('./Services/Context/classes/class.ilContext.php');
5require_once('./Services/Http/classes/class.ilHTTPS.php');
6require_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;
45 protected $mime_type = '';
49 protected $path_to_file = '';
53 protected $download_file_name = '';
61 protected $send_mime_type = true;
65 protected $exit_after = 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);
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);
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
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();
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;
181 $this->deliverXSendfile();
182 break;
184 $this->deliverXAccelRedirect();
185 break;
187 $this->deliverPHPChunked();
188 break;
190 $this->deliverVirtual();
191 break;
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()) {
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() {
381 }
382
383
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() {
461 }
462
463
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() {
573 }
574
575
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()) {
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
static getType()
Get context type.
Class ilFileDelivery.
setDeliveryType($delivery_type)
setDownloadFileName($download_file_name)
setDeleteFile($delete_file)
setUrlencodeFilename($urlencode_filename)
setHashFilename($hash_filename)
static setDEBUG($DEBUG)
static returnASCIIFileName($original_name)
__construct($path_to_file)
static streamVideoInline($path_to_file, $download_file_name=null)
static deliverFileAttached($path_to_file, $download_file_name=null, $mime_type=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
exit
Definition: login.php:54
if((!isset($_SERVER['DOCUMENT_ROOT'])) OR(empty($_SERVER['DOCUMENT_ROOT']))) $_SERVER['DOCUMENT_ROOT']