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