Completed
Push — master ( 2eca9c...0adc68 )
by Lukas
14:43 queued 06:36
created

Request::getPathInfo()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 14
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 8
c 0
b 0
f 0
nc 2
nop 0
dl 0
loc 14
rs 9.4285
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\)( Ubuntu Chromium\/[0-9.]+|) Chrome\/[0-9.]+ (Mobile Safari|Safari)\/[0-9.]+$/';
65
	// Safari User Agent from http://www.useragentstring.com/pages/Safari/
66
	const USER_AGENT_SAFARI = '/^Mozilla\/5\.0 \([^)]+\) AppleWebKit\/[0-9.]+ \(KHTML, like Gecko\) Version\/[0-9.]+ Safari\/[0-9.A-Z]+$/';
67
	// Android Chrome user agent: https://developers.google.com/chrome/mobile/docs/user-agent
68
	const USER_AGENT_ANDROID_MOBILE_CHROME = '#Android.*Chrome/[.0-9]*#';
69
	const USER_AGENT_FREEBOX = '#^Mozilla/5\.0$#';
70
	const REGEX_LOCALHOST = '/^(127\.0\.0\.1|localhost)$/';
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
	 * Wrapper around session_get_cookie_params
502
	 *
503
	 * @return array
504
	 */
505
	protected function getCookieParams() {
506
		return session_get_cookie_params();
507
	}
508
509
	/**
510
	 * Appends the __Host- prefix to the cookie if applicable
511
	 *
512
	 * @param string $name
513
	 * @return string
514
	 */
515
	protected function getProtectedCookieName($name) {
516
		$cookieParams = $this->getCookieParams();
517
		$prefix = '';
518
		if($cookieParams['secure'] === true && $cookieParams['path'] === '/') {
519
			$prefix = '__Host-';
520
		}
521
522
		return $prefix.$name;
523
	}
524
525
	/**
526
	 * Checks if the strict cookie has been sent with the request if the request
527
	 * is including any cookies.
528
	 *
529
	 * @return bool
530
	 * @since 9.1.0
531
	 */
532 View Code Duplication
	public function passesStrictCookieCheck() {
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...
533
		if(!$this->cookieCheckRequired()) {
534
			return true;
535
		}
536
537
		$cookieName = $this->getProtectedCookieName('nc_sameSiteCookiestrict');
538
		if($this->getCookie($cookieName) === 'true'
539
			&& $this->passesLaxCookieCheck()) {
540
			return true;
541
		}
542
		return false;
543
	}
544
545
	/**
546
	 * Checks if the lax cookie has been sent with the request if the request
547
	 * is including any cookies.
548
	 *
549
	 * @return bool
550
	 * @since 9.1.0
551
	 */
552 View Code Duplication
	public function passesLaxCookieCheck() {
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...
553
		if(!$this->cookieCheckRequired()) {
554
			return true;
555
		}
556
557
		$cookieName = $this->getProtectedCookieName('nc_sameSiteCookielax');
558
		if($this->getCookie($cookieName) === 'true') {
559
			return true;
560
		}
561
		return false;
562
	}
563
564
565
	/**
566
	 * Returns an ID for the request, value is not guaranteed to be unique and is mostly meant for logging
567
	 * If `mod_unique_id` is installed this value will be taken.
568
	 * @return string
569
	 */
570
	public function getId() {
571
		if(isset($this->server['UNIQUE_ID'])) {
572
			return $this->server['UNIQUE_ID'];
573
		}
574
575
		if(empty($this->requestId)) {
576
			$this->requestId = $this->secureRandom->generate(20);
577
		}
578
579
		return $this->requestId;
580
	}
581
582
	/**
583
	 * Returns the remote address, if the connection came from a trusted proxy
584
	 * and `forwarded_for_headers` has been configured then the IP address
585
	 * specified in this header will be returned instead.
586
	 * Do always use this instead of $_SERVER['REMOTE_ADDR']
587
	 * @return string IP address
588
	 */
589
	public function getRemoteAddress() {
590
		$remoteAddress = isset($this->server['REMOTE_ADDR']) ? $this->server['REMOTE_ADDR'] : '';
591
		$trustedProxies = $this->config->getSystemValue('trusted_proxies', []);
592
593
		if(is_array($trustedProxies) && in_array($remoteAddress, $trustedProxies)) {
594
			$forwardedForHeaders = $this->config->getSystemValue('forwarded_for_headers', [
595
				'HTTP_X_FORWARDED_FOR'
596
				// only have one default, so we cannot ship an insecure product out of the box
597
			]);
598
599
			foreach($forwardedForHeaders as $header) {
600
				if(isset($this->server[$header])) {
601
					foreach(explode(',', $this->server[$header]) as $IP) {
602
						$IP = trim($IP);
603
						if (filter_var($IP, FILTER_VALIDATE_IP) !== false) {
604
							return $IP;
605
						}
606
					}
607
				}
608
			}
609
		}
610
611
		return $remoteAddress;
612
	}
