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