ILIAS  trunk Revision v11.0_alpha-3011-gc6b235a2e85
RequestHandler.php
Go to the documentation of this file.
1<?php
2
19declare(strict_types=1);
20
21namespace ILIAS\WOPI\Handler;
22
28
32final class RequestHandler
33{
37 public const WOPI_BASE_URL = '/wopi/index.php/';
41 public const NAMESPACE_FILES = 'files';
45 private const HEADER_AUTHORIZATION = 'Authorization';
49 private const HEADER_AUTHORIZATION_BEARER = 'Bearer';
53 public const HEADER_X_WOPI_OVERRIDE = 'X-WOPI-Override';
57 public const HEADER_X_WOPI_LOCK = 'X-WOPI-Lock';
61 public const HEADER_X_WOPI_FILE_CONVERSION = 'X-WOPI-FileConversion';
62
63 private Services $http;
64 private \ILIAS\ResourceStorage\Services $irss;
66 private ?int $token_user_id = null;
67 private ?string $token_resource_id = null;
69 private int $saving_interval = 0;
70 private bool $editable = false;
71
72 public function __construct()
73 {
74 global $DIC;
75 $this->http = $DIC->http();
76 $this->data_signer = $DIC['file_delivery.data_signer'];
77 $this->irss = $DIC->resourceStorage();
78 $this->saving_interval = (int) $DIC->settings()->get('saving_interval');
79 }
80
81 private function checkAuth(): void
82 {
83 $auth = $this->http->request()->getHeader(self::HEADER_AUTHORIZATION)[0] ?? '';
84 // spit and check bearer token
85 $bearer = explode(' ', $auth);
86 if ($auth !== '' && ($bearer[0] ?? '') !== self::HEADER_AUTHORIZATION_BEARER) {
87 throw new \InvalidArgumentException();
88 }
89 $bearer_token = $bearer[1] ?? '';
90 if ($auth === '' && $bearer_token === '') {
91 // we try to get the token from GET
92 $bearer_token = $this->http->request()->getQueryParams()['access_token'] ?? '';
93 }
94 if ($bearer_token === '') {
95 throw new \InvalidArgumentException('No access token provided');
96 }
97 if (($token_data = $this->data_signer->verify($bearer_token, 'wopi')) === null) {
98 throw new \InvalidArgumentException('Token verification failed');
99 }
100
101 $this->token_user_id = (int) ($token_data['user_id'] ?? 0);
102 $this->token_resource_id = (string) ($token_data['resource_id'] ?? '');
103 $this->editable = (bool) ($token_data['editable'] ?? '');
104 $stakeholder = $token_data['stakeholder'] ?? null;
105 if ($stakeholder !== null) {
106 try {
107 $this->stakeholder = new WOPIStakeholderWrapper();
108 $this->stakeholder->init($stakeholder, $this->token_user_id);
109 } catch (\Throwable) {
110 $this->stakeholder = new WOPIUnknownStakeholder($this->token_user_id);
111 }
112 }
113 }
114
118 public function handleRequest(): void
119 {
120 try {
121 $this->checkAuth();
122
123 $uri = $this->http->request()->getUri()->getPath();
124 $request = substr($uri, strpos($uri, self::WOPI_BASE_URL) + strlen(self::WOPI_BASE_URL));
125 $request = explode('/', $request);
126 $method = $this->http->request()->getMethod();
127
128 $resource_id = $request[1];
129 $action = $request[2] ?? '';
130
131 // check resource_id
132 if ($this->token_resource_id !== $resource_id) {
133 $this->http->close();
134 }
135
136 $resource_id = $this->irss->manage()->find($resource_id);
137 if (!$resource_id instanceof ResourceIdentification) {
138 $this->http->close();
139 }
140 $resource = $this->irss->manage()->getResource($resource_id);
141 $current_revision = $this->editable ? $resource->getCurrentRevisionIncludingDraft() : $resource->getCurrentRevision();
142
143 $method_override = $this->http->request()->getHeader(self::HEADER_X_WOPI_OVERRIDE)[0] ?? null;
144 $is_file_convertion = (bool) ($this->http->request()->getHeader(
145 self::HEADER_X_WOPI_FILE_CONVERSION
146 )[0] ?? false);
147
148 // GET
149 switch ($method_override ?? $method) {
150 case 'GET':
151 switch ($action) {
152 case '':
153 // CheckFileInfo
155 $current_revision,
156 $this->token_user_id,
157 $this->editable
158 );
159 $this->http->saveResponse(
160 $this->http->response()->withBody(
161 Streams::ofString(json_encode($response, JSON_THROW_ON_ERROR))
162 )
163 );
164
165 break;
166 case 'contents':
167 // GetFile
168 $stream = $this->irss->consume()->stream($resource_id)->setRevisionNumber(
169 $current_revision->getVersionNumber()
170 )->getStream();
171 $this->http->saveResponse(
172 $this->http->response()->withBody($stream)
173 );
174
175 break;
176 }
177 break;
178 case 'PUT_RELATIVE':
179 if (!$is_file_convertion) {
180 throw new \InvalidArgumentException();
181 }
182 // no break
183 case 'PUT':
184 switch ($action) {
185 case 'contents':
186 // PutFile
187 $body_stream = $this->http->request()->getBody();
188 $body = $body_stream->getContents();
189 $file_stream = Streams::ofString($body);
190
191 $draft = true;
192
193 if ($this->saving_interval > 0) {
194 $latest_revision = $resource->getCurrentRevision();
195 $creation_time = $latest_revision->getInformation()->getCreationDate()->getTimestamp();
196 $current_time = time();
197 $time_diff = $current_time - $creation_time;
198 if ($time_diff > $this->saving_interval) {
199 $this->irss->manage()->publish($resource_id);
200 }
201 }
202
203 $new_revision = $this->irss->manage()->appendNewRevisionFromStream(
204 $resource_id,
205 $file_stream,
206 $this->stakeholder,
207 $current_revision->getTitle(),
208 $draft
209 );
210
211 // CheckFileInfo
213 $new_revision,
214 $this->token_user_id
215 );
216 $this->http->saveResponse(
217 $this->http->response()->withBody(
218 Streams::ofString(json_encode($response, JSON_THROW_ON_ERROR))
219 )
220 );
221
222 break;
223 case '': // if we want to create new files in the future, this will be a separate case
224 break;
225 }
226 break;
227 case 'POST':
228 switch ($action) {
229 case 'contents':
230 $this->http->saveResponse(
231 $this->http->response()->withBody(
233 )
234 );
235
236 break;
237 case '':
238 // Lock
239 $lock = $this->http->request()->getHeader(self::HEADER_X_WOPI_LOCK)[0] ?? null;
240 $this->http->saveResponse(
241 $this->http->response()->withBody(
243 )
244 );
245 }
246 break;
247 }
248 } catch (\Throwable $t) {
249 $message = $t->getMessage();
250 // append simple stacktrace
251 $trace = array_map(
252 static fn(array $trace): string => $trace['file'] . ':' . $trace['line'],
253 $t->getTrace()
254 );
255
256 $message .= "\n" . implode("\n", $trace);
257
258 $this->http->saveResponse(
259 $this->http->response()
260 ->withBody(Streams::ofString($message))
261 ->withStatus(500)
262 ->withHeader('X-WOPI-ServerError', $t->getMessage())
263 );
264 }
265 $this->http->sendResponse();
266 $this->http->close();
267 }
268}
Stream factory which enables the user to create streams without the knowledge of the concrete class.
Definition: Streams.php:32
static ofString(string $string)
Creates a new stream with an initial value.
Definition: Streams.php:41
Class Services.
Definition: Services.php:38
ILIAS ResourceStorage Services $irss
static http()
Fetches the global http state from ILIAS.
global $DIC
Definition: shib_login.php:26
$message
Definition: xapiexit.php:31
$response
Definition: xapitoken.php:93