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