ILIAS  trunk Revision v11.0_alpha-3011-gc6b235a2e85
StreamDelivery.php
Go to the documentation of this file.
1<?php
2
19declare(strict_types=1);
20
22
24use Psr\Http\Message\ResponseInterface;
32
36final 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,
65 ?string $mime_type = null
66 ): never {
67 $this->deliver(
68 $stream,
71 Disposition::ATTACHMENT
72 );
73 }
74
75 public function inline(
76 FileStream $stream,
78 ?string $mime_type = null
79 ): never {
80 $this->deliver(
81 $stream,
84 Disposition::INLINE
85 );
86 }
87
88 public function deliver(
89 FileStream $stream,
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),
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,
156 $disposition
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}
$filename
Definition: buildRTE.php:78
__construct(private DataSigner $data_signer, Services $http, ResponseBuilder $response_builder, ResponseBuilder $fallback_response_builder,)
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)
Stream factory which enables the user to create streams without the knowledge of the concrete class.
Definition: Streams.php:32
Class Services.
Definition: Services.php:38
$http
Definition: deliver.php:30
The base interface for all filesystem streams.
Definition: FileStream.php:32
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 http()
Fetches the global http state from ILIAS.
__construct(Container $dic, ilPlugin $plugin)
@inheritDoc
$token
Definition: xapitoken.php:70