Completed
Push — master ( c8a12a...a1709f )
by
unknown
41:04
created
lib/private/OCS/ApiHelper.php 1 patch
Indentation   +50 added lines, -50 removed lines patch added patch discarded remove patch
@@ -18,64 +18,64 @@
 block discarded – undo
18 18
 use OCP\Server;
19 19
 
20 20
 class ApiHelper {
21
-	/**
22
-	 * Respond to a call
23
-	 * @psalm-taint-escape html
24
-	 * @param int $overrideHttpStatusCode force the HTTP status code, only used for the special case of maintenance mode which return 503 even for v1
25
-	 */
26
-	public static function respond(int $statusCode, string $statusMessage, array $headers = [], ?int $overrideHttpStatusCode = null): void {
27
-		$request = Server::get(IRequest::class);
28
-		$format = $request->getFormat() ?? 'xml';
21
+    /**
22
+     * Respond to a call
23
+     * @psalm-taint-escape html
24
+     * @param int $overrideHttpStatusCode force the HTTP status code, only used for the special case of maintenance mode which return 503 even for v1
25
+     */
26
+    public static function respond(int $statusCode, string $statusMessage, array $headers = [], ?int $overrideHttpStatusCode = null): void {
27
+        $request = Server::get(IRequest::class);
28
+        $format = $request->getFormat() ?? 'xml';
29 29
 
30
-		if (self::isV2($request)) {
31
-			$response = new V2Response(new DataResponse([], $statusCode, $headers), $format, $statusMessage);
32
-		} else {
33
-			$response = new V1Response(new DataResponse([], $statusCode, $headers), $format, $statusMessage);
34
-		}
30
+        if (self::isV2($request)) {
31
+            $response = new V2Response(new DataResponse([], $statusCode, $headers), $format, $statusMessage);
32
+        } else {
33
+            $response = new V1Response(new DataResponse([], $statusCode, $headers), $format, $statusMessage);
34
+        }
35 35
 
36
-		// Send 401 headers if unauthorised
37
-		if ($response->getOCSStatus() === OCSController::RESPOND_UNAUTHORISED) {
38
-			// If request comes from JS return dummy auth request
39
-			if ($request->getHeader('X-Requested-With') === 'XMLHttpRequest') {
40
-				header('WWW-Authenticate: DummyBasic realm="Authorisation Required"');
41
-			} else {
42
-				header('WWW-Authenticate: Basic realm="Authorisation Required"');
43
-			}
44
-			http_response_code(401);
45
-		}
36
+        // Send 401 headers if unauthorised
37
+        if ($response->getOCSStatus() === OCSController::RESPOND_UNAUTHORISED) {
38
+            // If request comes from JS return dummy auth request
39
+            if ($request->getHeader('X-Requested-With') === 'XMLHttpRequest') {
40
+                header('WWW-Authenticate: DummyBasic realm="Authorisation Required"');
41
+            } else {
42
+                header('WWW-Authenticate: Basic realm="Authorisation Required"');
43
+            }
44
+            http_response_code(401);
45
+        }
46 46
 
47
-		foreach ($response->getHeaders() as $name => $value) {
48
-			header($name . ': ' . $value);
49
-		}
47
+        foreach ($response->getHeaders() as $name => $value) {
48
+            header($name . ': ' . $value);
49
+        }
50 50
 
51
-		http_response_code($overrideHttpStatusCode ?? $response->getStatus());
51
+        http_response_code($overrideHttpStatusCode ?? $response->getStatus());
52 52
 
53
-		self::setContentType($format);
54
-		$body = $response->render();
55
-		echo $body;
56
-	}
53
+        self::setContentType($format);
54
+        $body = $response->render();
55
+        echo $body;
56
+    }
57 57
 
58
-	/**
59
-	 * Based on the requested format the response content type is set
60
-	 */
61
-	public static function setContentType(?string $format = null): void {
62
-		$format ??= Server::get(IRequest::class)->getFormat() ?? 'xml';
63
-		if ($format === 'xml') {
64
-			header('Content-type: text/xml; charset=UTF-8');
65
-			return;
66
-		}
58
+    /**
59
+     * Based on the requested format the response content type is set
60
+     */
61
+    public static function setContentType(?string $format = null): void {
62
+        $format ??= Server::get(IRequest::class)->getFormat() ?? 'xml';
63
+        if ($format === 'xml') {
64
+            header('Content-type: text/xml; charset=UTF-8');
65
+            return;
66
+        }
67 67
 
68
-		if ($format === 'json') {
69
-			header('Content-Type: application/json; charset=utf-8');
70
-			return;
71
-		}
68
+        if ($format === 'json') {
69
+            header('Content-Type: application/json; charset=utf-8');
70
+            return;
71
+        }
72 72
 
73
-		header('Content-Type: application/octet-stream; charset=utf-8');
74
-	}
73
+        header('Content-Type: application/octet-stream; charset=utf-8');
74
+    }
75 75
 
76
-	protected static function isV2(IRequest $request): bool {
77
-		$script = $request->getScriptName();
76
+    protected static function isV2(IRequest $request): bool {
77
+        $script = $request->getScriptName();
78 78
 
79
-		return str_ends_with($script, '/ocs/v2.php');
80
-	}
79
+        return str_ends_with($script, '/ocs/v2.php');
80
+    }
81 81
 }
Please login to merge, or discard this patch.
lib/private/AppFramework/Http/Request.php 1 patch
Indentation   +866 added lines, -866 removed lines patch added patch discarded remove patch
@@ -30,870 +30,870 @@
 block discarded – undo
30 30
  * @template-implements \ArrayAccess<string,mixed>
31 31
  */
