ILIAS  release_7 Revision v7.30-3-g800a261c036
PHPChunked.php
Go to the documentation of this file.
1<?php
2
20
24
25require_once('./Services/FileDelivery/interfaces/int.ilFileDeliveryType.php');
26
34final class PHPChunked implements ilFileDeliveryType
35{
36
40 private $httpService;
41
42
48 public function __construct(GlobalHttpState $httpState)
49 {
50 $this->httpService = $httpState;
51 }
52
53
57 public function doesFileExists($path_to_file)
58 {
59 return is_readable($path_to_file);
60 }
61
62
66 public function prepare($path_to_file)
67 {
68 return true;
69 }
70
71
75 public function deliver($path_to_file, $file_marked_to_delete)
76 {
77 $file = $path_to_file;
78 $fp = @fopen($file, 'rb');
79 // see https://mantis.ilias.de/view.php?id=36970
80 if ($fp === false) {
81 $response = $this->httpService->response()->withStatus(404);
82 $this->httpService->saveResponse($response);
83 $this->close();
84 }
85
86 $size = filesize($file); // File size
87 $length = $size; // Content length
88 $start = 0; // Start byte
89 $end = $size - 1; // End byte
90 // Now that we've gotten so far without errors we send the accept range header
91 /* At the moment we only support single ranges.
92 * Multiple ranges requires some more work to ensure it works correctly
93 * and comply with the spesifications: http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html#sec19.2
94 *
95 * Multirange support annouces itself with:
96 * header('Accept-Ranges: bytes');
97 *
98 * Multirange content must be sent with multipart/byteranges mediatype,
99 * (mediatype = mimetype)
100 * as well as a boundry header to indicate the various chunks of data.
101 */
102 $response = $this->httpService->response()->withHeader("Accept-Ranges", "0-$length");
103 $this->httpService->saveResponse($response);
104 $server = $this->httpService->request()->getServerParams();
105 // header('Accept-Ranges: bytes');
106 // multipart/byteranges
107 // http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html#sec19.2
108 if (isset($server['HTTP_RANGE'])) {
109 $c_start = $start;
110 $c_end = $end;
111
112 // Extract the range string
113 list(, $range) = explode('=', $server['HTTP_RANGE'], 2);
114 // Make sure the client hasn't sent us a multibyte range
115 if (strpos($range, ',') !== false) {
116 // (?) Shoud this be issued here, or should the first
117 // range be used? Or should the header be ignored and
118 // we output the whole content?
119 $response = $this->httpService->response()->withStatus(416)->withHeader(ResponseHeader::CONTENT_RANGE, "bytes $start-$end/$size");
120 $this->httpService->saveResponse($response);
121
122 //header("Content-Range: bytes $start-$end/$size");
123 // (?) Echo some info to the client?
124 $this->close();
125 } // fim do if
126 // If the range starts with an '-' we start from the beginning
127 // If not, we forward the file pointer
128 // And make sure to get the end byte if spesified
129 if ($range[0] == '-') {
130 // The n-number of the last bytes is requested
131 $c_start = $size - substr($range, 1);
132 } else {
133 $range = explode('-', $range);
134 $c_start = $range[0];
135 $c_end = (isset($range[1]) && is_numeric($range[1])) ? $range[1] : $size;
136 } // fim do if
137 /* Check the range and make sure it's treated according to the specs.
138 * http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
139 */
140 // End bytes can not be larger than $end.
141 $c_end = ($c_end > $end) ? $end : $c_end;
142 // Validate the requested range and return an error if it's not correct.
143 if ($c_start > $c_end || $c_start > $size - 1 || $c_end >= $size) {
144 $response = $this->httpService->response()->withStatus(416)->withHeader(ResponseHeader::CONTENT_RANGE, "bytes $start-$end/$size");
145
146 $this->httpService->saveResponse($response);
147 // (?) Echo some info to the client?
148 $this->close();
149 } // fim do if
150
151 $start = $c_start;
152 $end = $c_end;
153 $length = $end - $start + 1; // Calculate new content length
154 fseek($fp, $start);
155
156 $response = $this->httpService->response()->withStatus(206);
157
158 $this->httpService->saveResponse($response);
159 } // fim do if
160
161 // Notify the client the byte range we'll be outputting
162 $response = $this->httpService->response()->withHeader(ResponseHeader::CONTENT_RANGE, "bytes $start-$end/$size")->withHeader(ResponseHeader::CONTENT_LENGTH, $length);
163
164 $this->httpService->saveResponse($response);
165
166 //render response and start buffered download
167 $this->httpService->sendResponse();
168
169 // Start buffered download
170 $buffer = 1024 * 8;
171 while (!feof($fp) && ($p = ftell($fp)) <= $end) {
172 if ($p + $buffer > $end) {
173 // In case we're only outputtin a chunk, make sure we don't
174 // read past the length
175 $buffer = $end - $p + 1;
176 } // fim do if
177
178 set_time_limit(0); // Reset time limit for big files
179 echo fread($fp, $buffer);
180 flush(); // Free up memory. Otherwise large files will trigger PHP's memory limit.
181 } // fim do while
182
183 fclose($fp);
184
185 return true;
186 }
187
188
192 public function supportsInlineDelivery()
193 {
194 return true;
195 }
196
197
202 {
203 return true;
204 }
205
206
210 public function supportsStreaming()
211 {
212 return true;
213 }
214
215
216 private function close()
217 {
218 //render response
219 $this->httpService->sendResponse();
220 exit;
221 }
222
223
227 public function handleFileDeletion($path_to_file)
228 {
229 return unlink($path_to_file);
230 }
231}
$size
Definition: RandomTest.php:84
An exception for terminatinating execution or to throw for unit testing.
deliver($path_to_file, $file_marked_to_delete)
bool
Definition: PHPChunked.php:75
__construct(GlobalHttpState $httpState)
PHP constructor.
Definition: PHPChunked.php:48
$server
Interface GlobalHttpState.
Interface ResponseHeader.
exit
Definition: login.php:29
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
$response