Completed
Push — master ( e8cbe7...f3c40f )
by Victor
21:02 queued 11:32
created

Request::decodeContent()   C

Complexity

Conditions 11
Paths 13

Size

Total Lines 33
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 11
eloc 19
nc 13
nop 0
dl 0
loc 33
rs 5.2653
c 0
b 0
f 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * @author Bart Visscher <[email protected]>
4
 * @author Bernhard Posselt <[email protected]>
5
 * @author Joas Schilling <[email protected]>
6
 * @author Jörn Friedrich Dreyer <[email protected]>
7
 * @author Lukas Reschke <[email protected]>
8
 * @author Mitar <[email protected]>
9
 * @author Morris Jobke <[email protected]>
10
 * @author Robin Appelman <[email protected]>
11
 * @author Robin McCorkell <[email protected]>
12
 * @author Roeland Jago Douma <[email protected]>
13
 * @author Thomas Müller <[email protected]>
14
 * @author Thomas Tanghus <[email protected]>
15
 * @author Vincent Petry <[email protected]>
16
 *
17
 * @copyright Copyright (c) 2017, ownCloud GmbH
18
 * @license AGPL-3.0
19
 *
20
 * This code is free software: you can redistribute it and/or modify
21
 * it under the terms of the GNU Affero General Public License, version 3,
22
 * as published by the Free Software Foundation.
23
 *
24
 * This program is distributed in the hope that it will be useful,
25
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
26
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
27
 * GNU Affero General Public License for more details.
28
 *
29
 * You should have received a copy of the GNU Affero General Public License, version 3,
30
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
31
 *
32
 */
33
34
namespace OC\AppFramework\Http;
35
36
use OC\Security\CSRF\CsrfToken;
37
use OC\Security\CSRF\CsrfTokenManager;
38
use OC\Security\TrustedDomainHelper;
39
use OCP\IConfig;
40
use OCP\IRequest;
41
use OCP\Security\ICrypto;
42
use OCP\Security\ISecureRandom;
43
44
/**
45
 * Class for accessing variables in the request.
46
 * This class provides an immutable object with request variables.
47
 *
48
 * @property mixed[] cookies
49
 * @property mixed[] env
50
 * @property mixed[] files
51
 * @property string method
52
 * @property mixed[] parameters
53
 * @property mixed[] server
54
 */