32 32
 class Request implements \ArrayAccess, \Countable, IRequest {
33
-	public const USER_AGENT_IE = '/(MSIE)|(Trident)/';
34
-	// Microsoft Edge User Agent from https://msdn.microsoft.com/en-us/library/hh869301(v=vs.85).aspx
35
-	public const USER_AGENT_MS_EDGE = '/^Mozilla\/5\.0 \([^)]+\) AppleWebKit\/[0-9.]+ \(KHTML, like Gecko\) Chrome\/[0-9.]+ (Mobile Safari|Safari)\/[0-9.]+ Edge?\/[0-9.]+$/';
36
-	// Firefox User Agent from https://developer.mozilla.org/en-US/docs/Web/HTTP/Gecko_user_agent_string_reference
37
-	public const USER_AGENT_FIREFOX = '/^Mozilla\/5\.0 \([^)]+\) Gecko\/[0-9.]+ Firefox\/[0-9.]+$/';
38
-	// Chrome User Agent from https://developer.chrome.com/multidevice/user-agent
39
-	public const USER_AGENT_CHROME = '/^Mozilla\/5\.0 \([^)]+\) AppleWebKit\/[0-9.]+ \(KHTML, like Gecko\)( Ubuntu Chromium\/[0-9.]+|) Chrome\/[0-9.]+ (Mobile Safari|Safari)\/[0-9.]+( (Vivaldi|Brave|OPR)\/[0-9.]+|)$/';
40
-	// Safari User Agent from http://www.useragentstring.com/pages/Safari/
41
-	public const USER_AGENT_SAFARI = '/^Mozilla\/5\.0 \([^)]+\) AppleWebKit\/[0-9.]+ \(KHTML, like Gecko\) Version\/[0-9.]+ Safari\/[0-9.A-Z]+$/';
42
-	public const USER_AGENT_SAFARI_MOBILE = '/^Mozilla\/5\.0 \([^)]+\) AppleWebKit\/[0-9.]+ \(KHTML, like Gecko\) Version\/[0-9.]+ (Mobile\/[0-9.A-Z]+) Safari\/[0-9.A-Z]+$/';
43
-	// Android Chrome user agent: https://developers.google.com/chrome/mobile/docs/user-agent
44
-	public const USER_AGENT_ANDROID_MOBILE_CHROME = '#Android.*Chrome/[.0-9]*#';
45
-	public const USER_AGENT_FREEBOX = '#^Mozilla/5\.0$#';
46
-	public const REGEX_LOCALHOST = '/^(127\.0\.0\.1|localhost|\[::1\])$/';
47
-
48
-	protected string $inputStream;
49
-	private bool $isPutStreamContentAlreadySent = false;
50
-	protected array $items = [];
51
-	protected array $allowedKeys = [
52
-		'get',
53
-		'post',
54
-		'files',
55
-		'server',
56
-		'env',
57
-		'cookies',
58
-		'urlParams',
59
-		'parameters',
60
-		'method',
61
-		'requesttoken',
62
-	];
63
-	protected IRequestId $requestId;
64
-	protected IConfig $config;
65
-	protected ?CsrfTokenManager $csrfTokenManager;
66
-
67
-	protected bool $contentDecoded = false;
68
-	private ?\JsonException $decodingException = null;
69
-
70
-	/**
71
-	 * @param array $vars An associative array with the following optional values:
72
-	 *                    - array 'urlParams' the parameters which were matched from the URL
73
-	 *                    - array 'get' the $_GET array
74
-	 *                    - array|string 'post' the $_POST array or JSON string
75
-	 *                    - array 'files' the $_FILES array
76
-	 *                    - array 'server' the $_SERVER array
77
-	 *                    - array 'env' the $_ENV array
78
-	 *                    - array 'cookies' the $_COOKIE array
79
-	 *                    - string 'method' the request method (GET, POST etc)
80
-	 *                    - string|false 'requesttoken' the requesttoken or false when not available
81
-	 * @param IRequestId $requestId
82
-	 * @param IConfig $config
83
-	 * @param CsrfTokenManager|null $csrfTokenManager
84
-	 * @param string $stream
85
-	 * @see https://www.php.net/manual/en/reserved.variables.php
86
-	 */
87
-	public function __construct(array $vars,
88
-		IRequestId $requestId,
89
-		IConfig $config,
90
-		?CsrfTokenManager $csrfTokenManager = null,
91
-		string $stream = 'php://input') {
92
-		$this->inputStream = $stream;
93
-		$this->items['params'] = [];
94
-		$this->requestId = $requestId;
95
-		$this->config = $config;
96
-		$this->csrfTokenManager = $csrfTokenManager;
97
-
98
-		if (!array_key_exists('method', $vars)) {
99
-			$vars['method'] = 'GET';
100
-		}
101
-
102
-		foreach ($this->allowedKeys as $name) {
103
-			$this->items[$name] = $vars[$name] ?? [];
104
-		}
105
-
106
-		$this->items['parameters'] = array_merge(
107
-			$this->items['get'],
108
-			$this->items['post'],
109
-			$this->items['urlParams'],
110
-			$this->items['params']
111
-		);
112
-	}
113
-	/**
114
-	 * @param array $parameters
115
-	 */
116
-	public function setUrlParameters(array $parameters) {
117
-		$this->items['urlParams'] = $parameters;
118
-		$this->items['parameters'] = array_merge(
119
-			$this->items['parameters'],
120
-			$this->items['urlParams']
121
-		);
122
-	}
123
-
124
-	/**
125
-	 * Countable method
126
-	 * @return int
127
-	 */
128
-	public function count(): int {
129
-		return \count($this->items['parameters']);
130
-	}
131
-
132
-	/**
133
-	 * ArrayAccess methods
134
-	 *
135
-	 * Gives access to the combined GET, POST and urlParams arrays
136
-	 *
137
-	 * Examples:
138
-	 *
139
-	 * $var = $request['myvar'];
140
-	 *
141
-	 * or
142
-	 *
143
-	 * if(!isset($request['myvar']) {
144
-	 * 	// Do something
145
-	 * }
146
-	 *
147
-	 * $request['myvar'] = 'something'; // This throws an exception.
148
-	 *
149
-	 * @param string $offset The key to lookup
150
-	 * @return boolean
151
-	 */
152
-	public function offsetExists($offset): bool {
153
-		return isset($this->items['parameters'][$offset]);
154
-	}
155
-
156
-	/**
157
-	 * @see offsetExists
158
-	 * @param string $offset
159
-	 * @return mixed
160
-	 */
161
-	#[\ReturnTypeWillChange]
162
-	public function offsetGet($offset) {
163
-		return $this->items['parameters'][$offset] ?? null;
164
-	}
165
-
166
-	/**
167
-	 * @see offsetExists
168
-	 * @param string $offset
169
-	 * @param mixed $value
170
-	 */
171
-	public function offsetSet($offset, $value): void {
172
-		throw new \RuntimeException('You cannot change the contents of the request object');
173
-	}
174
-
175
-	/**
176
-	 * @see offsetExists
177
-	 * @param string $offset
178
-	 */
179
-	public function offsetUnset($offset): void {
180
-		throw new \RuntimeException('You cannot change the contents of the request object');
181
-	}
182
-
183
-	/**
184
-	 * Magic property accessors
185
-	 * @param string $name
186
-	 * @param mixed $value
187
-	 */
188
-	public function __set($name, $value) {
189
-		throw new \RuntimeException('You cannot change the contents of the request object');
190
-	}
191
-
192
-	/**
193
-	 * Access request variables by method and name.
194
-	 * Examples:
195
-	 *
196
-	 * $request->post['myvar']; // Only look for POST variables
197
-	 * $request->myvar; or $request->{'myvar'}; or $request->{$myvar}
198
-	 * Looks in the combined GET, POST and urlParams array.
199
-	 *
200
-	 * If you access e.g. ->post but the current HTTP request method
201
-	 * is GET a \LogicException will be thrown.
202
-	 *
203
-	 * @param string $name The key to look for.
204
-	 * @throws \LogicException
205
-	 * @return mixed|null
206
-	 */
207
-	public function __get($name) {
208
-		switch ($name) {
209
-			case 'put':
210
-			case 'patch':
211
-			case 'get':
212
-			case 'post':
213
-				if ($this->method !== strtoupper($name)) {
214
-					throw new \LogicException(sprintf('%s cannot be accessed in a %s request.', $name, $this->method));
215
-				}
216
-				return $this->getContent();
217
-			case 'files':
218
-			case 'server':
219
-			case 'env':
220
-			case 'cookies':
221
-			case 'urlParams':
222
-			case 'method':
223
-				return $this->items[$name] ?? null;
224
-			case 'parameters':
225
-			case 'params':
226
-				if ($this->isPutStreamContent()) {
227
-					return $this->items['parameters'];
228
-				}
229
-				return $this->getContent();
230
-			default:
231
-				return isset($this[$name])
232
-					? $this[$name]
233
-					: null;
234
-		}
235
-	}
236
-
237
-	/**
238
-	 * @param string $name
239
-	 * @return bool
240
-	 */
241
-	public function __isset($name) {
242
-		if (\in_array($name, $this->allowedKeys, true)) {
243
-			return true;
244
-		}
245
-		return isset($this->items['parameters'][$name]);
246
-	}
247
-
248
-	/**
249
-	 * @param string $id
250
-	 */
251
-	public function __unset($id) {
252
-		throw new \RuntimeException('You cannot change the contents of the request object');
253
-	}
254
-
255
-	/**
256
-	 * Returns the value for a specific http header.
257
-	 *
258
-	 * This method returns an empty string if the header did not exist.
259
-	 *
260
-	 * @param string $name
261
-	 * @return string
262
-	 */
263
-	public function getHeader(string $name): string {
264
-		$name = strtoupper(str_replace('-', '_', $name));
265
-		if (isset($this->server['HTTP_' . $name])) {
266
-			return $this->server['HTTP_' . $name];
267
-		}
268
-
269
-		// There's a few headers that seem to end up in the top-level
270
-		// server array.
271
-		switch ($name) {
272
-			case 'CONTENT_TYPE':
273
-			case 'CONTENT_LENGTH':
274
-			case 'REMOTE_ADDR':
275
-				if (isset($this->server[$name])) {
276
-					return $this->server[$name];
277
-				}
278
-				break;
279
-		}
280
-
281
-		return '';
282
-	}
283
-
284
-	/**
285
-	 * Lets you access post and get parameters by the index
286
-	 * In case of json requests the encoded json body is accessed
287
-	 *
288
-	 * @param string $key the key which you want to access in the URL Parameter
289
-	 *                    placeholder, $_POST or $_GET array.
290
-	 *                    The priority how they're returned is the following:
291
-	 *                    1. URL parameters
292
-	 *                    2. POST parameters
293
-	 *                    3. GET parameters
294
-	 * @param mixed $default If the key is not found, this value will be returned
295
-	 * @return mixed the content of the array
296
-	 */
297
-	public function getParam(string $key, $default = null) {
298
-		return isset($this->parameters[$key])
299
-			? $this->parameters[$key]
300
-			: $default;
301
-	}
302
-
303
-	/**
304
-	 * Returns all params that were received, be it from the request
305
-	 * (as GET or POST) or through the URL by the route
306
-	 * @return array the array with all parameters
307
-	 */
308
-	public function getParams(): array {
309
-		return is_array($this->parameters) ? $this->parameters : [];
310
-	}
311
-
312
-	/**
313
-	 * Returns the method of the request
314
-	 * @return string the method of the request (POST, GET, etc)
315
-	 */
316
-	public function getMethod(): string {
317
-		return $this->method;
318
-	}
319
-
320
-	/**
321
-	 * Shortcut for accessing an uploaded file through the $_FILES array
322
-	 * @param string $key the key that will be taken from the $_FILES array
323
-	 * @return array the file in the $_FILES element
324
-	 */
325
-	public function getUploadedFile(string $key) {
326
-		return isset($this->files[$key]) ? $this->files[$key] : null;
327
-	}
328
-
329
-	/**
330
-	 * Shortcut for getting env variables
331
-	 * @param string $key the key that will be taken from the $_ENV array
332
-	 * @return array the value in the $_ENV element
333
-	 */
334
-	public function getEnv(string $key) {
335
-		return isset($this->env[$key]) ? $this->env[$key] : null;
336
-	}
337
-
338
-	/**
339
-	 * Shortcut for getting cookie variables
340
-	 * @param string $key the key that will be taken from the $_COOKIE array
341
-	 * @return string the value in the $_COOKIE element
342
-	 */
343
-	public function getCookie(string $key) {
344
-		return isset($this->cookies[$key]) ? $this->cookies[$key] : null;
345
-	}
346
-
347
-	/**
348
-	 * Returns the request body content.
349
-	 *
350
-	 * If the HTTP request method is PUT and the body
351
-	 * not application/x-www-form-urlencoded or application/json a stream
352
-	 * resource is returned, otherwise an array.
353
-	 *
354
-	 * @return array|string|resource The request body content or a resource to read the body stream.
355
-	 *
356
-	 * @throws \LogicException
357
-	 */
358
-	protected function getContent() {
359
-		// If the content can't be parsed into an array then return a stream resource.
360
-		if ($this->isPutStreamContent()) {
361
-			if ($this->isPutStreamContentAlreadySent) {
362
-				throw new \LogicException(
363
-					'"put" can only be accessed once if not '
364
-					. 'application/x-www-form-urlencoded or application/json.'
365
-				);
366
-			}
367
-			$this->isPutStreamContentAlreadySent = true;
368
-			return fopen($this->inputStream, 'rb');
369
-		} else {
370
-			$this->decodeContent();
371
-			return $this->items['parameters'];
372
-		}
373
-	}
374
-
375
-	private function isPutStreamContent(): bool {
376
-		return $this->method === 'PUT'
377
-			&& $this->getHeader('Content-Length') !== '0'
378
-			&& $this->getHeader('Content-Length') !== ''
379
-			&& !str_contains($this->getHeader('Content-Type'), 'application/x-www-form-urlencoded')
380
-			&& !str_contains($this->getHeader('Content-Type'), 'application/json');
381
-	}
382
-
383
-	/**
384
-	 * Attempt to decode the content and populate parameters
385
-	 */
386
-	protected function decodeContent() {
387
-		if ($this->contentDecoded) {
388
-			return;
389
-		}
390
-		$params = [];
391
-
392
-		// 'application/json' and other JSON-related content types must be decoded manually.
393
-		if (preg_match(self::JSON_CONTENT_TYPE_REGEX, $this->getHeader('Content-Type')) === 1) {
394
-			$content = file_get_contents($this->inputStream);
395
-			if ($content !== '') {
396
-				try {
397
-					$params = json_decode($content, true, flags:JSON_THROW_ON_ERROR);
398
-				} catch (\JsonException $e) {
399
-					$this->decodingException = $e;
400
-				}
401
-			}
402
-			if (\is_array($params) && \count($params) > 0) {
403
-				$this->items['params'] = $params;
404
-				if ($this->method === 'POST') {
405
-					$this->items['post'] = $params;
406
-				}
407
-			}
408
-			// Handle application/x-www-form-urlencoded for methods other than GET
409
-			// or post correctly
410
-		} elseif ($this->method !== 'GET'
411
-				&& $this->method !== 'POST'
412
-				&& str_contains($this->getHeader('Content-Type'), 'application/x-www-form-urlencoded')) {
413
-			parse_str(file_get_contents($this->inputStream), $params);
414
-			if (\is_array($params)) {
415
-				$this->items['params'] = $params;
416
-			}
417
-		}
418
-
419
-		if (\is_array($params)) {
420
-			$this->items['parameters'] = array_merge($this->items['parameters'], $params);
421
-		}
422
-		$this->contentDecoded = true;
423
-	}
424
-
425
-	public function throwDecodingExceptionIfAny(): void {
426
-		if ($this->decodingException !== null) {
427
-			throw $this->decodingException;
428
-		}
429
-	}
430
-
431
-
432
-	/**
433
-	 * Checks if the CSRF check was correct
434
-	 * @return bool true if CSRF check passed
435
-	 */
436
-	public function passesCSRFCheck(): bool {
437
-		if ($this->csrfTokenManager === null) {
438
-			return false;
439
-		}
440
-
441
-		if (!$this->passesStrictCookieCheck()) {
442
-			return false;
443
-		}
444
-
445
-		if ($this->getHeader('OCS-APIRequest') !== '') {
446
-			return true;
447
-		}
448
-
449
-		if (isset($this->items['get']['requesttoken'])) {
450
-			$token = $this->items['get']['requesttoken'];
451
-		} elseif (isset($this->items['post']['requesttoken'])) {
452
-			$token = $this->items['post']['requesttoken'];
453
-		} elseif (isset($this->items['server']['HTTP_REQUESTTOKEN'])) {
454
-			$token = $this->items['server']['HTTP_REQUESTTOKEN'];
455
-		} else {
456
-			//no token found.
457
-			return false;
458
-		}
459
-		$token = new CsrfToken($token);
460
-
461
-		return $this->csrfTokenManager->isTokenValid($token);
462
-	}
463
-
464
-	/**
465
-	 * Whether the cookie checks are required
466
-	 *
467
-	 * @return bool
468
-	 */
469
-	private function cookieCheckRequired(): bool {
470
-		if ($this->getHeader('OCS-APIREQUEST')) {
471
-			return false;
472
-		}
473
-		if ($this->getCookie(session_name()) === null && $this->getCookie('nc_token') === null) {
474
-			return false;
475
-		}
476
-
477
-		return true;
478
-	}
479
-
480
-	/**
481
-	 * Wrapper around session_get_cookie_params
482
-	 *
483
-	 * @return array
484
-	 */
485
-	public function getCookieParams(): array {
486
-		return session_get_cookie_params();
487
-	}
488
-
489
-	/**
490
-	 * Appends the __Host- prefix to the cookie if applicable
491
-	 *
492
-	 * @param string $name
493
-	 * @return string
494
-	 */
495
-	protected function getProtectedCookieName(string $name): string {
496
-		$cookieParams = $this->getCookieParams();
497
-		$prefix = '';
498
-		if ($cookieParams['secure'] === true && $cookieParams['path'] === '/') {
499
-			$prefix = '__Host-';
500
-		}
501
-
502
-		return $prefix . $name;
503
-	}
504
-
505
-	/**
506
-	 * Checks if the strict cookie has been sent with the request if the request
507
-	 * is including any cookies.
508
-	 *
509
-	 * @return bool
510
-	 * @since 9.1.0
511
-	 */
512
-	public function passesStrictCookieCheck(): bool {
513
-		if (!$this->cookieCheckRequired()) {
514
-			return true;
515
-		}
516
-
517
-		$cookieName = $this->getProtectedCookieName('nc_sameSiteCookiestrict');
518
-		if ($this->getCookie($cookieName) === 'true'
519
-			&& $this->passesLaxCookieCheck()) {
520
-			return true;
521
-		}
522
-		return false;
523
-	}
524
-
525
-	/**
526
-	 * Checks if the lax cookie has been sent with the request if the request
527
-	 * is including any cookies.
528
-	 *
529
-	 * @return bool
530
-	 * @since 9.1.0
531
-	 */
532
-	public function passesLaxCookieCheck(): bool {
533
-		if (!$this->cookieCheckRequired()) {
534
-			return true;
535
-		}
536
-
537
-		$cookieName = $this->getProtectedCookieName('nc_sameSiteCookielax');
538
-		if ($this->getCookie($cookieName) === 'true') {
539
-			return true;
540
-		}
541
-		return false;
542
-	}
543
-
544
-
545
-	/**
546
-	 * Returns an ID for the request, value is not guaranteed to be unique and is mostly meant for logging
547
-	 * If `mod_unique_id` is installed this value will be taken.
548
-	 * @return string
549
-	 */
550
-	public function getId(): string {
551
-		return $this->requestId->getId();
552
-	}
553
-
554
-	/**
555
-	 * Checks if given $remoteAddress matches any entry in the given array $trustedProxies.
556
-	 * For details regarding what "match" means, refer to `matchesTrustedProxy`.
557
-	 * @return boolean true if $remoteAddress matches any entry in $trustedProxies, false otherwise
558
-	 */
559
-	protected function isTrustedProxy($trustedProxies, $remoteAddress) {
560
-		try {
561
-			return IpUtils::checkIp($remoteAddress, $trustedProxies);
562
-		} catch (\Throwable) {
563
-			// We can not log to our log here as the logger is using `getRemoteAddress` which uses the function, so we would have a cyclic dependency
564
-			// Reaching this line means `trustedProxies` is in invalid format.
565
-			error_log('Nextcloud trustedProxies has malformed entries');
566
-			return false;
567
-		}
568
-	}
569
-
570
-	/**
571
-	 * Returns the remote address, if the connection came from a trusted proxy
572
-	 * and `forwarded_for_headers` has been configured then the IP address
573
-	 * specified in this header will be returned instead.
574
-	 * Do always use this instead of $_SERVER['REMOTE_ADDR']
575
-	 * @return string IP address
576
-	 */
577
-	public function getRemoteAddress(): string {
578
-		$remoteAddress = isset($this->server['REMOTE_ADDR']) ? $this->server['REMOTE_ADDR'] : '';
579
-		$trustedProxies = $this->config->getSystemValue('trusted_proxies', []);
580
-
581
-		if (\is_array($trustedProxies) && $this->isTrustedProxy($trustedProxies, $remoteAddress)) {
582
-			$forwardedForHeaders = $this->config->getSystemValue('forwarded_for_headers', [
583
-				'HTTP_X_FORWARDED_FOR'
584
-				// only have one default, so we cannot ship an insecure product out of the box
585
-			]);
586
-
587
-			// Read the x-forwarded-for headers and values in reverse order as per
588
-			// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-For#selecting_an_ip_address
589
-			foreach (array_reverse($forwardedForHeaders) as $header) {
590
-				if (isset($this->server[$header])) {
591
-					foreach (array_reverse(explode(',', $this->server[$header])) as $IP) {
592
-						$IP = trim($IP);
593
-						$colons = substr_count($IP, ':');
594
-						if ($colons > 1) {
595
-							// Extract IP from string with brackets and optional port
596
-							if (preg_match('/^\[(.+?)\](?::\d+)?$/', $IP, $matches) && isset($matches[1])) {
597
-								$IP = $matches[1];
598
-							}
599
-						} elseif ($colons === 1) {
600
-							// IPv4 with port
601
-							$IP = substr($IP, 0, strpos($IP, ':'));
602
-						}
603
-
604
-						if ($this->isTrustedProxy($trustedProxies, $IP)) {
605
-							continue;
606
-						}
607
-
608
-						if (filter_var($IP, FILTER_VALIDATE_IP) !== false) {
609
-							return $IP;
610
-						}
611
-					}
612
-				}
613
-			}
614
-		}
615
-
616
-		return $remoteAddress;
617
-	}
618
-
619
-	/**
620
-	 * Check overwrite condition
621
-	 * @return bool
622
-	 */
623
-	private function isOverwriteCondition(): bool {
624
-		$regex = '/' . $this->config->getSystemValueString('overwritecondaddr', '') . '/';
625
-		$remoteAddr = isset($this->server['REMOTE_ADDR']) ? $this->server['REMOTE_ADDR'] : '';
626
-		return $regex === '//' || preg_match($regex, $remoteAddr) === 1;
627
-	}
628
-
629
-	/**
630
-	 * Returns the server protocol. It respects one or more reverse proxies servers
631
-	 * and load balancers. Precedence:
632
-	 *   1. `overwriteprotocol` config value
633
-	 *   2. `X-Forwarded-Proto` header value
634
-	 *   3. $_SERVER['HTTPS'] value
635
-	 * If an invalid protocol is provided, defaults to http, continues, but logs as an error.
636
-	 *
637
-	 * @return string Server protocol (http or https)
638
-	 */
639
-	public function getServerProtocol(): string {
640
-		$proto = 'http';
641
-
642
-		if ($this->config->getSystemValueString('overwriteprotocol') !== ''
643
-			&& $this->isOverwriteCondition()
644
-		) {
645
-			$proto = strtolower($this->config->getSystemValueString('overwriteprotocol'));
646
-		} elseif ($this->fromTrustedProxy()
647
-			&& isset($this->server['HTTP_X_FORWARDED_PROTO'])
648
-		) {
649
-			if (str_contains($this->server['HTTP_X_FORWARDED_PROTO'], ',')) {
650
-				$parts = explode(',', $this->server['HTTP_X_FORWARDED_PROTO']);
651
-				$proto = strtolower(trim($parts[0]));
652
-			} else {
653
-				$proto = strtolower($this->server['HTTP_X_FORWARDED_PROTO']);
654
-			}
655
-		} elseif (!empty($this->server['HTTPS'])
656
-			&& $this->server['HTTPS'] !== 'off'
657
-		) {
658
-			$proto = 'https';
659
-		}
660
-
661
-		if ($proto !== 'https' && $proto !== 'http') {
662
-			// log unrecognized value so admin has a chance to fix it
663
-			\OCP\Server::get(LoggerInterface::class)->critical(
664
-				'Server protocol is malformed [falling back to http] (check overwriteprotocol and/or X-Forwarded-Proto to remedy): ' . $proto,
665
-				['app' => 'core']
666
-			);
667
-		}
668
-
669
-		// default to http if provided an invalid value
670
-		return $proto === 'https' ? 'https' : 'http';
671
-	}
672
-
673
-	/**
674
-	 * Returns the used HTTP protocol.
675
-	 *
676
-	 * @return string HTTP protocol. HTTP/2, HTTP/1.1 or HTTP/1.0.
677
-	 */
678
-	public function getHttpProtocol(): string {
679
-		$claimedProtocol = $this->server['SERVER_PROTOCOL'];
680
-
681
-		if (\is_string($claimedProtocol)) {
682
-			$claimedProtocol = strtoupper($claimedProtocol);
683
-		}
684
-
685
-		$validProtocols = [
686
-			'HTTP/1.0',
687
-			'HTTP/1.1',
688
-			'HTTP/2',
689
-		];
690
-
691
-		if (\in_array($claimedProtocol, $validProtocols, true)) {
692
-			return $claimedProtocol;
693
-		}
694
-
695
-		return 'HTTP/1.1';
696
-	}
697
-
698
-	/**
699
-	 * Returns the request uri, even if the website uses one or more
700
-	 * reverse proxies
701
-	 * @return string
702
-	 */
703
-	public function getRequestUri(): string {
704
-		$uri = isset($this->server['REQUEST_URI']) ? $this->server['REQUEST_URI'] : '';
705
-		if ($this->config->getSystemValueString('overwritewebroot') !== '' && $this->isOverwriteCondition()) {
706
-			$uri = $this->getScriptName() . substr($uri, \strlen($this->server['SCRIPT_NAME']));
707
-		}
708
-		return $uri;
709
-	}
710
-
711
-	/**
712
-	 * Get raw PathInfo from request (not urldecoded)
713
-	 * @throws \Exception
714
-	 * @return string Path info
715
-	 */
716
-	public function getRawPathInfo(): string {
717
-		$requestUri = isset($this->server['REQUEST_URI']) ? $this->server['REQUEST_URI'] : '';
718
-		// remove too many slashes - can be caused by reverse proxy configuration
719
-		$requestUri = preg_replace('%/{2,}%', '/', $requestUri);
720
-
721
-		// Remove the query string from REQUEST_URI
722
-		if ($pos = strpos($requestUri, '?')) {
723
-			$requestUri = substr($requestUri, 0, $pos);
724
-		}
725
-
726
-		$scriptName = $this->server['SCRIPT_NAME'];
727
-		$pathInfo = $requestUri;
728
-
729
-		// strip off the script name's dir and file name
730
-		// FIXME: Sabre does not really belong here
731
-		[$path, $name] = \Sabre\Uri\split($scriptName);
732
-		if (!empty($path)) {
733
-			if ($path === $pathInfo || str_starts_with($pathInfo, $path . '/')) {
734
-				$pathInfo = substr($pathInfo, \strlen($path));
735
-			} else {
736
-				throw new \Exception("The requested uri($requestUri) cannot be processed by the script '$scriptName')");
737
-			}
738
-		}
739
-		if ($name === null) {
740
-			$name = '';
741
-		}
742
-
743
-		if (str_starts_with($pathInfo, '/' . $name)) {
744
-			$pathInfo = substr($pathInfo, \strlen($name) + 1);
745
-		}
746
-		if ($name !== '' && str_starts_with($pathInfo, $name)) {
747
-			$pathInfo = substr($pathInfo, \strlen($name));
748
-		}
749
-		if ($pathInfo === false || $pathInfo === '/') {
750
-			return '';
751
-		} else {
752
-			return $pathInfo;
753
-		}
754
-	}
755
-
756
-	/**
757
-	 * Get PathInfo from request (rawurldecoded)
758
-	 * @throws \Exception
759
-	 * @return string|false Path info or false when not found
760
-	 */
761
-	public function getPathInfo(): string|false {
762
-		$pathInfo = $this->getRawPathInfo();
763
-		return \Sabre\HTTP\decodePath($pathInfo);
764
-	}
765
-
766
-	/**
767
-	 * Returns the script name, even if the website uses one or more
768
-	 * reverse proxies
769
-	 * @return string the script name
770
-	 */
771
-	public function getScriptName(): string {
772
-		$name = $this->server['SCRIPT_NAME'];
773
-		$overwriteWebRoot = $this->config->getSystemValueString('overwritewebroot');
774
-		if ($overwriteWebRoot !== '' && $this->isOverwriteCondition()) {
775
-			// FIXME: This code is untestable due to __DIR__, also that hardcoded path is really dangerous
776
-			$serverRoot = str_replace('\\', '/', substr(__DIR__, 0, -\strlen('lib/private/appframework/http/')));
777
-			$suburi = str_replace('\\', '/', substr(realpath($this->server['SCRIPT_FILENAME']), \strlen($serverRoot)));
778
-			$name = '/' . ltrim($overwriteWebRoot . $suburi, '/');
779
-		}
780
-		return $name;
781
-	}
782
-
783
-	/**
784
-	 * Checks whether the user agent matches a given regex
785
-	 * @param array $agent array of agent names
786
-	 * @return bool true if at least one of the given agent matches, false otherwise
787
-	 */
788
-	public function isUserAgent(array $agent): bool {
789
-		if (!isset($this->server['HTTP_USER_AGENT'])) {
790
-			return false;
791
-		}
792
-		foreach ($agent as $regex) {
793
-			if (preg_match($regex, $this->server['HTTP_USER_AGENT'])) {
794
-				return true;
795
-			}
796
-		}
797
-		return false;
798
-	}
799
-
800
-	/**
801
-	 * Returns the unverified server host from the headers without checking
802
-	 * whether it is a trusted domain
803
-	 * @return string Server host
804
-	 */
805
-	public function getInsecureServerHost(): string {
806
-		if ($this->fromTrustedProxy() && $this->getOverwriteHost() !== null) {
807
-			return $this->getOverwriteHost();
808
-		}
809
-
810
-		$host = 'localhost';
811
-		if ($this->fromTrustedProxy() && isset($this->server['HTTP_X_FORWARDED_HOST'])) {
812
-			if (str_contains($this->server['HTTP_X_FORWARDED_HOST'], ',')) {
813
-				$parts = explode(',', $this->server['HTTP_X_FORWARDED_HOST']);
814
-				$host = trim(current($parts));
815
-			} else {
816
-				$host = $this->server['HTTP_X_FORWARDED_HOST'];
817
-			}
818
-		} else {
819
-			if (isset($this->server['HTTP_HOST'])) {
820
-				$host = $this->server['HTTP_HOST'];
821
-			} elseif (isset($this->server['SERVER_NAME'])) {
822
-				$host = $this->server['SERVER_NAME'];
823
-			}
824
-		}
825
-
826
-		return $host;
827
-	}
828
-
829
-
830
-	/**
831
-	 * Returns the server host from the headers, or the first configured
832
-	 * trusted domain if the host isn't in the trusted list
833
-	 * @return string Server host
834
-	 */
835
-	public function getServerHost(): string {
836
-		// overwritehost is always trusted
837
-		$host = $this->getOverwriteHost();
838
-		if ($host !== null) {
839
-			return $host;
840
-		}
841
-
842
-		// get the host from the headers
843
-		$host = $this->getInsecureServerHost();
844
-
845
-		// Verify that the host is a trusted domain if the trusted domains
846
-		// are defined
847
-		// If no trusted domain is provided the first trusted domain is returned
848
-		$trustedDomainHelper = new TrustedDomainHelper($this->config);
849
-		if ($trustedDomainHelper->isTrustedDomain($host)) {
850
-			return $host;
851
-		}
852
-
853
-		$trustedList = (array)$this->config->getSystemValue('trusted_domains', []);
854
-		if (count($trustedList) > 0) {
855
-			return reset($trustedList);
856
-		}
857
-
858
-		return '';
859
-	}
860
-
861
-	/**
862
-	 * Returns the overwritehost setting from the config if set and
863
-	 * if the overwrite condition is met
864
-	 * @return string|null overwritehost value or null if not defined or the defined condition
865
-	 *                     isn't met
866
-	 */
867
-	private function getOverwriteHost() {
868
-		if ($this->config->getSystemValueString('overwritehost') !== '' && $this->isOverwriteCondition()) {
869
-			return $this->config->getSystemValueString('overwritehost');
870
-		}
871
-		return null;
872
-	}
873
-
874
-	private function fromTrustedProxy(): bool {
875
-		$remoteAddress = isset($this->server['REMOTE_ADDR']) ? $this->server['REMOTE_ADDR'] : '';
876
-		$trustedProxies = $this->config->getSystemValue('trusted_proxies', []);
877
-
878
-		return \is_array($trustedProxies) && $this->isTrustedProxy($trustedProxies, $remoteAddress);
879
-	}
880
-
881
-	public function getFormat(): ?string {
882
-		$format = $this->getParam('format');
883
-		if ($format !== null) {
884
-			return $format;
885
-		}
886
-
887
-		$prefix = 'application/';
888
-		$headers = explode(',', $this->getHeader('Accept'));
889
-		foreach ($headers as $header) {
890
-			$header = strtolower(trim($header));
891
-
892
-			if (str_starts_with($header, $prefix)) {
893
-				return substr($header, strlen($prefix));
894
-			}
895
-		}
896
-
897
-		return null;
898
-	}
33
+    public const USER_AGENT_IE = '/(MSIE)|(Trident)/';
34
+    // Microsoft Edge User Agent from https://msdn.microsoft.com/en-us/library/hh869301(v=vs.85).aspx
35
+    public const USER_AGENT_MS_EDGE = '/^Mozilla\/5\.0 \([^)]+\) AppleWebKit\/[0-9.]+ \(KHTML, like Gecko\) Chrome\/[0-9.]+ (Mobile Safari|Safari)\/[0-9.]+ Edge?\/[0-9.]+$/';
36
+    // Firefox User Agent from https://developer.mozilla.org/en-US/docs/Web/HTTP/Gecko_user_agent_string_reference
37
+    public const USER_AGENT_FIREFOX = '/^Mozilla\/5\.0 \([^)]+\) Gecko\/[0-9.]+ Firefox\/[0-9.]+$/';
38
+    // Chrome User Agent from https://developer.chrome.com/multidevice/user-agent
39
+    public const USER_AGENT_CHROME = '/^Mozilla\/5\.0 \([^)]+\) AppleWebKit\/[0-9.]+ \(KHTML, like Gecko\)( Ubuntu Chromium\/[0-9.]+|) Chrome\/[0-9.]+ (Mobile Safari|Safari)\/[0-9.]+( (Vivaldi|Brave|OPR)\/[0-9.]+|)$/';
40
+    // Safari User Agent from http://www.useragentstring.com/pages/Safari/
41
+    public const USER_AGENT_SAFARI = '/^Mozilla\/5\.0 \([^)]+\) AppleWebKit\/[0-9.]+ \(KHTML, like Gecko\) Version\/[0-9.]+ Safari\/[0-9.A-Z]+$/';
42
+    public const USER_AGENT_SAFARI_MOBILE = '/^Mozilla\/5\.0 \([^)]+\) AppleWebKit\/[0-9.]+ \(KHTML, like Gecko\) Version\/[0-9.]+ (Mobile\/[0-9.A-Z]+) Safari\/[0-9.A-Z]+$/';
43
+    // Android Chrome user agent: https://developers.google.com/chrome/mobile/docs/user-agent
44
+    public const USER_AGENT_ANDROID_MOBILE_CHROME = '#Android.*Chrome/[.0-9]*#';
45
+    public const USER_AGENT_FREEBOX = '#^Mozilla/5\.0$#';
46
+    public const REGEX_LOCALHOST = '/^(127\.0\.0\.1|localhost|\[::1\])$/';
47
+
48
+    protected string $inputStream;
49
+    private bool $isPutStreamContentAlreadySent = false;
50
+    protected array $items = [];
51
+    protected array $allowedKeys = [
52
+        'get',
53
+        'post',
54
+        'files',
55
+        'server',
56
+        'env',
57
+        'cookies',
58
+        'urlParams',
59
+        'parameters',
60
+        'method',
61
+        'requesttoken',
62
+    ];
63
+    protected IRequestId $requestId;
64
+    protected IConfig $config;
65
+    protected ?CsrfTokenManager $csrfTokenManager;
66
+
67
+    protected bool $contentDecoded = false;
68
+    private ?\JsonException $decodingException = null;
69
+
70
+    /**
71
+     * @param array $vars An associative array with the following optional values:
72
+     *                    - array 'urlParams' the parameters which were matched from the URL
73
+     *                    - array 'get' the $_GET array
74
+     *                    - array|string 'post' the $_POST array or JSON string
75
+     *                    - array 'files' the $_FILES array
76
+     *                    - array 'server' the $_SERVER array
77
+     *                    - array 'env' the $_ENV array
78
+     *                    - array 'cookies' the $_COOKIE array
79
+     *                    - string 'method' the request method (GET, POST etc)
80
+     *                    - string|false 'requesttoken' the requesttoken or false when not available
81
+     * @param IRequestId $requestId
82
+     * @param IConfig $config
83
+     * @param CsrfTokenManager|null $csrfTokenManager
84
+     * @param string $stream
85
+     * @see https://www.php.net/manual/en/reserved.variables.php
86
+     */
87
+    public function __construct(array $vars,
88
+        IRequestId $requestId,
89
+        IConfig $config,
90
+        ?CsrfTokenManager $csrfTokenManager = null,
91
+        string $stream = 'php://input') {
92
+        $this->inputStream = $stream;
93
+        $this->items['params'] = [];
94
+        $this->requestId = $requestId;
95
+        $this->config = $config;
96
+        $this->csrfTokenManager = $csrfTokenManager;
97
+
98
+        if (!array_key_exists('method', $vars)) {
99
+            $vars['method'] = 'GET';
100
+        }
101
+
102
+        foreach ($this->allowedKeys as $name) {
103
+            $this->items[$name] = $vars[$name] ?? [];
104
+        }
105
+
106
+        $this->items['parameters'] = array_merge(
107
+            $this->items['get'],
108
+            $this->items['post'],
109
+            $this->items['urlParams'],
110
+            $this->items['params']
111
+        );
112
+    }
113
+    /**
114
+     * @param array $parameters
115
+     */
116
+    public function setUrlParameters(array $parameters) {
117
+        $this->items['urlParams'] = $parameters;
118
+        $this->items['parameters'] = array_merge(
119
+            $this->items['parameters'],
120
+            $this->items['urlParams']
121
+        );
122
+    }
123
+
124
+    /**
125
+     * Countable method
126
+     * @return int
127
+     */
128
+    public function count(): int {
129
+        return \count($this->items['parameters']);
130
+    }
131
+
132
+    /**
133
+     * ArrayAccess methods
134
+     *
135
+     * Gives access to the combined GET, POST and urlParams arrays
136
+     *
137
+     * Examples:
138
+     *
139
+     * $var = $request['myvar'];
140
+     *
141
+     * or
142
+     *
143
+     * if(!isset($request['myvar']) {
144
+     * 	// Do something
145
+     * }
146
+     *
147
+     * $request['myvar'] = 'something'; // This throws an exception.
148
+     *
149
+     * @param string $offset The key to lookup
150
+     * @return boolean
151
+     */
152
+    public function offsetExists($offset): bool {
153
+        return isset($this->items['parameters'][$offset]);
154
+    }
155
+
156
+    /**
157
+     * @see offsetExists
158
+     * @param string $offset
159
+     * @return mixed
160
+     */
161
+    #[\ReturnTypeWillChange]
162
+    public function offsetGet($offset) {
163
+        return $this->items['parameters'][$offset] ?? null;
164
+    }
165
+
166
+    /**
167
+     * @see offsetExists
168
+     * @param string $offset
169
+     * @param mixed $value
170
+     */
171
+    public function offsetSet($offset, $value): void {
172
+        throw new \RuntimeException('You cannot change the contents of the request object');
173
+    }
174
+
175
+    /**
176
+     * @see offsetExists
177
+     * @param string $offset
178
+     */
179
+    public function offsetUnset($offset): void {
180
+        throw new \RuntimeException('You cannot change the contents of the request object');
181
+    }
182
+
183
+    /**
184
+     * Magic property accessors
185
+     * @param string $name
186
+     * @param mixed $value
187
+     */
188
+    public function __set($name, $value) {
189
+        throw new \RuntimeException('You cannot change the contents of the request object');
190
+    }
191
+
192
+    /**
193
+     * Access request variables by method and name.
194
+     * Examples:
195
+     *
196
+     * $request->post['myvar']; // Only look for POST variables
197
+     * $request->myvar; or $request->{'myvar'}; or $request->{$myvar}
198
+     * Looks in the combined GET, POST and urlParams array.
199
+     *
200
+     * If you access e.g. ->post but the current HTTP request method
201
+     * is GET a \LogicException will be thrown.
202
+     *
203
+     * @param string $name The key to look for.
204
+     * @throws \LogicException
205
+     * @return mixed|null
206
+     */
207
+    public function __get($name) {
208
+        switch ($name) {
209
+            case 'put':
210
+            case 'patch':
211
+            case 'get':
212
+            case 'post':
213
+                if ($this->method !== strtoupper($name)) {
214
+                    throw new \LogicException(sprintf('%s cannot be accessed in a %s request.', $name, $this->method));
215
+                }
216
+                return $this->getContent();
217
+            case 'files':
218
+            case 'server':
219
+            case 'env':
220
+            case 'cookies':
221
+            case 'urlParams':
222
+            case 'method':
223
+                return $this->items[$name] ?? null;
224
+            case 'parameters':
225
+            case 'params':
226
+                if ($this->isPutStreamContent()) {
227
+                    return $this->items['parameters'];
228
+                }
229
+                return $this->getContent();
230
+            default:
231
+                return isset($this[$name])
232
+                    ? $this[$name]
233
+                    : null;
234
+        }
235
+    }
236
+
237
+    /**
238
+     * @param string $name
239
+     * @return bool
240
+     */
241
+    public function __isset($name) {
242
+        if (\in_array($name, $this->allowedKeys, true)) {
243
+            return true;
244
+        }
245
+        return isset($this->items['parameters'][$name]);
246
+    }
247
+
248
+    /**
249
+     * @param string $id
250
+     */
251
+    public function __unset($id) {
252
+        throw new \RuntimeException('You cannot change the contents of the request object');
253
+    }
254
+
255
+    /**
256
+     * Returns the value for a specific http header.
257
+     *
258
+     * This method returns an empty string if the header did not exist.
259
+     *
260
+     * @param string $name
261
+     * @return string
262
+     */
263
+    public function getHeader(string $name): string {
264
+        $name = strtoupper(str_replace('-', '_', $name));
265
+        if (isset($this->server['HTTP_' . $name])) {
266
+            return $this->server['HTTP_' . $name];
267
+        }
268
+
269
+        // There's a few headers that seem to end up in the top-level
270
+        // server array.
271
+        switch ($name) {
272
+            case 'CONTENT_TYPE':
273
+            case 'CONTENT_LENGTH':
274
+            case 'REMOTE_ADDR':
275
+                if (isset($this->server[$name])) {
276
+                    return $this->server[$name];
277
+                }
278
+                break;
279
+        }
280
+
281
+        return '';
282
+    }
283
+
284
+    /**
285
+     * Lets you access post and get parameters by the index
286
+     * In case of json requests the encoded json body is accessed
287
+     *
288
+     * @param string $key the key which you want to access in the URL Parameter
289
+     *                    placeholder, $_POST or $_GET array.
290
+     *                    The priority how they're returned is the following:
291
+     *                    1. URL parameters
292
+     *                    2. POST parameters
293
+     *                    3. GET parameters
294
+     * @param mixed $default If the key is not found, this value will be returned
295
+     * @return mixed the content of the array
296
+     */
297
+    public function getParam(string $key, $default = null) {
298
+        return isset($this->parameters[$key])
299
+            ? $this->parameters[$key]
300
+            : $default;
301
+    }
302
+
303
+    /**
304
+     * Returns all params that were received, be it from the request
305
+     * (as GET or POST) or through the URL by the route
306
+     * @return array the array with all parameters
307
+     */
308
+    public function getParams(): array {
309
+        return is_array($this->parameters) ? $this->parameters : [];
310
+    }
311
+
312
+    /**
313
+     * Returns the method of the request
314
+     * @return string the method of the request (POST, GET, etc)
315
+     */
316
+    public function getMethod(): string {
317
+        return $this->method;
318
+    }
319
+
320
+    /**
321
+     * Shortcut for accessing an uploaded file through the $_FILES array
322
+     * @param string $key the key that will be taken from the $_FILES array
323
+     * @return array the file in the $_FILES element
324
+     */
325
+    public function getUploadedFile(string $key) {
326
+        return isset($this->files[$key]) ? $this->files[$key] : null;
327
+    }
328
+
329
+    /**
330
+     * Shortcut for getting env variables
331
+     * @param string $key the key that will be taken from the $_ENV array
332
+     * @return array the value in the $_ENV element
333
+     */
334
+    public function getEnv(string $key) {
335
+        return isset($this->env[$key]) ? $this->env[$key] : null;
336
+    }
337
+
338
+    /**
339
+     * Shortcut for getting cookie variables
340
+     * @param string $key the key that will be taken from the $_COOKIE array
341
+     * @return string the value in the $_COOKIE element
342
+     */
343
+    public function getCookie(string $key) {
344
+        return isset($this->cookies[$key]) ? $this->cookies[$key] : null;
345
+    }
346
+
347
+    /**
348
+     * Returns the request body content.
349
+     *
350
+     * If the HTTP request method is PUT and the body
351
+     * not application/x-www-form-urlencoded or application/json a stream
352
+     * resource is returned, otherwise an array.
353
+     *
354
+     * @return array|string|resource The request body content or a resource to read the body stream.
355
+     *
356
+     * @throws \LogicException
357
+     */
358
+    protected function getContent() {
359
+        // If the content can't be parsed into an array then return a stream resource.
360
+        if ($this->isPutStreamContent()) {
361
+            if ($this->isPutStreamContentAlreadySent) {
362
+                throw new \LogicException(
363
+                    '"put" can only be accessed once if not '
364
+                    . 'application/x-www-form-urlencoded or application/json.'
365
+                );
366
+            }
367
+            $this->isPutStreamContentAlreadySent = true;
368
+            return fopen($this->inputStream, 'rb');
369
+        } else {
370
+            $this->decodeContent();
371
+            return $this->items['parameters'];
372
+        }
373
+    }
374
+
375
+    private function isPutStreamContent(): bool {
376
+        return $this->method === 'PUT'
377
+            && $this->getHeader('Content-Length') !== '0'
378
+            && $this->getHeader('Content-Length') !== ''
379
+            && !str_contains($this->getHeader('Content-Type'), 'application/x-www-form-urlencoded')
380
+            && !str_contains($this->getHeader('Content-Type'), 'application/json');
381
+    }
382
+
383
+    /**
384
+     * Attempt to decode the content and populate parameters
385
+     */
386
+    protected function decodeContent() {
387
+        if ($this->contentDecoded) {
388
+            return;
389
+        }
390
+        $params = [];
391
+
392
+        // 'application/json' and other JSON-related content types must be decoded manually.
393
+        if (preg_match(self::JSON_CONTENT_TYPE_REGEX, $this->getHeader('Content-Type')) === 1) {
394
+            $content = file_get_contents($this->inputStream);
395
+            if ($content !== '') {
396
+                try {
397
+                    $params = json_decode($content, true, flags:JSON_THROW_ON_ERROR);
398
+                } catch (\JsonException $e) {
399
+                    $this->decodingException = $e;
400
+                }
401
+            }
402
+            if (\is_array($params) && \count($params) > 0) {
403
+                $this->items['params'] = $params;
404
+                if ($this->method === 'POST') {
405
+                    $this->items['post'] = $params;
406
+                }
407
+            }
408
+            // Handle application/x-www-form-urlencoded for methods other than GET
409
+            // or post correctly
410
+        } elseif ($this->method !== 'GET'
411
+                && $this->method !== 'POST'
412
+                && str_contains($this->getHeader('Content-Type'), 'application/x-www-form-urlencoded')) {
413
+            parse_str(file_get_contents($this->inputStream), $params);
414
+            if (\is_array($params)) {
415
+                $this->items['params'] = $params;
416
+            }
417
+        }
418
+
419
+        if (\is_array($params)) {
420
+            $this->items['parameters'] = array_merge($this->items['parameters'], $params);
421
+        }
422
+        $this->contentDecoded = true;
423
+    }
424
+
425
+    public function throwDecodingExceptionIfAny(): void {
426
+        if ($this->decodingException !== null) {
427
+            throw $this->decodingException;
428
+        }
429
+    }
430
+
431
+
432
+    /**
433
+     * Checks if the CSRF check was correct
434
+     * @return bool true if CSRF check passed
435
+     */
436
+    public function passesCSRFCheck(): bool {
437
+        if ($this->csrfTokenManager === null) {
438
+            return false;
439
+        }
440
+
441
+        if (!$this->passesStrictCookieCheck()) {
442
+            return false;
443
+        }
444
+
445
+        if ($this->getHeader('OCS-APIRequest') !== '') {
446
+            return true;
447
+        }
448
+
449
+        if (isset($this->items['get']['requesttoken'])) {
450
+            $token = $this->items['get']['requesttoken'];
451
+        } elseif (isset($this->items['post']['requesttoken'])) {
452
+            $token = $this->items['post']['requesttoken'];
453
+        } elseif (isset($this->items['server']['HTTP_REQUESTTOKEN'])) {
454
+            $token = $this->items['server']['HTTP_REQUESTTOKEN'];
455
+        } else {
456
+            //no token found.
457
+            return false;
458
+        }
459
+        $token = new CsrfToken($token);
460
+
461
+        return $this->csrfTokenManager->isTokenValid($token);
462
+    }
463
+
464
+    /**
465
+     * Whether the cookie checks are required
466
+     *
467
+     * @return bool
468
+     */
469
+    private function cookieCheckRequired(): bool {
470
+        if ($this->getHeader('OCS-APIREQUEST')) {
471
+            return false;
472
+        }
473
+        if ($this->getCookie(session_name()) === null && $this->getCookie('nc_token') === null) {
474
+            return false;
475
+        }
476
+
477
+        return true;
478
+    }
479
+
480
+    /**
481
+     * Wrapper around session_get_cookie_params
482
+     *
483
+     * @return array
484
+     */
485
+    public function getCookieParams(): array {
486
+        return session_get_cookie_params();
487
+    }
488
+
489
+    /**
490
+     * Appends the __Host- prefix to the cookie if applicable
491
+     *
492
+     * @param string $name
493
+     * @return string
494
+     */
495
+    protected function getProtectedCookieName(string $name): string {
496
+        $cookieParams = $this->getCookieParams();
497
+        $prefix = '';
498
+        if ($cookieParams['secure'] === true && $cookieParams['path'] === '/') {
499
+            $prefix = '__Host-';
500
+        }
501
+
502
+        return $prefix . $name;
503
+    }
504
+
505
+    /**
506
+     * Checks if the strict cookie has been sent with the request if the request
507
+     * is including any cookies.
508
+     *
509
+     * @return bool
510
+     * @since 9.1.0
511
+     */
512
+    public function passesStrictCookieCheck(): bool {
513
+        if (!$this->cookieCheckRequired()) {
514
+            return true;
515
+        }
516
+
517
+        $cookieName = $this->getProtectedCookieName('nc_sameSiteCookiestrict');
518
+        if ($this->getCookie($cookieName) === 'true'
519
+            && $this->passesLaxCookieCheck()) {
520
+            return true;
521
+        }
522
+        return false;
523
+    }
524
+
525
+    /**
526
+     * Checks if the lax cookie has been sent with the request if the request
527
+     * is including any cookies.
528
+     *
529
+     * @return bool
530
+     * @since 9.1.0
531
+     */
532
+    public function passesLaxCookieCheck(): bool {
533
+        if (!$this->cookieCheckRequired()) {
534
+            return true;
535
+        }
536
+
537
+        $cookieName = $this->getProtectedCookieName('nc_sameSiteCookielax');
538
+        if ($this->getCookie($cookieName) === 'true') {
539
+            return true;
540
+        }
541
+        return false;
542
+    }
543
+
544
+
545
+    /**
546
+     * Returns an ID for the request, value is not guaranteed to be unique and is mostly meant for logging
547
+     * If `mod_unique_id` is installed this value will be taken.
548
+     * @return string
549
+     */
550
+    public function getId(): string {
551
+        return $this->requestId->getId();
552
+    }
553
+
554
+    /**
555
+     * Checks if given $remoteAddress matches any entry in the given array $trustedProxies.
556
+     * For details regarding what "match" means, refer to `matchesTrustedProxy`.
557
+     * @return boolean true if $remoteAddress matches any entry in $trustedProxies, false otherwise
558
+     */
559
+    protected function isTrustedProxy($trustedProxies, $remoteAddress) {
560
+        try {
561
+            return IpUtils::checkIp($remoteAddress, $trustedProxies);
562
+        } catch (\Throwable) {
563
+            // We can not log to our log here as the logger is using `getRemoteAddress` which uses the function, so we would have a cyclic dependency
564
+            // Reaching this line means `trustedProxies` is in invalid format.
565
+            error_log('Nextcloud trustedProxies has malformed entries');
566
+            return false;
567
+        }
568
+    }
569
+
570
+    /**
571
+     * Returns the remote address, if the connection came from a trusted proxy
572
+     * and `forwarded_for_headers` has been configured then the IP address
573
+     * specified in this header will be returned instead.
574
+     * Do always use this instead of $_SERVER['REMOTE_ADDR']
575
+     * @return string IP address
576
+     */
577
+    public function getRemoteAddress(): string {
578
+        $remoteAddress = isset($this->server['REMOTE_ADDR']) ? $this->server['REMOTE_ADDR'] : '';
579
+        $trustedProxies = $this->config->getSystemValue('trusted_proxies', []);
580
+
581
+        if (\is_array($trustedProxies) && $this->isTrustedProxy($trustedProxies, $remoteAddress)) {
582
+            $forwardedForHeaders = $this->config->getSystemValue('forwarded_for_headers', [
583
+                'HTTP_X_FORWARDED_FOR'
584
+                // only have one default, so we cannot ship an insecure product out of the box
585
+            ]);
586
+
587
+            // Read the x-forwarded-for headers and values in reverse order as per
588
+            // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-For#selecting_an_ip_address
589
+            foreach (array_reverse($forwardedForHeaders) as $header) {
590
+                if (isset($this->server[$header])) {
591
+                    foreach (array_reverse(explode(',', $this->server[$header])) as $IP) {
592
+                        $IP = trim($IP);
593
+                        $colons = substr_count($IP, ':');
594
+                        if ($colons > 1) {
595
+                            // Extract IP from string with brackets and optional port
596
+                            if (preg_match('/^\[(.+?)\](?::\d+)?$/', $IP, $matches) && isset($matches[1])) {
597
+                                $IP = $matches[1];
598
+                            }
599
+                        } elseif ($colons === 1) {
600
+                            // IPv4 with port
601
+                            $IP = substr($IP, 0, strpos($IP, ':'));
602
+                        }
603
+
604
+                        if ($this->isTrustedProxy($trustedProxies, $IP)) {
605
+                            continue;
606
+                        }
607
+
608
+                        if (filter_var($IP, FILTER_VALIDATE_IP) !== false) {
609
+                            return $IP;
610
+                        }
611
+                    }
612
+                }
613
+            }
614
+        }
615
+
616
+        return $remoteAddress;
617
+    }
618
+
619
+    /**
620
+     * Check overwrite condition
621
+     * @return bool
622
+     */
623
+    private function isOverwriteCondition(): bool {
624
+        $regex = '/' . $this->config->getSystemValueString('overwritecondaddr', '') . '/';
625
+        $remoteAddr = isset($this->server['REMOTE_ADDR']) ? $this->server['REMOTE_ADDR'] : '';
626
+        return $regex === '//' || preg_match($regex, $remoteAddr) === 1;
627
+    }
628
+
629
+    /**
630
+     * Returns the server protocol. It respects one or more reverse proxies servers
631
+     * and load balancers. Precedence:
632
+     *   1. `overwriteprotocol` config value
633
+     *   2. `X-Forwarded-Proto` header value
634
+     *   3. $_SERVER['HTTPS'] value
635
+     * If an invalid protocol is provided, defaults to http, continues, but logs as an error.
636
+     *
637
+     * @return string Server protocol (http or https)
638
+     */
639
+    public function getServerProtocol(): string {
640
+        $proto = 'http';
641
+
642
+        if ($this->config->getSystemValueString('overwriteprotocol') !== ''
643
+            && $this->isOverwriteCondition()
644
+        ) {
645
+            $proto = strtolower($this->config->getSystemValueString('overwriteprotocol'));
646
+        } elseif ($this->fromTrustedProxy()
647
+            && isset($this->server['HTTP_X_FORWARDED_PROTO'])
648
+        ) {
649
+            if (str_contains($this->server['HTTP_X_FORWARDED_PROTO'], ',')) {
650
+                $parts = explode(',', $this->server['HTTP_X_FORWARDED_PROTO']);
651
+                $proto = strtolower(trim($parts[0]));
652
+            } else {
653
+                $proto = strtolower($this->server['HTTP_X_FORWARDED_PROTO']);
654
+            }
655
+        } elseif (!empty($this->server['HTTPS'])
656
+            && $this->server['HTTPS'] !== 'off'
657
+        ) {
658
+            $proto = 'https';
659
+        }
660
+
661
+        if ($proto !== 'https' && $proto !== 'http') {
662
+            // log unrecognized value so admin has a chance to fix it
663
+            \OCP\Server::get(LoggerInterface::class)->critical(
664
+                'Server protocol is malformed [falling back to http] (check overwriteprotocol and/or X-Forwarded-Proto to remedy): ' . $proto,
665
+                ['app' => 'core']
666
+            );
667
+        }
668
+
669
+        // default to http if provided an invalid value
670
+        return $proto === 'https' ? 'https' : 'http';
671
+    }
672
+
673
+    /**
674
+     * Returns the used HTTP protocol.
675
+     *
676
+     * @return string HTTP protocol. HTTP/2, HTTP/1.1 or HTTP/1.0.
677
+     */
678
+    public function getHttpProtocol(): string {
679
+        $claimedProtocol = $this->server['SERVER_PROTOCOL'];
680
+
681
+        if (\is_string($claimedProtocol)) {
682
+            $claimedProtocol = strtoupper($claimedProtocol);
683
+        }
684
+
685
+        $validProtocols = [
686
+            'HTTP/1.0',
687
+            'HTTP/1.1',
688
+            'HTTP/2',
689
+        ];
690
+
691
+        if (\in_array($claimedProtocol, $validProtocols, true)) {
692
+            return $claimedProtocol;
693
+        }
694
+
695
+        return 'HTTP/1.1';
696
+    }
697
+
698
+    /**
699
+     * Returns the request uri, even if the website uses one or more
700
+     * reverse proxies
701
+     * @return string
702
+     */
703
+    public function getRequestUri(): string {
704
+        $uri = isset($this->server['REQUEST_URI']) ? $this->server['REQUEST_URI'] : '';
705
+        if ($this->config->getSystemValueString('overwritewebroot') !== '' && $this->isOverwriteCondition()) {
706
+            $uri = $this->getScriptName() . substr($uri, \strlen($this->server['SCRIPT_NAME']));
707
+        }
708
+        return $uri;
709
+    }
710
+
711
+    /**
712
+     * Get raw PathInfo from request (not urldecoded)
713
+     * @throws \Exception
714
+     * @return string Path info
715
+     */
716
+    public function getRawPathInfo(): string {
717
+        $requestUri = isset($this->server['REQUEST_URI']) ? $this->server['REQUEST_URI'] : '';
718
+        // remove too many slashes - can be caused by reverse proxy configuration
719
+        $requestUri = preg_replace('%/{2,}%', '/', $requestUri);
720
+
721
+        // Remove the query string from REQUEST_URI
722
+        if ($pos = strpos($requestUri, '?')) {
723
+            $requestUri = substr($requestUri, 0, $pos);
724
+        }
725
+
726
+        $scriptName = $this->server['SCRIPT_NAME'];
727
+        $pathInfo = $requestUri;
728
+
729
+        // strip off the script name's dir and file name
730
+        // FIXME: Sabre does not really belong here
731
+        [$path, $name] = \Sabre\Uri\split($scriptName);
732
+        if (!empty($path)) {
733
+            if ($path === $pathInfo || str_starts_with($pathInfo, $path . '/')) {
734
+                $pathInfo = substr($pathInfo, \strlen($path));
735
+            } else {
736
+                throw new \Exception("The requested uri($requestUri) cannot be processed by the script '$scriptName')");
737
+            }
738
+        }
739
+        if ($name === null) {
740
+            $name = '';
741
+        }
742
+
743
+        if (str_starts_with($pathInfo, '/' . $name)) {
744
+            $pathInfo = substr($pathInfo, \strlen($name) + 1);
745
+        }
746
+        if ($name !== '' && str_starts_with($pathInfo, $name)) {
747
+            $pathInfo = substr($pathInfo, \strlen($name));
748
+        }
749
+        if ($pathInfo === false || $pathInfo === '/') {
750
+            return '';
751
+        } else {
752
+            return $pathInfo;
753
+        }
754
+    }
755
+
756
+    /**
757
+     * Get PathInfo from request (rawurldecoded)
758
+     * @throws \Exception
759
+     * @return string|false Path info or false when not found
760
+     */
761
+    public function getPathInfo(): string|false {
762
+        $pathInfo = $this->getRawPathInfo();
763
+        return \Sabre\HTTP\decodePath($pathInfo);
764
+    }
765
+
766
+    /**
767
+     * Returns the script name, even if the website uses one or more
768
+     * reverse proxies
769
+     * @return string the script name
770
+     */
771
+    public function getScriptName(): string {
772
+        $name = $this->server['SCRIPT_NAME'];
773
+        $overwriteWebRoot = $this->config->getSystemValueString('overwritewebroot');
774
+        if ($overwriteWebRoot !== '' && $this->isOverwriteCondition()) {
775
+            // FIXME: This code is untestable due to __DIR__, also that hardcoded path is really dangerous
776
+            $serverRoot = str_replace('\\', '/', substr(__DIR__, 0, -\strlen('lib/private/appframework/http/')));
777
+            $suburi = str_replace('\\', '/', substr(realpath($this->server['SCRIPT_FILENAME']), \strlen($serverRoot)));
778
+            $name = '/' . ltrim($overwriteWebRoot . $suburi, '/');
779
+        }
780
+        return $name;
781
+    }
782
+
783
+    /**
784
+     * Checks whether the user agent matches a given regex
785
+     * @param array $agent array of agent names
786
+     * @return bool true if at least one of the given agent matches, false otherwise
787
+     */
788
+    public function isUserAgent(array $agent): bool {
789
+        if (!isset($this->server['HTTP_USER_AGENT'])) {
790
+            return false;
791
+        }
792
+        foreach ($agent as $regex) {
793
+            if (preg_match($regex, $this->server['HTTP_USER_AGENT'])) {
794
+                return true;
795
+            }
796
+        }
797
+        return false;
798
+    }
799
+
800
+    /**
801
+     * Returns the unverified server host from the headers without checking
802
+     * whether it is a trusted domain
803
+     * @return string Server host
804
+     */
805
+    public function getInsecureServerHost(): string {
806
+        if ($this->fromTrustedProxy() && $this->getOverwriteHost() !== null) {
807
+            return $this->getOverwriteHost();
808
+        }
809
+
810
+        $host = 'localhost';
811
+        if ($this->fromTrustedProxy() && isset($this->server['HTTP_X_FORWARDED_HOST'])) {
812
+            if (str_contains($this->server['HTTP_X_FORWARDED_HOST'], ',')) {
813
+                $parts = explode(',', $this->server['HTTP_X_FORWARDED_HOST']);
814
+                $host = trim(current($parts));
815
+            } else {
816
+                $host = $this->server['HTTP_X_FORWARDED_HOST'];
817
+            }
818
+        } else {
819
+            if (isset($this->server['HTTP_HOST'])) {
820
+                $host = $this->server['HTTP_HOST'];
821
+            } elseif (isset($this->server['SERVER_NAME'])) {
822
+                $host = $this->server['SERVER_NAME'];
823
+            }
824
+        }
825
+
826
+        return $host;
827
+    }
828
+
829
+
830
+    /**
831
+     * Returns the server host from the headers, or the first configured
832
+     * trusted domain if the host isn't in the trusted list
833
+     * @return string Server host
834
+     */
835
+    public function getServerHost(): string {
836
+        // overwritehost is always trusted
837
+        $host = $this->getOverwriteHost();
838
+        if ($host !== null) {
839
+            return $host;
840
+        }
841
+
842
+        // get the host from the headers
843
+        $host = $this->getInsecureServerHost();
844
+
845
+        // Verify that the host is a trusted domain if the trusted domains
846
+        // are defined
847
+        // If no trusted domain is provided the first trusted domain is returned
848
+        $trustedDomainHelper = new TrustedDomainHelper($this->config);
849
+        if ($trustedDomainHelper->isTrustedDomain($host)) {
850
+            return $host;
851
+        }
852
+
853
+        $trustedList = (array)$this->config->getSystemValue('trusted_domains', []);
854
+        if (count($trustedList) > 0) {
855
+            return reset($trustedList);
856
+        }
857
+
858
+        return '';
859
+    }
860
+
861
+    /**
862
+     * Returns the overwritehost setting from the config if set and
863
+     * if the overwrite condition is met
864
+     * @return string|null overwritehost value or null if not defined or the defined condition
865
+     *                     isn't met
866
+     */
867
+    private function getOverwriteHost() {
868
+        if ($this->config->getSystemValueString('overwritehost') !== '' && $this->isOverwriteCondition()) {
869
+            return $this->config->getSystemValueString('overwritehost');
870
+        }
871
+        return null;
872
+    }
873
+
874
+    private function fromTrustedProxy(): bool {
875
+        $remoteAddress = isset($this->server['REMOTE_ADDR']) ? $this->server['REMOTE_ADDR'] : '';
876
+        $trustedProxies = $this->config->getSystemValue('trusted_proxies', []);
877
+
878
+        return \is_array($trustedProxies) && $this->isTrustedProxy($trustedProxies, $remoteAddress);
879
+    }
880
+
881
+    public function getFormat(): ?string {
882
+        $format = $this->getParam('format');
883
+        if ($format !== null) {
884
+            return $format;
885
+        }
886
+
887
+        $prefix = 'application/';
888
+        $headers = explode(',', $this->getHeader('Accept'));
889
+        foreach ($headers as $header) {
890
+            $header = strtolower(trim($header));
891
+
892
+            if (str_starts_with($header, $prefix)) {
893
+                return substr($header, strlen($prefix));
894
+            }
895
+        }
896
+
897
+        return null;
898
+    }
899 899
 }
