ILIAS  trunk Revision v11.0_alpha-3011-gc6b235a2e85
PHPChunked.php
Go to the documentation of this file.
1<?php
2
19declare(strict_types=1);
20
22
27
35final class PHPChunked implements ilFileDeliveryType
36{
40 private $file;
41
42
48 public function __construct(private Services $httpService)
49 {
50 }
51
52
56 public function doesFileExists(string $path_to_file): bool
57 {
58 return is_readable($path_to_file);
59 }
60
61
65 public function prepare(string $path_to_file, ?FileStream $possible_stream): bool
66 {
67 set_time_limit(0);
68 if ($possible_stream !== null) {
69 $this->file = $possible_stream->detach();
70 } else {
71 $resource = fopen($path_to_file, 'rb');
72 $this->file = $resource === false ? null : $resource;
73 }
74 return true;
75 }
76
77
81 public function deliver(string $path_to_file, bool $file_marked_to_delete): void
82 {
83 $file = $path_to_file;
84 $fp = $this->file;
85
86 // see https://mantis.ilias.de/view.php?id=36970
87 if ($fp === null) {
88 $response = $this->httpService->response()->withStatus(404);
89 $this->httpService->saveResponse($response);
90 $this->close();
91 }
92
93 $size = filesize($file); // File size
94 $length = $size; // Content length
95 $start = 0; // Start byte
96 $end = $size - 1; // End byte
97 // Now that we've gotten so far without errors we send the accept range header
98 /* At the moment we only support single ranges.
99 * Multiple ranges requires some more work to ensure it works correctly
100 * and comply with the spesifications: http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html#sec19.2
101 *
102 * Multirange support annouces itself with:
103 * header('Accept-Ranges: bytes');
104 *
105 * Multirange content must be sent with multipart/byteranges mediatype,
106 * (mediatype = mimetype)
107 * as well as a boundry header to indicate the various chunks of data.
108 */
109 $response = $this->httpService->response()->withHeader("Accept-Ranges", "0-$length");
110 $this->httpService->saveResponse($response);
111 $server = $this->httpService->request()->getServerParams();
112 // header('Accept-Ranges: bytes');
113 // multipart/byteranges
114 // http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html#sec19.2
115 if (isset($server['HTTP_RANGE'])) {
116 $c_start = $start;
117 $c_end = $end;
118
119 // Extract the range string
120 [, $range] = explode('=', (string) $server['HTTP_RANGE'], 2);
121 // Make sure the client hasn't sent us a multibyte range
122 if (str_contains($range, ',')) {
123 // (?) Shoud this be issued here, or should the first
124 // range be used? Or should the header be ignored and
125 // we output the whole content?
126 $response = $this->httpService->response()->withStatus(416)->withHeader(ResponseHeader::CONTENT_RANGE, "bytes $start-$end/$size");
127 $this->httpService->saveResponse($response);
128
129 //header("Content-Range: bytes $start-$end/$size");
130 // (?) Echo some info to the client?
131 $this->close();
132 } // fim do if
133 // If the range starts with an '-' we start from the beginning
134 // If not, we forward the file pointer
135 // And make sure to get the end byte if spesified
136 if ($range[0] === '-') {
137 // The n-number of the last bytes is requested
138 $c_start = $size - substr($range, 1);
139 } else {
140 $range = explode('-', $range);
141 $c_start = $range[0];
142 $c_end = (isset($range[1]) && is_numeric($range[1])) ? $range[1] : $size;
143 } // fim do if
144 /* Check the range and make sure it's treated according to the specs.
145 * http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
146 */
147 // End bytes can not be larger than $end.
148 $c_end = ($c_end > $end) ? $end : $c_end;
149 // Validate the requested range and return an error if it's not correct.
150 if ($c_start > $c_end || $c_start > $size - 1 || $c_end >= $size) {
151 $response = $this->httpService->response()->withStatus(416)->withHeader(ResponseHeader::CONTENT_RANGE, "bytes $start-$end/$size");
152
153 $this->httpService->saveResponse($response);
154 // (?) Echo some info to the client?
155 $this->close();
156 } // fim do if
157
158 $start = $c_start;
159 $end = $c_end;
160 $length = $end - $start + 1; // Calculate new content length
161 fseek($fp, (int) $start);
162
163 $response = $this->httpService->response()->withStatus(206);
164
165 $this->httpService->saveResponse($response);
166 } // fim do if
167
168 // Notify the client the byte range we'll be outputting
169 $response = $this->httpService->response()->withHeader(ResponseHeader::CONTENT_RANGE, "bytes $start-$end/$size")->withHeader(ResponseHeader::CONTENT_LENGTH, $length);
170
171 $this->httpService->saveResponse($response);
172
173 //render response and start buffered download
174 $this->httpService->sendResponse();
175
176 // Start buffered download
177 $buffer = 1024 * 8;
178 while (!feof($fp) && ($p = ftell($fp)) <= $end) {
179 if ($p + $buffer > $end) {
180 // In case we're only outputtin a chunk, make sure we don't
181 // read past the length
182 $buffer = $end - $p + 1;
183 } // fim do if
184
185 set_time_limit(0); // Reset time limit for big files
186 echo fread($fp, $buffer);
187 flush(); // Free up memory. Otherwise large files will trigger PHP's memory limit.
188 } // fim do while
189
190 fclose($fp);
191 }
192
193
197 public function supportsInlineDelivery(): bool
198 {
199 return true;
200 }
201
202
206 public function supportsAttachmentDelivery(): bool
207 {
208 return true;
209 }
210
211
215 public function supportsStreaming(): bool
216 {
217 return true;
218 }
219
220
221 private function close(): never
222 {
223 //render response
224 $this->httpService->sendResponse();
225 exit;
226 }
227
228
232 public function handleFileDeletion(string $path_to_file): bool
233 {
234 return unlink($path_to_file);
235 }
236}
prepare(string $path_to_file, ?FileStream $possible_stream)
bool
Definition: PHPChunked.php:65
handleFileDeletion(string $path_to_file)
bool
Definition: PHPChunked.php:232
deliver(string $path_to_file, bool $file_marked_to_delete)
void
Definition: PHPChunked.php:81
doesFileExists(string $path_to_file)
@inheritDoc
Definition: PHPChunked.php:56
__construct(private Services $httpService)
PHP constructor.
Definition: PHPChunked.php:48
Class Services.
Definition: Services.php:38
exit
The base interface for all filesystem streams.
Definition: FileStream.php:32
Interface ResponseHeader.
$server
Definition: shib_login.php:28
$response
Definition: xapitoken.php:93