Completed
Pull Request — master (#25747)
by Kevin
18:52
created

Request::getRawPathInfo()   C

Complexity

Conditions 16
Paths 21

Size

Total Lines 58
Code Lines 28

Duplication

Lines 0
Ratio 0 %

Importance

Changes 6
Bugs 4 Features 1
Metric Value
cc 16
eloc 28
c 6
b 4
f 1
nc 21
nop 0
dl 0
loc 58
rs 6.4446

How to fix   Long Method    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) 2016, 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 = array();
78
	protected $allowedKeys = array(
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=array(),
122
								ISecureRandom $secureRandom = null,
123
								IConfig $config,
124
								CsrfTokenManager $csrfTokenManager = null,
125
								$stream = 'php://input') {
126
		$this->inputStream = $stream;
127
		$this->items['params'] = array();
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
				: array();
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) {
0 ignored issues
show
Unused Code introduced by
The parameter $name is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $value is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
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) {
0 ignored issues
show
Unused Code introduced by
The parameter $id is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
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(array('-'),array('_'),$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(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
		if (isset($this->items['get']['requesttoken'])) {
459
			$token = $this->items['get']['requesttoken'];
460
		} elseif (isset($this->items['post']['requesttoken'])) {
461
			$token = $this->items['post']['requesttoken'];
462
		} elseif (isset($this->items['server']['HTTP_REQUESTTOKEN'])) {
463
			$token = $this->items['server']['HTTP_REQUESTTOKEN'];
464
		} else {
465
			//no token found.
466
			return false;
467
		}
468
		$token = new CsrfToken($token);
469
470
		return $this->csrfTokenManager->isTokenValid($token);
471
	}
472
473
	/**
474
	 * Returns an ID for the request, value is not guaranteed to be unique and is mostly meant for logging
475
	 * If `mod_unique_id` is installed this value will be taken.
476
	 * @return string
477
	 */
478
	public function getId() {
479
		if(isset($this->server['UNIQUE_ID'])) {
480
			return $this->server['UNIQUE_ID'];
481
		}
482
483
		if(empty($this->requestId)) {
484
			$this->requestId = $this->secureRandom->generate(20);
485
		}
486
487
		return $this->requestId;
488
	}
489
490
	/**
491
	 * Returns the remote address, if the connection came from a trusted proxy
492
	 * and `forwarded_for_headers` has been configured then the IP address
493
	 * specified in this header will be returned instead.
494
	 * Do always use this instead of $_SERVER['REMOTE_ADDR']
495
	 * @return string IP address
496
	 */
497
	public function getRemoteAddress() {
498
		$remoteAddress = isset($this->server['REMOTE_ADDR']) ? $this->server['REMOTE_ADDR'] : '';
499
		$trustedProxies = $this->config->getSystemValue('trusted_proxies', []);
500
501
		if(is_array($trustedProxies) && in_array($remoteAddress, $trustedProxies)) {
502
			$forwardedForHeaders = $this->config->getSystemValue('forwarded_for_headers', [
503
				'HTTP_X_FORWARDED_FOR'
504
				// only have one default, so we cannot ship an insecure product out of the box
505
			]);
506
507
			foreach($forwardedForHeaders as $header) {
508
				if(isset($this->server[$header])) {
509
					foreach(explode(',', $this->server[$header]) as $IP) {
510
						$IP = trim($IP);
511
						if (filter_var($IP, FILTER_VALIDATE_IP) !== false) {
512
							return $IP;
513
						}
514
					}
515
				}
516
			}
517
		}
518
519
		return $remoteAddress;
520
	}
521
522
	/**
523
	 * Check overwrite condition
524
	 * @param string $type
525
	 * @return bool
526
	 */
527
	private function isOverwriteCondition($type = '') {
528
		$regex = '/' . $this->config->getSystemValue('overwritecondaddr', '')  . '/';
529
		$remoteAddr = isset($this->server['REMOTE_ADDR']) ? $this->server['REMOTE_ADDR'] : '';
530
		return $regex === '//' || preg_match($regex, $remoteAddr) === 1
531
		|| $type !== 'protocol';
532
	}
533
534
	/**
535
	 * Returns the server protocol. It respects one or more reverse proxies servers
536
	 * and load balancers
537
	 * @return string Server protocol (http or https)
538
	 */
539
	public function getServerProtocol() {
540
		if($this->config->getSystemValue('overwriteprotocol') !== ''
541
			&& $this->isOverwriteCondition('protocol')) {
542
			return $this->config->getSystemValue('overwriteprotocol');
543
		}
544
545
		if (isset($this->server['HTTP_X_FORWARDED_PROTO'])) {
546 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...
547
				$parts = explode(',', $this->server['HTTP_X_FORWARDED_PROTO']);
548
				$proto = strtolower(trim($parts[0]));
549
			} else {
550
				$proto = strtolower($this->server['HTTP_X_FORWARDED_PROTO']);
551
			}
552
553
			// Verify that the protocol is always HTTP or HTTPS
554
			// default to http if an invalid value is provided
555
			return $proto === 'https' ? 'https' : 'http';
556
		}
557
558
		if (isset($this->server['HTTPS'])
559
			&& $this->server['HTTPS'] !== null
560
			&& $this->server['HTTPS'] !== 'off'
561
			&& $this->server['HTTPS'] !== '') {
562
			return 'https';
563
		}
564
565
		return 'http';
566
	}
567
568
	/**
569
	 * Returns the used HTTP protocol.
570
	 *
571
	 * @return string HTTP protocol. HTTP/2, HTTP/1.1 or HTTP/1.0.
572
	 */
573 View Code Duplication
	public function getHttpProtocol() {
574
		$claimedProtocol = strtoupper($this->server['SERVER_PROTOCOL']);
575
576
		$validProtocols = [
577
			'HTTP/1.0',
578
			'HTTP/1.1',
579
			'HTTP/2',
580
		];
581
582
		if(in_array($claimedProtocol, $validProtocols, true)) {
583
			return $claimedProtocol;
584
		}
585
586
		return 'HTTP/1.1';
587
	}
588
589
	/**
590
	 * Returns the request uri, even if the website uses one or more
591
	 * reverse proxies
592
	 * @return string
593
	 */
594
	public function getRequestUri() {
595
		$uri = isset($this->server['REQUEST_URI']) ? $this->server['REQUEST_URI'] : '';
596
		if($this->config->getSystemValue('overwritewebroot') !== '' && $this->isOverwriteCondition()) {
597
			$uri = $this->getScriptName() . substr($uri, strlen($this->getRawScriptName()));
598
		}
599
		return $uri;
600
	}
601
602
	/**
603
	 * Get raw PathInfo from request (not urldecoded)
604
	 * @throws \Exception
605
	 * @return string Path info
606
	 */
607
	public function getRawPathInfo() {
608
        
609
        /** cleans leading slashes and query params before use */
610
        $requestUri = $this->getCleanRequestUri();
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->getCleanRequestUri(); of type string|string[] adds the type string[] to the return on line 662 which is incompatible with the return type declared by the interface OCP\IRequest::getRawPathInfo of type string.
Loading history...
611
612
        /** fixes php-fpm proxypassmatch setups - convert script name to only the .php file name */
613
		$scriptName = $this->getRawScriptName($requestUri);
614
615
        if(!empty($scriptName) && !empty($requestUri) && strpos($requestUri, $scriptName) !== false) {
616
            /** fixes php-fpm sethandler setups - remove script name from path_info */
617
            $scriptNameInRequestUri = strpos($requestUri, $scriptName);
618
            $position = $scriptNameInRequestUri + strlen($scriptName);//get end position of SCRIPT_NAME
619
            $pathInfo = substr($requestUri, $position); // get the remaining path after SCRIPT_NAME for PATH_INFO
620
621
        } elseif(!empty($requestUri) && strpos($requestUri, '.php') === false) {
622
            
623
            if(empty($scriptName) && !empty($requestUri) && $requestUri !== DIRECTORY_SEPARATOR) {
624
                
625
                $scriptName=DIRECTORY_SEPARATOR;
626
                
627
                if(strpos($requestUri, $scriptName) === 0) {
628
                    $requestUri = ltrim($requestUri, $scriptName);
629
                    $pathInfo = $requestUri;
630
                }
631
                
632
            } elseif(empty($requestUri)) {
633
                
634
                $pathInfo = $requestUri;
635
                
636
            } else {
637
                /** 
638
                * Add script name to the front of requesturi if no 
639
                * script name found to pass uri script processing check below
640
                */
641
                $pathInfo = $requestUri;
642
                $requestUri = $scriptName . $requestUri;
643
            }
644
645
        } else {
646
            
647
            $pathInfo = $requestUri;
648
            
649
            /** stop empty uri with / scriptnames causing errors as this would go to index.php */
650
            if(empty($requestUri)) {
651
                $requestUri = $scriptName;
652
            }
653
        }
654
655
        if(!empty($scriptName) && (strpos($requestUri, $scriptName) === false )) {
656
            throw new \Exception("The requested uri($requestUri) cannot be processed by the script '$scriptName')");
657
        }
658
        
659
		if($pathInfo === false || $pathInfo === '/'){
660
			return '';
661
		} else {
662
			return $pathInfo;
0 ignored issues
show
Bug introduced by
The variable $pathInfo does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
663
		}
664
	}
665
666
	/**
667
	 * Get PathInfo from request
668
	 * @throws \Exception
669
	 * @return string|false Path info or false when not found
670
	 */
671
	public function getPathInfo() {
672
		if(isset($this->server['PATH_INFO'])) {
673
			return $this->server['PATH_INFO'];
674
		}
675
        
676
		$pathInfo = $this->getRawPathInfo();
677
		// following is taken from \Sabre\HTTP\URLUtil::decodePathSegment
678
		$pathInfo = rawurldecode($pathInfo);
679
		$encoding = mb_detect_encoding($pathInfo, ['UTF-8', 'ISO-8859-1']);
680
681
		switch($encoding) {
682
			case 'ISO-8859-1' :
683
				$pathInfo = utf8_encode($pathInfo);
684
		}
685
		// end copy
686
687
		return $pathInfo;
688
	}
689
690
	/**
691
	 * Returns the script name, even if the website uses one or more
692
	 * reverse proxies
693
	 * @return string the script name
694
	 */
695
	public function getScriptName() {
696
		$name = $this->getRawScriptName($this->getCleanRequestUri());
697
		$overwriteWebRoot =  $this->config->getSystemValue('overwritewebroot');
698
		if ($overwriteWebRoot !== '' && $this->isOverwriteCondition()) {
699
			// FIXME: This code is untestable due to __DIR__, also that hardcoded path is really dangerous
700
			$serverRoot = str_replace('\\', '/', substr(__DIR__, 0, -strlen('lib/private/appframework/http/')));
701
			$suburi = str_replace('\\', '/', substr(realpath($this->server['SCRIPT_FILENAME']), strlen($serverRoot)));
702
			$name = '/' . ltrim($overwriteWebRoot . $suburi, '/');
703
		}
704
		return $name;
705
	}
706
707
	/**
708
	 * Checks whether the user agent matches a given regex
709
	 * @param array $agent array of agent names
710
	 * @return bool true if at least one of the given agent matches, false otherwise
711
	 */
712
	public function isUserAgent(array $agent) {
713
		if (!isset($this->server['HTTP_USER_AGENT'])) {
714
			return false;
715
		}
716
		foreach ($agent as $regex) {
717
			if (preg_match($regex, $this->server['HTTP_USER_AGENT'])) {
718
				return true;
719
			}
720
		}
721
		return false;
722
	}
723
724
	/**
725
	 * Returns the unverified server host from the headers without checking
726
	 * whether it is a trusted domain
727
	 * @return string Server host
728
	 */
729
	public function getInsecureServerHost() {
730
		$host = 'localhost';
731
		if (isset($this->server['HTTP_X_FORWARDED_HOST'])) {
732 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...
733
				$parts = explode(',', $this->server['HTTP_X_FORWARDED_HOST']);
734
				$host = trim(current($parts));
735
			} else {
736
				$host = $this->server['HTTP_X_FORWARDED_HOST'];
737
			}
738
		} else {
739
			if (isset($this->server['HTTP_HOST'])) {
740
				$host = $this->server['HTTP_HOST'];
741
			} else if (isset($this->server['SERVER_NAME'])) {
742
				$host = $this->server['SERVER_NAME'];
743
			}
744
		}
745
		return $host;
746
	}
