Completed
Push — master ( a0af51...f9cea0 )
by Joas
41:02 queued 29:36
created

Request::getMethod()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * @copyright Copyright (c) 2016, ownCloud, Inc.
4
 *
5
 * @author Bart Visscher <[email protected]>
6
 * @author Bernhard Posselt <[email protected]>
7
 * @author Joas Schilling <[email protected]>
8
 * @author Jörn Friedrich Dreyer <[email protected]>
9
 * @author Lukas Reschke <[email protected]>
10
 * @author Mitar <[email protected]>
11
 * @author Morris Jobke <[email protected]>
12
 * @author Robin Appelman <[email protected]>
13
 * @author Robin McCorkell <[email protected]>
14
 * @author Roeland Jago Douma <[email protected]>
15
 * @author Thomas Müller <[email protected]>
16
 * @author Thomas Tanghus <[email protected]>
17
 * @author Vincent Petry <[email protected]>
18
 *
19
 * @license AGPL-3.0
20
 *
21
 * This code is free software: you can redistribute it and/or modify
22
 * it under the terms of the GNU Affero General Public License, version 3,
23
 * as published by the Free Software Foundation.
24
 *
25
 * This program is distributed in the hope that it will be useful,
26
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
27
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
28
 * GNU Affero General Public License for more details.
29
 *
30
 * You should have received a copy of the GNU Affero General Public License, version 3,
31
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
32
 *
33
 */
34
35
namespace OC\AppFramework\Http;
36
37
use OC\Security\CSRF\CsrfToken;
38
use OC\Security\CSRF\CsrfTokenManager;
39
use OC\Security\TrustedDomainHelper;
40
use OCP\IConfig;
41
use OCP\IRequest;
42
use OCP\Security\ICrypto;
43
use OCP\Security\ISecureRandom;
44
45
/**
46
 * Class for accessing variables in the request.
47
 * This class provides an immutable object with request variables.
48
 *
49
 * @property mixed[] cookies
50
 * @property mixed[] env
51
 * @property mixed[] files
52
 * @property string method
53
 * @property mixed[] parameters
54
 * @property mixed[] server
55
 */
56
class Request implements \ArrayAccess, \Countable, IRequest {
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\) 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)$/';
71
72
	/**
73
	 * @deprecated use \OCP\IRequest::USER_AGENT_CLIENT_IOS instead
74
	 */
75
	const USER_AGENT_OWNCLOUD_IOS = '/^Mozilla\/5\.0 \(iOS\) ownCloud\-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
			&& strpos($this->getHeader('Content-Type'), 'application/x-www-form-urlencoded') === false
405
			&& strpos($this->getHeader('Content-Type'), 'application/json') === false
406
		) {
407
			if ($this->content === false) {
408
				throw new \LogicException(
409
					'"put" can only be accessed once if not '
410
					. 'application/x-www-form-urlencoded or application/json.'
411
				);
412
			}
413
			$this->content = false;
414
			return fopen($this->inputStream, 'rb');
415
		} else {
416
			$this->decodeContent();
417
			return $this->items['parameters'];
418
		}
419
	}
420
421
	/**
422
	 * Attempt to decode the content and populate parameters
423
	 */
424
	protected function decodeContent() {
425
		if ($this->contentDecoded) {
426
			return;
427
		}
428
		$params = [];
429
430
		// 'application/json' must be decoded manually.
431
		if (strpos($this->getHeader('Content-Type'), 'application/json') !== false) {
432
			$params = json_decode(file_get_contents($this->inputStream), true);
433
			if(count($params) > 0) {
434
				$this->items['params'] = $params;
435
				if($this->method === 'POST') {
436
					$this->items['post'] = $params;
437
				}
438
			}
439
440
		// Handle application/x-www-form-urlencoded for methods other than GET
441
		// or post correctly
442
		} elseif($this->method !== 'GET'
443
				&& $this->method !== 'POST'
444
				&& strpos($this->getHeader('Content-Type'), 'application/x-www-form-urlencoded') !== false) {
445
446
			parse_str(file_get_contents($this->inputStream), $params);
447
			if(is_array($params)) {
448
				$this->items['params'] = $params;
449
			}
450
		}
451
452
		if (is_array($params)) {
453
			$this->items['parameters'] = array_merge($this->items['parameters'], $params);
454
		}
455
		$this->contentDecoded = true;
456
	}
457
458
459
	/**
460
	 * Checks if the CSRF check was correct
461
	 * @return bool true if CSRF check passed
462
	 */
