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