613
614
	/**
615
	 * Check overwrite condition
616
	 * @param string $type
617
	 * @return bool
618
	 */
619
	private function isOverwriteCondition($type = '') {
620
		$regex = '/' . $this->config->getSystemValue('overwritecondaddr', '')  . '/';
621
		$remoteAddr = isset($this->server['REMOTE_ADDR']) ? $this->server['REMOTE_ADDR'] : '';
622
		return $regex === '//' || preg_match($regex, $remoteAddr) === 1
623
		|| $type !== 'protocol';
624
	}
625
626
	/**
627
	 * Returns the server protocol. It respects one or more reverse proxies servers
628
	 * and load balancers
629
	 * @return string Server protocol (http or https)
630
	 */
631
	public function getServerProtocol() {
632
		if($this->config->getSystemValue('overwriteprotocol') !== ''
633
			&& $this->isOverwriteCondition('protocol')) {
634
			return $this->config->getSystemValue('overwriteprotocol');
635
		}
636
637
		if (isset($this->server['HTTP_X_FORWARDED_PROTO'])) {
638 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...
639
				$parts = explode(',', $this->server['HTTP_X_FORWARDED_PROTO']);
640
				$proto = strtolower(trim($parts[0]));
641
			} else {
642
				$proto = strtolower($this->server['HTTP_X_FORWARDED_PROTO']);
643
			}
644
645
			// Verify that the protocol is always HTTP or HTTPS
646
			// default to http if an invalid value is provided
647
			return $proto === 'https' ? 'https' : 'http';
648
		}
649
650
		if (isset($this->server['HTTPS'])
651
			&& $this->server['HTTPS'] !== null
652
			&& $this->server['HTTPS'] !== 'off'
653
			&& $this->server['HTTPS'] !== '') {
654
			return 'https';
655
		}
656
657
		return 'http';
658
	}
659
660
	/**
661
	 * Returns the used HTTP protocol.
662
	 *
663
	 * @return string HTTP protocol. HTTP/2, HTTP/1.1 or HTTP/1.0.
664
	 */
665 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...
666
		$claimedProtocol = strtoupper($this->server['SERVER_PROTOCOL']);
667
668
		$validProtocols = [
669
			'HTTP/1.0',
670
			'HTTP/1.1',
671
			'HTTP/2',
672
		];
673
674
		if(in_array($claimedProtocol, $validProtocols, true)) {
675
			return $claimedProtocol;
676
		}
677
678
		return 'HTTP/1.1';
679
	}
680
681
	/**
682
	 * Returns the request uri, even if the website uses one or more
683
	 * reverse proxies
684
	 * @return string
685
	 */
686
	public function getRequestUri() {
687
		$uri = isset($this->server['REQUEST_URI']) ? $this->server['REQUEST_URI'] : '';
688
		if($this->config->getSystemValue('overwritewebroot') !== '' && $this->isOverwriteCondition()) {
689
			$uri = $this->getScriptName() . substr($uri, strlen($this->server['SCRIPT_NAME']));
690
		}
691
		return $uri;
692
	}
693
694
	/**
695
	 * Get raw PathInfo from request (not urldecoded)
696
	 * @throws \Exception
697
	 * @return string Path info
698
	 */
699
	public function getRawPathInfo() {
700
		$requestUri = isset($this->server['REQUEST_URI']) ? $this->server['REQUEST_URI'] : '';
701
		// remove too many leading slashes - can be caused by reverse proxy configuration
702
		if (strpos($requestUri, '/') === 0) {
703
			$requestUri = '/' . ltrim($requestUri, '/');
704
		}
705
706
		$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 735 which is incompatible with the return type declared by the interface OCP\IRequest::getRawPathInfo of type string.
Loading history...
707
708
		// Remove the query string from REQUEST_URI
709
		if ($pos = strpos($requestUri, '?')) {
710
			$requestUri = substr($requestUri, 0, $pos);
711
		}
712
713
		$scriptName = $this->server['SCRIPT_NAME'];
714
		$pathInfo = $requestUri;
715
716
		// strip off the script name's dir and file name
717
		// FIXME: Sabre does not really belong here
718
		list($path, $name) = \Sabre\HTTP\URLUtil::splitPath($scriptName);
719
		if (!empty($path)) {
720
			if($path === $pathInfo || strpos($pathInfo, $path.'/') === 0) {
721
				$pathInfo = substr($pathInfo, strlen($path));
722
			} else {
723
				throw new \Exception("The requested uri($requestUri) cannot be processed by the script '$scriptName')");
724
			}
725
		}
726
		if (strpos($pathInfo, '/'.$name) === 0) {
727
			$pathInfo = substr($pathInfo, strlen($name) + 1);
728
		}
729
		if (strpos($pathInfo, $name) === 0) {
730
			$pathInfo = substr($pathInfo, strlen($name));
731
		}
732
		if($pathInfo === false || $pathInfo === '/'){
733
			return '';
734
		} else {
735
			return $pathInfo;
736
		}
737
	}