55
class Request implements \ArrayAccess, \Countable, IRequest {
56
57
	const USER_AGENT_IE = '/(MSIE)|(Trident)/';
58
	const USER_AGENT_IE_8 = '/MSIE 8.0/';
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 USER_AGENT_OWNCLOUD_IOS = '/^Mozilla\/5\.0 \(iOS\) ownCloud\-iOS.*$/';
71
	const USER_AGENT_OWNCLOUD_ANDROID = '/^Mozilla\/5\.0 \(Android\) ownCloud\-android.*$/';
72
	const USER_AGENT_OWNCLOUD_DESKTOP = '/^Mozilla\/5\.0 \([A-Za-z ]+\) (mirall|csyncoC)\/.*$/';
73
	const REGEX_LOCALHOST = '/^(127\.0\.0\.1|localhost)$/';
74
75
	protected $inputStream;
76
	protected $content;
77
	protected $items = [];
78
	protected $allowedKeys = [
79
		'get',
80
		'post',
81
		'files',
82
		'server',
83
		'env',
84
		'cookies',
85
		'urlParams',
86
		'parameters',
87
		'method',
88
		'requesttoken',
89
	];
90
	/** @var ISecureRandom */
91
	protected $secureRandom;
92
	/** @var IConfig */
93
	protected $config;
94
	/** @var string */
95
	protected $requestId = '';
96
	/** @var ICrypto */
97
	protected $crypto;
98
	/** @var CsrfTokenManager|null */
99
	protected $csrfTokenManager;
100
101
	/** @var bool */
102
	protected $contentDecoded = false;
103
104
	/**
105
	 * @param array $vars An associative array with the following optional values:
106
	 *        - array 'urlParams' the parameters which were matched from the URL
107
	 *        - array 'get' the $_GET array
108
	 *        - array|string 'post' the $_POST array or JSON string
109
	 *        - array 'files' the $_FILES array
110
	 *        - array 'server' the $_SERVER array
111
	 *        - array 'env' the $_ENV array
112
	 *        - array 'cookies' the $_COOKIE array
113
	 *        - string 'method' the request method (GET, POST etc)
114
	 *        - string|false 'requesttoken' the requesttoken or false when not available
115
	 * @param ISecureRandom $secureRandom
116
	 * @param IConfig $config
117
	 * @param CsrfTokenManager|null $csrfTokenManager
118
	 * @param string $stream
119
	 * @see http://www.php.net/manual/en/reserved.variables.php
120
	 */
121
	public function __construct(array $vars= [],
122
								ISecureRandom $secureRandom = null,
123
								IConfig $config = null,
124
								CsrfTokenManager $csrfTokenManager = null,
125
								$stream = 'php://input') {
126
		$this->inputStream = $stream;
127
		$this->items['params'] = [];
128
		$this->secureRandom = $secureRandom;
129
		$this->config = $config;
130
		$this->csrfTokenManager = $csrfTokenManager;
131
132
		if(!array_key_exists('method', $vars)) {
133
			$vars['method'] = 'GET';
134
		}
135
136
		foreach($this->allowedKeys as $name) {
137
			$this->items[$name] = isset($vars[$name])
138
				? $vars[$name]
139
				: [];
140
		}
141
142
		$this->items['parameters'] = array_merge(
143
			$this->items['get'],
144
			$this->items['post'],
145
			$this->items['urlParams'],
146
			$this->items['params']
147
		);
148
149
	}
150
	/**
151
	 * @param array $parameters
152
	 */
153
	public function setUrlParameters(array $parameters) {
154
		$this->items['urlParams'] = $parameters;
155
		$this->items['parameters'] = array_merge(
156
			$this->items['parameters'],
157
			$this->items['urlParams']
158
		);
159
	}
160
161
	/**
162
	 * Countable method
163
	 * @return int
164
	 */
165
	public function count() {
166
		return count(array_keys($this->items['parameters']));
167
	}
168
169
	/**
170
	* ArrayAccess methods
171
	*
172
	* Gives access to the combined GET, POST and urlParams arrays
173
	*
174
	* Examples:
175
	*
176
	* $var = $request['myvar'];
177
	*
178
	* or
179
	*
180
	* if(!isset($request['myvar']) {
181
	* 	// Do something
182
	* }
183
	*
184
	* $request['myvar'] = 'something'; // This throws an exception.
185
	*
186
	* @param string $offset The key to lookup
187
	* @return boolean
188
	*/
189
	public function offsetExists($offset) {
190
		return isset($this->items['parameters'][$offset]);
191
	}
192
193
	/**
194
	* @see offsetExists
195
	*/
196
	public function offsetGet($offset) {
197
		return isset($this->items['parameters'][$offset])
198
			? $this->items['parameters'][$offset]
199
			: null;
200
	}
201
202
	/**
203
	* @see offsetExists
204
	*/
205
	public function offsetSet($offset, $value) {
206
		throw new \RuntimeException('You cannot change the contents of the request object');
207
	}
208
209
	/**
210
	* @see offsetExists
211
	*/
212
	public function offsetUnset($offset) {
213
		throw new \RuntimeException('You cannot change the contents of the request object');
214
	}
215
216
	/**
217
	 * Magic property accessors
218
	 * @param string $name
219
	 * @param mixed $value
220
	 */
221
	public function __set($name, $value) {
222
		throw new \RuntimeException('You cannot change the contents of the request object');
223
	}
224
225
	/**
226
	* Access request variables by method and name.
227
	* Examples:
228
	*
229
	* $request->post['myvar']; // Only look for POST variables
230
	* $request->myvar; or $request->{'myvar'}; or $request->{$myvar}
231
	* Looks in the combined GET, POST and urlParams array.
232
	*
233
	* If you access e.g. ->post but the current HTTP request method
234
	* is GET a \LogicException will be thrown.
235
	*
236
	* @param string $name The key to look for.
237
	* @throws \LogicException
238
	* @return mixed|null
239
	*/
240
	public function __get($name) {
241
		switch($name) {
242
			case 'put':
243
			case 'patch':
244
			case 'get':
245
			case 'post':
246
				if($this->method !== strtoupper($name)) {
247
					throw new \LogicException(sprintf('%s cannot be accessed in a %s request.', $name, $this->method));
248
				}
249
				return $this->getContent();
250
			case 'files':
251
			case 'server':
252
			case 'env':
253
			case 'cookies':
254
			case 'urlParams':
255
			case 'method':
256
				return isset($this->items[$name])
257
					? $this->items[$name]
258
					: null;
259
			case 'parameters':
260
			case 'params':
261
				return $this->getContent();
262
			default;
263
				return isset($this[$name])
264
					? $this[$name]
265
					: null;
266
		}
267
	}
268
269
	/**
270
	 * @param string $name
271
	 * @return bool
272
	 */
273
	public function __isset($name) {
274
		if (in_array($name, $this->allowedKeys, true)) {
275
			return true;
276
		}
277
		return isset($this->items['parameters'][$name]);
278
	}
279
280
	/**
281
	 * @param string $id
282
	 */
283
	public function __unset($id) {
284
		throw new \RuntimeException('You cannot change the contents of the request object');
285
	}
286
287
	/**
288
	 * Returns the value for a specific http header.
289
	 *
290
	 * This method returns null if the header did not exist.
291
	 *
292
	 * @param string $name
293
	 * @return string
294
	 */
295
	public function getHeader($name) {
296
297
		$name = strtoupper(str_replace(['-'], ['_'],$name));
298
		if (isset($this->server['HTTP_' . $name])) {
299
			return $this->server['HTTP_' . $name];
300
		}
301
302
		// There's a few headers that seem to end up in the top-level
303
		// server array.
304
		switch($name) {
305
			case 'CONTENT_TYPE' :
306
			case 'CONTENT_LENGTH' :
307
				if (isset($this->server[$name])) {
308
					return $this->server[$name];
309
				}
310
				break;
311
312
		}
313
314
		return null;
315
	}
316
317
	/**
318
	 * Lets you access post and get parameters by the index
319
	 * In case of json requests the encoded json body is accessed
320
	 *
321
	 * @param string $key the key which you want to access in the URL Parameter
322
	 *                     placeholder, $_POST or $_GET array.
323
	 *                     The priority how they're returned is the following:
324
	 *                     1. URL parameters
325
	 *                     2. POST parameters
326
	 *                     3. GET parameters
327
	 * @param mixed $default If the key is not found, this value will be returned
328
	 * @return mixed the content of the array
329
	 */
330
	public function getParam($key, $default = null) {
331
		return isset($this->parameters[$key])
332
			? $this->parameters[$key]
333
			: $default;
334
	}
335
336
	/**
337
	 * Returns all params that were received, be it from the request
338
	 * (as GET or POST) or throuh the URL by the route
339
	 * @return array the array with all parameters
340
	 */
341
	public function getParams() {
342
		return $this->parameters;
343
	}
344
345
	/**
346
	 * Returns the method of the request
347
	 * @return string the method of the request (POST, GET, etc)
348
	 */
349
	public function getMethod() {
350
		return $this->method;
351
	}
352
353
	/**
354
	 * Shortcut for accessing an uploaded file through the $_FILES array
355
	 * @param string $key the key that will be taken from the $_FILES array
356
	 * @return array the file in the $_FILES element
357
	 */
358
	public function getUploadedFile($key) {
359
		return isset($this->files[$key]) ? $this->files[$key] : null;
360
	}
361
362
	/**
363
	 * Shortcut for getting env variables
364
	 * @param string $key the key that will be taken from the $_ENV array
365
	 * @return array the value in the $_ENV element
366
	 */
367
	public function getEnv($key) {
368
		return isset($this->env[$key]) ? $this->env[$key] : null;
369
	}
370
371
	/**
372
	 * Shortcut for getting cookie variables
373
	 * @param string $key the key that will be taken from the $_COOKIE array
374
	 * @return string the value in the $_COOKIE element
375
	 */
376
	public function getCookie($key) {
377
		return isset($this->cookies[$key]) ? $this->cookies[$key] : null;
378
	}
379
380
	/**
381
	 * Returns the request body content.
382
	 *
383
	 * If the HTTP request method is PUT and the body
384
	 * not application/x-www-form-urlencoded or application/json a stream
385
	 * resource is returned, otherwise an array.
386
	 *
387
	 * @return array|string|resource The request body content or a resource to read the body stream.
388
	 *
389
	 * @throws \LogicException
390
	 */
391
	protected function getContent() {
392
		// If the content can't be parsed into an array then return a stream resource.
393
		if ($this->method === 'PUT'
394
			&& strpos($this->getHeader('Content-Type'), 'application/x-www-form-urlencoded') === false
395
			&& strpos($this->getHeader('Content-Type'), 'application/json') === false
396
		) {
397
			if ($this->content === false) {
398
				throw new \LogicException(
399
					'"put" can only be accessed once if not '
400
					. 'application/x-www-form-urlencoded or application/json.'
401
				);
402
			}
403
			$this->content = false;
404
			return fopen($this->inputStream, 'rb');
405
		} else {
406
			$this->decodeContent();
407
			return $this->items['parameters'];
408
		}
409
	}
410
411
	/**
412
	 * Attempt to decode the content and populate parameters
413
	 */
414
	protected function decodeContent() {
415
		if ($this->contentDecoded) {
416
			return;
417
		}
418
		$params = [];
419
420
		// 'application/json' must be decoded manually.
421
		if (strpos($this->getHeader('Content-Type'), 'application/json') !== false) {
422
			$params = json_decode(file_get_contents($this->inputStream), true);
423
			if(is_array($params) && count($params) > 0) {
424
				$this->items['params'] = $params;
425
				if($this->method === 'POST') {
426
					$this->items['post'] = $params;
427
				}
428
			}
429
430
		// Handle application/x-www-form-urlencoded for methods other than GET
431
		// or post correctly
432
		} elseif($this->method !== 'GET'
433
				&& $this->method !== 'POST'
434
				&& strpos($this->getHeader('Content-Type'), 'application/x-www-form-urlencoded') !== false) {
435
436
			parse_str(file_get_contents($this->inputStream), $params);
437
			if(is_array($params)) {
438
				$this->items['params'] = $params;
439
			}
440
		}
441
442
		if (is_array($params)) {
443
			$this->items['parameters'] = array_merge($this->items['parameters'], $params);
444
		}
445
		$this->contentDecoded = true;
446
	}
447
448
449
	/**
450
	 * Checks if the CSRF check was correct
451
	 * @return bool true if CSRF check passed
452
	 */
453
	public function passesCSRFCheck() {
454
		if($this->csrfTokenManager === null) {
455
			return false;
456
		}
457
458
		// Is CSRF protection handled outside of ownCloud?
459
		if ($this->config->getSystemValue('csrf.disabled', false)) {
460
			return true;
461
		}
462
463
		if (isset($this->items['get']['requesttoken'])) {
464
			$token = $this->items['get']['requesttoken'];
465
		} elseif (isset($this->items['post']['requesttoken'])) {
466
			$token = $this->items['post']['requesttoken'];
467
		} elseif (isset($this->items['server']['HTTP_REQUESTTOKEN'])) {
468
			$token = $this->items['server']['HTTP_REQUESTTOKEN'];
469
		} else {
470
			//no token found.
471
			return false;
472
		}
473
		$token = new CsrfToken($token);
474
475
		return $this->csrfTokenManager->isTokenValid($token);
476
	}
477
478
	/**
479
	 * Returns an ID for the request, value is not guaranteed to be unique and is mostly meant for logging
480
	 * If an X-Request-ID header is sent by the client this value will be taken.
481
	 * If `mod_unique_id` is installed this value will be taken.
482
	 * @return string
483
	 */
484
	public function getId() {
485
		// allow clients to provide a request id
486
		if(isset($this->server['HTTP_X_REQUEST_ID'])) {
487
			$reqId = $this->server['HTTP_X_REQUEST_ID'];
488
			if (strlen($reqId) > 19
489
				&& strlen($reqId) < 200
490
				&& preg_match('%^[a-zA-Z0-9-+/_=.:]+$%', $reqId)) {
491
				return $this->server['HTTP_X_REQUEST_ID'];
492
			} else {
493
				throw new \InvalidArgumentException('X-Request-ID must be 20-200 bytes of chars, numbers and -+/_=.:');
494
			}
495
		}
496
		if(isset($this->server['UNIQUE_ID'])) {
497
			return $this->server['UNIQUE_ID'];
498
		}
499
500
		if(empty($this->requestId)) {
501
			$validChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
502
			$this->requestId = $this->secureRandom->generate(20, $validChars);
503
		}
504
505
		return $this->requestId;
506
	}
507
508
	/**
509
	 * Returns the remote address, if the connection came from a trusted proxy
510
	 * and `forwarded_for_headers` has been configured then the IP address
511
	 * specified in this header will be returned instead.
512
	 * Do always use this instead of $_SERVER['REMOTE_ADDR']
513
	 * @return string IP address
514
	 */
515
	public function getRemoteAddress() {
516
		$remoteAddress = isset($this->server['REMOTE_ADDR']) ? $this->server['REMOTE_ADDR'] : '';
517
		$trustedProxies = $this->config->getSystemValue('trusted_proxies', []);
518
519
		if(is_array($trustedProxies) && in_array($remoteAddress, $trustedProxies)) {
520
			$forwardedForHeaders = $this->config->getSystemValue('forwarded_for_headers', [
521
				'HTTP_X_FORWARDED_FOR'
522
				// only have one default, so we cannot ship an insecure product out of the box
523
			]);
524
525
			foreach($forwardedForHeaders as $header) {
526
				if(isset($this->server[$header])) {
527
					foreach(explode(',', $this->server[$header]) as $IP) {
528
						$IP = trim($IP);
529
						if (filter_var($IP, FILTER_VALIDATE_IP) !== false) {
530
							return $IP;
531
						}
532
					}
533
				}
534
			}
535
		}
536
537
		return $remoteAddress;
538
	}
539
540
	/**
541
	 * Check overwrite condition
542
	 * @param string $type
543
	 * @return bool
544
	 */
545
	private function isOverwriteCondition($type = '') {
546
		$regex = '/' . $this->config->getSystemValue('overwritecondaddr', '')  . '/';
547
		$remoteAddr = isset($this->server['REMOTE_ADDR']) ? $this->server['REMOTE_ADDR'] : '';
548
		return $regex === '//' || preg_match($regex, $remoteAddr) === 1
549
		|| $type !== 'protocol';
550
	}
551
552
	/**
553
	 * Returns the server protocol. It respects one or more reverse proxies servers
554
	 * and load balancers
555
	 * @return string Server protocol (http or https)
556
	 */
557
	public function getServerProtocol() {
558
		if($this->config->getSystemValue('overwriteprotocol') !== ''
559
			&& $this->isOverwriteCondition('protocol')) {
560
			return $this->config->getSystemValue('overwriteprotocol');
561
		}
562
563
		if (isset($this->server['HTTP_X_FORWARDED_PROTO'])) {
564 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...
565
				$parts = explode(',', $this->server['HTTP_X_FORWARDED_PROTO']);
566
				$proto = strtolower(trim($parts[0]));
567
			} else {
568
				$proto = strtolower($this->server['HTTP_X_FORWARDED_PROTO']);
569
			}
570
571
			// Verify that the protocol is always HTTP or HTTPS
572
			// default to http if an invalid value is provided
573
			return $proto === 'https' ? 'https' : 'http';
574
		}
575
576
		if (isset($this->server['HTTPS'])
577
			&& $this->server['HTTPS'] !== null
578
			&& $this->server['HTTPS'] !== 'off'
579
			&& $this->server['HTTPS'] !== '') {
580
			return 'https';
581
		}
582
583
		return 'http';
584
	}