Please login to merge, or discard this patch.
lib/private/AppFramework/Http/Dispatcher.php 1 patch
Indentation   +213 added lines, -213 removed lines patch added patch discarded remove patch
@@ -26,217 +26,217 @@
 block discarded – undo
26 26
  * Class to dispatch the request to the middleware dispatcher
27 27
  */
28 28
 class Dispatcher {
29
-	/** @var MiddlewareDispatcher */
30
-	private $middlewareDispatcher;
31
-
32
-	/** @var Http */
33
-	private $protocol;
34
-
35
-	/** @var ControllerMethodReflector */
36
-	private $reflector;
37
-
38
-	/** @var IRequest */
39
-	private $request;
40
-
41
-	/** @var IConfig */
42
-	private $config;
43
-
44
-	/** @var ConnectionAdapter */
45
-	private $connection;
46
-
47
-	/** @var LoggerInterface */
48
-	private $logger;
49
-
50
-	/** @var IEventLogger */
51
-	private $eventLogger;
52
-
53
-	private ContainerInterface $appContainer;
54
-
55
-	/**
56
-	 * @param Http $protocol the http protocol with contains all status headers
57
-	 * @param MiddlewareDispatcher $middlewareDispatcher the dispatcher which
58
-	 *                                                   runs the middleware
59
-	 * @param ControllerMethodReflector $reflector the reflector that is used to inject
60
-	 *                                             the arguments for the controller
61
-	 * @param IRequest $request the incoming request
62
-	 * @param IConfig $config
63
-	 * @param ConnectionAdapter $connection
64
-	 * @param LoggerInterface $logger
65
-	 * @param IEventLogger $eventLogger
66
-	 */
67
-	public function __construct(
68
-		Http $protocol,
69
-		MiddlewareDispatcher $middlewareDispatcher,
70
-		ControllerMethodReflector $reflector,
71
-		IRequest $request,
72
-		IConfig $config,
73
-		ConnectionAdapter $connection,
74
-		LoggerInterface $logger,
75
-		IEventLogger $eventLogger,
76
-		ContainerInterface $appContainer,
77
-	) {
78
-		$this->protocol = $protocol;
79
-		$this->middlewareDispatcher = $middlewareDispatcher;
80
-		$this->reflector = $reflector;
81
-		$this->request = $request;
82
-		$this->config = $config;
83
-		$this->connection = $connection;
84
-		$this->logger = $logger;
85
-		$this->eventLogger = $eventLogger;
86
-		$this->appContainer = $appContainer;
87
-	}
88
-
89
-
90
-	/**
91
-	 * Handles a request and calls the dispatcher on the controller
92
-	 * @param Controller $controller the controller which will be called
93
-	 * @param string $methodName the method name which will be called on
94
-	 *                           the controller
95
-	 * @return array $array[0] contains the http status header as a string,
96
-	 *               $array[1] contains response headers as an array,
97
-	 *               $array[2] contains response cookies as an array,
98
-	 *               $array[3] contains the response output as a string,
99
-	 *               $array[4] contains the response object
100
-	 * @throws \Exception
101
-	 */
102
-	public function dispatch(Controller $controller, string $methodName): array {
103
-		$out = [null, [], null];
104
-
105
-		try {
106
-			// prefill reflector with everything that's needed for the
107
-			// middlewares
108
-			$this->reflector->reflect($controller, $methodName);
109
-
110
-			$this->middlewareDispatcher->beforeController($controller,
111
-				$methodName);
112
-
113
-			$databaseStatsBefore = [];
114
-			if ($this->config->getSystemValueBool('debug', false)) {
115
-				$databaseStatsBefore = $this->connection->getInner()->getStats();
116
-			}
117
-
118
-			$response = $this->executeController($controller, $methodName);
119
-
120
-			if (!empty($databaseStatsBefore)) {
121
-				$databaseStatsAfter = $this->connection->getInner()->getStats();
122
-				$numBuilt = $databaseStatsAfter['built'] - $databaseStatsBefore['built'];
123
-				$numExecuted = $databaseStatsAfter['executed'] - $databaseStatsBefore['executed'];
124
-
125
-				if ($numBuilt > 50) {
126
-					$this->logger->debug('Controller {class}::{method} created {count} QueryBuilder objects, please check if they are created inside a loop by accident.', [
127
-						'class' => get_class($controller),
128
-						'method' => $methodName,
129
-						'count' => $numBuilt,
130
-					]);
131
-				}
132
-
133
-				if ($numExecuted > 100) {
134
-					$this->logger->warning('Controller {class}::{method} executed {count} queries.', [
135
-						'class' => get_class($controller),
136
-						'method' => $methodName,
137
-						'count' => $numExecuted,
138
-					]);
139
-				}
140
-			}
141
-
142
-			// if an exception appears, the middleware checks if it can handle the
143
-			// exception and creates a response. If no response is created, it is
144
-			// assumed that there's no middleware who can handle it and the error is
145
-			// thrown again
146
-		} catch (\Exception $exception) {
147
-			$response = $this->middlewareDispatcher->afterException(
148
-				$controller, $methodName, $exception);
149
-		} catch (\Throwable $throwable) {
150
-			$exception = new \Exception($throwable->getMessage() . ' in file \'' . $throwable->getFile() . '\' line ' . $throwable->getLine(), $throwable->getCode(), $throwable);
151
-			$response = $this->middlewareDispatcher->afterException(
152
-				$controller, $methodName, $exception);
153
-		}
154
-
155
-		$response = $this->middlewareDispatcher->afterController(
156
-			$controller, $methodName, $response);
157
-
158
-		// depending on the cache object the headers need to be changed
159
-		$out[0] = $this->protocol->getStatusHeader($response->getStatus());
160
-		$out[1] = array_merge($response->getHeaders());
161
-		$out[2] = $response->getCookies();
162
-		$out[3] = $this->middlewareDispatcher->beforeOutput(
163
-			$controller, $methodName, $response->render()
164
-		);
165
-		$out[4] = $response;
166
-
167
-		return $out;
168
-	}
169
-
170
-
171
-	/**
172
-	 * Uses the reflected parameters, types and request parameters to execute
173
-	 * the controller
174
-	 * @param Controller $controller the controller to be executed
175
-	 * @param string $methodName the method on the controller that should be executed
176
-	 * @return Response
177
-	 */
178
-	private function executeController(Controller $controller, string $methodName): Response {
179
-		$arguments = [];
180
-
181
-		// valid types that will be cast
182
-		$types = ['int', 'integer', 'bool', 'boolean', 'float', 'double'];
183
-
184
-		foreach ($this->reflector->getParameters() as $param => $default) {
185
-			// try to get the parameter from the request object and cast
186
-			// it to the type annotated in the @param annotation
187
-			$value = $this->request->getParam($param, $default);
188
-			$type = $this->reflector->getType($param);
189
-
190
-			// Converted the string `'false'` to false when the controller wants a boolean
191
-			if ($value === 'false' && ($type === 'bool' || $type === 'boolean')) {
192
-				$value = false;
193
-			} elseif ($value !== null && \in_array($type, $types, true)) {
194
-				settype($value, $type);
195
-				$this->ensureParameterValueSatisfiesRange($param, $value);
196
-			} elseif ($value === null && $type !== null && $this->appContainer->has($type)) {
197
-				$value = $this->appContainer->get($type);
198
-			}
199
-
200
-			$arguments[] = $value;
201
-		}
202
-
203
-		$this->eventLogger->start('controller:' . get_class($controller) . '::' . $methodName, 'App framework controller execution');
204
-		$response = \call_user_func_array([$controller, $methodName], $arguments);
205
-		$this->eventLogger->end('controller:' . get_class($controller) . '::' . $methodName);
206
-
207
-		if (!($response instanceof Response)) {
208
-			$this->logger->debug($controller::class . '::' . $methodName . ' returned raw data. Please wrap it in a Response or one of it\'s inheritors.');
209
-		}
210
-
211
-		// format response
212
-		if ($response instanceof DataResponse || !($response instanceof Response)) {
213
-			$format = $this->request->getFormat();
214
-
215
-			if ($format !== null) {
216
-				$response = $controller->buildResponse($response, $format);
217
-			} else {
218
-				$response = $controller->buildResponse($response);
219
-			}
220
-		}
221
-
222
-		return $response;
223
-	}
224
-
225
-	/**
226
-	 * @psalm-param mixed $value
227
-	 * @throws ParameterOutOfRangeException
228
-	 */
229
-	private function ensureParameterValueSatisfiesRange(string $param, $value): void {
230
-		$rangeInfo = $this->reflector->getRange($param);
231
-		if ($rangeInfo) {
232
-			if ($value < $rangeInfo['min'] || $value > $rangeInfo['max']) {
233
-				throw new ParameterOutOfRangeException(
234
-					$param,
235
-					$value,
236
-					$rangeInfo['min'],
237
-					$rangeInfo['max'],
238
-				);
239
-			}
240
-		}
241
-	}
29
+    /** @var MiddlewareDispatcher */
30
+    private $middlewareDispatcher;
31
+
32
+    /** @var Http */
33
+    private $protocol;
34
+
35
+    /** @var ControllerMethodReflector */
36
+    private $reflector;
37
+
38
+    /** @var IRequest */
39
+    private $request;
40
+
41
+    /** @var IConfig */
42
+    private $config;
43
+
44
+    /** @var ConnectionAdapter */
45
+    private $connection;
46
+
47
+    /** @var LoggerInterface */
48
+    private $logger;
49
+
50
+    /** @var IEventLogger */
51
+    private $eventLogger;
52
+
53
+    private ContainerInterface $appContainer;
54
+
55
+    /**
56
+     * @param Http $protocol the http protocol with contains all status headers
57
+     * @param MiddlewareDispatcher $middlewareDispatcher the dispatcher which
58
+     *                                                   runs the middleware
59
+     * @param ControllerMethodReflector $reflector the reflector that is used to inject
60
+     *                                             the arguments for the controller
61
+     * @param IRequest $request the incoming request
62
+     * @param IConfig $config
63
+     * @param ConnectionAdapter $connection
64
+     * @param LoggerInterface $logger
65
+     * @param IEventLogger $eventLogger
66
+     */
67
+    public function __construct(
68
+        Http $protocol,
69
+        MiddlewareDispatcher $middlewareDispatcher,
70
+        ControllerMethodReflector $reflector,
71
+        IRequest $request,
72
+        IConfig $config,
73
+        ConnectionAdapter $connection,
74
+        LoggerInterface $logger,
75
+        IEventLogger $eventLogger,
76
+        ContainerInterface $appContainer,
77
+    ) {
78
+        $this->protocol = $protocol;
79
+        $this->middlewareDispatcher = $middlewareDispatcher;
80
+        $this->reflector = $reflector;
81
+        $this->request = $request;
82
+        $this->config = $config;
83
+        $this->connection = $connection;
84
+        $this->logger = $logger;
85
+        $this->eventLogger = $eventLogger;
86
+        $this->appContainer = $appContainer;
87
+    }
88
+
89
+
90
+    /**
91
+     * Handles a request and calls the dispatcher on the controller
92
+     * @param Controller $controller the controller which will be called
93
+     * @param string $methodName the method name which will be called on
94
+     *                           the controller
95
+     * @return array $array[0] contains the http status header as a string,
96
+     *               $array[1] contains response headers as an array,
97
+     *               $array[2] contains response cookies as an array,
98
+     *               $array[3] contains the response output as a string,
99
+     *               $array[4] contains the response object
100
+     * @throws \Exception
101
+     */
102
+    public function dispatch(Controller $controller, string $methodName): array {
103
+        $out = [null, [], null];
104
+
105
+        try {
106
+            // prefill reflector with everything that's needed for the
107
+            // middlewares
108
+            $this->reflector->reflect($controller, $methodName);
109
+
110
+            $this->middlewareDispatcher->beforeController($controller,
111
+                $methodName);
112
+
113
+            $databaseStatsBefore = [];
114
+            if ($this->config->getSystemValueBool('debug', false)) {
115
+                $databaseStatsBefore = $this->connection->getInner()->getStats();
116
+            }
117
+
118
+            $response = $this->executeController($controller, $methodName);
119
+
120
+            if (!empty($databaseStatsBefore)) {
121
+                $databaseStatsAfter = $this->connection->getInner()->getStats();
122
+                $numBuilt = $databaseStatsAfter['built'] - $databaseStatsBefore['built'];
123
+                $numExecuted = $databaseStatsAfter['executed'] - $databaseStatsBefore['executed'];
124
+
125
+                if ($numBuilt > 50) {
126
+                    $this->logger->debug('Controller {class}::{method} created {count} QueryBuilder objects, please check if they are created inside a loop by accident.', [
127
+                        'class' => get_class($controller),
128
+                        'method' => $methodName,
129
+                        'count' => $numBuilt,
130
+                    ]);
131
+                }
132
+
133
+                if ($numExecuted > 100) {
134
+                    $this->logger->warning('Controller {class}::{method} executed {count} queries.', [
135
+                        'class' => get_class($controller),
136
+                        'method' => $methodName,
137
+                        'count' => $numExecuted,
138
+                    ]);
139
+                }
140
+            }
141
+
142
+            // if an exception appears, the middleware checks if it can handle the
143
+            // exception and creates a response. If no response is created, it is
144
+            // assumed that there's no middleware who can handle it and the error is
145
+            // thrown again
146
+        } catch (\Exception $exception) {
147
+            $response = $this->middlewareDispatcher->afterException(
148
+                $controller, $methodName, $exception);
149
+        } catch (\Throwable $throwable) {
150
+            $exception = new \Exception($throwable->getMessage() . ' in file \'' . $throwable->getFile() . '\' line ' . $throwable->getLine(), $throwable->getCode(), $throwable);
151
+            $response = $this->middlewareDispatcher->afterException(
152
+                $controller, $methodName, $exception);
153
+        }
154
+
155
+        $response = $this->middlewareDispatcher->afterController(
156
+            $controller, $methodName, $response);
157
+
158
+        // depending on the cache object the headers need to be changed
159
+        $out[0] = $this->protocol->getStatusHeader($response->getStatus());
160
+        $out[1] = array_merge($response->getHeaders());
161
+        $out[2] = $response->getCookies();
162
+        $out[3] = $this->middlewareDispatcher->beforeOutput(
163
+            $controller, $methodName, $response->render()
164
+        );
165
+        $out[4] = $response;
166
+
167
+        return $out;
168
+    }
169
+
170
+
171
+    /**
172
+     * Uses the reflected parameters, types and request parameters to execute
173
+     * the controller
174
+     * @param Controller $controller the controller to be executed
175
+     * @param string $methodName the method on the controller that should be executed
176
+     * @return Response
177
+     */
178
+    private function executeController(Controller $controller, string $methodName): Response {
179
+        $arguments = [];
180
+
181
+        // valid types that will be cast
182
+        $types = ['int', 'integer', 'bool', 'boolean', 'float', 'double'];
183
+
184
+        foreach ($this->reflector->getParameters() as $param => $default) {
185
+            // try to get the parameter from the request object and cast
186
+            // it to the type annotated in the @param annotation
187
+            $value = $this->request->getParam($param, $default);
188
+            $type = $this->reflector->getType($param);
189
+
190
+            // Converted the string `'false'` to false when the controller wants a boolean
191
+            if ($value === 'false' && ($type === 'bool' || $type === 'boolean')) {
192
+                $value = false;
193
+            } elseif ($value !== null && \in_array($type, $types, true)) {
194
+                settype($value, $type);
195
+                $this->ensureParameterValueSatisfiesRange($param, $value);
196
+            } elseif ($value === null && $type !== null && $this->appContainer->has($type)) {
197
+                $value = $this->appContainer->get($type);
198
+            }
199
+
200
+            $arguments[] = $value;
201
+        }
202
+
203
+        $this->eventLogger->start('controller:' . get_class($controller) . '::' . $methodName, 'App framework controller execution');
204
+        $response = \call_user_func_array([$controller, $methodName], $arguments);
205
+        $this->eventLogger->end('controller:' . get_class($controller) . '::' . $methodName);
206
+
207
+        if (!($response instanceof Response)) {
208
+            $this->logger->debug($controller::class . '::' . $methodName . ' returned raw data. Please wrap it in a Response or one of it\'s inheritors.');
209
+        }
210
+
211
+        // format response
212
+        if ($response instanceof DataResponse || !($response instanceof Response)) {
213
+            $format = $this->request->getFormat();
214
+
215
+            if ($format !== null) {
216
+                $response = $controller->buildResponse($response, $format);
217
+            } else {
218
+                $response = $controller->buildResponse($response);
219
+            }
220
+        }
221
+
222
+        return $response;
223
+    }
224
+
225
+    /**
226
+     * @psalm-param mixed $value
227
+     * @throws ParameterOutOfRangeException
228
+     */
229
+    private function ensureParameterValueSatisfiesRange(string $param, $value): void {
230
+        $rangeInfo = $this->reflector->getRange($param);
231
+        if ($rangeInfo) {
232
+            if ($value < $rangeInfo['min'] || $value > $rangeInfo['max']) {
233
+                throw new ParameterOutOfRangeException(
234
+                    $param,
235
+                    $value,
236
+                    $rangeInfo['min'],
237
+                    $rangeInfo['max'],
238
+                );
239
+            }
240
+        }
241
+    }
242 242
 }
