ILIAS  release_10 Revision v10.1-43-ga1241a92c2f
RequestHandler.php
Go to the documentation of this file.
1 <?php
2 
19 declare(strict_types=1);
20 
22 
26 
30 final class RequestHandler
31 {
32  public const WOPI_BASE_URL = '/wopi/index.php/';
33  public const NAMESPACE_FILES = 'files';
34 
35  // WOPI Header
36  private const HEADER_X_REQUEST_ID = 'X-Request-ID';
37  private const HEADER_AUTHORIZATION = 'Authorization';
38  private const HEADER_AUTHORIZATION_BEARER = 'Bearer';
39  public const HEADER_X_WOPI_OVERRIDE = 'X-WOPI-Override';
40  public const HEADER_X_WOPI_LOCK = 'X-WOPI-Lock';
41  public const HEADER_X_WOPI_FILE_CONVERSION = 'X-WOPI-FileConversion';
42 
43  private \ILIAS\HTTP\Services $http;
44  private \ILIAS\ResourceStorage\Services $irss;
46  private ?int $token_user_id = null;
47  private ?string $token_resource_id = null;
49  private int $saving_interval = 0;
50  private bool $editable = false;
51 
52  public function __construct()
53  {
54  global $DIC;
55  $this->http = $DIC->http();
56  $this->data_signer = $DIC['file_delivery.data_signer'];
57  $this->irss = $DIC->resourceStorage();
58  $this->saving_interval = (int) $DIC->settings()->get('saving_interval');
59  }
60 
61  protected function checkAuth(): void
62  {
63  $auth = $this->http->request()->getHeader(self::HEADER_AUTHORIZATION)[0] ?? '';
64  // spit and check bearer token
65  $bearer = explode(' ', $auth);
66  if ($auth !== '' && ($bearer[0] ?? '') !== self::HEADER_AUTHORIZATION_BEARER) {
67  throw new \InvalidArgumentException();
68  }
69  $bearer_token = $bearer[1] ?? '';
70  if ($auth === '' && $bearer_token === '') {
71  // we try to get the token from GET
72  $bearer_token = $this->http->request()->getQueryParams()['access_token'] ?? '';
73  }
74  if ($bearer_token === '') {
75  throw new \InvalidArgumentException('No access token provided');
76  }
77  if (($token_data = $this->data_signer->verify($bearer_token, 'wopi')) === null) {
78  throw new \InvalidArgumentException('Token verification failed');
79  }
80 
81  $this->token_user_id = (int) ($token_data['user_id'] ?? 0);
82  $this->token_resource_id = (string) ($token_data['resource_id'] ?? '');
83  $this->editable = (bool) ($token_data['editable'] ?? '');
84  $stakeholder = $token_data['stakeholder'] ?? null;
85  if ($stakeholder !== null) {
86  try {
87  $this->stakeholder = new WOPIStakeholderWrapper();
88  $this->stakeholder->init($stakeholder, $this->token_user_id);
89  } catch (\Throwable $t) {
90  $this->stakeholder = new WOPIUnknownStakeholder($this->token_user_id);
91  }
92  }
93  }
94 
98  public function handleRequest(): void
99  {
100  try {
101  $this->checkAuth();
102 
103  $uri = $this->http->request()->getUri()->getPath();
104  $request = substr($uri, strpos($uri, self::WOPI_BASE_URL) + strlen(self::WOPI_BASE_URL));
105  $request = explode('/', $request);
106  $method = $this->http->request()->getMethod();
107 
108  $resource_id = $request[1];
109  $action = $request[2] ?? '';
110 
111  // check resource_id
112  if ($this->token_resource_id !== $resource_id) {
113  $this->http->close();
114  }
115 
116  $resource_id = $this->irss->manage()->find($resource_id);
117  if (!$resource_id instanceof \ILIAS\ResourceStorage\Identification\ResourceIdentification) {
118  $this->http->close();
119  }
120  $resource = $this->irss->manage()->getResource($resource_id);
121  $current_revision = $resource->getCurrentRevisionIncludingDraft();
122 
123  $method_override = $this->http->request()->getHeader(self::HEADER_X_WOPI_OVERRIDE)[0] ?? null;
124  $is_file_convertion = (bool) ($this->http->request()->getHeader(
125  self::HEADER_X_WOPI_FILE_CONVERSION
126  )[0] ?? false);
127 
128  // GET
129  switch ($method_override ?? $method) {
130  case 'GET':
131  switch ($action) {
132  case '':
133  // CheckFileInfo
135  $current_revision,
136  $this->token_user_id,
137  $this->editable
138  );
139  $this->http->saveResponse(
140  $this->http->response()->withBody(
141  Streams::ofString(json_encode($response, JSON_THROW_ON_ERROR))
142  )
143  );
144 
145  break;
146  case 'contents':
147  // GetFile
148  $stream = $this->irss->consume()->stream($resource_id)->setRevisionNumber(
149  $current_revision->getVersionNumber()
150  )->getStream();
151  $this->http->saveResponse(
152  $this->http->response()->withBody($stream)
153  );
154 
155  break;
156  }
157  break;
158  case 'PUT_RELATIVE':
159  if (!$is_file_convertion) {
160  throw new \InvalidArgumentException();
161  }
162  // no break
163  case 'PUT':
164  switch ($action) {
165  case 'contents':
166  // PutFile
167  $body_stream = $this->http->request()->getBody();
168  $body = $body_stream->getContents();
169  $file_stream = Streams::ofString($body);
170 
171  $draft = true;
172 
173  if ($this->saving_interval > 0) {
174  $latest_revision = $resource->getCurrentRevision();
175  $creation_time = $latest_revision->getInformation()->getCreationDate()->getTimestamp();
176  $current_time = time();
177  $time_diff = $current_time - $creation_time;
178  if ($time_diff > $this->saving_interval) {
179  $this->irss->manage()->publish($resource_id);
180  }
181  }
182 
183  $new_revision = $this->irss->manage()->appendNewRevisionFromStream(
184  $resource_id,
185  $file_stream,
186  $this->stakeholder,
187  $current_revision->getTitle(),
188  $draft
189  );
190 
191  // CheckFileInfo
193  $new_revision,
194  $this->token_user_id
195  );
196  $this->http->saveResponse(
197  $this->http->response()->withBody(
198  Streams::ofString(json_encode($response, JSON_THROW_ON_ERROR))
199  )
200  );
201 
202  break;
203  case '': // if we want to create new files in the future, this will be a separate case
204  break;
205  }
206  break;
207  case 'POST':
208  switch ($action) {
209  case 'contents':
210  $this->http->saveResponse(
211  $this->http->response()->withBody(
213  )
214  );
215 
216  break;
217  case '':
218  // Lock
219  $lock = $this->http->request()->getHeader(self::HEADER_X_WOPI_LOCK)[0] ?? null;
220  $this->http->saveResponse(
221  $this->http->response()->withBody(
223  )
224  );
225  }
226  break;
227  }
228  } catch (\Throwable $t) {
229  $message = $t->getMessage();
230  // append simple stacktrace
231  $trace = array_map(
232  static function ($trace): string {
233  return $trace['file'] . ':' . $trace['line'];
234  },
235  $t->getTrace()
236  );
237 
238  $message .= "\n" . implode("\n", $trace);
239 
240  $this->http->saveResponse(
241  $this->http->response()
242  ->withBody(Streams::ofString($message))
243  ->withStatus(500)
244  ->withHeader('X-WOPI-ServerError', $t->getMessage())
245  );
246  }
247  $this->http->sendResponse();
248  $this->http->close();
249  }
250 }
Interface Observer Contains several chained tasks and infos about them.
$response
Definition: xapitoken.php:90
static http()
Fetches the global http state from ILIAS.
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
global $DIC
Definition: shib_login.php:25
static ofString(string $string)
Creates a new stream with an initial value.
Definition: Streams.php:41
ILIAS ResourceStorage Services $irss