585
586
	/**
587
	 * Returns the used HTTP protocol.
588
	 *
589
	 * @return string HTTP protocol. HTTP/2, HTTP/1.1 or HTTP/1.0.
590
	 */
591 View Code Duplication
	public function getHttpProtocol() {
592
		$claimedProtocol = strtoupper($this->server['SERVER_PROTOCOL']);
593
594
		$validProtocols = [
595
			'HTTP/1.0',
596
			'HTTP/1.1',
597
			'HTTP/2',
598
		];
599
600
		if(in_array($claimedProtocol, $validProtocols, true)) {
601
			return $claimedProtocol;
602
		}
603
604
		return 'HTTP/1.1';
605
	}
606
607
	/**
608
	 * Returns the request uri, even if the website uses one or more
609
	 * reverse proxies
610
	 * @return string
611
	 */
612
	public function getRequestUri() {
613
		$uri = isset($this->server['REQUEST_URI']) ? $this->server['REQUEST_URI'] : '';
614
		if($this->config->getSystemValue('overwritewebroot') !== '' && $this->isOverwriteCondition()) {
615
			$uri = $this->getScriptName() . substr($uri, strlen($this->server['SCRIPT_NAME']));
616
		} else {
617
			$components = parse_url($uri);
618
			if ($components === false) {
619
				// due to https://bugs.php.net/bug.php?id=70942 we have to add a
620
				// fake schema and host to successfully extract path, query and fragment
621
				$components = parse_url("http://localhost$uri");
622
			}
623
			$uri = $components['path'];
624
			if (isset($components['query'])) {
625
				$uri .= '?'.$components['query'];
626
			}
627
			if (isset($components['fragment'])) {
628
				$uri .= '#'.$components['fragment'];
629
			}
630
		}
631
		return $uri;
632
	}