Please login to merge, or discard this patch.
lib/private/AppFramework/Middleware/OCSMiddleware.php 1 patch
Indentation   +99 added lines, -99 removed lines patch added patch discarded remove patch
@@ -20,106 +20,106 @@
 block discarded – undo
20 20
 use OCP\IRequest;
21 21
 
22 22
 class OCSMiddleware extends Middleware {
23
-	/** @var IRequest */
24
-	private $request;
25
-
26
-	/** @var int */
27
-	private $ocsVersion;
28
-
29
-	/**
30
-	 * @param IRequest $request
31
-	 */
32
-	public function __construct(IRequest $request) {
33
-		$this->request = $request;
34
-	}
35
-
36
-	/**
37
-	 * @param Controller $controller
38
-	 * @param string $methodName
39
-	 */
40
-	public function beforeController($controller, $methodName) {
41
-		if ($controller instanceof OCSController) {
42
-			if (substr_compare($this->request->getScriptName(), '/ocs/v2.php', -strlen('/ocs/v2.php')) === 0) {
43
-				$this->ocsVersion = 2;
44
-			} else {
45
-				$this->ocsVersion = 1;
46
-			}
47
-			$controller->setOCSVersion($this->ocsVersion);
48
-		}
49
-	}
50
-
51
-	/**
52
-	 * @param Controller $controller
53
-	 * @param string $methodName
54
-	 * @param \Exception $exception
55
-	 * @throws \Exception
56
-	 * @return BaseResponse
57
-	 */
58
-	public function afterException($controller, $methodName, \Exception $exception) {
59
-		if ($controller instanceof OCSController && $exception instanceof OCSException) {
60
-			$code = $exception->getCode();
61
-			if ($code === 0) {
62
-				$code = \OCP\AppFramework\OCSController::RESPOND_UNKNOWN_ERROR;
63
-			}
64
-
65
-			return $this->buildNewResponse($controller, $code, $exception->getMessage());
66
-		}
67
-
68
-		throw $exception;
69
-	}
70
-
71
-	/**
72
-	 * @param Controller $controller
73
-	 * @param string $methodName
74
-	 * @param Response $response
75
-	 * @return \OCP\AppFramework\Http\Response
76
-	 */
77
-	public function afterController($controller, $methodName, Response $response) {
78
-		/*
23
+    /** @var IRequest */
24
+    private $request;
25
+
26
+    /** @var int */
27
+    private $ocsVersion;
28
+
29
+    /**
30
+     * @param IRequest $request
31
+     */
32
+    public function __construct(IRequest $request) {
33
+        $this->request = $request;
34
+    }
35
+
36
+    /**
37
+     * @param Controller $controller
38
+     * @param string $methodName
39
+     */
40
+    public function beforeController($controller, $methodName) {
41
+        if ($controller instanceof OCSController) {
42
+            if (substr_compare($this->request->getScriptName(), '/ocs/v2.php', -strlen('/ocs/v2.php')) === 0) {
43
+                $this->ocsVersion = 2;
44
+            } else {
45
+                $this->ocsVersion = 1;
46
+            }
47
+            $controller->setOCSVersion($this->ocsVersion);
48
+        }
49
+    }
50
+
51
+    /**
52
+     * @param Controller $controller
53
+     * @param string $methodName
54
+     * @param \Exception $exception
55
+     * @throws \Exception
56
+     * @return BaseResponse
57
+     */
58
+    public function afterException($controller, $methodName, \Exception $exception) {
59
+        if ($controller instanceof OCSController && $exception instanceof OCSException) {
60
+            $code = $exception->getCode();
61
+            if ($code === 0) {
62
+                $code = \OCP\AppFramework\OCSController::RESPOND_UNKNOWN_ERROR;
63
+            }
64
+
65
+            return $this->buildNewResponse($controller, $code, $exception->getMessage());
66
+        }
67
+
68
+        throw $exception;
69
+    }
70
+
71
+    /**
72
+     * @param Controller $controller
73
+     * @param string $methodName
74
+     * @param Response $response
75
+     * @return \OCP\AppFramework\Http\Response
76
+     */
77
+    public function afterController($controller, $methodName, Response $response) {
78
+        /*
79 79
 		 * If a different middleware has detected that a request unauthorized or forbidden
80 80
 		 * we need to catch the response and convert it to a proper OCS response.
81 81
 		 */
82
-		if ($controller instanceof OCSController && !($response instanceof BaseResponse)) {
83
-			if ($response->getStatus() === Http::STATUS_UNAUTHORIZED) {
84
-				$message = '';
85
-				if ($response instanceof JSONResponse) {
86
-					/** @var DataResponse $response */
87
-					$message = $response->getData()['message'];
88
-				}
89
-
90
-				return $this->buildNewResponse($controller, OCSController::RESPOND_UNAUTHORISED, $message);
91
-			}
92
-			if ($response->getStatus() === Http::STATUS_FORBIDDEN) {
93
-				$message = '';
94
-				if ($response instanceof JSONResponse) {
95
-					/** @var DataResponse $response */
96
-					$message = $response->getData()['message'];
97
-				}
98
-
99
-				return $this->buildNewResponse($controller, Http::STATUS_FORBIDDEN, $message);
100
-			}
101
-		}
102
-
103
-		return $response;
104
-	}
105
-
106
-	/**
107
-	 * @param Controller $controller
108
-	 * @param int $code
109
-	 * @param string $message
110
-	 * @return V1Response|V2Response
111
-	 */
112
-	private function buildNewResponse(Controller $controller, $code, $message) {
113
-		$format = $this->request->getFormat() ?? 'xml';
114
-
115
-		$data = new DataResponse();
116
-		$data->setStatus($code);
117
-		if ($this->ocsVersion === 1) {
118
-			$response = new V1Response($data, $format, $message);
119
-		} else {
120
-			$response = new V2Response($data, $format, $message);
121
-		}
122
-
123
-		return $response;
124
-	}
82
+        if ($controller instanceof OCSController && !($response instanceof BaseResponse)) {
83
+            if ($response->getStatus() === Http::STATUS_UNAUTHORIZED) {
84
+                $message = '';
85
+                if ($response instanceof JSONResponse) {
86
+                    /** @var DataResponse $response */
87
+                    $message = $response->getData()['message'];
88
+                }
89
+
90
+                return $this->buildNewResponse($controller, OCSController::RESPOND_UNAUTHORISED, $message);
91
+            }
92
+            if ($response->getStatus() === Http::STATUS_FORBIDDEN) {
93
+                $message = '';
94
+                if ($response instanceof JSONResponse) {
95
+                    /** @var DataResponse $response */
96
+                    $message = $response->getData()['message'];
97
+                }
98
+
99
+                return $this->buildNewResponse($controller, Http::STATUS_FORBIDDEN, $message);
100
+            }
101
+        }
102
+
103
+        return $response;
104
+    }
105
+
106
+    /**
107
+     * @param Controller $controller
108
+     * @param int $code
109
+     * @param string $message
110
+     * @return V1Response|V2Response
111
+     */
112
+    private function buildNewResponse(Controller $controller, $code, $message) {
113
+        $format = $this->request->getFormat() ?? 'xml';
114
+
115
+        $data = new DataResponse();
116
+        $data->setStatus($code);
117
+        if ($this->ocsVersion === 1) {
118
+            $response = new V1Response($data, $format, $message);
119
+        } else {
120
+            $response = new V2Response($data, $format, $message);
121
+        }
122
+
123
+        return $response;
124
+    }
125 125
 }
