Passed
Push — master ( 73495b...610a20 )
by Julius
29:00 queued 14:44
created
lib/private/AppFramework/Http/Request.php 1 patch
Indentation   +806 added lines, -806 removed lines patch added patch discarded remove patch
@@ -65,810 +65,810 @@
 block discarded – undo
65 65
  * @property mixed[] server
66 66
  */
67 67
 class Request implements \ArrayAccess, \Countable, IRequest {
68
-	public const USER_AGENT_IE = '/(MSIE)|(Trident)/';
69
-	// Microsoft Edge User Agent from https://msdn.microsoft.com/en-us/library/hh869301(v=vs.85).aspx
70
-	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.]+$/';
71
-	// Firefox User Agent from https://developer.mozilla.org/en-US/docs/Web/HTTP/Gecko_user_agent_string_reference
72
-	public const USER_AGENT_FIREFOX = '/^Mozilla\/5\.0 \([^)]+\) Gecko\/[0-9.]+ Firefox\/[0-9.]+$/';
73
-	// Chrome User Agent from https://developer.chrome.com/multidevice/user-agent
74
-	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.]+|)$/';
75
-	// Safari User Agent from http://www.useragentstring.com/pages/Safari/
76
-	public const USER_AGENT_SAFARI = '/^Mozilla\/5\.0 \([^)]+\) AppleWebKit\/[0-9.]+ \(KHTML, like Gecko\) Version\/[0-9.]+ Safari\/[0-9.A-Z]+$/';
77
-	// Android Chrome user agent: https://developers.google.com/chrome/mobile/docs/user-agent
78
-	public const USER_AGENT_ANDROID_MOBILE_CHROME = '#Android.*Chrome/[.0-9]*#';
79
-	public const USER_AGENT_FREEBOX = '#^Mozilla/5\.0$#';
80
-	public const REGEX_LOCALHOST = '/^(127\.0\.0\.1|localhost|\[::1\])$/';
81
-
82
-	protected string $inputStream;
83
-	protected $content;
84
-	protected array $items = [];
85
-	protected array $allowedKeys = [
86
-		'get',
87
-		'post',
88
-		'files',
89
-		'server',
90
-		'env',
91
-		'cookies',
92
-		'urlParams',
93
-		'parameters',
94
-		'method',
95
-		'requesttoken',
96
-	];
97
-	protected IRequestId $requestId;
98
-	protected IConfig $config;
99
-	protected ?CsrfTokenManager $csrfTokenManager;
100
-
101
-	protected bool $contentDecoded = false;
102
-
103
-	/**
104
-	 * @param array $vars An associative array with the following optional values:
105
-	 *        - array 'urlParams' the parameters which were matched from the URL
106
-	 *        - array 'get' the $_GET array
107
-	 *        - array|string 'post' the $_POST array or JSON string
108
-	 *        - array 'files' the $_FILES array
109
-	 *        - array 'server' the $_SERVER array
110
-	 *        - array 'env' the $_ENV array
111
-	 *        - array 'cookies' the $_COOKIE array
112
-	 *        - string 'method' the request method (GET, POST etc)
113
-	 *        - string|false 'requesttoken' the requesttoken or false when not available
114
-	 * @param IRequestId $requestId
115
-	 * @param IConfig $config
116
-	 * @param CsrfTokenManager|null $csrfTokenManager
117
-	 * @param string $stream
118
-	 * @see https://www.php.net/manual/en/reserved.variables.php
119
-	 */
120
-	public function __construct(array $vars,
121
-								IRequestId $requestId,
122
-								IConfig $config,
123
-								CsrfTokenManager $csrfTokenManager = null,
124
-								string $stream = 'php://input') {
125
-		$this->inputStream = $stream;
126
-		$this->items['params'] = [];
127
-		$this->requestId = $requestId;
128
-		$this->config = $config;
129
-		$this->csrfTokenManager = $csrfTokenManager;
130
-
131
-		if (!array_key_exists('method', $vars)) {
132
-			$vars['method'] = 'GET';
133
-		}
134
-
135
-		foreach ($this->allowedKeys as $name) {
136
-			$this->items[$name] = $vars[$name] ?? [];
137
-		}
138
-
139
-		$this->items['parameters'] = array_merge(
140
-			$this->items['get'],
141
-			$this->items['post'],
142
-			$this->items['urlParams'],
143
-			$this->items['params']
144
-		);
145
-	}
146
-	/**
147
-	 * @param array $parameters
148
-	 */
149
-	public function setUrlParameters(array $parameters) {
150
-		$this->items['urlParams'] = $parameters;
151
-		$this->items['parameters'] = array_merge(
152
-			$this->items['parameters'],
153
-			$this->items['urlParams']
154
-		);
155
-	}
156
-
157
-	/**
158
-	 * Countable method
159
-	 * @return int
160
-	 */
161
-	public function count(): int {
162
-		return \count($this->items['parameters']);
163
-	}
164
-
165
-	/**
166
-	 * ArrayAccess methods
167
-	 *
168
-	 * Gives access to the combined GET, POST and urlParams arrays
169
-	 *
170
-	 * Examples:
171
-	 *
172
-	 * $var = $request['myvar'];
173
-	 *
174
-	 * or
175
-	 *
176
-	 * if(!isset($request['myvar']) {
177
-	 * 	// Do something
178
-	 * }
179
-	 *
180
-	 * $request['myvar'] = 'something'; // This throws an exception.
181
-	 *
182
-	 * @param string $offset The key to lookup
183
-	 * @return boolean
184
-	 */
185
-	public function offsetExists($offset): bool {
186
-		return isset($this->items['parameters'][$offset]);
187
-	}
188
-
189
-	/**
190
-	 * @see offsetExists
191
-	 * @param string $offset
192
-	 * @return mixed
193
-	 */
194
-	#[\ReturnTypeWillChange]
195
-	public function offsetGet($offset) {
196
-		return isset($this->items['parameters'][$offset])
197
-			? $this->items['parameters'][$offset]
198
-			: null;
199
-	}
200
-
201
-	/**
202
-	 * @see offsetExists
203
-	 * @param string $offset
204
-	 * @param mixed $value
205
-	 */
206
-	public function offsetSet($offset, $value): void {
207
-		throw new \RuntimeException('You cannot change the contents of the request object');
208
-	}
209
-
210
-	/**
211
-	 * @see offsetExists
212
-	 * @param string $offset
213
-	 */
214
-	public function offsetUnset($offset): void {
215
-		throw new \RuntimeException('You cannot change the contents of the request object');
216
-	}
217
-
218
-	/**
219
-	 * Magic property accessors
220
-	 * @param string $name
221
-	 * @param mixed $value
222
-	 */
223
-	public function __set($name, $value) {
224
-		throw new \RuntimeException('You cannot change the contents of the request object');
225
-	}
226
-
227
-	/**
228
-	 * Access request variables by method and name.
229
-	 * Examples:
230
-	 *
231
-	 * $request->post['myvar']; // Only look for POST variables
232
-	 * $request->myvar; or $request->{'myvar'}; or $request->{$myvar}
233
-	 * Looks in the combined GET, POST and urlParams array.
234
-	 *
235
-	 * If you access e.g. ->post but the current HTTP request method
236
-	 * is GET a \LogicException will be thrown.
237
-	 *
238
-	 * @param string $name The key to look for.
239
-	 * @throws \LogicException
240
-	 * @return mixed|null
241
-	 */
242
-	public function __get($name) {
243
-		switch ($name) {
244
-			case 'put':
245
-			case 'patch':
246
-			case 'get':
247
-			case 'post':
248
-				if ($this->method !== strtoupper($name)) {
249
-					throw new \LogicException(sprintf('%s cannot be accessed in a %s request.', $name, $this->method));
250
-				}
251
-				return $this->getContent();
252
-			case 'files':
253
-			case 'server':
254
-			case 'env':
255
-			case 'cookies':
256
-			case 'urlParams':
257
-			case 'method':
258
-				return isset($this->items[$name])
259
-					? $this->items[$name]
260
-					: null;
261
-			case 'parameters':
262
-			case 'params':
263
-				if ($this->isPutStreamContent()) {
264
-					return $this->items['parameters'];
265
-				}
266
-				return $this->getContent();
267
-			default:
268
-				return isset($this[$name])
269
-					? $this[$name]
270
-					: null;
271
-		}
272
-	}
273
-
274
-	/**
275
-	 * @param string $name
276
-	 * @return bool
277
-	 */
278
-	public function __isset($name) {
279
-		if (\in_array($name, $this->allowedKeys, true)) {
280
-			return true;
281
-		}
282
-		return isset($this->items['parameters'][$name]);
283
-	}
284
-
285
-	/**
286
-	 * @param string $id
287
-	 */
288
-	public function __unset($id) {
289
-		throw new \RuntimeException('You cannot change the contents of the request object');
290
-	}
291
-
292
-	/**
293
-	 * Returns the value for a specific http header.
294
-	 *
295
-	 * This method returns an empty string if the header did not exist.
296
-	 *
297
-	 * @param string $name
298
-	 * @return string
299
-	 */
300
-	public function getHeader(string $name): string {
301
-		$name = strtoupper(str_replace('-', '_', $name));
302
-		if (isset($this->server['HTTP_' . $name])) {
303
-			return $this->server['HTTP_' . $name];
304
-		}
305
-
306
-		// There's a few headers that seem to end up in the top-level
307
-		// server array.
308
-		switch ($name) {
309
-			case 'CONTENT_TYPE':
310
-			case 'CONTENT_LENGTH':
311
-			case 'REMOTE_ADDR':
312
-				if (isset($this->server[$name])) {
313
-					return $this->server[$name];
314
-				}
315
-				break;
316
-		}
317
-
318
-		return '';
319
-	}
320
-
321
-	/**
322
-	 * Lets you access post and get parameters by the index
323
-	 * In case of json requests the encoded json body is accessed
324
-	 *
325
-	 * @param string $key the key which you want to access in the URL Parameter
326
-	 *                     placeholder, $_POST or $_GET array.
327
-	 *                     The priority how they're returned is the following:
328
-	 *                     1. URL parameters
329
-	 *                     2. POST parameters
330
-	 *                     3. GET parameters
331
-	 * @param mixed $default If the key is not found, this value will be returned
332
-	 * @return mixed the content of the array
333
-	 */
334
-	public function getParam(string $key, $default = null) {
335
-		return isset($this->parameters[$key])
336
-			? $this->parameters[$key]
337
-			: $default;
338
-	}
339
-
340
-	/**
341
-	 * Returns all params that were received, be it from the request
342
-	 * (as GET or POST) or through the URL by the route
343
-	 * @return array the array with all parameters
344
-	 */
345
-	public function getParams(): array {
346
-		return is_array($this->parameters) ? $this->parameters : [];
347
-	}
348
-
349
-	/**
350
-	 * Returns the method of the request
351
-	 * @return string the method of the request (POST, GET, etc)
352
-	 */
353
-	public function getMethod(): string {
354
-		return $this->method;
355
-	}
356
-
357
-	/**
358
-	 * Shortcut for accessing an uploaded file through the $_FILES array
359
-	 * @param string $key the key that will be taken from the $_FILES array
360
-	 * @return array the file in the $_FILES element
361
-	 */
362
-	public function getUploadedFile(string $key) {
363
-		return isset($this->files[$key]) ? $this->files[$key] : null;
364
-	}
365
-
366
-	/**
367
-	 * Shortcut for getting env variables
368
-	 * @param string $key the key that will be taken from the $_ENV array
369
-	 * @return array the value in the $_ENV element
370
-	 */
371
-	public function getEnv(string $key) {
372
-		return isset($this->env[$key]) ? $this->env[$key] : null;
373
-	}
374
-
375
-	/**
376
-	 * Shortcut for getting cookie variables
377
-	 * @param string $key the key that will be taken from the $_COOKIE array
378
-	 * @return string the value in the $_COOKIE element
379
-	 */
380
-	public function getCookie(string $key) {
381
-		return isset($this->cookies[$key]) ? $this->cookies[$key] : null;
382
-	}
383
-
384
-	/**
385
-	 * Returns the request body content.
386
-	 *
387
-	 * If the HTTP request method is PUT and the body
388
-	 * not application/x-www-form-urlencoded or application/json a stream
389
-	 * resource is returned, otherwise an array.
390
-	 *
391
-	 * @return array|string|resource The request body content or a resource to read the body stream.
392
-	 *
393
-	 * @throws \LogicException
394
-	 */
395
-	protected function getContent() {
396
-		// If the content can't be parsed into an array then return a stream resource.
397
-		if ($this->isPutStreamContent()) {
398
-			if ($this->content === false) {
399
-				throw new \LogicException(
400
-					'"put" can only be accessed once if not '
401
-					. 'application/x-www-form-urlencoded or application/json.'
402
-				);
403
-			}
404
-			$this->content = false;
405
-			return fopen($this->inputStream, 'rb');
406
-		} else {
407
-			$this->decodeContent();
408
-			return $this->items['parameters'];
409
-		}
410
-	}
411
-
412
-	private function isPutStreamContent(): bool {
413
-		return $this->method === 'PUT'
414
-			&& $this->getHeader('Content-Length') !== '0'
415
-			&& $this->getHeader('Content-Length') !== ''
416
-			&& strpos($this->getHeader('Content-Type'), 'application/x-www-form-urlencoded') === false
417
-			&& strpos($this->getHeader('Content-Type'), 'application/json') === false;
418
-	}
419
-
420
-	/**
421
-	 * Attempt to decode the content and populate parameters
422
-	 */
423
-	protected function decodeContent() {
424
-		if ($this->contentDecoded) {
425
-			return;
426
-		}
427
-		$params = [];
428
-
429
-		// 'application/json' and other JSON-related content types must be decoded manually.
430
-		if (preg_match(self::JSON_CONTENT_TYPE_REGEX, $this->getHeader('Content-Type')) === 1) {
431
-			$params = json_decode(file_get_contents($this->inputStream), true);
432
-			if (\is_array($params) && \count($params) > 0) {
433
-				$this->items['params'] = $params;
434
-				if ($this->method === 'POST') {
435
-					$this->items['post'] = $params;
436
-				}
437
-			}
438
-		// Handle application/x-www-form-urlencoded for methods other than GET
439
-		// or post correctly
440
-		} elseif ($this->method !== 'GET'
441
-				&& $this->method !== 'POST'
442
-				&& strpos($this->getHeader('Content-Type'), 'application/x-www-form-urlencoded') !== false) {
443
-			parse_str(file_get_contents($this->inputStream), $params);
444
-			if (\is_array($params)) {
445
-				$this->items['params'] = $params;
446
-			}
447
-		}
448
-
449
-		if (\is_array($params)) {
450
-			$this->items['parameters'] = array_merge($this->items['parameters'], $params);
451
-		}
452
-		$this->contentDecoded = true;
453
-	}
454
-
455
-
456
-	/**
457
-	 * Checks if the CSRF check was correct
458
-	 * @return bool true if CSRF check passed
459
-	 */
460
-	public function passesCSRFCheck(): bool {
461
-		if ($this->csrfTokenManager === null) {
462
-			return false;
463
-		}
464
-
465
-		if (!$this->passesStrictCookieCheck()) {
466
-			return false;
467
-		}
468
-
469
-		if (isset($this->items['get']['requesttoken'])) {
470
-			$token = $this->items['get']['requesttoken'];
471
-		} elseif (isset($this->items['post']['requesttoken'])) {
472
-			$token = $this->items['post']['requesttoken'];
473
-		} elseif (isset($this->items['server']['HTTP_REQUESTTOKEN'])) {
474
-			$token = $this->items['server']['HTTP_REQUESTTOKEN'];
475
-		} else {
476
-			//no token found.
477
-			return false;
478
-		}
479
-		$token = new CsrfToken($token);
480
-
481
-		return $this->csrfTokenManager->isTokenValid($token);
482
-	}
483
-
484
-	/**
485
-	 * Whether the cookie checks are required
486
-	 *
487
-	 * @return bool
488
-	 */
489
-	private function cookieCheckRequired(): bool {
490
-		if ($this->getHeader('OCS-APIREQUEST')) {
491
-			return false;
492
-		}
493
-		if ($this->getCookie(session_name()) === null && $this->getCookie('nc_token') === null) {
494
-			return false;
495
-		}
496
-
497
-		return true;
498
-	}
499
-
500
-	/**
501
-	 * Wrapper around session_get_cookie_params
502
-	 *
503
-	 * @return array
504
-	 */
505
-	public function getCookieParams(): array {
506
-		return session_get_cookie_params();
507
-	}
508
-
509
-	/**
510
-	 * Appends the __Host- prefix to the cookie if applicable
511
-	 *
512
-	 * @param string $name
513
-	 * @return string
514
-	 */
515
-	protected function getProtectedCookieName(string $name): string {
516
-		$cookieParams = $this->getCookieParams();
517
-		$prefix = '';
518
-		if ($cookieParams['secure'] === true && $cookieParams['path'] === '/') {
519
-			$prefix = '__Host-';
520
-		}
521
-
522
-		return $prefix.$name;
523
-	}
524
-
525
-	/**
526
-	 * Checks if the strict 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 passesStrictCookieCheck(): bool {
533
-		if (!$this->cookieCheckRequired()) {
534
-			return true;
535
-		}
536
-
537
-		$cookieName = $this->getProtectedCookieName('nc_sameSiteCookiestrict');
538
-		if ($this->getCookie($cookieName) === 'true'
539
-			&& $this->passesLaxCookieCheck()) {
540
-			return true;
541
-		}
542
-		return false;
543
-	}
544
-
545
-	/**
546
-	 * Checks if the lax cookie has been sent with the request if the request
547
-	 * is including any cookies.
548
-	 *
549
-	 * @return bool
550
-	 * @since 9.1.0
551
-	 */
552
-	public function passesLaxCookieCheck(): bool {
553
-		if (!$this->cookieCheckRequired()) {
554
-			return true;
555
-		}
556
-
557
-		$cookieName = $this->getProtectedCookieName('nc_sameSiteCookielax');
558
-		if ($this->getCookie($cookieName) === 'true') {
559
-			return true;
560
-		}
561
-		return false;
562
-	}
563
-
564
-
565
-	/**
566
-	 * Returns an ID for the request, value is not guaranteed to be unique and is mostly meant for logging
567
-	 * If `mod_unique_id` is installed this value will be taken.
568
-	 * @return string
569
-	 */
570
-	public function getId(): string {
571
-		return $this->requestId->getId();
572
-	}
573
-
574
-	/**
575
-	 * Checks if given $remoteAddress matches any entry in the given array $trustedProxies.
576
-	 * For details regarding what "match" means, refer to `matchesTrustedProxy`.
577
-	 * @return boolean true if $remoteAddress matches any entry in $trustedProxies, false otherwise
578
-	 */
579
-	protected function isTrustedProxy($trustedProxies, $remoteAddress) {
580
-		return IpUtils::checkIp($remoteAddress, $trustedProxies);
581
-	}
582
-
583
-	/**
584
-	 * Returns the remote address, if the connection came from a trusted proxy
585
-	 * and `forwarded_for_headers` has been configured then the IP address
586
-	 * specified in this header will be returned instead.
587
-	 * Do always use this instead of $_SERVER['REMOTE_ADDR']
588
-	 * @return string IP address
589
-	 */
590
-	public function getRemoteAddress(): string {
591
-		$remoteAddress = isset($this->server['REMOTE_ADDR']) ? $this->server['REMOTE_ADDR'] : '';
592
-		$trustedProxies = $this->config->getSystemValue('trusted_proxies', []);
593
-
594
-		if (\is_array($trustedProxies) && $this->isTrustedProxy($trustedProxies, $remoteAddress)) {
595
-			$forwardedForHeaders = $this->config->getSystemValue('forwarded_for_headers', [
596
-				'HTTP_X_FORWARDED_FOR'
597
-				// only have one default, so we cannot ship an insecure product out of the box
598
-			]);
599
-
600
-			foreach ($forwardedForHeaders as $header) {
601
-				if (isset($this->server[$header])) {
602
-					foreach (explode(',', $this->server[$header]) as $IP) {
603
-						$IP = trim($IP);
604
-
605
-						// remove brackets from IPv6 addresses
606
-						if (strpos($IP, '[') === 0 && substr($IP, -1) === ']') {
607
-							$IP = substr($IP, 1, -1);
608
-						}
609
-
610
-						if (filter_var($IP, FILTER_VALIDATE_IP) !== false) {
611
-							return $IP;
612
-						}
613
-					}
614
-				}
615
-			}
616
-		}
617
-
618
-		return $remoteAddress;
619
-	}
620
-
621
-	/**
622
-	 * Check overwrite condition
623
-	 * @param string $type
624
-	 * @return bool
625
-	 */
626
-	private function isOverwriteCondition(string $type = ''): bool {
627
-		$regex = '/' . $this->config->getSystemValue('overwritecondaddr', '')  . '/';
628
-		$remoteAddr = isset($this->server['REMOTE_ADDR']) ? $this->server['REMOTE_ADDR'] : '';
629
-		return $regex === '//' || preg_match($regex, $remoteAddr) === 1
630
-		|| $type !== 'protocol';
631
-	}
632
-
633
-	/**
634
-	 * Returns the server protocol. It respects one or more reverse proxies servers
635
-	 * and load balancers
636
-	 * @return string Server protocol (http or https)
637
-	 */
638
-	public function getServerProtocol(): string {
639
-		if ($this->config->getSystemValue('overwriteprotocol') !== ''
640
-			&& $this->isOverwriteCondition('protocol')) {
641
-			return $this->config->getSystemValue('overwriteprotocol');
642
-		}
643
-
644
-		if ($this->fromTrustedProxy() && isset($this->server['HTTP_X_FORWARDED_PROTO'])) {
645
-			if (strpos($this->server['HTTP_X_FORWARDED_PROTO'], ',') !== false) {
646
-				$parts = explode(',', $this->server['HTTP_X_FORWARDED_PROTO']);
647
-				$proto = strtolower(trim($parts[0]));
648
-			} else {
649
-				$proto = strtolower($this->server['HTTP_X_FORWARDED_PROTO']);
650
-			}
651
-
652
-			// Verify that the protocol is always HTTP or HTTPS
653
-			// default to http if an invalid value is provided
654
-			return $proto === 'https' ? 'https' : 'http';
655
-		}
656
-
657
-		if (isset($this->server['HTTPS'])
658
-			&& $this->server['HTTPS'] !== null
659
-			&& $this->server['HTTPS'] !== 'off'
660
-			&& $this->server['HTTPS'] !== '') {
661
-			return 'https';
662
-		}
663
-
664
-		return 'http';
665
-	}
666
-
667
-	/**
668
-	 * Returns the used HTTP protocol.
669
-	 *
670
-	 * @return string HTTP protocol. HTTP/2, HTTP/1.1 or HTTP/1.0.
671
-	 */
672
-	public function getHttpProtocol(): string {
673
-		$claimedProtocol = $this->server['SERVER_PROTOCOL'];
674
-
675
-		if (\is_string($claimedProtocol)) {
676
-			$claimedProtocol = strtoupper($claimedProtocol);
677
-		}
678
-
679
-		$validProtocols = [
680
-			'HTTP/1.0',
681
-			'HTTP/1.1',
682
-			'HTTP/2',
683
-		];
684
-
685
-		if (\in_array($claimedProtocol, $validProtocols, true)) {
686
-			return $claimedProtocol;
687
-		}
688
-
689
-		return 'HTTP/1.1';
690
-	}
691
-
692
-	/**
693
-	 * Returns the request uri, even if the website uses one or more
694
-	 * reverse proxies
695
-	 * @return string
696
-	 */
697
-	public function getRequestUri(): string {
698
-		$uri = isset($this->server['REQUEST_URI']) ? $this->server['REQUEST_URI'] : '';
699
-		if ($this->config->getSystemValue('overwritewebroot') !== '' && $this->isOverwriteCondition()) {
700
-			$uri = $this->getScriptName() . substr($uri, \strlen($this->server['SCRIPT_NAME']));
701
-		}
702
-		return $uri;
703
-	}
704
-
705
-	/**
706
-	 * Get raw PathInfo from request (not urldecoded)
707
-	 * @throws \Exception
708
-	 * @return string Path info
709
-	 */
710
-	public function getRawPathInfo(): string {
711
-		$requestUri = isset($this->server['REQUEST_URI']) ? $this->server['REQUEST_URI'] : '';
712
-		// remove too many slashes - can be caused by reverse proxy configuration
713
-		$requestUri = preg_replace('%/{2,}%', '/', $requestUri);
714
-
715
-		// Remove the query string from REQUEST_URI
716
-		if ($pos = strpos($requestUri, '?')) {
717
-			$requestUri = substr($requestUri, 0, $pos);
718
-		}
719
-
720
-		$scriptName = $this->server['SCRIPT_NAME'];
721
-		$pathInfo = $requestUri;
722
-
723
-		// strip off the script name's dir and file name
724
-		// FIXME: Sabre does not really belong here
725
-		[$path, $name] = \Sabre\Uri\split($scriptName);
726
-		if (!empty($path)) {
727
-			if ($path === $pathInfo || strpos($pathInfo, $path.'/') === 0) {
728
-				$pathInfo = substr($pathInfo, \strlen($path));
729
-			} else {
730
-				throw new \Exception("The requested uri($requestUri) cannot be processed by the script '$scriptName')");
731
-			}
732
-		}
733
-		if ($name === null) {
734
-			$name = '';
735
-		}
736
-
737
-		if (strpos($pathInfo, '/'.$name) === 0) {
738
-			$pathInfo = substr($pathInfo, \strlen($name) + 1);
739
-		}
740
-		if ($name !== '' && strpos($pathInfo, $name) === 0) {
741
-			$pathInfo = substr($pathInfo, \strlen($name));
742
-		}
743
-		if ($pathInfo === false || $pathInfo === '/') {
744
-			return '';
745
-		} else {
746
-			return $pathInfo;
747
-		}
748
-	}
749
-
750
-	/**
751
-	 * Get PathInfo from request
752
-	 * @throws \Exception
753
-	 * @return string|false Path info or false when not found
754
-	 */
755
-	public function getPathInfo() {
756
-		$pathInfo = $this->getRawPathInfo();
757
-		return \Sabre\HTTP\decodePath($pathInfo);
758
-	}
759
-
760
-	/**
761
-	 * Returns the script name, even if the website uses one or more
762
-	 * reverse proxies
763
-	 * @return string the script name
764
-	 */
765
-	public function getScriptName(): string {
766
-		$name = $this->server['SCRIPT_NAME'];
767
-		$overwriteWebRoot = $this->config->getSystemValue('overwritewebroot');
768
-		if ($overwriteWebRoot !== '' && $this->isOverwriteCondition()) {
769
-			// FIXME: This code is untestable due to __DIR__, also that hardcoded path is really dangerous
770
-			$serverRoot = str_replace('\\', '/', substr(__DIR__, 0, -\strlen('lib/private/appframework/http/')));
771
-			$suburi = str_replace('\\', '/', substr(realpath($this->server['SCRIPT_FILENAME']), \strlen($serverRoot)));
772
-			$name = '/' . ltrim($overwriteWebRoot . $suburi, '/');
773
-		}
774
-		return $name;
775
-	}
776
-
777
-	/**
778
-	 * Checks whether the user agent matches a given regex
779
-	 * @param array $agent array of agent names
780
-	 * @return bool true if at least one of the given agent matches, false otherwise
781
-	 */
782
-	public function isUserAgent(array $agent): bool {
783
-		if (!isset($this->server['HTTP_USER_AGENT'])) {
784
-			return false;
785
-		}
786
-		foreach ($agent as $regex) {
787
-			if (preg_match($regex, $this->server['HTTP_USER_AGENT'])) {
788
-				return true;
789
-			}
790
-		}
791
-		return false;
792
-	}
793
-
794
-	/**
795
-	 * Returns the unverified server host from the headers without checking
796
-	 * whether it is a trusted domain
797
-	 * @return string Server host
798
-	 */
799
-	public function getInsecureServerHost(): string {
800
-		if ($this->fromTrustedProxy() && $this->getOverwriteHost() !== null) {
801
-			return $this->getOverwriteHost();
802
-		}
803
-
804
-		$host = 'localhost';
805
-		if ($this->fromTrustedProxy() && isset($this->server['HTTP_X_FORWARDED_HOST'])) {
806
-			if (strpos($this->server['HTTP_X_FORWARDED_HOST'], ',') !== false) {
807
-				$parts = explode(',', $this->server['HTTP_X_FORWARDED_HOST']);
808
-				$host = trim(current($parts));
809
-			} else {
810
-				$host = $this->server['HTTP_X_FORWARDED_HOST'];
811
-			}
812
-		} else {
813
-			if (isset($this->server['HTTP_HOST'])) {
814
-				$host = $this->server['HTTP_HOST'];
815
-			} elseif (isset($this->server['SERVER_NAME'])) {
816
-				$host = $this->server['SERVER_NAME'];
817
-			}
818
-		}
819
-
820
-		return $host;
821
-	}
822
-
823
-
824
-	/**
825
-	 * Returns the server host from the headers, or the first configured
826
-	 * trusted domain if the host isn't in the trusted list
827
-	 * @return string Server host
828
-	 */
829
-	public function getServerHost(): string {
830
-		// overwritehost is always trusted
831
-		$host = $this->getOverwriteHost();
832
-		if ($host !== null) {
833
-			return $host;
834
-		}
835
-
836
-		// get the host from the headers
837
-		$host = $this->getInsecureServerHost();
838
-
839
-		// Verify that the host is a trusted domain if the trusted domains
840
-		// are defined
841
-		// If no trusted domain is provided the first trusted domain is returned
842
-		$trustedDomainHelper = new TrustedDomainHelper($this->config);
843
-		if ($trustedDomainHelper->isTrustedDomain($host)) {
844
-			return $host;
845
-		}
846
-
847
-		$trustedList = (array)$this->config->getSystemValue('trusted_domains', []);
848
-		if (count($trustedList) > 0) {
849
-			return reset($trustedList);
850
-		}
851
-
852
-		return '';
853
-	}
854
-
855
-	/**
856
-	 * Returns the overwritehost setting from the config if set and
857
-	 * if the overwrite condition is met
858
-	 * @return string|null overwritehost value or null if not defined or the defined condition
859
-	 * isn't met
860
-	 */
861
-	private function getOverwriteHost() {
862
-		if ($this->config->getSystemValue('overwritehost') !== '' && $this->isOverwriteCondition()) {
863
-			return $this->config->getSystemValue('overwritehost');
864
-		}
865
-		return null;
866
-	}
867
-
868
-	private function fromTrustedProxy(): bool {
869
-		$remoteAddress = isset($this->server['REMOTE_ADDR']) ? $this->server['REMOTE_ADDR'] : '';
870
-		$trustedProxies = $this->config->getSystemValue('trusted_proxies', []);
871
-
872
-		return \is_array($trustedProxies) && $this->isTrustedProxy($trustedProxies, $remoteAddress);
873
-	}
68
+    public const USER_AGENT_IE = '/(MSIE)|(Trident)/';
69
+    // Microsoft Edge User Agent from https://msdn.microsoft.com/en-us/library/hh869301(v=vs.85).aspx
70
+    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.]+$/';
71
+    // Firefox User Agent from https://developer.mozilla.org/en-US/docs/Web/HTTP/Gecko_user_agent_string_reference
72
+    public const USER_AGENT_FIREFOX = '/^Mozilla\/5\.0 \([^)]+\) Gecko\/[0-9.]+ Firefox\/[0-9.]+$/';
73
+    // Chrome User Agent from https://developer.chrome.com/multidevice/user-agent
74
+    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.]+|)$/';
75
+    // Safari User Agent from http://www.useragentstring.com/pages/Safari/
76
+    public const USER_AGENT_SAFARI = '/^Mozilla\/5\.0 \([^)]+\) AppleWebKit\/[0-9.]+ \(KHTML, like Gecko\) Version\/[0-9.]+ Safari\/[0-9.A-Z]+$/';
77
+    // Android Chrome user agent: https://developers.google.com/chrome/mobile/docs/user-agent
78
+    public const USER_AGENT_ANDROID_MOBILE_CHROME = '#Android.*Chrome/[.0-9]*#';
79
+    public const USER_AGENT_FREEBOX = '#^Mozilla/5\.0$#';
80
+    public const REGEX_LOCALHOST = '/^(127\.0\.0\.1|localhost|\[::1\])$/';
81
+
82
+    protected string $inputStream;
83
+    protected $content;
84
+    protected array $items = [];
85
+    protected array $allowedKeys = [
86
+        'get',
87
+        'post',
88
+        'files',
89
+        'server',
90
+        'env',
91
+        'cookies',
92
+        'urlParams',
93
+        'parameters',
94
+        'method',
95
+        'requesttoken',
96
+    ];
97
+    protected IRequestId $requestId;
98
+    protected IConfig $config;
99
+    protected ?CsrfTokenManager $csrfTokenManager;
100
+
101
+    protected bool $contentDecoded = false;
102
+
103
+    /**
104
+     * @param array $vars An associative array with the following optional values:
105
+     *        - array 'urlParams' the parameters which were matched from the URL
106
+     *        - array 'get' the $_GET array
107
+     *        - array|string 'post' the $_POST array or JSON string
108
+     *        - array 'files' the $_FILES array
109
+     *        - array 'server' the $_SERVER array
110
+     *        - array 'env' the $_ENV array
111
+     *        - array 'cookies' the $_COOKIE array
112
+     *        - string 'method' the request method (GET, POST etc)
113
+     *        - string|false 'requesttoken' the requesttoken or false when not available
114
+     * @param IRequestId $requestId
115
+     * @param IConfig $config
116
+     * @param CsrfTokenManager|null $csrfTokenManager
117
+     * @param string $stream
118
+     * @see https://www.php.net/manual/en/reserved.variables.php
119
+     */
120
+    public function __construct(array $vars,
121
+                                IRequestId $requestId,
122
+                                IConfig $config,
123
+                                CsrfTokenManager $csrfTokenManager = null,
124
+                                string $stream = 'php://input') {
125
+        $this->inputStream = $stream;
126
+        $this->items['params'] = [];
127
+        $this->requestId = $requestId;
128
+        $this->config = $config;
129
+        $this->csrfTokenManager = $csrfTokenManager;
130
+
131
+        if (!array_key_exists('method', $vars)) {
132
+            $vars['method'] = 'GET';
133
+        }
134
+
135
+        foreach ($this->allowedKeys as $name) {
136
+            $this->items[$name] = $vars[$name] ?? [];
137
+        }
138
+
139
+        $this->items['parameters'] = array_merge(
140
+            $this->items['get'],
141
+            $this->items['post'],
142
+            $this->items['urlParams'],
143
+            $this->items['params']
144
+        );
145
+    }
146
+    /**
147
+     * @param array $parameters
148
+     */
149
+    public function setUrlParameters(array $parameters) {
150
+        $this->items['urlParams'] = $parameters;
151
+        $this->items['parameters'] = array_merge(
152
+            $this->items['parameters'],
153
+            $this->items['urlParams']
154
+        );
155
+    }
156
+
157
+    /**
158
+     * Countable method
159
+     * @return int
160
+     */
161
+    public function count(): int {
162
+        return \count($this->items['parameters']);
163
+    }
164
+
165
+    /**
166
+     * ArrayAccess methods
167
+     *
168
+     * Gives access to the combined GET, POST and urlParams arrays
169
+     *
170
+     * Examples:
171
+     *
172
+     * $var = $request['myvar'];
173
+     *
174
+     * or
175
+     *
176
+     * if(!isset($request['myvar']) {
177
+     * 	// Do something
178
+     * }
179
+     *
180
+     * $request['myvar'] = 'something'; // This throws an exception.
181
+     *
182
+     * @param string $offset The key to lookup
183
+     * @return boolean
184
+     */
185
+    public function offsetExists($offset): bool {
186
+        return isset($this->items['parameters'][$offset]);
187
+    }
188
+
189
+    /**
190
+     * @see offsetExists
191
+     * @param string $offset
192
+     * @return mixed
193
+     */
194
+    #[\ReturnTypeWillChange]
195
+    public function offsetGet($offset) {
196
+        return isset($this->items['parameters'][$offset])
197
+            ? $this->items['parameters'][$offset]
198
+            : null;
199
+    }
200
+
201
+    /**
202
+     * @see offsetExists
203
+     * @param string $offset
204
+     * @param mixed $value
205
+     */
206
+    public function offsetSet($offset, $value): void {
207
+        throw new \RuntimeException('You cannot change the contents of the request object');
208
+    }
209
+
210
+    /**
211
+     * @see offsetExists
212
+     * @param string $offset
213
+     */
214
+    public function offsetUnset($offset): void {
215
+        throw new \RuntimeException('You cannot change the contents of the request object');
216
+    }
217
+
218
+    /**
219
+     * Magic property accessors
220
+     * @param string $name
221
+     * @param mixed $value
222
+     */
223
+    public function __set($name, $value) {
224
+        throw new \RuntimeException('You cannot change the contents of the request object');
225
+    }
226
+
227
+    /**
228
+     * Access request variables by method and name.
229
+     * Examples:
230
+     *
231
+     * $request->post['myvar']; // Only look for POST variables
232
+     * $request->myvar; or $request->{'myvar'}; or $request->{$myvar}
233
+     * Looks in the combined GET, POST and urlParams array.
234
+     *
235
+     * If you access e.g. ->post but the current HTTP request method
236
+     * is GET a \LogicException will be thrown.
237
+     *
238
+     * @param string $name The key to look for.
239
+     * @throws \LogicException
240
+     * @return mixed|null
241
+     */
242
+    public function __get($name) {
243
+        switch ($name) {
244
+            case 'put':
245
+            case 'patch':
246
+            case 'get':
247
+            case 'post':
248
+                if ($this->method !== strtoupper($name)) {
249
+                    throw new \LogicException(sprintf('%s cannot be accessed in a %s request.', $name, $this->method));
250
+                }
251
+                return $this->getContent();
252
+            case 'files':
253
+            case 'server':
254
+            case 'env':
255
+            case 'cookies':
256
+            case 'urlParams':
257
+            case 'method':
258
+                return isset($this->items[$name])
259
+                    ? $this->items[$name]
260
+                    : null;
261
+            case 'parameters':
262
+            case 'params':
263
+                if ($this->isPutStreamContent()) {
264
+                    return $this->items['parameters'];
265
+                }
266
+                return $this->getContent();
267
+            default:
268
+                return isset($this[$name])
269
+                    ? $this[$name]
270
+                    : null;
271
+        }
272
+    }
273
+
274
+    /**
275
+     * @param string $name
276
+     * @return bool
277
+     */
278
+    public function __isset($name) {
279
+        if (\in_array($name, $this->allowedKeys, true)) {
280
+            return true;
281
+        }
282
+        return isset($this->items['parameters'][$name]);
283
+    }
284
+
285
+    /**
286
+     * @param string $id
287
+     */
288
+    public function __unset($id) {
289
+        throw new \RuntimeException('You cannot change the contents of the request object');
290
+    }
291
+
292
+    /**
293
+     * Returns the value for a specific http header.
294
+     *
295
+     * This method returns an empty string if the header did not exist.
296
+     *
297
+     * @param string $name
298
+     * @return string
299
+     */
300
+    public function getHeader(string $name): string {
301
+        $name = strtoupper(str_replace('-', '_', $name));
302
+        if (isset($this->server['HTTP_' . $name])) {
303
+            return $this->server['HTTP_' . $name];
304
+        }
305
+
306
+        // There's a few headers that seem to end up in the top-level
307
+        // server array.
308
+        switch ($name) {
309
+            case 'CONTENT_TYPE':
310
+            case 'CONTENT_LENGTH':
311
+            case 'REMOTE_ADDR':
312
+                if (isset($this->server[$name])) {
313
+                    return $this->server[$name];
314
+                }
315
+                break;
316
+        }
317
+
318
+        return '';
319
+    }
320
+
321
+    /**
322
+     * Lets you access post and get parameters by the index
323
+     * In case of json requests the encoded json body is accessed
324
+     *
325
+     * @param string $key the key which you want to access in the URL Parameter
326
+     *                     placeholder, $_POST or $_GET array.
327
+     *                     The priority how they're returned is the following:
328
+     *                     1. URL parameters
329
+     *                     2. POST parameters
330
+     *                     3. GET parameters
331
+     * @param mixed $default If the key is not found, this value will be returned
332
+     * @return mixed the content of the array
333
+     */
334
+    public function getParam(string $key, $default = null) {
335
+        return isset($this->parameters[$key])
336
+            ? $this->parameters[$key]
337
+            : $default;
338
+    }
339
+
340
+    /**
341
+     * Returns all params that were received, be it from the request
342
+     * (as GET or POST) or through the URL by the route
343
+     * @return array the array with all parameters
344
+     */
345
+    public function getParams(): array {
346
+        return is_array($this->parameters) ? $this->parameters : [];
347
+    }
348
+
349
+    /**
350
+     * Returns the method of the request
351
+     * @return string the method of the request (POST, GET, etc)
352
+     */
353
+    public function getMethod(): string {
354
+        return $this->method;
355
+    }
356
+
357
+    /**
358
+     * Shortcut for accessing an uploaded file through the $_FILES array
359
+     * @param string $key the key that will be taken from the $_FILES array
360
+     * @return array the file in the $_FILES element
361
+     */
362
+    public function getUploadedFile(string $key) {
363
+        return isset($this->files[$key]) ? $this->files[$key] : null;
364
+    }
365
+
366
+    /**
367
+     * Shortcut for getting env variables
368
+     * @param string $key the key that will be taken from the $_ENV array
369
+     * @return array the value in the $_ENV element
370
+     */
371
+    public function getEnv(string $key) {
372
+        return isset($this->env[$key]) ? $this->env[$key] : null;
373
+    }
374
+
375
+    /**
376
+     * Shortcut for getting cookie variables
377
+     * @param string $key the key that will be taken from the $_COOKIE array
378
+     * @return string the value in the $_COOKIE element
379
+     */
380
+    public function getCookie(string $key) {
381
+        return isset($this->cookies[$key]) ? $this->cookies[$key] : null;
382
+    }
383
+
384
+    /**
385
+     * Returns the request body content.
386
+     *
387
+     * If the HTTP request method is PUT and the body
388
+     * not application/x-www-form-urlencoded or application/json a stream
389
+     * resource is returned, otherwise an array.
390
+     *
391
+     * @return array|string|resource The request body content or a resource to read the body stream.
392
+     *
393
+     * @throws \LogicException
394
+     */
395
+    protected function getContent() {
396
+        // If the content can't be parsed into an array then return a stream resource.
397
+        if ($this->isPutStreamContent()) {
398
+            if ($this->content === false) {
399
+                throw new \LogicException(
400
+                    '"put" can only be accessed once if not '
401
+                    . 'application/x-www-form-urlencoded or application/json.'
402
+                );
403
+            }
404
+            $this->content = false;
405
+            return fopen($this->inputStream, 'rb');
406
+        } else {
407
+            $this->decodeContent();
408
+            return $this->items['parameters'];
409
+        }
410
+    }
411
+
412
+    private function isPutStreamContent(): bool {
413
+        return $this->method === 'PUT'
414
+            && $this->getHeader('Content-Length') !== '0'
415
+            && $this->getHeader('Content-Length') !== ''
416
+            && strpos($this->getHeader('Content-Type'), 'application/x-www-form-urlencoded') === false
417
+            && strpos($this->getHeader('Content-Type'), 'application/json') === false;
418
+    }
419
+
420
+    /**
421
+     * Attempt to decode the content and populate parameters
422
+     */
423
+    protected function decodeContent() {
424
+        if ($this->contentDecoded) {
425
+            return;
426
+        }
427
+        $params = [];
428
+
429
+        // 'application/json' and other JSON-related content types must be decoded manually.
430
+        if (preg_match(self::JSON_CONTENT_TYPE_REGEX, $this->getHeader('Content-Type')) === 1) {
431
+            $params = json_decode(file_get_contents($this->inputStream), true);
432
+            if (\is_array($params) && \count($params) > 0) {
433
+                $this->items['params'] = $params;
434
+                if ($this->method === 'POST') {
435
+                    $this->items['post'] = $params;
436
+                }
437
+            }
438
+        // Handle application/x-www-form-urlencoded for methods other than GET
439
+        // or post correctly
440
+        } elseif ($this->method !== 'GET'
441
+                && $this->method !== 'POST'
442
+                && strpos($this->getHeader('Content-Type'), 'application/x-www-form-urlencoded') !== false) {
443
+            parse_str(file_get_contents($this->inputStream), $params);
444
+            if (\is_array($params)) {
445
+                $this->items['params'] = $params;
446
+            }
447
+        }
448
+
449
+        if (\is_array($params)) {
450
+            $this->items['parameters'] = array_merge($this->items['parameters'], $params);
451
+        }
452
+        $this->contentDecoded = true;
453
+    }
454
+
455
+
456
+    /**
457
+     * Checks if the CSRF check was correct
458
+     * @return bool true if CSRF check passed
459
+     */
460
+    public function passesCSRFCheck(): bool {
461
+        if ($this->csrfTokenManager === null) {
462
+            return false;
463
+        }
464
+
465
+        if (!$this->passesStrictCookieCheck()) {
466
+            return false;
467
+        }
468
+
469
+        if (isset($this->items['get']['requesttoken'])) {
470
+            $token = $this->items['get']['requesttoken'];
471
+        } elseif (isset($this->items['post']['requesttoken'])) {
472
+            $token = $this->items['post']['requesttoken'];
473
+        } elseif (isset($this->items['server']['HTTP_REQUESTTOKEN'])) {
474
+            $token = $this->items['server']['HTTP_REQUESTTOKEN'];
475
+        } else {
476
+            //no token found.
477
+            return false;
478
+        }
479
+        $token = new CsrfToken($token);
480
+
481
+        return $this->csrfTokenManager->isTokenValid($token);
482
+    }
483
+
484
+    /**
485
+     * Whether the cookie checks are required
486
+     *
487
+     * @return bool
488
+     */
489
+    private function cookieCheckRequired(): bool {
490
+        if ($this->getHeader('OCS-APIREQUEST')) {
491
+            return false;
492
+        }
493
+        if ($this->getCookie(session_name()) === null && $this->getCookie('nc_token') === null) {
494
+            return false;
495
+        }
496
+
497
+        return true;
498
+    }
499
+
500
+    /**
501
+     * Wrapper around session_get_cookie_params
502
+     *
503
+     * @return array
504
+     */
505
+    public function getCookieParams(): array {
506
+        return session_get_cookie_params();
507
+    }
508
+
509
+    /**
510
+     * Appends the __Host- prefix to the cookie if applicable
511
+     *
512
+     * @param string $name
513
+     * @return string
514
+     */
515
+    protected function getProtectedCookieName(string $name): string {
516
+        $cookieParams = $this->getCookieParams();
517
+        $prefix = '';
518
+        if ($cookieParams['secure'] === true && $cookieParams['path'] === '/') {
519
+            $prefix = '__Host-';
520
+        }
521
+
522
+        return $prefix.$name;
523
+    }
524
+
525
+    /**
526
+     * Checks if the strict 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 passesStrictCookieCheck(): bool {
533
+        if (!$this->cookieCheckRequired()) {
534
+            return true;
535
+        }
536
+
537
+        $cookieName = $this->getProtectedCookieName('nc_sameSiteCookiestrict');
538
+        if ($this->getCookie($cookieName) === 'true'
539
+            && $this->passesLaxCookieCheck()) {
540
+            return true;
541
+        }
542
+        return false;
543
+    }
544
+
545
+    /**
546
+     * Checks if the lax cookie has been sent with the request if the request
547
+     * is including any cookies.
548
+     *
549
+     * @return bool
550
+     * @since 9.1.0
551
+     */
552
+    public function passesLaxCookieCheck(): bool {
553
+        if (!$this->cookieCheckRequired()) {
554
+            return true;
555
+        }
556
+
557
+        $cookieName = $this->getProtectedCookieName('nc_sameSiteCookielax');
558
+        if ($this->getCookie($cookieName) === 'true') {
559
+            return true;
560
+        }
561
+        return false;
562
+    }
563
+
564
+
565
+    /**
566
+     * Returns an ID for the request, value is not guaranteed to be unique and is mostly meant for logging
567
+     * If `mod_unique_id` is installed this value will be taken.
568
+     * @return string
569
+     */
570
+    public function getId(): string {
571
+        return $this->requestId->getId();
572
+    }
573
+
574
+    /**
575
+     * Checks if given $remoteAddress matches any entry in the given array $trustedProxies.
576
+     * For details regarding what "match" means, refer to `matchesTrustedProxy`.
577
+     * @return boolean true if $remoteAddress matches any entry in $trustedProxies, false otherwise
578
+     */
579
+    protected function isTrustedProxy($trustedProxies, $remoteAddress) {
580
+        return IpUtils::checkIp($remoteAddress, $trustedProxies);
581
+    }
582
+
583
+    /**
584
+     * Returns the remote address, if the connection came from a trusted proxy
585
+     * and `forwarded_for_headers` has been configured then the IP address
586
+     * specified in this header will be returned instead.
587
+     * Do always use this instead of $_SERVER['REMOTE_ADDR']
588
+     * @return string IP address
589
+     */
590
+    public function getRemoteAddress(): string {
591
+        $remoteAddress = isset($this->server['REMOTE_ADDR']) ? $this->server['REMOTE_ADDR'] : '';
592
+        $trustedProxies = $this->config->getSystemValue('trusted_proxies', []);
593
+
594
+        if (\is_array($trustedProxies) && $this->isTrustedProxy($trustedProxies, $remoteAddress)) {
595
+            $forwardedForHeaders = $this->config->getSystemValue('forwarded_for_headers', [
596
+                'HTTP_X_FORWARDED_FOR'
597
+                // only have one default, so we cannot ship an insecure product out of the box
598
+            ]);
599
+
600
+            foreach ($forwardedForHeaders as $header) {
601
+                if (isset($this->server[$header])) {
602
+                    foreach (explode(',', $this->server[$header]) as $IP) {
603
+                        $IP = trim($IP);
604
+
605
+                        // remove brackets from IPv6 addresses
606
+                        if (strpos($IP, '[') === 0 && substr($IP, -1) === ']') {
607
+                            $IP = substr($IP, 1, -1);
608
+                        }
609
+
610
+                        if (filter_var($IP, FILTER_VALIDATE_IP) !== false) {
611
+                            return $IP;
612
+                        }
613
+                    }
614
+                }
615
+            }
616
+        }
617
+
618
+        return $remoteAddress;
619
+    }
620
+
621
+    /**
622
+     * Check overwrite condition
623
+     * @param string $type
624
+     * @return bool
625
+     */
626
+    private function isOverwriteCondition(string $type = ''): bool {
627
+        $regex = '/' . $this->config->getSystemValue('overwritecondaddr', '')  . '/';
628
+        $remoteAddr = isset($this->server['REMOTE_ADDR']) ? $this->server['REMOTE_ADDR'] : '';
629
+        return $regex === '//' || preg_match($regex, $remoteAddr) === 1
630
+        || $type !== 'protocol';
631
+    }
632
+
633
+    /**
634
+     * Returns the server protocol. It respects one or more reverse proxies servers
635
+     * and load balancers
636
+     * @return string Server protocol (http or https)
637
+     */
638
+    public function getServerProtocol(): string {
639
+        if ($this->config->getSystemValue('overwriteprotocol') !== ''
640
+            && $this->isOverwriteCondition('protocol')) {
641
+            return $this->config->getSystemValue('overwriteprotocol');
642
+        }
643
+
644
+        if ($this->fromTrustedProxy() && isset($this->server['HTTP_X_FORWARDED_PROTO'])) {
645
+            if (strpos($this->server['HTTP_X_FORWARDED_PROTO'], ',') !== false) {
646
+                $parts = explode(',', $this->server['HTTP_X_FORWARDED_PROTO']);
647
+                $proto = strtolower(trim($parts[0]));
648
+            } else {
649
+                $proto = strtolower($this->server['HTTP_X_FORWARDED_PROTO']);
650
+            }
651
+
652
+            // Verify that the protocol is always HTTP or HTTPS
653
+            // default to http if an invalid value is provided
654
+            return $proto === 'https' ? 'https' : 'http';
655
+        }
656
+
657
+        if (isset($this->server['HTTPS'])
658
+            && $this->server['HTTPS'] !== null
659
+            && $this->server['HTTPS'] !== 'off'
660
+            && $this->server['HTTPS'] !== '') {
661
+            return 'https';
662
+        }
663
+
664
+        return 'http';
665
+    }
666
+
667
+    /**
668
+     * Returns the used HTTP protocol.
669
+     *
670
+     * @return string HTTP protocol. HTTP/2, HTTP/1.1 or HTTP/1.0.
671
+     */
672
+    public function getHttpProtocol(): string {
673
+        $claimedProtocol = $this->server['SERVER_PROTOCOL'];
674
+
675
+        if (\is_string($claimedProtocol)) {
676
+            $claimedProtocol = strtoupper($claimedProtocol);
677
+        }
678
+
679
+        $validProtocols = [
680
+            'HTTP/1.0',
681
+            'HTTP/1.1',
682
+            'HTTP/2',
683
+        ];
684
+
685
+        if (\in_array($claimedProtocol, $validProtocols, true)) {
686
+            return $claimedProtocol;
687
+        }
688
+
689
+        return 'HTTP/1.1';
690
+    }
691
+
692
+    /**
693
+     * Returns the request uri, even if the website uses one or more
694
+     * reverse proxies
695
+     * @return string
696
+     */
697
+    public function getRequestUri(): string {
698
+        $uri = isset($this->server['REQUEST_URI']) ? $this->server['REQUEST_URI'] : '';
699
+        if ($this->config->getSystemValue('overwritewebroot') !== '' && $this->isOverwriteCondition()) {
700
+            $uri = $this->getScriptName() . substr($uri, \strlen($this->server['SCRIPT_NAME']));
701
+        }
702
+        return $uri;
703
+    }
704
+
705
+    /**
706
+     * Get raw PathInfo from request (not urldecoded)
707
+     * @throws \Exception
708
+     * @return string Path info
709
+     */
710
+    public function getRawPathInfo(): string {
711
+        $requestUri = isset($this->server['REQUEST_URI']) ? $this->server['REQUEST_URI'] : '';
712
+        // remove too many slashes - can be caused by reverse proxy configuration
713
+        $requestUri = preg_replace('%/{2,}%', '/', $requestUri);
714
+
715
+        // Remove the query string from REQUEST_URI
716
+        if ($pos = strpos($requestUri, '?')) {
717
+            $requestUri = substr($requestUri, 0, $pos);
718
+        }
719
+
720
+        $scriptName = $this->server['SCRIPT_NAME'];
721
+        $pathInfo = $requestUri;
722
+
723
+        // strip off the script name's dir and file name
724
+        // FIXME: Sabre does not really belong here
725
+        [$path, $name] = \Sabre\Uri\split($scriptName);
726
+        if (!empty($path)) {
727
+            if ($path === $pathInfo || strpos($pathInfo, $path.'/') === 0) {
728
+                $pathInfo = substr($pathInfo, \strlen($path));
729
+            } else {
730
+                throw new \Exception("The requested uri($requestUri) cannot be processed by the script '$scriptName')");
731
+            }
732
+        }
733
+        if ($name === null) {
734
+            $name = '';
735
+        }
736
+
737
+        if (strpos($pathInfo, '/'.$name) === 0) {
738
+            $pathInfo = substr($pathInfo, \strlen($name) + 1);
739
+        }
740
+        if ($name !== '' && strpos($pathInfo, $name) === 0) {
741
+            $pathInfo = substr($pathInfo, \strlen($name));
742
+        }
743
+        if ($pathInfo === false || $pathInfo === '/') {
744
+            return '';
745
+        } else {
746
+            return $pathInfo;
747
+        }
748
+    }
749
+
750
+    /**
751
+     * Get PathInfo from request
752
+     * @throws \Exception
753
+     * @return string|false Path info or false when not found
754
+     */
755
+    public function getPathInfo() {
756
+        $pathInfo = $this->getRawPathInfo();
757
+        return \Sabre\HTTP\decodePath($pathInfo);
758
+    }
759
+
760
+    /**
761
+     * Returns the script name, even if the website uses one or more
762
+     * reverse proxies
763
+     * @return string the script name
764
+     */
765
+    public function getScriptName(): string {
766
+        $name = $this->server['SCRIPT_NAME'];
767
+        $overwriteWebRoot = $this->config->getSystemValue('overwritewebroot');
768
+        if ($overwriteWebRoot !== '' && $this->isOverwriteCondition()) {
769
+            // FIXME: This code is untestable due to __DIR__, also that hardcoded path is really dangerous
770
+            $serverRoot = str_replace('\\', '/', substr(__DIR__, 0, -\strlen('lib/private/appframework/http/')));
771
+            $suburi = str_replace('\\', '/', substr(realpath($this->server['SCRIPT_FILENAME']), \strlen($serverRoot)));
772
+            $name = '/' . ltrim($overwriteWebRoot . $suburi, '/');
773
+        }
774
+        return $name;
775
+    }
776
+
777
+    /**
778
+     * Checks whether the user agent matches a given regex
779
+     * @param array $agent array of agent names
780
+     * @return bool true if at least one of the given agent matches, false otherwise
781
+     */
782
+    public function isUserAgent(array $agent): bool {
783
+        if (!isset($this->server['HTTP_USER_AGENT'])) {
784
+            return false;
785
+        }
786
+        foreach ($agent as $regex) {
787
+            if (preg_match($regex, $this->server['HTTP_USER_AGENT'])) {
788
+                return true;
789
+            }
790
+        }
791
+        return false;
792
+    }
793
+
794
+    /**
795
+     * Returns the unverified server host from the headers without checking
796
+     * whether it is a trusted domain
797
+     * @return string Server host
798
+     */
799
+    public function getInsecureServerHost(): string {
800
+        if ($this->fromTrustedProxy() && $this->getOverwriteHost() !== null) {
801
+            return $this->getOverwriteHost();
802
+        }
803
+
804
+        $host = 'localhost';
805
+        if ($this->fromTrustedProxy() && isset($this->server['HTTP_X_FORWARDED_HOST'])) {
806
+            if (strpos($this->server['HTTP_X_FORWARDED_HOST'], ',') !== false) {
807
+                $parts = explode(',', $this->server['HTTP_X_FORWARDED_HOST']);
808
+                $host = trim(current($parts));
809
+            } else {
810
+                $host = $this->server['HTTP_X_FORWARDED_HOST'];
811
+            }
812
+        } else {
813
+            if (isset($this->server['HTTP_HOST'])) {
814
+                $host = $this->server['HTTP_HOST'];
815
+            } elseif (isset($this->server['SERVER_NAME'])) {
816
+                $host = $this->server['SERVER_NAME'];
817
+            }
818
+        }
819
+
820
+        return $host;
821
+    }
822
+
823
+
824
+    /**
825
+     * Returns the server host from the headers, or the first configured
826
+     * trusted domain if the host isn't in the trusted list
827
+     * @return string Server host
828
+     */
829
+    public function getServerHost(): string {
830
+        // overwritehost is always trusted
831
+        $host = $this->getOverwriteHost();
832
+        if ($host !== null) {
833
+            return $host;
834
+        }
835
+
836
+        // get the host from the headers
837
+        $host = $this->getInsecureServerHost();
838
+
839
+        // Verify that the host is a trusted domain if the trusted domains
840
+        // are defined
841
+        // If no trusted domain is provided the first trusted domain is returned
842
+        $trustedDomainHelper = new TrustedDomainHelper($this->config);
843
+        if ($trustedDomainHelper->isTrustedDomain($host)) {
844
+            return $host;
845
+        }
846
+
847
+        $trustedList = (array)$this->config->getSystemValue('trusted_domains', []);
848
+        if (count($trustedList) > 0) {
849
+            return reset($trustedList);
850
+        }
851
+
852
+        return '';
853
+    }
854
+
855
+    /**
856
+     * Returns the overwritehost setting from the config if set and
857
+     * if the overwrite condition is met
858
+     * @return string|null overwritehost value or null if not defined or the defined condition
859
+     * isn't met
860
+     */
861
+    private function getOverwriteHost() {
862
+        if ($this->config->getSystemValue('overwritehost') !== '' && $this->isOverwriteCondition()) {
863
+            return $this->config->getSystemValue('overwritehost');
864
+        }
865
+        return null;
866
+    }
867
+
868
+    private function fromTrustedProxy(): bool {
869
+        $remoteAddress = isset($this->server['REMOTE_ADDR']) ? $this->server['REMOTE_ADDR'] : '';
870
+        $trustedProxies = $this->config->getSystemValue('trusted_proxies', []);
871
+
872
+        return \is_array($trustedProxies) && $this->isTrustedProxy($trustedProxies, $remoteAddress);
873
+    }
874 874
 }
Please login to merge, or discard this patch.