633
634
	/**
635
	 * Get raw PathInfo from request (not urldecoded)
636
	 * @throws \Exception
637
	 * @return string Path info
638
	 */
639
	public function getRawPathInfo() {
640
		$requestUri = isset($this->server['REQUEST_URI']) ? $this->server['REQUEST_URI'] : '';
641
		// remove too many leading slashes - can be caused by reverse proxy configuration
642
		if (strpos($requestUri, '/') === 0) {
643
			$requestUri = '/' . ltrim($requestUri, '/');
644
		}
645
646
		$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 675 which is incompatible with the return type declared by the interface OCP\IRequest::getRawPathInfo of type string.
Loading history...
647
648
		// Remove the query string from REQUEST_URI
649
		if ($pos = strpos($requestUri, '?')) {
650
			$requestUri = substr($requestUri, 0, $pos);
651
		}
652
653
		$scriptName = $this->server['SCRIPT_NAME'];
654
		$pathInfo = $requestUri;
655
656
		// strip off the script name's dir and file name
657
		// FIXME: Sabre does not really belong here
658
		list($path, $name) = \Sabre\HTTP\URLUtil::splitPath($scriptName);
659
		if (!empty($path)) {
660
			if($path === $pathInfo || strpos($pathInfo, $path.'/') === 0) {
661
				$pathInfo = substr($pathInfo, strlen($path));
662
			} else {
663
				throw new \Exception("The requested uri($requestUri) cannot be processed by the script '$scriptName')");
664
			}
665
		}
666
		if (strpos($pathInfo, '/'.$name) === 0) {
667
			$pathInfo = substr($pathInfo, strlen($name) + 1);
668
		}
669
		if (strpos($pathInfo, $name) === 0) {
670
			$pathInfo = substr($pathInfo, strlen($name));
671
		}
672
		if($pathInfo === false || $pathInfo === '/'){
673
			return '';
674
		} else {
675
			return $pathInfo;
676
		}
677
	}
