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