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