738
739
	/**
740
	 * Get PathInfo from request
741
	 * @throws \Exception
742
	 * @return string|false Path info or false when not found
743
	 */
744
	public function getPathInfo() {
745
		$pathInfo = $this->getRawPathInfo();
746
		// following is taken from \Sabre\HTTP\URLUtil::decodePathSegment
747
		$pathInfo = rawurldecode($pathInfo);
748
		$encoding = mb_detect_encoding($pathInfo, ['UTF-8', 'ISO-8859-1']);
749
750
		switch($encoding) {
751
			case 'ISO-8859-1' :
752
				$pathInfo = utf8_encode($pathInfo);
753
		}
754
		// end copy
755
756
		return $pathInfo;
757
	}
758
759
	/**
760
	 * Returns the script name, even if the website uses one or more
761
	 * reverse proxies
762
	 * @return string the script name
763
	 */
764
	public function getScriptName() {
765
		$name = $this->server['SCRIPT_NAME'];
766
		$overwriteWebRoot =  $this->config->getSystemValue('overwritewebroot');
767
		if ($overwriteWebRoot !== '' && $this->isOverwriteCondition()) {
768
			// FIXME: This code is untestable due to __DIR__, also that hardcoded path is really dangerous
769
			$serverRoot = str_replace('\\', '/', substr(__DIR__, 0, -strlen('lib/private/appframework/http/')));
770
			$suburi = str_replace('\\', '/', substr(realpath($this->server['SCRIPT_FILENAME']), strlen($serverRoot)));
771
			$name = '/' . ltrim($overwriteWebRoot . $suburi, '/');
772
		}
773
		return $name;
774
	}
775
776
	/**
777
	 * Checks whether the user agent matches a given regex
778
	 * @param array $agent array of agent names
779
	 * @return bool true if at least one of the given agent matches, false otherwise
780
	 */
781
	public function isUserAgent(array $agent) {
782
		if (!isset($this->server['HTTP_USER_AGENT'])) {
783
			return false;
784
		}
785
		foreach ($agent as $regex) {
786
			if (preg_match($regex, $this->server['HTTP_USER_AGENT'])) {
787
				return true;
788
			}
789
		}
790
		return false;
791
	}
792
793
	/**
794
	 * Returns the unverified server host from the headers without checking
795
	 * whether it is a trusted domain
796
	 * @return string Server host
797
	 */
798
	public function getInsecureServerHost() {
799
		$host = 'localhost';
800
		if (isset($this->server['HTTP_X_FORWARDED_HOST'])) {
801 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...
802
				$parts = explode(',', $this->server['HTTP_X_FORWARDED_HOST']);
803
				$host = trim(current($parts));
804
			} else {
805
				$host = $this->server['HTTP_X_FORWARDED_HOST'];
806
			}
807
		} else {
808
			if (isset($this->server['HTTP_HOST'])) {
809
				$host = $this->server['HTTP_HOST'];
810
			} else if (isset($this->server['SERVER_NAME'])) {
811
				$host = $this->server['SERVER_NAME'];
812
			}
813
		}
814
		return $host;
815
	}
816
817
818
	/**
819
	 * Returns the server host from the headers, or the first configured
820
	 * trusted domain if the host isn't in the trusted list
821
	 * @return string Server host
822
	 */
823
	public function getServerHost() {
824
		// overwritehost is always trusted
825
		$host = $this->getOverwriteHost();
826
		if ($host !== null) {
827
			return $host;
828
		}
829
830
		// get the host from the headers
831
		$host = $this->getInsecureServerHost();
832
833
		// Verify that the host is a trusted domain if the trusted domains
834
		// are defined
835
		// If no trusted domain is provided the first trusted domain is returned
836
		$trustedDomainHelper = new TrustedDomainHelper($this->config);
837
		if ($trustedDomainHelper->isTrustedDomain($host)) {
838
			return $host;
839
		} else {
840
			$trustedList = $this->config->getSystemValue('trusted_domains', []);
841
			if(!empty($trustedList)) {
842
				return $trustedList[0];
843
			} else {
844
				return '';
845
			}
846
		}
847
	}
848
849
	/**
850
	 * Returns the overwritehost setting from the config if set and
851
	 * if the overwrite condition is met
852
	 * @return string|null overwritehost value or null if not defined or the defined condition
853
	 * isn't met
854
	 */
855
	private function getOverwriteHost() {
856
		if($this->config->getSystemValue('overwritehost') !== '' && $this->isOverwriteCondition()) {
857
			return $this->config->getSystemValue('overwritehost');
858
		}
859
		return null;
860
	}
861
862
}
863