463
	public function passesCSRFCheck() {
464
		if($this->csrfTokenManager === null) {
465
			return false;
466
		}
467
468
		if(!$this->passesStrictCookieCheck()) {
469
			return false;
470
		}
471
472
		if (isset($this->items['get']['requesttoken'])) {
473
			$token = $this->items['get']['requesttoken'];
474
		} elseif (isset($this->items['post']['requesttoken'])) {
475
			$token = $this->items['post']['requesttoken'];
476
		} elseif (isset($this->items['server']['HTTP_REQUESTTOKEN'])) {
477
			$token = $this->items['server']['HTTP_REQUESTTOKEN'];
478
		} else {
479
			//no token found.
480
			return false;
481
		}
482
		$token = new CsrfToken($token);
483
484
		return $this->csrfTokenManager->isTokenValid($token);
485
	}
486
487
	/**
488
	 * Whether the cookie checks are required
489
	 *
490
	 * @return bool
491
	 */
492
	private function cookieCheckRequired() {
493
		if($this->getCookie(session_name()) === null && $this->getCookie('oc_token') === null) {
494
			return false;
495
		}
496
497
		return true;
498
	}
499
500
	/**
501
	 * Checks if the strict cookie has been sent with the request if the request
502
	 * is including any cookies.
503
	 *
504
	 * @return bool
505
	 * @since 9.1.0
506
	 */
507
	public function passesStrictCookieCheck() {
508
		if(!$this->cookieCheckRequired()) {
509
			return true;
510
		}
511
		if($this->getCookie('nc_sameSiteCookiestrict') === 'true'
512
			&& $this->passesLaxCookieCheck()) {
513
			return true;
514
		}
515
		return false;
516
	}
517
518
	/**
519
	 * Checks if the lax cookie has been sent with the request if the request
520
	 * is including any cookies.
521
	 *
522
	 * @return bool
523
	 * @since 9.1.0
524
	 */
525
	public function passesLaxCookieCheck() {
526
		if(!$this->cookieCheckRequired()) {
527
			return true;
528
		}
529
		if($this->getCookie('nc_sameSiteCookielax') === 'true') {
530
			return true;
531
		}
532
		return false;
533
	}
534
535
536
	/**
537
	 * Returns an ID for the request, value is not guaranteed to be unique and is mostly meant for logging
538
	 * If `mod_unique_id` is installed this value will be taken.
539
	 * @return string
540
	 */
541
	public function getId() {
542
		if(isset($this->server['UNIQUE_ID'])) {
543
			return $this->server['UNIQUE_ID'];
544
		}
545
546
		if(empty($this->requestId)) {
547
			$this->requestId = $this->secureRandom->generate(20);
548
		}
549
550
		return $this->requestId;
551
	}
552
553
	/**
554
	 * Returns the remote address, if the connection came from a trusted proxy
555
	 * and `forwarded_for_headers` has been configured then the IP address
556
	 * specified in this header will be returned instead.
557
	 * Do always use this instead of $_SERVER['REMOTE_ADDR']
558
	 * @return string IP address
559
	 */
560
	public function getRemoteAddress() {
561
		$remoteAddress = isset($this->server['REMOTE_ADDR']) ? $this->server['REMOTE_ADDR'] : '';
562
		$trustedProxies = $this->config->getSystemValue('trusted_proxies', []);
563
564
		if(is_array($trustedProxies) && in_array($remoteAddress, $trustedProxies)) {
565
			$forwardedForHeaders = $this->config->getSystemValue('forwarded_for_headers', [
566
				'HTTP_X_FORWARDED_FOR'
567
				// only have one default, so we cannot ship an insecure product out of the box
568
			]);
569
570
			foreach($forwardedForHeaders as $header) {
571
				if(isset($this->server[$header])) {
572
					foreach(explode(',', $this->server[$header]) as $IP) {
573
						$IP = trim($IP);
574
						if (filter_var($IP, FILTER_VALIDATE_IP) !== false) {
575
							return $IP;
576
						}
577
					}
578
				}
579
			}
580
		}
581
582
		return $remoteAddress;
583
	}
584
585
	/**
586
	 * Check overwrite condition
587
	 * @param string $type
588
	 * @return bool
589
	 */
590
	private function isOverwriteCondition($type = '') {
591
		$regex = '/' . $this->config->getSystemValue('overwritecondaddr', '')  . '/';
592
		$remoteAddr = isset($this->server['REMOTE_ADDR']) ? $this->server['REMOTE_ADDR'] : '';
593
		return $regex === '//' || preg_match($regex, $remoteAddr) === 1
594
		|| $type !== 'protocol';
595
	}
596
597
	/**
598
	 * Returns the server protocol. It respects one or more reverse proxies servers
599
	 * and load balancers
600
	 * @return string Server protocol (http or https)
601
	 */
