Passed
Push — master ( fca16e...a09e23 )
by Alexander
04:10
created

HttpResources::parseRequestUri()   B

Complexity

Conditions 11
Paths 10

Size

Total Lines 45
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

Changes 4
Bugs 0 Features 0
Metric Value
cc 11
eloc 23
nc 10
nop 0
dl 0
loc 45
rs 7.3166
c 4
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
/**
4
 * Lenevor Framework
5
 *
6
 * LICENSE
7
 *
8
 * This source file is subject to the new BSD license that is bundled
9
 * with this package in the file license.md.
10
 * It is also available through the world-wide-web at this URL:
11
 * https://lenevor.com/license
12
 * If you did not receive a copy of the license and are unable to
13
 * obtain it through the world-wide-web, please send an email
14
 * to [email protected] so we can send you a copy immediately.
15
 *
16
 * @package     Lenevor
17
 * @subpackage  Base
18
 * @link        https://lenevor.com
19
 * @copyright   Copyright (c) 2019 - 2023 Alexander Campo <[email protected]>
20
 * @license     https://opensource.org/licenses/BSD-3-Clause New BSD license or see https://lenevor.com/license or see /license.md
21
 */
22
23
namespace Syscodes\Components\Http\Resources;
24
25
use Syscodes\Components\Support\Str;
26
27
/**
28
 * Returns the HTTP requests is filtered and detected in the routes set by the user.
29
 */