678
679
	/**
680
	 * Get PathInfo from request
681
	 * @throws \Exception
682
	 * @return string|false Path info or false when not found
683
	 */
684
	public function getPathInfo() {
685
		$pathInfo = isset($this->server['PATH_INFO']) ? $this->server['PATH_INFO'] : '';
686
		if($pathInfo !== '') {
687
			return $pathInfo;
688
		}
689
690
		$pathInfo = $this->getRawPathInfo();
691
		// following is taken from \Sabre\HTTP\URLUtil::decodePathSegment
692
		$pathInfo = rawurldecode($pathInfo);
693
		$encoding = mb_detect_encoding($pathInfo, ['UTF-8', 'ISO-8859-1']);
694
695
		switch($encoding) {
696
			case 'ISO-8859-1' :
697
				$pathInfo = utf8_encode($pathInfo);
698
		}
699
		// end copy
700
701
		return $pathInfo;
702
	}
703
704
	/**
705
	 * Returns the script name, even if the website uses one or more
706
	 * reverse proxies
707
	 * @return string the script name
708
	 */
709
	public function getScriptName() {
710
		$name = $this->server['SCRIPT_NAME'];
711
		$overwriteWebRoot =  $this->config->getSystemValue('overwritewebroot');
712
		if ($overwriteWebRoot !== '' && $this->isOverwriteCondition()) {
713
			// FIXME: This code is untestable due to __DIR__, also that hardcoded path is really dangerous
714
			$serverRoot = str_replace('\\', '/', substr(__DIR__, 0, -strlen('lib/private/appframework/http/')));
715
			$suburi = str_replace('\\', '/', substr(realpath($this->server['SCRIPT_FILENAME']), strlen($serverRoot)));
716
			$name = '/' . ltrim($overwriteWebRoot . $suburi, '/');
717
		}
718
		return $name;
719
	}