Please login to merge, or discard this patch.
lib/public/IRequest.php 1 patch
Indentation   +287 added lines, -287 removed lines patch added patch discarded remove patch
@@ -38,291 +38,291 @@
 block discarded – undo
38 38
  * @since 6.0.0
39 39
  */
40 40
 interface IRequest {
41
-	/**
42
-	 * @since 9.1.0
43
-	 * @since 28.0.0 The regex has a group matching the version number
44
-	 */
45
-	public const USER_AGENT_CLIENT_ANDROID = '/^Mozilla\/5\.0 \(Android\) (?:ownCloud|Nextcloud)\-android\/([^ ]*).*$/';
46
-
47
-	/**
48
-	 * @since 13.0.0
49
-	 * @since 28.0.0 The regex has a group matching the version number
50
-	 */
51
-	public const USER_AGENT_TALK_ANDROID = '/^Mozilla\/5\.0 \(Android\) Nextcloud\-Talk v([^ ]*).*$/';
52
-
53
-	/**
54
-	 * @since 9.1.0
55
-	 * @since 28.0.0 The regex has a group matching the version number
56
-	 */
57
-	public const USER_AGENT_CLIENT_DESKTOP = '/^Mozilla\/5\.0 \([A-Za-z ]+\) (?:mirall|csyncoC)\/([^ ]*).*$/';
58
-
59
-	/**
60
-	 * @since 26.0.0
61
-	 * @since 28.0.0 The regex has a group matching the version number
62
-	 */
63
-	public const USER_AGENT_TALK_DESKTOP = '/^Mozilla\/5\.0 \((?!Android|iOS)[A-Za-z ]+\) Nextcloud\-Talk v([^ ]*).*$/';
64
-
65
-	/**
66
-	 * @since 9.1.0
67
-	 * @since 28.0.0 The regex has a group matching the version number
68
-	 */
69
-	public const USER_AGENT_CLIENT_IOS = '/^Mozilla\/5\.0 \(iOS\) (?:ownCloud|Nextcloud)\-iOS\/([^ ]*).*$/';
70
-
71
-	/**
72
-	 * @since 13.0.0
73
-	 * @since 28.0.0 The regex has a group matching the version number
74
-	 */
75
-	public const USER_AGENT_TALK_IOS = '/^Mozilla\/5\.0 \(iOS\) Nextcloud\-Talk v([^ ]*).*$/';
76
-
77
-	/**
78
-	 * @since 13.0.1
79
-	 * @since 28.0.0 The regex has a group matching the version number
80
-	 */
81
-	public const USER_AGENT_OUTLOOK_ADDON = '/^Mozilla\/5\.0 \([A-Za-z ]+\) Nextcloud\-Outlook v([^ ]*).*$/';
82
-
83
-	/**
84
-	 * @since 13.0.1
85
-	 * @since 28.0.0 The regex has a group matching the version number
86
-	 */
87
-	public const USER_AGENT_THUNDERBIRD_ADDON = '/^Filelink for \*cloud\/([1-9]\d*\.\d+\.\d+)$/';
88
-
89
-	/**
90
-	 * @since 26.0.0
91
-	 */
92
-	public const JSON_CONTENT_TYPE_REGEX = '/^application\/(?:[a-z0-9.-]+\+)?json\b/';
93
-
94
-	/**
95
-	 * @param string $name
96
-	 *
97
-	 * @psalm-taint-source input
98
-	 *
99
-	 * @return string
100
-	 * @since 6.0.0
101
-	 */
102
-	public function getHeader(string $name): string;
103
-
104
-	/**
105
-	 * Lets you access post and get parameters by the index
106
-	 * In case of json requests the encoded json body is accessed
107
-	 *
108
-	 * @psalm-taint-source input
109
-	 *
110
-	 * @param string $key the key which you want to access in the URL Parameter
111
-	 *                    placeholder, $_POST or $_GET array.
112
-	 *                    The priority how they're returned is the following:
113
-	 *                    1. URL parameters
114
-	 *                    2. POST parameters
115
-	 *                    3. GET parameters
116
-	 * @param mixed $default If the key is not found, this value will be returned
117
-	 * @return mixed the content of the array
118
-	 * @since 6.0.0
119
-	 */
120
-	public function getParam(string $key, $default = null);
121
-
122
-
123
-	/**
124
-	 * Returns all params that were received, be it from the request
125
-	 *
126
-	 * (as GET or POST) or through the URL by the route
127
-	 *
128
-	 * @psalm-taint-source input
129
-	 *
130
-	 * @return array the array with all parameters
131
-	 * @since 6.0.0
132
-	 */
133
-	public function getParams(): array;
134
-
135
-	/**
136
-	 * Returns the method of the request
137
-	 *
138
-	 * @return string the method of the request (POST, GET, etc)
139
-	 * @since 6.0.0
140
-	 */
141
-	public function getMethod(): string;
142
-
143
-	/**
144
-	 * Shortcut for accessing an uploaded file through the $_FILES array
145
-	 *
146
-	 * @param string $key the key that will be taken from the $_FILES array
147
-	 * @return array the file in the $_FILES element
148
-	 * @since 6.0.0
149
-	 */
150
-	public function getUploadedFile(string $key);
151
-
152
-
153
-	/**
154
-	 * Shortcut for getting env variables
155
-	 *
156
-	 * @param string $key the key that will be taken from the $_ENV array
157
-	 * @return array the value in the $_ENV element
158
-	 * @since 6.0.0
159
-	 */
160
-	public function getEnv(string $key);
161
-
162
-
163
-	/**
164
-	 * Shortcut for getting cookie variables
165
-	 *
166
-	 * @psalm-taint-source input
167
-	 *
168
-	 * @param string $key the key that will be taken from the $_COOKIE array
169
-	 * @return string|null the value in the $_COOKIE element
170
-	 * @since 6.0.0
171
-	 */
172
-	public function getCookie(string $key);
173
-
174
-
175
-	/**
176
-	 * Checks if the CSRF check was correct
177
-	 *
178
-	 * @return bool true if CSRF check passed
179
-	 * @since 6.0.0
180
-	 */
181
-	public function passesCSRFCheck(): bool;
182
-
183
-	/**
184
-	 * Checks if the strict cookie has been sent with the request if the request
185
-	 * is including any cookies.
186
-	 *
187
-	 * @return bool
188
-	 * @since 9.0.0
189
-	 */
190
-	public function passesStrictCookieCheck(): bool;
191
-
192
-	/**
193
-	 * Checks if the lax cookie has been sent with the request if the request
194
-	 * is including any cookies.
195
-	 *
196
-	 * @return bool
197
-	 * @since 9.0.0
198
-	 */
199
-	public function passesLaxCookieCheck(): bool;
200
-
201
-	/**
202
-	 * Returns an ID for the request, value is not guaranteed to be unique and is mostly meant for logging
203
-	 * If `mod_unique_id` is installed this value will be taken.
204
-	 *
205
-	 * @return string
206
-	 * @since 8.1.0
207
-	 */
208
-	public function getId(): string;
209
-
210
-	/**
211
-	 * Returns the remote address, if the connection came from a trusted proxy
212
-	 * and `forwarded_for_headers` has been configured then the IP address
213
-	 * specified in this header will be returned instead.
214
-	 * Do always use this instead of $_SERVER['REMOTE_ADDR']
215
-	 *
216
-	 * @return string IP address
217
-	 * @since 8.1.0
218
-	 */
219
-	public function getRemoteAddress(): string;
220
-
221
-	/**
222
-	 * Returns the server protocol. It respects reverse proxy servers and load
223
-	 * balancers.
224
-	 *
225
-	 * @return string Server protocol (http or https)
226
-	 * @since 8.1.0
227
-	 */
228
-	public function getServerProtocol(): string;
229
-
230
-	/**
231
-	 * Returns the used HTTP protocol.
232
-	 *
233
-	 * @return string HTTP protocol. HTTP/2, HTTP/1.1 or HTTP/1.0.
234
-	 * @since 8.2.0
235
-	 */
236
-	public function getHttpProtocol(): string;
237
-
238
-	/**
239
-	 * Returns the request uri, even if the website uses one or more
240
-	 * reverse proxies
241
-	 *
242
-	 * @psalm-taint-source input
243
-	 *
244
-	 * @return string
245
-	 * @since 8.1.0
246
-	 */
247
-	public function getRequestUri(): string;
248
-
249
-	/**
250
-	 * Get raw PathInfo from request (not urldecoded)
251
-	 *
252
-	 * @psalm-taint-source input
253
-	 *
254
-	 * @throws \Exception
255
-	 * @return string Path info
256
-	 * @since 8.1.0
257
-	 */
258
-	public function getRawPathInfo(): string;
259
-
260
-	/**
261
-	 * Get PathInfo from request
262
-	 *
263
-	 * @psalm-taint-source input
264
-	 *
265
-	 * @throws \Exception
266
-	 * @return string|false Path info or false when not found
267
-	 * @since 8.1.0
268
-	 */
269
-	public function getPathInfo();
270
-
271
-	/**
272
-	 * Returns the script name, even if the website uses one or more
273
-	 * reverse proxies
274
-	 *
275
-	 * @return string the script name
276
-	 * @since 8.1.0
277
-	 */
278
-	public function getScriptName(): string;
279
-
280
-	/**
281
-	 * Checks whether the user agent matches a given regex
282
-	 *
283
-	 * @param array $agent array of agent names
284
-	 * @return bool true if at least one of the given agent matches, false otherwise
285
-	 * @since 8.1.0
286
-	 */
287
-	public function isUserAgent(array $agent): bool;
288
-
289
-	/**
290
-	 * Returns the unverified server host from the headers without checking
291
-	 * whether it is a trusted domain
292
-	 *
293
-	 * @psalm-taint-source input
294
-	 *
295
-	 * @return string Server host
296
-	 * @since 8.1.0
297
-	 */
298
-	public function getInsecureServerHost(): string;
299
-
300
-	/**
301
-	 * Returns the server host from the headers, or the first configured
302
-	 * trusted domain if the host isn't in the trusted list
303
-	 *
304
-	 * @return string Server host
305
-	 * @since 8.1.0
306
-	 */
307
-	public function getServerHost(): string;
308
-
309
-	/**
310
-	 * If decoding the request content failed, throw an exception.
311
-	 * Currently only \JsonException for json decoding errors,
312
-	 * but in the future may throw other exceptions for other decoding issues.
313
-	 *
314
-	 * @throws \Exception
315
-	 * @since 32.0.0
316
-	 */
317
-	public function throwDecodingExceptionIfAny(): void;
318
-
319
-	/**
320
-	 * Returns the format of the response to this request.
321
-	 *
322
-	 * The `Accept` header and the `format` query parameter control the format.
323
-	 *
324
-	 * @return string|null
325
-	 * @since 33.0.0
326
-	 */
327
-	public function getFormat(): ?string;
41
+    /**
42
+     * @since 9.1.0
43
+     * @since 28.0.0 The regex has a group matching the version number
44
+     */
45
+    public const USER_AGENT_CLIENT_ANDROID = '/^Mozilla\/5\.0 \(Android\) (?:ownCloud|Nextcloud)\-android\/([^ ]*).*$/';
46
+
47
+    /**
48
+     * @since 13.0.0
49
+     * @since 28.0.0 The regex has a group matching the version number
50
+     */
51
+    public const USER_AGENT_TALK_ANDROID = '/^Mozilla\/5\.0 \(Android\) Nextcloud\-Talk v([^ ]*).*$/';
52
+
53
+    /**
54
+     * @since 9.1.0
55
+     * @since 28.0.0 The regex has a group matching the version number
56
+     */
57
+    public const USER_AGENT_CLIENT_DESKTOP = '/^Mozilla\/5\.0 \([A-Za-z ]+\) (?:mirall|csyncoC)\/([^ ]*).*$/';
58
+
59
+    /**
60
+     * @since 26.0.0
61
+     * @since 28.0.0 The regex has a group matching the version number
62
+     */
63
+    public const USER_AGENT_TALK_DESKTOP = '/^Mozilla\/5\.0 \((?!Android|iOS)[A-Za-z ]+\) Nextcloud\-Talk v([^ ]*).*$/';
64
+
65
+    /**
66
+     * @since 9.1.0
67
+     * @since 28.0.0 The regex has a group matching the version number
68
+     */
69
+    public const USER_AGENT_CLIENT_IOS = '/^Mozilla\/5\.0 \(iOS\) (?:ownCloud|Nextcloud)\-iOS\/([^ ]*).*$/';
70
+
71
+    /**
72
+     * @since 13.0.0
73
+     * @since 28.0.0 The regex has a group matching the version number
74
+     */
75
+    public const USER_AGENT_TALK_IOS = '/^Mozilla\/5\.0 \(iOS\) Nextcloud\-Talk v([^ ]*).*$/';
76
+
77
+    /**
78
+     * @since 13.0.1
79
+     * @since 28.0.0 The regex has a group matching the version number
80
+     */
81
+    public const USER_AGENT_OUTLOOK_ADDON = '/^Mozilla\/5\.0 \([A-Za-z ]+\) Nextcloud\-Outlook v([^ ]*).*$/';
82
+
83
+    /**
84
+     * @since 13.0.1
85
+     * @since 28.0.0 The regex has a group matching the version number
86
+     */
87
+    public const USER_AGENT_THUNDERBIRD_ADDON = '/^Filelink for \*cloud\/([1-9]\d*\.\d+\.\d+)$/';
88
+
89
+    /**
90
+     * @since 26.0.0
91
+     */
92
+    public const JSON_CONTENT_TYPE_REGEX = '/^application\/(?:[a-z0-9.-]+\+)?json\b/';
93
+
94
+    /**
95
+     * @param string $name
96
+     *
97
+     * @psalm-taint-source input
98
+     *
99
+     * @return string
100
+     * @since 6.0.0
101
+     */
102
+    public function getHeader(string $name): string;
103
+
104
+    /**
105
+     * Lets you access post and get parameters by the index
106
+     * In case of json requests the encoded json body is accessed
107
+     *
108
+     * @psalm-taint-source input
109
+     *
110
+     * @param string $key the key which you want to access in the URL Parameter
111
+     *                    placeholder, $_POST or $_GET array.
112
+     *                    The priority how they're returned is the following:
113
+     *                    1. URL parameters
114
+     *                    2. POST parameters
115
+     *                    3. GET parameters
116
+     * @param mixed $default If the key is not found, this value will be returned
117
+     * @return mixed the content of the array
118
+     * @since 6.0.0
119
+     */
120
+    public function getParam(string $key, $default = null);
121
+
122
+
123
+    /**
124
+     * Returns all params that were received, be it from the request
125
+     *
126
+     * (as GET or POST) or through the URL by the route
127
+     *
128
+     * @psalm-taint-source input
129
+     *
130
+     * @return array the array with all parameters
131
+     * @since 6.0.0
132
+     */
133
+    public function getParams(): array;
134
+
135
+    /**
136
+     * Returns the method of the request
137
+     *
138
+     * @return string the method of the request (POST, GET, etc)
139
+     * @since 6.0.0
140
+     */
141
+    public function getMethod(): string;
142
+
143
+    /**
144
+     * Shortcut for accessing an uploaded file through the $_FILES array
145
+     *
146
+     * @param string $key the key that will be taken from the $_FILES array
147
+     * @return array the file in the $_FILES element
148
+     * @since 6.0.0
149
+     */
150
+    public function getUploadedFile(string $key);
151
+
152
+
153
+    /**
154
+     * Shortcut for getting env variables
155
+     *
156
+     * @param string $key the key that will be taken from the $_ENV array
157
+     * @return array the value in the $_ENV element
158
+     * @since 6.0.0
159
+     */
160
+    public function getEnv(string $key);
161
+
162
+
163
+    /**
164
+     * Shortcut for getting cookie variables
165
+     *
166
+     * @psalm-taint-source input
167
+     *
168
+     * @param string $key the key that will be taken from the $_COOKIE array
169
+     * @return string|null the value in the $_COOKIE element
170
+     * @since 6.0.0
171
+     */
172
+    public function getCookie(string $key);
173
+
174
+
175
+    /**
176
+     * Checks if the CSRF check was correct
177
+     *
178
+     * @return bool true if CSRF check passed
179
+     * @since 6.0.0
180
+     */
181
+    public function passesCSRFCheck(): bool;
182
+
183
+    /**
184
+     * Checks if the strict cookie has been sent with the request if the request
185
+     * is including any cookies.
186
+     *
187
+     * @return bool
188
+     * @since 9.0.0
189
+     */
190
+    public function passesStrictCookieCheck(): bool;
191
+
192
+    /**
193
+     * Checks if the lax cookie has been sent with the request if the request
194
+     * is including any cookies.
195
+     *
196
+     * @return bool
197
+     * @since 9.0.0
198
+     */
199
+    public function passesLaxCookieCheck(): bool;
200
+
201
+    /**
202
+     * Returns an ID for the request, value is not guaranteed to be unique and is mostly meant for logging
203
+     * If `mod_unique_id` is installed this value will be taken.
204
+     *
205
+     * @return string
206
+     * @since 8.1.0
207
+     */
208
+    public function getId(): string;
209
+
210
+    /**
211
+     * Returns the remote address, if the connection came from a trusted proxy
212
+     * and `forwarded_for_headers` has been configured then the IP address
213
+     * specified in this header will be returned instead.
214
+     * Do always use this instead of $_SERVER['REMOTE_ADDR']
215
+     *
216
+     * @return string IP address
217
+     * @since 8.1.0
218
+     */
219
+    public function getRemoteAddress(): string;
220
+
221
+    /**
222
+     * Returns the server protocol. It respects reverse proxy servers and load
223
+     * balancers.
224
+     *
225
+     * @return string Server protocol (http or https)
226
+     * @since 8.1.0
227
+     */
228
+    public function getServerProtocol(): string;
229
+
230
+    /**
231
+     * Returns the used HTTP protocol.
232
+     *
233
+     * @return string HTTP protocol. HTTP/2, HTTP/1.1 or HTTP/1.0.
234
+     * @since 8.2.0
235
+     */
236
+    public function getHttpProtocol(): string;
237
+
238
+    /**
239
+     * Returns the request uri, even if the website uses one or more
240
+     * reverse proxies
241
+     *
242
+     * @psalm-taint-source input
243
+     *
244
+     * @return string
245
+     * @since 8.1.0
246
+     */
247
+    public function getRequestUri(): string;
248
+
249
+    /**
250
+     * Get raw PathInfo from request (not urldecoded)
251
+     *
252
+     * @psalm-taint-source input
253
+     *
254
+     * @throws \Exception
255
+     * @return string Path info
256
+     * @since 8.1.0
257
+     */
258
+    public function getRawPathInfo(): string;
259
+
260
+    /**
261
+     * Get PathInfo from request
262
+     *
263
+     * @psalm-taint-source input
264
+     *
265
+     * @throws \Exception
266
+     * @return string|false Path info or false when not found
267
+     * @since 8.1.0
268
+     */
269
+    public function getPathInfo();
270
+
271
+    /**
272
+     * Returns the script name, even if the website uses one or more
273
+     * reverse proxies
274
+     *
275
+     * @return string the script name
276
+     * @since 8.1.0
277
+     */
278
+    public function getScriptName(): string;
279
+
280
+    /**
281
+     * Checks whether the user agent matches a given regex
282
+     *
283
+     * @param array $agent array of agent names
284
+     * @return bool true if at least one of the given agent matches, false otherwise
285
+     * @since 8.1.0
286
+     */
287
+    public function isUserAgent(array $agent): bool;
288
+
289
+    /**
290
+     * Returns the unverified server host from the headers without checking
291
+     * whether it is a trusted domain
292
+     *
293
+     * @psalm-taint-source input
294
+     *
295
+     * @return string Server host
296
+     * @since 8.1.0
297
+     */
298
+    public function getInsecureServerHost(): string;
299
+
300
+    /**
301
+     * Returns the server host from the headers, or the first configured
302
+     * trusted domain if the host isn't in the trusted list
303
+     *
304
+     * @return string Server host
305
+     * @since 8.1.0
306
+     */
307
+    public function getServerHost(): string;
308
+
309
+    /**
310
+     * If decoding the request content failed, throw an exception.
311
+     * Currently only \JsonException for json decoding errors,
312
+     * but in the future may throw other exceptions for other decoding issues.
313
+     *
314
+     * @throws \Exception
315
+     * @since 32.0.0
316
+     */
317
+    public function throwDecodingExceptionIfAny(): void;
318
+
319
+    /**
320
+     * Returns the format of the response to this request.
321
+     *
322
+     * The `Accept` header and the `format` query parameter control the format.
323
+     *
324
+     * @return string|null
325
+     * @since 33.0.0
326
+     */
327
+    public function getFormat(): ?string;
328 328
 }