30
trait HttpResources
31
{
32
	/**
33
	 * Filters a value from the start of a string in this case the passed URI string.
34
	 *
35
	 * @return string
36
	 */
37
	protected function parseRequestUri(): string
38
	{
39
		$requestUri = '';
40
		
41
		if ('1' == $this->server->get('IIS_WasUrlRewritten') && '' != $this->server->get('UNENCODED_URL')) {
42
			// IIS7 with URL Rewrite: make sure we get the unencoded URL (double slash problem)
43
			$requestUri = $this->server->get('UNENCODED_URL');
44
			$this->server->remove('UNENCODED_URL');
45
			$this->server->remove('IIS_WasUrlRewritten');
46
		} elseif ($this->server->has('REQUEST_URI')) {
47
			$requestUri = $this->server->get('REQUEST_URI');
48
			
49
			if ('' !== $requestUri && '/' === $requestUri[0]) {
50
				// To only use path and query remove the fragment.
51
				if (false !== $pos = strpos($requestUri, '#')) {
52
					$requestUri = substr($requestUri, 0, $pos);
53
				}
54
			} else {
55
				// HTTP proxy reqs setup request URI with scheme and host [and port] + the URL path,
56
				// only use URL path.
57
				$uriComponents = parse_url($requestUri);
58
				
59
				if (isset($uriComponents['path'])) {
60
					$requestUri = $uriComponents['path'];
61
				}
62
				
63
				if (isset($uriComponents['query'])) {
64
					$requestUri .= '?'.$uriComponents['query'];
65
				}
66
			}
67
		} elseif ($this->server->has('ORIG_PATH_INFO')) {
68
			// IIS 5.0, PHP as CGI
69
			$requestUri = $this->server->get('ORIG_PATH_INFO');
70
			
71
			if ('' != $this->server->get('QUERY_STRING')) {
72
				$requestUri .= '?'.$this->server->get('QUERY_STRING');
73
			}
74
			
75
			$this->server->remove('ORIG_PATH_INFO');
76
		}
77
		
78
		// normalize the request URI to ease creating sub-requests from this request
79
		$this->server->set('REQUEST_URI', $requestUri);
80
		
81
		return $this->filterDecode($requestUri);
82
	}
83
84
	/**
85
	 * Will parse QUERY_STRING and automatically detect the URI from it.
86
	 * 
87
	 * @return string
88
	 */
89
	protected function parseQueryString(): string
90
	{
91
		$uri = $_SERVER['QUERY_STRING'] ?? @getenv('QUERY_STRING');
92
93
		if (trim($uri, '/') === '') {
94
			return '';
95
		} elseif (0 === strncmp($uri, '/', 1)) {
96
			$uri    				 = explode('?', $uri, 2);
97
			$_SERVER['QUERY_STRING'] = $uri[1] ?? '';
98
			$uri    				 = $uri[0] ?? '';
99
		}
100
101
		parse_str($_SERVER['QUERY_STRING'], $_GET);
102
103
		return $this->filterDecode($uri);
104
	}
105
	
106
	/**
107
	 * Parse the base URL.
108
	 * 
109
	 * @return string
110
	 */
111
	public function parseBaseUrl(): string
112
	{
113
		$filename = basename($this->server('SCRIPT_FILENAME'));
0 ignored issues
show
Bug introduced by
It seems like server() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

113
		$filename = basename($this->/** @scrutinizer ignore-call */ server('SCRIPT_FILENAME'));
Loading history...
114
		
115
		if ($filename === basename($this->server('SCRIPT_NAME'))) {
116
			$baseUrl = $this->server('SCRIPT_NAME');
117
		} elseif ($filename === basename($this->server('PHP_SELF'))) {
118
			$baseUrl = $this->server('PHP_SELF');
119
		} elseif ($filename === basename($this->server('ORIG_SCRIPT_NAME'))) {
120
			$baseUrl = $this->server('ORIG_SCRIPT_NAME');
121
		} else {
122
			$path    = $this->server('PHP_SELF', '');
123
			$file    = $this->server('SCRIPT_NAME', '');
124
			$segs    = explode('/', trim($file, '/'));
125
			$segs    = array_reverse($segs);
126
			$index   = 0;
127
			$last    = count($segs);
128
			$baseUrl = '';
129
			
130
			do 	{
131
				$seg     = $segs[$index];
132
				$baseUrl = '/'.$seg.$baseUrl;
133
				++$index;
134
			} while ($last > $index && (false !== $pos = strpos($path, $baseUrl)) && 0 != $pos);
135
		}
136
		
137
		// Does the baseUrl have anything in common with the request_uri?
138
		$requestUri = $this->getRequestUri();
0 ignored issues
show
Bug introduced by
It seems like getRequestUri() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

138
		/** @scrutinizer ignore-call */ 
139
  $requestUri = $this->getRequestUri();
Loading history...
139
		
140
		if ('' !== $requestUri && '/' !== $requestUri[0]) {
141
			$requestUri = '/'.$requestUri;
142
		}
143
		
144
		if ($baseUrl && null !== $uri = $this->getUrlencoded($requestUri, $baseUrl)) {
145
			// Full $baseUrl matches
146
			return $this->filterDecode($uri);
147
		}
148
		
149
		if ($baseUrl && null !== $uri = $this->getUrlencoded($requestUri, rtrim(dirname($baseUrl), '/'.DIRECTORY_SEPARATOR))) {
150
			// Directory portion of $baseUrl matches
151
			return $this->filterDecode($uri);
152
		}
153
154
		$baseUrl = dirname($baseUrl ?? '');
155
		
156
		// If using mod_rewrite or ISAPI_Rewrite strip the script filename
157
		// out of baseUrl. $pos !== 0 makes sure it is not matching a value
158
		// from PATH_INFO or QUERY_STRING
159
		if (strlen($requestUri) >= strlen($baseUrl) && (false !== $pos = strpos($requestUri, $baseUrl)) && 0 !== $pos) {
160
			$baseUrl = substr($requestUri, 0, $pos + strlen($baseUrl));
161
		}
162
		
163
		return $this->filterDecode($baseUrl);
164
	}
165
	
166
	/**
167
	 * Returns the prefix as encoded in the string when the string starts with
168
	 * the given prefix, null otherwise.
169
	 *
170
	 * return string|null
171
	 */
172
	private function getUrlencoded(string $string, string $prefix): ?string
173
	{
174
		if ( ! Str::startsWith(rawurldecode($string), $prefix)) {
175
			return null;
176
		}
177
		
178
		$length = strlen($prefix);
179
		
180
		if (preg_match(sprintf('#^(%%[[:xdigit:]]{2}|.){%d}#', $length), $string, $match)) {
181
			return $match[0];
182
		}
183
		
184
		return null;
185
	}
186
187
188
	/**
189
	 * Parse the path info.
190
	 * 
191
	 * @return string
192
	 */
193
	public function parsePathInfo(): string
194
	{
195
		if (null === ($requestUri = $this->getRequestUri())) {
196
			return '/';
197
		}
198
		
199
		// Remove the query string from REQUEST_URI
200
		if (false !== $pos = strpos($requestUri, '?')) {
201
			$requestUri = substr($requestUri, 0, $pos);
202
		}
203
		
204
		if ('' !== $requestUri && '/' !== $requestUri[0]) {
205
			$requestUri = '/'.$requestUri;
206
		}
207
		
208
		if (null === ($baseUrl = $this->getBaseUrl())) {
0 ignored issues
show
Bug introduced by
It seems like getBaseUrl() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

208
		if (null === ($baseUrl = $this->/** @scrutinizer ignore-call */ getBaseUrl())) {
Loading history...
209
			return $requestUri;
210
		}
211
		
212
		$pathInfo = substr($requestUri, \strlen($baseUrl));
213
		
214
		if (false === $pathInfo || '' === $pathInfo) {
215
			// If substr() returns false then PATH_INFO is set to an empty string
216
			return '/';
217
		}
218
		
219
		return $this->filterDecode($pathInfo);
220
	}
221
222
	/**
223
	 * Filters the uri string remove any malicious characters and inappropriate slashes.
224
	 *
225
	 * @param  string  $uri
226
	 *
227
	 * @return string
228
	 */
229
	protected function filterDecode($uri): string
230
	{
231
		// Remove all characters except letters,
232
		// digits and $-_.+!*'(),{}|\\^~[]`<>#%";/?:@&=.
233
		$uri = filter_var(rawurldecode($uri), FILTER_SANITIZE_URL);
234
		$uri = mb_strtolower(trim($uri), 'UTF-8');
235
		
236
		// Return argument if not empty or return a single slash
237
		return trim($uri);
238
	}
239
}