720
721
	/**
722
	 * Checks whether the user agent matches a given regex
723
	 * @param array $agent array of agent names
724
	 * @return bool true if at least one of the given agent matches, false otherwise
725
	 */
726
	public function isUserAgent(array $agent) {
727
		if (!isset($this->server['HTTP_USER_AGENT'])) {
728
			return false;
729
		}
730
		foreach ($agent as $regex) {
731
			if (preg_match($regex, $this->server['HTTP_USER_AGENT'])) {
732
				return true;
733
			}
734
		}
735
		return false;
736
	}
737
738
	/**
739
	 * Returns the unverified server host from the headers without checking
740
	 * whether it is a trusted domain
741
	 * @return string Server host
742
	 */
743
	public function getInsecureServerHost() {
744
		$host = 'localhost';
745
		if (isset($this->server['HTTP_X_FORWARDED_HOST'])) {
746 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...
747
				$parts = explode(',', $this->server['HTTP_X_FORWARDED_HOST']);
748
				$host = trim(current($parts));
749
			} else {
750
				$host = $this->server['HTTP_X_FORWARDED_HOST'];
751
			}
752
		} else {
753
			if (isset($this->server['HTTP_HOST'])) {
754
				$host = $this->server['HTTP_HOST'];
755
			} else if (isset($this->server['SERVER_NAME'])) {
756
				$host = $this->server['SERVER_NAME'];
757
			}
758
		}