602
	public function getServerProtocol() {
603
		if($this->config->getSystemValue('overwriteprotocol') !== ''
604
			&& $this->isOverwriteCondition('protocol')) {
605
			return $this->config->getSystemValue('overwriteprotocol');
606
		}
607
608
		if (isset($this->server['HTTP_X_FORWARDED_PROTO'])) {
609 View Code Duplication
			if (strpos($this->server['HTTP_X_FORWARDED_PROTO'], ',') !== false) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
610
				$parts = explode(',', $this->server['HTTP_X_FORWARDED_PROTO']);
611
				$proto = strtolower(trim($parts[0]));
612
			} else {
613
				$proto = strtolower($this->server['HTTP_X_FORWARDED_PROTO']);
614
			}
615
616
			// Verify that the protocol is always HTTP or HTTPS
617
			// default to http if an invalid value is provided
618
			return $proto === 'https' ? 'https' : 'http';
619
		}
620
621
		if (isset($this->server['HTTPS'])
622
			&& $this->server['HTTPS'] !== null
623
			&& $this->server['HTTPS'] !== 'off'
624
			&& $this->server['HTTPS'] !== '') {
625
			return 'https';
626
		}
627
628
		return 'http';
629
	}
630
631
	/**
632
	 * Returns the used HTTP protocol.
633
	 *
634
	 * @return string HTTP protocol. HTTP/2, HTTP/1.1 or HTTP/1.0.
635
	 */
636 View Code Duplication
	public function getHttpProtocol() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
637
		$claimedProtocol = strtoupper($this->server['SERVER_PROTOCOL']);
638
639
		$validProtocols = [
640
			'HTTP/1.0',
641
			'HTTP/1.1',
642
			'HTTP/2',
643
		];
644
645
		if(in_array($claimedProtocol, $validProtocols, true)) {
646
			return $claimedProtocol;
647
		}
648
649
		return 'HTTP/1.1';
650
	}
651
652
	/**
653
	 * Returns the request uri, even if the website uses one or more
654
	 * reverse proxies
655
	 * @return string
656
	 */
657
	public function getRequestUri() {
658
		$uri = isset($this->server['REQUEST_URI']) ? $this->server['REQUEST_URI'] : '';
659
		if($this->config->getSystemValue('overwritewebroot') !== '' && $this->isOverwriteCondition()) {
660
			$uri = $this->getScriptName() . substr($uri, strlen($this->server['SCRIPT_NAME']));
661
		}
662
		return $uri;
663
	}
664
665
	/**
666
	 * Get raw PathInfo from request (not urldecoded)
667
	 * @throws \Exception
668
	 * @return string Path info
669
	 */
670
	public function getRawPathInfo() {
671
		$requestUri = isset($this->server['REQUEST_URI']) ? $this->server['REQUEST_URI'] : '';
672
		// remove too many leading slashes - can be caused by reverse proxy configuration
673
		if (strpos($requestUri, '/') === 0) {
674
			$requestUri = '/' . ltrim($requestUri, '/');
675
		}
676
677
		$requestUri = preg_replace('%/{2,}%', '/', $requestUri);
0 ignored issues
show
Bug Compatibility introduced by
The expression preg_replace('%/{2,}%', '/', $requestUri); of type string|string[] adds the type string[] to the return on line 706 which is incompatible with the return type declared by the interface OCP\IRequest::getRawPathInfo of type string.
Loading history...
678
679
		// Remove the query string from REQUEST_URI
680
		if ($pos = strpos($requestUri, '?')) {
681
			$requestUri = substr($requestUri, 0, $pos);
682
		}
683
684
		$scriptName = $this->server['SCRIPT_NAME'];
685
		$pathInfo = $requestUri;
686
687
		// strip off the script name's dir and file name
688
		// FIXME: Sabre does not really belong here
689
		list($path, $name) = \Sabre\HTTP\URLUtil::splitPath($scriptName);
690
		if (!empty($path)) {
691
			if($path === $pathInfo || strpos($pathInfo, $path.'/') === 0) {
692
				$pathInfo = substr($pathInfo, strlen($path));
693
			} else {
694
				throw new \Exception("The requested uri($requestUri) cannot be processed by the script '$scriptName')");
695
			}
696
		}
697
		if (strpos($pathInfo, '/'.$name) === 0) {
698
			$pathInfo = substr($pathInfo, strlen($name) + 1);
699
		}
700
		if (strpos($pathInfo, $name) === 0) {
701
			$pathInfo = substr($pathInfo, strlen($name));
702
		}
703
		if($pathInfo === false || $pathInfo === '/'){
704
			return '';
705
		} else {
706
			return $pathInfo;
707
		}
708
	}
