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