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