759
		return $host;
760
	}
761
762
763
	/**
764
	 * Returns the server host from the headers, or the first configured
765
	 * trusted domain if the host isn't in the trusted list
766
	 * @return string Server host
767
	 */
768
	public function getServerHost() {
769
		// overwritehost is always trusted
770
		$host = $this->getOverwriteHost();
771
		if ($host !== null) {
772
			return $host;
773
		}
774
775
		// get the host from the headers
776
		$host = $this->getInsecureServerHost();
777
778
		// Verify that the host is a trusted domain if the trusted domains
779
		// are defined
780
		// If no trusted domain is provided the first trusted domain is returned
781
		$trustedDomainHelper = new TrustedDomainHelper($this->config);
782
		if ($trustedDomainHelper->isTrustedDomain($host)) {
783
			return $host;
784
		} else {
785
			$trustedList = $this->config->getSystemValue('trusted_domains', []);
786
			if (!empty($trustedList)) {
787
				return $trustedList[0];
788
			} else {
789
				return '';
790
			}
791
		}
792
	}
793
794
	/**
795
	 * Returns the overwritehost setting from the config if set and
796
	 * if the overwrite condition is met
797
	 * @return string|null overwritehost value or null if not defined or the defined condition
798
	 * isn't met
799
	 */
800
	private function getOverwriteHost() {
801
		if($this->config->getSystemValue('overwritehost') !== '' && $this->isOverwriteCondition()) {
802
			return $this->config->getSystemValue('overwritehost');
803
		}
804
		return null;
805
	}
806
807
}
808