ILIAS  release_9 Revision v9.13-25-g2c18ec4c24f
StreamDelivery.php
Go to the documentation of this file.
1 <?php
2 
19 declare(strict_types=1);
20 
22 
32 
36 final class StreamDelivery extends BaseDelivery
37 {
38  public const SUBREQUEST_SEPARATOR = '/-/';
39 
40  public function __construct(
41  private DataSigner $data_signer,
43  ResponseBuilder $response_builder,
44  ResponseBuilder $fallback_response_builder,
45  ) {
46  parent::__construct($http, $response_builder, $fallback_response_builder);
47  }
48 
52  private function notFound(ResponseInterface $r): void
53  {
54  $this->http->saveResponse($r->withStatus(404));
55  $this->http->sendResponse();
56  $this->http->close();
57  }
58 
59  public function attached(
60  FileStream $stream,
61  string $download_file_name,
62  ?string $mime_type = null
63  ): never {
64  $this->deliver(
65  $stream,
66  $download_file_name,
67  $mime_type,
68  Disposition::ATTACHMENT
69  );
70  }
71 
72  public function inline(
73  FileStream $stream,
74  string $download_file_name,
75  ?string $mime_type = null
76  ): never {
77  $this->deliver(
78  $stream,
79  $download_file_name,
80  $mime_type,
81  Disposition::INLINE
82  );
83  }
84 
85  public function deliver(
86  FileStream $stream,
87  string $download_file_name,
88  ?string $mime_type = null,
89  Disposition $disposition = Disposition::INLINE
90  ): never {
91  $r = $this->http->response();
92  $uri = $stream->getMetadata()['uri'];
93 
94  if ($stream instanceof ZIPStream || $stream->getMetadata()['uri'] === 'php://memory') {
95  $this->response_builder = $this->fallback_response_builder;
96  }
97 
98  $r = $this->setGeneralHeaders(
99  $r,
100  $uri,
101  $mime_type ?? mime_content_type($uri),
102  $download_file_name,
104  );
105 
106  $r = $this->response_builder->buildForStream(
107  $this->http->request(),
108  $r,
109  $stream
110  );
111  $this->saveAndClose($r);
112  }
113 
114  public function deliverFromToken(string $token): never
115  {
116  // check if $token has a sub-request, such as .../index.html
117  $parts = explode(self::SUBREQUEST_SEPARATOR, $token);
118  $sub_request = null;
119  if (count($parts) > 1) {
120  $token = $parts[0];
121  $sub_request = implode('/', array_slice($parts, 1));
122  }
123 
124  $r = $this->http->response();
125  $payload = $this->data_signer->verifyStreamToken($token);
126 
127  switch (true) {
128  case $payload instanceof FilePayload:
129  $uri = $payload->getUri();
130  $mime_type = $payload->getMimeType();
131  $file_name = $payload->getFilename();
132  $disposition = Disposition::tryFrom($payload->getDisposition()) ?? Disposition::INLINE;
133  break;
134  case $payload instanceof ShortFilePayload:
135  $uri = $payload->getUri();
136  $mime_type = $this->determineMimeType($uri);
137  $file_name = $payload->getFilename();
138  $disposition = Disposition::INLINE;
139  break;
140  default:
141  $this->notFound($r);
142  }
143  unset($payload);
144 
145  // handle direct access to file
146 
147  if ($sub_request === null) {
148  $r = $this->setGeneralHeaders(
149  $r,
150  $uri,
151  $mime_type,
152  $file_name,
154  );
155 
156  $this->http->saveResponse(
157  $this->response_builder->buildForStream(
158  $this->http->request(),
159  $r,
160  Streams::ofResource(fopen($uri, 'rb'))
161  )
162  );
163  } else { // handle subrequest, aka file in a ZIP
164  $requested_zip = $uri;
165  $sub_request = urldecode($sub_request);
166  // remove query
167  $sub_request = explode('?', $sub_request)[0];
168 
169  try {
170  $file_inside_ZIP = Streams::ofFileInsideZIP($requested_zip, $sub_request);
171  } catch (\Throwable) {
172  $this->notFound($r);
173  }
174  $file_inside_zip_uri = $file_inside_ZIP->getMetadata()['uri'];
175  $file_inside_zip_stream = fopen($file_inside_zip_uri, 'rb');
176 
177  if ($file_inside_zip_stream === false) {
178  $this->notFound($r);
179  }
180 
181  // we must use PHPResponseBuilder here, because the streams inside zips cant be delivered using XSendFile or others
182  $this->response_builder = $this->fallback_response_builder;
183 
184  $mime_type = $this->determineMimeType($file_inside_zip_uri);
185  $r = $this->setGeneralHeaders(
186  $r,
187  $file_inside_zip_uri,
188  $mime_type,
189  basename($sub_request),
190  Disposition::INLINE // subrequests are always inline per default, browsers may change this to download
191  );
192 
193 
194  $this->http->saveResponse(
195  $this->response_builder->buildForStream(
196  $this->http->request(),
197  $r,
198  $file_inside_ZIP
199  )
200  );
201  }
202  $this->http->sendResponse();
203  $this->http->close();
204  }
205 
206  private function determineMimeType(string $filename): string
207  {
208  $suffix = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
209  if (isset($this->mime_type_map[$suffix])) {
210  if (is_array($this->mime_type_map[$suffix]) && isset($this->mime_type_map[$suffix][0])) {
211  return $this->mime_type_map[$suffix][0];
212  }
213 
214  return $this->mime_type_map[$suffix];
215  }
216 
217  $mime_type = mime_content_type($filename);
218  if ($mime_type === 'application/octet-stream') {
219  $mime_type = mime_content_type(substr($filename, 64));
220  }
221  return $mime_type ?: 'application/octet-stream';
222  }
223 }
setGeneralHeaders(ResponseInterface $r, string $uri, string $mime_type, string $file_name, Disposition $disposition=Disposition::INLINE)
deliver(FileStream $stream, string $download_file_name, ?string $mime_type=null, Disposition $disposition=Disposition::INLINE)
attached(FileStream $stream, string $download_file_name, ?string $mime_type=null)
if($clientAssertionType !='urn:ietf:params:oauth:client-assertion-type:jwt-bearer'|| $grantType !='client_credentials') $parts
Definition: ltitoken.php:64
if(count($parts) !=3) $payload
Definition: ltitoken.php:70
static ofFileInsideZIP(string $path_to_zip, string $path_inside_zip)
Definition: Streams.php:84
static ofResource($resource)
Wraps an already created resource with the stream abstraction.
Definition: Streams.php:64
static http()
Fetches the global http state from ILIAS.
__construct(VocabulariesInterface $vocabularies)
__construct(private DataSigner $data_signer, Services $http, ResponseBuilder $response_builder, ResponseBuilder $fallback_response_builder,)
$token
Definition: xapitoken.php:70
saveAndClose(ResponseInterface $r, string $path_to_delete=null)
$filename
Definition: buildRTE.php:78
The base interface for all filesystem streams.
Definition: FileStream.php:31
$r