Please login to merge, or discard this patch.
lib/public/AppFramework/Controller.php 1 patch
Indentation   +122 added lines, -122 removed lines patch added patch discarded remove patch
@@ -17,126 +17,126 @@
 block discarded – undo
17 17
  * @since 6.0.0
18 18
  */
19 19
 abstract class Controller {
20
-	/**
21
-	 * app name
22
-	 * @var string
23
-	 * @since 7.0.0
24
-	 */
25
-	protected $appName;
26
-
27
-	/**
28
-	 * current request
29
-	 * @var \OCP\IRequest
30
-	 * @since 6.0.0
31
-	 */
32
-	protected $request;
33
-
34
-	/**
35
-	 * @var array
36
-	 * @since 7.0.0
37
-	 */
38
-	private $responders;
39
-
40
-	/**
41
-	 * constructor of the controller
42
-	 * @param string $appName the name of the app
43
-	 * @param IRequest $request an instance of the request
44
-	 * @since 6.0.0 - parameter $appName was added in 7.0.0 - parameter $app was removed in 7.0.0
45
-	 */
46
-	public function __construct($appName,
47
-		IRequest $request) {
48
-		$this->appName = $appName;
49
-		$this->request = $request;
50
-
51
-		// default responders
52
-		$this->responders = [
53
-			'json' => function ($data) {
54
-				if ($data instanceof DataResponse) {
55
-					$response = new JSONResponse(
56
-						$data->getData(),
57
-						$data->getStatus()
58
-					);
59
-					$dataHeaders = $data->getHeaders();
60
-					$headers = $response->getHeaders();
61
-					// do not overwrite Content-Type if it already exists
62
-					if (isset($dataHeaders['Content-Type'])) {
63
-						unset($headers['Content-Type']);
64
-					}
65
-					$response->setHeaders(array_merge($dataHeaders, $headers));
66
-
67
-					if ($data->getETag() !== null) {
68
-						$response->setETag($data->getETag());
69
-					}
70
-					if ($data->getLastModified() !== null) {
71
-						$response->setLastModified($data->getLastModified());
72
-					}
73
-					if ($data->isThrottled()) {
74
-						$response->throttle($data->getThrottleMetadata());
75
-					}
76
-
77
-					return $response;
78
-				}
79
-				return new JSONResponse($data);
80
-			}
81
-		];
82
-	}
83
-
84
-
85
-	/**
86
-	 * Parses an HTTP accept header and returns the supported responder type
87
-	 * @param string $acceptHeader
88
-	 * @param string $default
89
-	 * @return string the responder type
90
-	 * @since 7.0.0
91
-	 * @since 9.1.0 Added default parameter
92
-	 * @deprecated 33.0.0 Use {@see \OCP\IRequest::getFormat} instead
93
-	 */
94
-	public function getResponderByHTTPHeader($acceptHeader, $default = 'json') {
95
-		$headers = explode(',', $acceptHeader);
96
-
97
-		// return the first matching responder
98
-		foreach ($headers as $header) {
99
-			$header = strtolower(trim($header));
100
-
101
-			$responder = str_replace('application/', '', $header);
102
-
103
-			if (array_key_exists($responder, $this->responders)) {
104
-				return $responder;
105
-			}
106
-		}
107
-
108
-		// no matching header return default
109
-		return $default;
110
-	}
111
-
112
-
113
-	/**
114
-	 * Registers a formatter for a type
115
-	 * @param string $format
116
-	 * @param \Closure $responder
117
-	 * @since 7.0.0
118
-	 */
119
-	protected function registerResponder($format, \Closure $responder) {
120
-		$this->responders[$format] = $responder;
121
-	}
122
-
123
-
124
-	/**
125
-	 * Serializes and formats a response
126
-	 * @param mixed $response the value that was returned from a controller and
127
-	 *                        is not a Response instance
128
-	 * @param string $format the format for which a formatter has been registered
129
-	 * @throws \DomainException if format does not match a registered formatter
130
-	 * @return Response
131
-	 * @since 7.0.0
132
-	 */
133
-	public function buildResponse($response, $format = 'json') {
134
-		if (array_key_exists($format, $this->responders)) {
135
-			$responder = $this->responders[$format];
136
-
137
-			return $responder($response);
138
-		}
139
-		throw new \DomainException('No responder registered for format '
140
-			. $format . '!');
141
-	}
20
+    /**
21
+     * app name
22
+     * @var string
23
+     * @since 7.0.0
24
+     */
25
+    protected $appName;
26
+
27
+    /**
28
+     * current request
29
+     * @var \OCP\IRequest
30
+     * @since 6.0.0
31
+     */
32
+    protected $request;
33
+
34
+    /**
35
+     * @var array
36
+     * @since 7.0.0
37
+     */
38
+    private $responders;
39
+
40
+    /**
41
+     * constructor of the controller
42
+     * @param string $appName the name of the app
43
+     * @param IRequest $request an instance of the request
44
+     * @since 6.0.0 - parameter $appName was added in 7.0.0 - parameter $app was removed in 7.0.0
45
+     */
46
+    public function __construct($appName,
47
+        IRequest $request) {
48
+        $this->appName = $appName;
49
+        $this->request = $request;
50
+
51
+        // default responders
52
+        $this->responders = [
53
+            'json' => function ($data) {
54
+                if ($data instanceof DataResponse) {
55
+                    $response = new JSONResponse(
56
+                        $data->getData(),
57
+                        $data->getStatus()
58
+                    );
59
+                    $dataHeaders = $data->getHeaders();
60
+                    $headers = $response->getHeaders();
61
+                    // do not overwrite Content-Type if it already exists
62
+                    if (isset($dataHeaders['Content-Type'])) {
63
+                        unset($headers['Content-Type']);
64
+                    }
65
+                    $response->setHeaders(array_merge($dataHeaders, $headers));
66
+
67
+                    if ($data->getETag() !== null) {
68
+                        $response->setETag($data->getETag());
69
+                    }
70
+                    if ($data->getLastModified() !== null) {
71
+                        $response->setLastModified($data->getLastModified());
72
+                    }
73
+                    if ($data->isThrottled()) {
74
+                        $response->throttle($data->getThrottleMetadata());
75
+                    }
76
+
77
+                    return $response;
78
+                }
79
+                return new JSONResponse($data);
80
+            }
81
+        ];
82
+    }
83
+
84
+
85
+    /**
86
+     * Parses an HTTP accept header and returns the supported responder type
87
+     * @param string $acceptHeader
88
+     * @param string $default
89
+     * @return string the responder type
90
+     * @since 7.0.0
91
+     * @since 9.1.0 Added default parameter
92
+     * @deprecated 33.0.0 Use {@see \OCP\IRequest::getFormat} instead
93
+     */
94
+    public function getResponderByHTTPHeader($acceptHeader, $default = 'json') {
95
+        $headers = explode(',', $acceptHeader);
96
+
97
+        // return the first matching responder
98
+        foreach ($headers as $header) {
99
+            $header = strtolower(trim($header));
100
+
101
+            $responder = str_replace('application/', '', $header);
102
+
103
+            if (array_key_exists($responder, $this->responders)) {
104
+                return $responder;
105
+            }
106
+        }
107
+
108
+        // no matching header return default
109
+        return $default;
110
+    }
111
+
112
+
113
+    /**
114
+     * Registers a formatter for a type
115
+     * @param string $format
116
+     * @param \Closure $responder
117
+     * @since 7.0.0
118
+     */
119
+    protected function registerResponder($format, \Closure $responder) {
120
+        $this->responders[$format] = $responder;
121
+    }
122
+
123
+
124
+    /**
125
+     * Serializes and formats a response
126
+     * @param mixed $response the value that was returned from a controller and
127
+     *                        is not a Response instance
128
+     * @param string $format the format for which a formatter has been registered
129
+     * @throws \DomainException if format does not match a registered formatter
130
+     * @return Response
131
+     * @since 7.0.0
132
+     */
133
+    public function buildResponse($response, $format = 'json') {
134
+        if (array_key_exists($format, $this->responders)) {
135
+            $responder = $this->responders[$format];
136
+
137
+            return $responder($response);
138
+        }
139
+        throw new \DomainException('No responder registered for format '
140
+            . $format . '!');
141
+    }
142 142
 }
Please login to merge, or discard this patch.