Completed
Pull Request — development (#2329)
by Joshua
09:15
created

Request::user_agent()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1
Metric Value
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
cc 1
eloc 2
nc 1
nop 0
crap 1
1
<?php
2
3
/**
4
 * This parses PHP server variables, and initializes its own checking variables for use
5
 *
6
 * @name      ElkArte Forum
7
 * @copyright ElkArte Forum contributors
8
 * @license   BSD http://opensource.org/licenses/BSD-3-Clause
9
 *
10
 * @version 1.1 dev
11
 *
12
 */
13
14
/**
15
 * Class to parse $_REQUEST for always necessary data, such as 'action', 'board', 'topic', 'start'.
16
 *
17
 * What it does:
18
 * - Sanitizes the necessary data
19
 * - Determines the origin of $_REQUEST for use in security checks
20
 */
21
class Request
22
{
23
	/**
24
	 * Remote IP, if we can know it easily (as found in $_SERVER['REMOTE_ADDR'])
25
	 * @var string
26
	 */
27
	private $_client_ip;
28
29
	/**
30
	 * Secondary IP, a double-check for the most accurate client IP we can get
31
	 * @var string
32
	 */
33
	private $_ban_ip;
34
35
	/**
36
	 * HTTP or HTTPS scheme
37
	 * @var string
38
	 */
39
	private $_scheme;
40
41
	/**
42
	 * User agent
43
	 * @var string
44
	 */
45
	private $_user_agent;
46
47
	/**
48
	 * Whether the request is an XmlHttpRequest
49
	 * @var bool
50
	 */
51
	private $_xml;
52
53
	/**
54
	 * Web server software
55
	 * @var string
56
	 */
57
	private $_server_software;
58
59
	/**
60
	 * This is the pattern of a local (or unknown) IP address in both IPv4 and IPv6
61
	 * @var string
62
	 */
63
	private $_local_ip_pattern = '((0|10|172\.(1[6-9]|2[0-9]|3[01])|192\.168|255|127)\.|unknown|::1|fe80::|fc00::)';
64
65
	/**
66
	 * Local copy of the server query string
67
	 * @var string
68
	 */
69
	private $_server_query_string;
70
71
	/**
72
	 * Creates the global and method internal
73
	 * @var string
74
	 */
75
	private $_scripturl;
76
77
	/**
78
	 * Sole private Request instance
79
	 * @var Request
80
	 */
81
	private static $_req = null;
82
83
	/**
84
	 * Retrieves client IP
85
	 */
86
	public function client_ip()
87
	{
88
		return $this->_client_ip;
89
	}
90
91
	/**
92
	 * Return a secondary IP, result of a deeper check for the IP
93
	 *
94
	 * - It can be identical with client IP (and many times it will be).
95
	 * - If the secondary IP is empty, then the client IP is returned
96
	 */
97
	public function ban_ip()
98
	{
99
		return !empty($this->_ban_ip) ? $this->_ban_ip : $this->client_ip();
100
	}
101
102
	/**
103
	 * Return the HTTP scheme
104
	 */
105
	public function scheme()
106
	{
107
		return $this->_scheme;
108
	}
109
110
	/**
111
	 * Return the user agent
112
	 */
113 1
	public function user_agent()
114
	{
115 1
		return $this->_user_agent;
116
	}
117
118
	/**
119
	 * Returns whether the request is XML
120
	 */
121
	public function is_xml()
122
	{
123
		return $this->_xml;
124
	}
125
126
	/**
127
	 * Returns server software (or empty string if it wasn't set for PHP)
128
	 */
129
	public function server_software()
130
	{
131
		return $this->_server_software;
132
	}
133
134
	/**
135
	 * Private constructor.
136
	 * It parses PHP server variables, and initializes its variables.
137
	 */
138
	private function __construct()
0 ignored issues
show
Coding Style introduced by
__construct uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
139
	{
140
		// Client IP: REMOTE_ADDR, unless missing
141
		$this->_getClientIP();
142
143
		// Second IP, guesswork it is, try to get the best IP we can, when using proxies or such
144
		$this->_getBanIP();
145
146
		// Keep compatibility with the uses of $_SERVER['REMOTE_ADDR']...
147
		$_SERVER['REMOTE_ADDR'] = $this->_client_ip;
148
149
		// Set the scheme, for later use
150
		$this->_scheme = (!empty($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) == 'on') ? 'https' : 'http';
151
152
		// Make sure we know everything about you... HTTP_USER_AGENT!
153
		$this->_user_agent = isset($_SERVER['HTTP_USER_AGENT']) ? htmlspecialchars($_SERVER['HTTP_USER_AGENT'], ENT_QUOTES, 'UTF-8') : '';
154
155
		// Keep compatibility with the uses of $_SERVER['HTTP_USER_AGENT']...
156
		$_SERVER['HTTP_USER_AGENT'] = $this->_user_agent;
157
158
		// We want to know who we are, too :P
159
		$this->_server_software = isset($_SERVER['SERVER_SOFTWARE']) ? $_SERVER['SERVER_SOFTWARE'] : '';
160
	}
161
162
	/**
163
	 * Finds the claimed client IP for this connection
164
	 */
165
	private function _getClientIP()
0 ignored issues
show
Coding Style introduced by
_getClientIP uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
166
	{
167
		// Client IP: REMOTE_ADDR, unless missing
168
		if (!isset($_SERVER['REMOTE_ADDR']))
169
		{
170
			// Command line, or else.
171
			$this->_client_ip = '';
172
		}
173
		// Perhaps we have a IPv6 address.
174
		elseif (!isValidIPv6($_SERVER['REMOTE_ADDR']) || preg_match('~::ffff:\d+\.\d+\.\d+\.\d+~', $_SERVER['REMOTE_ADDR']) !== 0)
175
		{
176
			$this->_client_ip = preg_replace('~^::ffff:(\d+\.\d+\.\d+\.\d+)~', '\1', $_SERVER['REMOTE_ADDR']);
177
178
			// Just in case we have a legacy IPv4 address.
179
			// @ TODO: Convert to IPv6.
180
			if (filter_var($this->_client_ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) === false)
181
				$this->_client_ip = 'unknown';
182
		}
183
		else
184
			$this->_client_ip = $_SERVER['REMOTE_ADDR'];
185
186
		// Final check
187
		if ($this->_client_ip == 'unknown')
188
			$this->_client_ip = '';
189
	}
190
191
	/**
192
	 * Hunts in most request areas for connection IP's for use in banning
193
	 */
194
	private function _getBanIP()
0 ignored issues
show
Coding Style introduced by
_getBanIP uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
195
	{
196
		// Start off the same as the client ip
197
		$this->_ban_ip = $this->_client_ip;
198
199
		// Forwarded, maybe?
200
		if (!empty($_SERVER['HTTP_X_FORWARDED_FOR']) && !empty($_SERVER['HTTP_CLIENT_IP']) && (preg_match('~^' . $this->_local_ip_pattern . '~', $_SERVER['HTTP_CLIENT_IP']) == 0 || preg_match('~^' . $this->_local_ip_pattern . '~', $this->_client_ip) != 0))
201
		{
202
			// Check the first forwarded for as the block - only switch if it's better that way.
203
			if (strtok($_SERVER['HTTP_X_FORWARDED_FOR'], '.') != strtok($_SERVER['HTTP_CLIENT_IP'], '.')
204
					&& '.' . strtok($_SERVER['HTTP_X_FORWARDED_FOR'], '.') == strrchr($_SERVER['HTTP_CLIENT_IP'], '.')
205
					&& (preg_match('~^((0|10|172\.(1[6-9]|2[0-9]|3[01])|192\.168|255|127)\.|unknown)~', $_SERVER['HTTP_X_FORWARDED_FOR']) == 0 || preg_match('~^((0|10|172\.(1[6-9]|2[0-9]|3[01])|192\.168|255|127)\.|unknown)~', $this->_client_ip) != 0))
206
				$this->_ban_ip = implode('.', array_reverse(explode('.', $_SERVER['HTTP_CLIENT_IP'])));
207
			else
208
				$this->_ban_ip = $_SERVER['HTTP_CLIENT_IP'];
209
		}
210
211
		if (!empty($_SERVER['HTTP_CLIENT_IP']) && (preg_match('~^' . $this->_local_ip_pattern . '~', $_SERVER['HTTP_CLIENT_IP']) == 0 || preg_match('~^' . $this->_local_ip_pattern . '~', $this->_client_ip) != 0))
212
		{
213
			// Since they are in different blocks, it's probably reversed.
214
			if (strtok($this->_client_ip, '.') != strtok($_SERVER['HTTP_CLIENT_IP'], '.'))
215
				$this->_ban_ip = implode('.', array_reverse(explode('.', $_SERVER['HTTP_CLIENT_IP'])));
216
			else
217
				$this->_ban_ip = $_SERVER['HTTP_CLIENT_IP'];
218
		}
219
		elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR']))
220
		{
221
			// If there are commas, get the last one.. probably.
222
			if (strpos($_SERVER['HTTP_X_FORWARDED_FOR'], ',') !== false)
223
			{
224
				$ips = array_reverse(explode(', ', $_SERVER['HTTP_X_FORWARDED_FOR']));
225
226
				// Go through each IP...
227
				foreach ($ips as $i => $ip)
228
				{
229
					// Make sure it's in a valid range...
230
					if (preg_match('~^' . $this->_local_ip_pattern . '~', $ip) != 0 && preg_match('~^' . $this->_local_ip_pattern . '~', $this->_client_ip) == 0)
231
						continue;
232
233
					// Otherwise, we've got an IP!
234
					$this->_ban_ip = trim($ip);
235
					break;
236
				}
237
			}
238
			// Otherwise just use the only one.
239
			elseif (preg_match('~^' . $this->_local_ip_pattern . '~', $_SERVER['HTTP_X_FORWARDED_FOR']) == 0 || preg_match('~^' . $this->_local_ip_pattern . '~', $this->_client_ip) != 0)
240
				$this->_ban_ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
241
		}
242
243
		// Some final checking.
244
		if (filter_var($this->_ban_ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) === false && !isValidIPv6($this->_ban_ip))
245
			$this->_ban_ip = '';
246
	}
247
248
	/**
249
	 * Parse the $_REQUEST, for always necessary data, such as 'action', 'board', 'topic', 'start'.
250
	 * Also figures out if this is an xml request.
251
	 *
252
	 * - Parse the request for our dear globals, I know they're in there somewhere...
253
	 */
254 5
	public function parseRequest()
0 ignored issues
show
Coding Style introduced by
parseRequest uses the super-global variable $_REQUEST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
parseRequest uses the super-global variable $_GET which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
parseRequest uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
255
	{
256 5
		global $board, $topic;
257
258
		// Look for $board first
259 5
		$board = $this->_checkBoard();
260
261
		// Look for $topic
262 5
		$topic = $this->_checkTopic();
263
264
		// There should be a $_REQUEST['start'], some at least.  If you need to default to other than 0, use $_GET['start'].
265 5
		if (empty($_REQUEST['start']) || $_REQUEST['start'] < 0 || (int) $_REQUEST['start'] > 2147473647)
266 5
			$_REQUEST['start'] = 0;
267
268
		// The action needs to be a string, too.
269 5
		if (isset($_REQUEST['action']))
270 5
			$_REQUEST['action'] = (string) $_REQUEST['action'];
271
272 5
		if (isset($_GET['action']))
273 5
			$_GET['action'] = (string) $_GET['action'];
274
275 5
		$this->_xml = (isset($_SERVER['X_REQUESTED_WITH']) && $_SERVER['X_REQUESTED_WITH'] == 'XMLHttpRequest') || isset($_REQUEST['xml']);
276 5
	}
277
278
	/**
279
	 * Finds and returns the board numeric if its been requested
280
	 *
281
	 * - helper function for parseRequest
282
	 */
283 5
	private function _checkBoard()
0 ignored issues
show
Coding Style introduced by
_checkBoard uses the super-global variable $_REQUEST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
_checkBoard uses the super-global variable $_GET which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
284
	{
285 5
		if (isset($_REQUEST['board']))
286 5
		{
287
			// Make sure it's a string (not an array, say)
288 3
			$_REQUEST['board'] = (string) $_REQUEST['board'];
289
290
			// If we have ?board=3/10, that's... board=3, start=10! (old, compatible links.)
291 3
			if (strpos($_REQUEST['board'], '/') !== false)
292 3
				list ($_REQUEST['board'], $_REQUEST['start']) = explode('/', $_REQUEST['board']);
293
			// Or perhaps we have... ?board=1.0...
294 2
			elseif (strpos($_REQUEST['board'], '.') !== false)
295
				list ($_REQUEST['board'], $_REQUEST['start']) = explode('.', $_REQUEST['board']);
296
297
			// $board and $_REQUEST['start'] are always numbers.
0 ignored issues
show
Unused Code Comprehensibility introduced by
38% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
298 3
			$board = (int) $_REQUEST['board'];
299 3
			$_REQUEST['start'] = isset($_REQUEST['start']) ? (int) $_REQUEST['start'] : 0;
300
301
			// This is for "Who's Online" because it might come via POST - and it should be an int here.
302 3
			$_GET['board'] = $board;
303 3
		}
304
		// None? We still need *something*, and it'd better be a number
305
		else
306 2
			$board = 0;
307
308 5
		return $board;
309
	}
310
311
	/**
312
	 * Finds and returns the topic numeric if its been requested
313
	 *
314
	 * helper function for parseRequest
315
	 */
316 5
	private function _checkTopic()
0 ignored issues
show
Coding Style introduced by
_checkTopic uses the super-global variable $_REQUEST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
_checkTopic uses the super-global variable $_GET which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
317
	{
318
		// Look for threadid, old YaBB SE links have those. Just read it as a topic.
319 5
		if (isset($_REQUEST['threadid']) && !isset($_REQUEST['topic']))
320 5
			$_REQUEST['topic'] = $_REQUEST['threadid'];
321
322 5
		if (isset($_REQUEST['topic']))
323 5
		{
324
			// Make sure it's a string (not an array, say)
325 3
			$_REQUEST['topic'] = (string) $_REQUEST['topic'];
326
327
			// It might come as ?topic=1/15, from an old, SMF beta style link
328 3
			if (strpos($_REQUEST['topic'], '/') !== false)
329 3
				list ($_REQUEST['topic'], $_REQUEST['start']) = explode('/', $_REQUEST['topic']);
330
			// Or it might come as ?topic=1.15.
331 3
			elseif (strpos($_REQUEST['topic'], '.') !== false)
332 1
				list ($_REQUEST['topic'], $_REQUEST['start']) = explode('.', $_REQUEST['topic']);
333
334
			// $topic and $_REQUEST['start'] are numbers, numbers I say.
335 3
			$topic = (int) $_REQUEST['topic'];
336
337
			// @todo in Display $_REQUEST['start'] is not always a number
338 3
			$_REQUEST['start'] = isset($_REQUEST['start']) && preg_match('~^(:?(:?from|msg)?\d+|new)$~', $_REQUEST['start']) ? $_REQUEST['start'] : 0;
339
340
			// Now make sure the online log gets the right number.
341 3
			$_GET['topic'] = $topic;
342 3
		}
343
		// No topic? Well, set something, and that something is 0.
344
		else
345 2
			$topic = 0;
346
347 5
		return $topic;
348
	}
349
350
	/**
351
	 * Clean the request variables - add html entities to GET and slashes if magic_quotes_gpc is Off.
352
	 *
353
	 * What it does:
354
	 * - cleans the request variables (ENV, GET, POST, COOKIE, SERVER) and
355
	 *   makes sure the query string was parsed correctly.
356
	 * - handles the URLs passed by the queryless URLs option.
357
	 * - makes sure, regardless of php.ini, everything has slashes.
358
	 * - use with ->parseRequest() to clean and set up variables like $board or $_REQUEST['start'].
359
	 * - uses Request to try to determine client IPs for the current request.
360
	 */
361
	public function cleanRequest()
0 ignored issues
show
Coding Style introduced by
cleanRequest uses the super-global variable $_GET which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
cleanRequest uses the super-global variable $_REQUEST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
cleanRequest uses the super-global variable $_POST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
cleanRequest uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
362
	{
363
		global $boardurl, $scripturl;
364
365
		// Makes it easier to refer to things this way.
366
		$scripturl = $boardurl . '/index.php';
367
		$this->_scripturl = $scripturl;
368
369
		// Live to die another day
370
		$this->_checkExit();
371
372
		// Process server_query_string as needed
373
		$this->_cleanArg();
374
375
		// Process request_uri
376
		$this->_cleanRequest();
377
378
		// Add entities to GET.  This is kinda like the slashes on everything else.
379
		$_GET = htmlspecialchars__recursive($_GET);
380
381
		// Let's not depend on the ini settings... why even have COOKIE in there, anyway?
382
		$_REQUEST = $_POST + $_GET;
383
384
		// Make sure REMOTE_ADDR, other IPs, and the like are parsed
385
		// Parse the $_REQUEST and make sure things like board, topic don't have weird stuff
386
		$this->parseRequest();
387
388
		// Make sure we know the URL of the current request.
389
		if (empty($_SERVER['REQUEST_URI']))
390
			$_SERVER['REQUEST_URL'] = $this->_scripturl . (!empty($this->_server_query_string) ? '?' . $this->_server_query_string : '');
391
		elseif (preg_match('~^([^/]+//[^/]+)~', $this->_scripturl, $match) == 1)
392
			$_SERVER['REQUEST_URL'] = $match[1] . $_SERVER['REQUEST_URI'];
393
		else
394
			$_SERVER['REQUEST_URL'] = $_SERVER['REQUEST_URI'];
395
	}
396
397
	/**
398
	 * Checks the request and abruptly stops processing if issues are found
399
	 *
400
	 * - No magic quotes allowed
401
	 * - Don't try to set a GLOBALS key in globals
402
	 * - No numeric keys in $_GET, $_POST or $_FILE
403
	 * - No URL's appended to the query string
404
	 */
405
	private function _checkExit()
0 ignored issues
show
Coding Style introduced by
_checkExit uses the super-global variable $GLOBALS which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
_checkExit uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
406
	{
407
		// Reject magic_quotes_sybase='on'.
408
		$this->_checkMagicQuotes();
409
410
		// Save some memory.. (since we don't use these anyway.)
411
		unset($GLOBALS['HTTP_POST_VARS'], $GLOBALS['HTTP_POST_VARS']);
412
		unset($GLOBALS['HTTP_POST_FILES'], $GLOBALS['HTTP_POST_FILES']);
413
414
		// These keys shouldn't be set...ever.
415
		$this->_checkNumericKeys();
416
417
		// Get the correct query string.  It may be in an environment variable...
418
		if (!isset($_SERVER['QUERY_STRING']))
419
			$_SERVER['QUERY_STRING'] = getenv('QUERY_STRING');
420
421
		// It seems that sticking a URL after the query string is mighty common, well, it's evil - don't.
422
		if (strpos($_SERVER['QUERY_STRING'], 'http') === 0)
423
		{
424
			header('HTTP/1.1 400 Bad Request');
425
			Errors::instance()->fatal_error('', false);
426
		}
427
428
		$this->_server_query_string = $_SERVER['QUERY_STRING'];
429
	}
430
431
	/**
432
	 * Check for illegal numeric keys
433
	 *
434
	 * - Fail on illegal keys
435
	 * - Clear ones that should not be allowed
436
	 */
437
	private function _checkNumericKeys()
0 ignored issues
show
Coding Style introduced by
_checkNumericKeys uses the super-global variable $_REQUEST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
_checkNumericKeys uses the super-global variable $_COOKIE which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
_checkNumericKeys uses the super-global variable $_POST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
_checkNumericKeys uses the super-global variable $_GET which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
_checkNumericKeys uses the super-global variable $_FILES which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
438
	{
439
		if (isset($_REQUEST['GLOBALS']) || isset($_COOKIE['GLOBALS']))
440
			Errors::instance()->fatal_error('Invalid request variable.', false);
441
442
		// Same goes for numeric keys.
443
		foreach (array_merge(array_keys($_POST), array_keys($_GET), array_keys($_FILES)) as $key)
444
		{
445
			if (is_numeric($key))
446
				Errors::instance()->fatal_error('Numeric request keys are invalid.', false);
447
		}
448
449
		// Numeric keys in cookies are less of a problem. Just unset those.
450
		foreach ($_COOKIE as $key => $value)
451
		{
452
			if (is_numeric($key))
453
				unset($_COOKIE[$key]);
454
		}
455
	}
456
457
	/**
458
	 * No magic quotes allowed.
459
	 *
460
	 * - depreciated in 5.3 and done in 5.4
461
	 */
462
	private function _checkMagicQuotes()
463
	{
464
		if (version_compare(PHP_VERSION, '5.4.0', '<'))
465
		{
466
			// Reject magic_quotes_sybase='on'.
467
			if (ini_get('magic_quotes_sybase') || strtolower(ini_get('magic_quotes_sybase')) == 'on')
468
				Errors::instance()->fatal_error('magic_quotes_sybase=on was detected: your host is using an unsecure PHP configuration, deprecated and removed in current versions. Please upgrade PHP.', false);
469
470
			// Reject magic_quotes_gpc='on'.
471
			if (function_exists('get_magic_quotes_gpc') && get_magic_quotes_gpc() != 0)
472
				Errors::instance()->fatal_error('magic_quotes_gpc=on was detected: your host is using an unsecure PHP configuration, deprecated and removed in current versions. Please upgrade PHP.', false);
473
		}
474
	}
475
476
	/**
477
	 * Helper method used to clean $_GET arguments
478
	 */
479
	private function _cleanArg()
0 ignored issues
show
Coding Style introduced by
_cleanArg uses the super-global variable $_GET which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
_cleanArg uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
480
	{
481
		// Are we going to need to parse the ; out?
482
		if (strpos(ini_get('arg_separator.input'), ';') === false && !empty($this->_server_query_string))
483
		{
484
			// Get rid of the old one! You don't know where it's been!
485
			$_GET = array();
486
487
			// Was this redirected? If so, get the REDIRECT_QUERY_STRING.
488
			// Do not urldecode() the querystring, unless you so much wish to break OpenID implementation. :)
489
			$this->_server_query_string = substr($this->_server_query_string, 0, 5) === 'url=/' ? $_SERVER['REDIRECT_QUERY_STRING'] : $this->_server_query_string;
490
491
			// Some german webmailers need a decoded string, so let's decode the string for sa=activate and action=reminder
492
			if (strpos($this->_server_query_string, 'activate') !== false || strpos($this->_server_query_string, 'reminder') !== false)
493
				$this->_server_query_string = urldecode($this->_server_query_string);
494
495
			// Replace ';' with '&' and '&something&' with '&something=&'.  (this is done for compatibility...)
496
			parse_str(preg_replace('/&(\w+)(?=&|$)/', '&$1=', strtr($this->_server_query_string, array(';?' => '&', ';' => '&', '%00' => '', "\0" => ''))), $_GET);
497
498
			// reSet the global in case an addon grabs it
499
			$_SERVER['SERVER_QUERY_STRING'] = $this->_server_query_string;
500
		}
501
		elseif (strpos(ini_get('arg_separator.input'), ';') !== false)
502
		{
503
			// Search engines will send action=profile%3Bu=1, which confuses PHP.
504
			foreach ($_GET as $k => $v)
505
			{
506
				if ((string) $v === $v && strpos($k, ';') !== false)
507
				{
508
					$temp = explode(';', $v);
509
					$_GET[$k] = $temp[0];
510
511
					for ($i = 1, $n = count($temp); $i < $n; $i++)
512
					{
513
						list ($key, $val) = array_pad(explode('=', $temp[$i], 2), 2, '');
514
						if (!isset($_GET[$key]))
515
							$_GET[$key] = $val;
516
					}
517
				}
518
519
				// This helps a lot with integration!
520
				if (strpos($k, '?') === 0)
521
				{
522
					$_GET[substr($k, 1)] = $v;
523
					unset($_GET[$k]);
524
				}
525
			}
526
		}
527
	}
528
529
	/**
530
	 * If a request URI is present, this will prepare it for use
531
	 */
532
	private function _cleanRequest()
0 ignored issues
show
Coding Style introduced by
_cleanRequest uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
_cleanRequest uses the super-global variable $_GET which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
533
	{
534
		// There's no query string, but there is a URL... try to get the data from there.
535
		if (!empty($_SERVER['REQUEST_URI']))
536
		{
537
			// Remove the .html, assuming there is one.
538
			if (substr($_SERVER['REQUEST_URI'], strrpos($_SERVER['REQUEST_URI'], '.'), 4) === '.htm')
539
				$request = substr($_SERVER['REQUEST_URI'], 0, strrpos($_SERVER['REQUEST_URI'], '.'));
540
			else
541
				$request = $_SERVER['REQUEST_URI'];
542
543
			// Replace 'index.php/a,b,c/d/e,f' with 'a=b,c&d=&e=f' and parse it into $_GET.
544
			if (strpos($request, basename($this->_scripturl) . '/') !== false)
545
			{
546
				parse_str(substr(preg_replace('/&(\w+)(?=&|$)/', '&$1=', strtr(preg_replace('~/([^,/]+),~', '/$1=', substr($request, strpos($request, basename($this->_scripturl)) + strlen(basename($this->_scripturl)))), '/', '&')), 1), $temp);
547
				$_GET += $temp;
548
			}
549
		}
550
	}
551
552
	/**
553
	 * Retrieve easily the sole instance of this class.
554
	 *
555
	 * @return Request
556
	 */
557 7
	public static function instance()
558
	{
559 7
		if (self::$_req === null)
560 7
			self::$_req = new Request();
561
562 7
		return self::$_req;
563
	}
564
}