747
748
749
	/**
750
	 * Returns the server host from the headers, or the first configured
751
	 * trusted domain if the host isn't in the trusted list
752
	 * @return string Server host
753
	 */
754
	public function getServerHost() {
755
		// overwritehost is always trusted
756
		$host = $this->getOverwriteHost();
757
		if ($host !== null) {
758
			return $host;
759
		}
760
761
		// get the host from the headers
762
		$host = $this->getInsecureServerHost();
763
764
		// Verify that the host is a trusted domain if the trusted domains
765
		// are defined
766
		// If no trusted domain is provided the first trusted domain is returned
767
		$trustedDomainHelper = new TrustedDomainHelper($this->config);
768
		if ($trustedDomainHelper->isTrustedDomain($host)) {
769
			return $host;
770
		} else {
771
			$trustedList = $this->config->getSystemValue('trusted_domains', []);
772
			if(!empty($trustedList)) {
773
				return $trustedList[0];
774
			} else {
775
				return '';
776
			}
777
		}
778
	}
779
780
	/**
781
	 * Returns the overwritehost setting from the config if set and
782
	 * if the overwrite condition is met
783
	 * @return string|null overwritehost value or null if not defined or the defined condition
784
	 * isn't met
785
	 */
786
	private function getOverwriteHost() {
787
		if($this->config->getSystemValue('overwritehost') !== '' && $this->isOverwriteCondition()) {
788
			return $this->config->getSystemValue('overwritehost');
789
		}
790
		return null;
791
	}