709
710
	/**
711
	 * Get PathInfo from request
712
	 * @throws \Exception
713
	 * @return string|false Path info or false when not found
714
	 */
715
	public function getPathInfo() {
716
		$pathInfo = $this->getRawPathInfo();
717
		// following is taken from \Sabre\HTTP\URLUtil::decodePathSegment
718
		$pathInfo = rawurldecode($pathInfo);
719
		$encoding = mb_detect_encoding($pathInfo, ['UTF-8', 'ISO-8859-1']);
720
721
		switch($encoding) {
722
			case 'ISO-8859-1' :
723
				$pathInfo = utf8_encode($pathInfo);
724
		}
725
		// end copy
726
727
		return $pathInfo;
728
	}
729
730
	/**
731
	 * Returns the script name, even if the website uses one or more
732
	 * reverse proxies
733
	 * @return string the script name
734
	 */
735
	public function getScriptName() {
736
		$name = $this->server['SCRIPT_NAME'];
737
		$overwriteWebRoot =  $this->config->getSystemValue('overwritewebroot');
738
		if ($overwriteWebRoot !== '' && $this->isOverwriteCondition()) {
739
			// FIXME: This code is untestable due to __DIR__, also that hardcoded path is really dangerous
740
			$serverRoot = str_replace('\\', '/', substr(__DIR__, 0, -strlen('lib/private/appframework/http/')));
741
			$suburi = str_replace('\\', '/', substr(realpath($this->server['SCRIPT_FILENAME']), strlen($serverRoot)));
742
			$name = '/' . ltrim($overwriteWebRoot . $suburi, '/');
743
		}
744
		return $name;
745
	}
746
747
	/**
748
	 * Checks whether the user agent matches a given regex
749
	 * @param array $agent array of agent names
750
	 * @return bool true if at least one of the given agent matches, false otherwise
751
	 */
752
	public function isUserAgent(array $agent) {
753
		if (!isset($this->server['HTTP_USER_AGENT'])) {
754
			return false;
755
		}
756
		foreach ($agent as $regex) {
757
			if (preg_match($regex, $this->server['HTTP_USER_AGENT'])) {
758
				return true;
759
			}
760
		}
761
		return false;
762
	}
763
764
	/**
765
	 * Returns the unverified server host from the headers without checking
766
	 * whether it is a trusted domain
767
	 * @return string Server host
768
	 */
769
	public function getInsecureServerHost() {
770
		$host = 'localhost';
771
		if (isset($this->server['HTTP_X_FORWARDED_HOST'])) {
772 View Code Duplication
			if (strpos($this->server['HTTP_X_FORWARDED_HOST'], ',') !== false) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
773
				$parts = explode(',', $this->server['HTTP_X_FORWARDED_HOST']);
774
				$host = trim(current($parts));
775
			} else {
776
				$host = $this->server['HTTP_X_FORWARDED_HOST'];
777
			}
778
		} else {
779
			if (isset($this->server['HTTP_HOST'])) {
780
				$host = $this->server['HTTP_HOST'];
781
			} else if (isset($this->server['SERVER_NAME'])) {
782
				$host = $this->server['SERVER_NAME'];
783
			}
784
		}
785
		return $host;
786
	}
787
788
789
	/**
790
	 * Returns the server host from the headers, or the first configured
791
	 * trusted domain if the host isn't in the trusted list
792
	 * @return string Server host
793
	 */
794
	public function getServerHost() {
795
		// overwritehost is always trusted
796
		$host = $this->getOverwriteHost();
797
		if ($host !== null) {
798
			return $host;
799
		}
800
801
		// get the host from the headers
802
		$host = $this->getInsecureServerHost();
803
804
		// Verify that the host is a trusted domain if the trusted domains
805
		// are defined
806
		// If no trusted domain is provided the first trusted domain is returned
807
		$trustedDomainHelper = new TrustedDomainHelper($this->config);
808
		if ($trustedDomainHelper->isTrustedDomain($host)) {
809
			return $host;
810
		} else {
811
			$trustedList = $this->config->getSystemValue('trusted_domains', []);
812
			if(!empty($trustedList)) {
813
				return $trustedList[0];
814
			} else {
815
				return '';
816
			}
817
		}
818
	}
819
820
	/**
821
	 * Returns the overwritehost setting from the config if set and
822
	 * if the overwrite condition is met
823
	 * @return string|null overwritehost value or null if not defined or the defined condition
824
	 * isn't met
825
	 */
826
	private function getOverwriteHost() {
827
		if($this->config->getSystemValue('overwritehost') !== '' && $this->isOverwriteCondition()) {
828
			return $this->config->getSystemValue('overwritehost');
829
		}
830
		return null;
831
	}
832
833
}
834