792
    
793
    /**
794
     * Fixes bug in Apache 2.4 / PHP FPM where script_name contains full
795
     * uri not just script name e.g /index.php/app/files instead of expected /index.php 
796
     * @see https://bugs.php.net/bug.php?id=65641
797
     * @param string $name
0 ignored issues
show
Bug introduced by
There is no parameter named $name. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
798
     * @return string
799
     */
800
    private function getRawScriptName($requestUri=null) {
801
        $scriptName = $this->server['SCRIPT_NAME'];
802
        
803
        if (strpos($scriptName, '.php') !== false) {
804
            
805
            $scriptName = DIRECTORY_SEPARATOR . basename($this->server['SCRIPT_FILENAME']);
806
            
807
            if(!empty($scriptName) && !empty($requestUri) && strpos($requestUri, $scriptName) !== false) {
808
                
809
                /** fixes php-fpm sethandler setups - remove script name from path_info */
810
                $scriptNameInRequestUri = strpos($requestUri, $scriptName);
811
                $position = $scriptNameInRequestUri + strlen($scriptName);//get end position of SCRIPT_NAME
812
                $scriptName = substr($requestUri, 0, $position);
813
            }
814
815
        } elseif(strlen($scriptName) === 0) {
816
            $scriptName = DIRECTORY_SEPARATOR;
817
        }
818
        
819
        return $scriptName;
820
    }
821
    
822
    /**
823
     * Removes leading extra slashes and query params
824
     * @return string
825
     */
826
    private function getCleanRequestUri()
827
    {
828
        $requestUri = isset($this->server['REQUEST_URI']) ? $this->server['REQUEST_URI'] : '/';
829
830
		// remove too many leading slashes - can be caused by reverse proxy configuration
831
		if (strpos($requestUri, '/') === 0) {
832
			$requestUri = '/' . ltrim($requestUri, '/');
833
		}
834
835
		$requestUri = preg_replace('%/{2,}%', '/', $requestUri);
836
837
		// Remove the query string from REQUEST_URI
838
		if ($pos = strpos($requestUri, '?')) {
839
			$requestUri = substr($requestUri, 0, $pos);
840
		}
841
        
842
        return $requestUri;
843
    }
844
}
845