Completed
Push — release-2.1 ( e55abf...f15ab1 )
by
unknown
08:31
created

QueryString.php ➔ cleanRequest()   F

Complexity

Conditions 90
Paths > 20000

Size

Total Lines 321
Code Lines 152

Duplication

Lines 34
Ratio 10.59 %

Importance

Changes 0
Metric Value
cc 90
eloc 152
nc 4294967295
nop 0
dl 34
loc 321
rs 2
c 0
b 0
f 0

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
/**
4
 * This file does a lot of important stuff.  Mainly, this means it handles
5
 * the query string, request variables, and session management.
6
 *
7
 * Simple Machines Forum (SMF)
8
 *
9
 * @package SMF
10
 * @author Simple Machines http://www.simplemachines.org
11
 * @copyright 2018 Simple Machines and individual contributors
12
 * @license http://www.simplemachines.org/about/smf/license.php BSD
13
 *
14
 * @version 2.1 Beta 4
15
 */
16
17
if (!defined('SMF'))
18
	die('No direct access...');
19
20
/**
21
 * Clean the request variables - add html entities to GET and slashes if magic_quotes_gpc is Off.
22
 *
23
 * What it does:
24
 * - cleans the request variables (ENV, GET, POST, COOKIE, SERVER) and
25
 * - makes sure the query string was parsed correctly.
26
 * - handles the URLs passed by the queryless URLs option.
27
 * - makes sure, regardless of php.ini, everything has slashes.
28
 * - sets up $board, $topic, and $scripturl and $_REQUEST['start'].
29
 * - determines, or rather tries to determine, the client's IP.
30
 */
31
32
function cleanRequest()
33
{
34
	global $board, $topic, $boardurl, $scripturl, $modSettings, $smcFunc;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
35
36
	// Makes it easier to refer to things this way.
37
	$scripturl = $boardurl . '/index.php';
38
39
	// What function to use to reverse magic quotes - if sybase is on we assume that the database sensibly has the right unescape function!
40
	$removeMagicQuoteFunction = ini_get('magic_quotes_sybase') || strtolower(ini_get('magic_quotes_sybase')) == 'on' ? 'unescapestring__recursive' : 'stripslashes__recursive';
41
42
	// Save some memory.. (since we don't use these anyway.)
43
	unset($GLOBALS['HTTP_POST_VARS'], $GLOBALS['HTTP_POST_VARS']);
44
	unset($GLOBALS['HTTP_POST_FILES'], $GLOBALS['HTTP_POST_FILES']);
45
46
	// These keys shouldn't be set...ever.
47
	if (isset($_REQUEST['GLOBALS']) || isset($_COOKIE['GLOBALS']))
48
		die('Invalid request variable.');
49
50
	// Same goes for numeric keys.
51
	foreach (array_merge(array_keys($_POST), array_keys($_GET), array_keys($_FILES)) as $key)
52
		if (is_numeric($key))
53
			die('Numeric request keys are invalid.');
54
55
	// Numeric keys in cookies are less of a problem. Just unset those.
56
	foreach ($_COOKIE as $key => $value)
57
		if (is_numeric($key))
58
			unset($_COOKIE[$key]);
59
60
	// Get the correct query string.  It may be in an environment variable...
61
	if (!isset($_SERVER['QUERY_STRING']))
62
		$_SERVER['QUERY_STRING'] = getenv('QUERY_STRING');
63
64
	// It seems that sticking a URL after the query string is mighty common, well, it's evil - don't.
65
	if (strpos($_SERVER['QUERY_STRING'], 'http') === 0)
66
	{
67
		header('HTTP/1.1 400 Bad Request');
68
		die;
69
	}
70
71
	// Are we going to need to parse the ; out?
72
	if (strpos(ini_get('arg_separator.input'), ';') === false && !empty($_SERVER['QUERY_STRING']))
73
	{
74
		// Get rid of the old one! You don't know where it's been!
75
		$_GET = array();
76
77
		// Was this redirected? If so, get the REDIRECT_QUERY_STRING.
78
		// Do not urldecode() the querystring.
79
		$_SERVER['QUERY_STRING'] = substr($_SERVER['QUERY_STRING'], 0, 5) === 'url=/' ? $_SERVER['REDIRECT_QUERY_STRING'] : $_SERVER['QUERY_STRING'];
80
81
		// Replace ';' with '&' and '&something&' with '&something=&'.  (this is done for compatibility...)
82
		// @todo smflib
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
83
		parse_str(preg_replace('/&(\w+)(?=&|$)/', '&$1=', strtr($_SERVER['QUERY_STRING'], array(';?' => '&', ';' => '&', '%00' => '', "\0" => ''))), $_GET);
84
85
		// Magic quotes still applies with parse_str - so clean it up.
86 View Code Duplication
		if (function_exists('get_magic_quotes_gpc') && @get_magic_quotes_gpc() != 0 && empty($modSettings['integrate_magic_quotes']))
87
			$_GET = $removeMagicQuoteFunction($_GET);
88
	}
89
	elseif (strpos(ini_get('arg_separator.input'), ';') !== false)
90
	{
91 View Code Duplication
		if (function_exists('get_magic_quotes_gpc') && @get_magic_quotes_gpc() != 0 && empty($modSettings['integrate_magic_quotes']))
92
			$_GET = $removeMagicQuoteFunction($_GET);
93
94
		// Search engines will send action=profile%3Bu=1, which confuses PHP.
95
		foreach ($_GET as $k => $v)
96
		{
97
			if ((string) $v === $v && strpos($k, ';') !== false)
98
			{
99
				$temp = explode(';', $v);
100
				$_GET[$k] = $temp[0];
101
102
				for ($i = 1, $n = count($temp); $i < $n; $i++)
103
				{
104
					@list ($key, $val) = @explode('=', $temp[$i], 2);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
105
					if (!isset($_GET[$key]))
106
						$_GET[$key] = $val;
107
				}
108
			}
109
110
			// This helps a lot with integration!
111
			if (strpos($k, '?') === 0)
112
			{
113
				$_GET[substr($k, 1)] = $v;
114
				unset($_GET[$k]);
115
			}
116
		}
117
	}
118
119
	// There's no query string, but there is a URL... try to get the data from there.
120
	if (!empty($_SERVER['REQUEST_URI']))
121
	{
122
		// Remove the .html, assuming there is one.
123
		if (substr($_SERVER['REQUEST_URI'], strrpos($_SERVER['REQUEST_URI'], '.'), 4) == '.htm')
124
			$request = substr($_SERVER['REQUEST_URI'], 0, strrpos($_SERVER['REQUEST_URI'], '.'));
125
		else
126
			$request = $_SERVER['REQUEST_URI'];
127
128
		// @todo smflib.
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
129
		// Replace 'index.php/a,b,c/d/e,f' with 'a=b,c&d=&e=f' and parse it into $_GET.
130
		if (strpos($request, basename($scripturl) . '/') !== false)
131
		{
132
			parse_str(substr(preg_replace('/&(\w+)(?=&|$)/', '&$1=', strtr(preg_replace('~/([^,/]+),~', '/$1=', substr($request, strpos($request, basename($scripturl)) + strlen(basename($scripturl)))), '/', '&')), 1), $temp);
133 View Code Duplication
			if (function_exists('get_magic_quotes_gpc') && @get_magic_quotes_gpc() != 0 && empty($modSettings['integrate_magic_quotes']))
134
				$temp = $removeMagicQuoteFunction($temp);
135
			$_GET += $temp;
136
		}
137
	}
138
139
	// If magic quotes is on we have some work...
140
	if (function_exists('get_magic_quotes_gpc') && @get_magic_quotes_gpc() != 0)
141
	{
142
		$_ENV = $removeMagicQuoteFunction($_ENV);
143
		$_POST = $removeMagicQuoteFunction($_POST);
144
		$_COOKIE = $removeMagicQuoteFunction($_COOKIE);
145
		foreach ($_FILES as $k => $dummy)
146
			if (isset($_FILES[$k]['name']))
147
				$_FILES[$k]['name'] = $removeMagicQuoteFunction($_FILES[$k]['name']);
148
	}
149
150
	// Add entities to GET.  This is kinda like the slashes on everything else.
151
	$_GET = htmlspecialchars__recursive($_GET);
152
153
	// Let's not depend on the ini settings... why even have COOKIE in there, anyway?
154
	$_REQUEST = $_POST + $_GET;
155
156
	// Make sure $board and $topic are numbers.
157
	if (isset($_REQUEST['board']))
158
	{
159
		// Make sure its a string and not something else like an array
160
		$_REQUEST['board'] = (string) $_REQUEST['board'];
161
162
		// If there's a slash in it, we've got a start value! (old, compatible links.)
163
		if (strpos($_REQUEST['board'], '/') !== false)
164
			list ($_REQUEST['board'], $_REQUEST['start']) = explode('/', $_REQUEST['board']);
165
		// Same idea, but dots.  This is the currently used format - ?board=1.0...
166
		elseif (strpos($_REQUEST['board'], '.') !== false)
167
			list ($_REQUEST['board'], $_REQUEST['start']) = explode('.', $_REQUEST['board']);
168
		// Now make absolutely sure it's a number.
169
		$board = (int) $_REQUEST['board'];
170
		$_REQUEST['start'] = isset($_REQUEST['start']) ? (int) $_REQUEST['start'] : 0;
171
172
		// This is for "Who's Online" because it might come via POST - and it should be an int here.
173
		$_GET['board'] = $board;
174
	}
175
	// Well, $board is going to be a number no matter what.
176
	else
177
		$board = 0;
178
179
	// If there's a threadid, it's probably an old YaBB SE link.  Flow with it.
180
	if (isset($_REQUEST['threadid']) && !isset($_REQUEST['topic']))
181
		$_REQUEST['topic'] = $_REQUEST['threadid'];
182
183
	// We've got topic!
184
	if (isset($_REQUEST['topic']))
185
	{
186
		// Make sure its a string and not something else like an array
187
		$_REQUEST['topic'] = (string) $_REQUEST['topic'];
188
189
		// Slash means old, beta style, formatting.  That's okay though, the link should still work.
190
		if (strpos($_REQUEST['topic'], '/') !== false)
191
			list ($_REQUEST['topic'], $_REQUEST['start']) = explode('/', $_REQUEST['topic']);
192
		// Dots are useful and fun ;).  This is ?topic=1.15.
193
		elseif (strpos($_REQUEST['topic'], '.') !== false)
194
			list ($_REQUEST['topic'], $_REQUEST['start']) = explode('.', $_REQUEST['topic']);
195
196
		// Topic should always be an integer
197
		$topic = $_GET['topic'] = $_REQUEST['topic'] = (int) $_REQUEST['topic'];
198
199
		// Start could be a lot of things...
200
		// ... empty ...
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% 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...
201
		if (empty($_REQUEST['start']))
202
		{
203
			$_REQUEST['start'] = 0;
204
		}
205
		// ... a simple number ...
206
		elseif (is_numeric($_REQUEST['start']))
207
		{
208
			$_REQUEST['start'] = (int) $_REQUEST['start'];
209
		}
210
		// ... or a specific message ...
211 View Code Duplication
		elseif (strpos($_REQUEST['start'], 'msg') === 0)
212
		{
213
			$virtual_msg = (int) substr($_REQUEST['start'], 3);
214
			$_REQUEST['start'] = $virtual_msg === 0 ? 0 : 'msg' . $virtual_msg;
215
		}
216
		// ... or whatever is new ...
217
		elseif (strpos($_REQUEST['start'], 'new') === 0)
218
		{
219
			$_REQUEST['start'] = 'new';
220
		}
221
		// ... or since a certain time ...
222 View Code Duplication
		elseif (strpos($_REQUEST['start'], 'from') === 0)
223
		{
224
			$timestamp = (int) substr($_REQUEST['start'], 4);
225
			$_REQUEST['start'] = $timestamp === 0 ? 0 : 'from' . $timestamp;
226
		}
227
		// ... or something invalid, in which case we reset it to 0.
228
		else
229
			$_REQUEST['start'] = 0;
230
	}
231
	else
232
		$topic = 0;
233
234
	// There should be a $_REQUEST['start'], some at least.  If you need to default to other than 0, use $_GET['start'].
235
	if (empty($_REQUEST['start']) || $_REQUEST['start'] < 0 || (int) $_REQUEST['start'] > 2147473647)
236
		$_REQUEST['start'] = 0;
237
238
	// The action needs to be a string and not an array or anything else
239
	if (isset($_REQUEST['action']))
240
		$_REQUEST['action'] = (string) $_REQUEST['action'];
241
	if (isset($_GET['action']))
242
		$_GET['action'] = (string) $_GET['action'];
243
244
	// Some mail providers like to encode semicolons in activation URLs...
245
	if (!empty($_REQUEST['action']) && substr($_SERVER['QUERY_STRING'], 0, 18) == 'action=activate%3b')
246
	{
247
		header('location: ' . $scripturl . '?' . str_replace('%3b', ';', $_SERVER['QUERY_STRING']));
0 ignored issues
show
Security Response Splitting introduced by
'location: ' . $scriptur...SERVER['QUERY_STRING']) can contain request data and is used in response header context(s) leading to a potential security vulnerability.

1 path for user data to reach this point

  1. Fetching key QUERY_STRING from $_SERVER, and $_SERVER['QUERY_STRING'] is passed through str_replace()
    in Sources/QueryString.php on line 247

Response Splitting Attacks

Allowing an attacker to set a response header, opens your application to response splitting attacks; effectively allowing an attacker to send any response, he would like.

General Strategies to prevent injection

In general, it is advisable to prevent any user-data to reach this point. This can be done by white-listing certain values:

if ( ! in_array($value, array('this-is-allowed', 'and-this-too'), true)) {
    throw new \InvalidArgumentException('This input is not allowed.');
}

For numeric data, we recommend to explicitly cast the data:

$sanitized = (integer) $tainted;
Loading history...
248
		exit;
249
	}
250
251
	// Make sure we have a valid REMOTE_ADDR.
252
	if (!isset($_SERVER['REMOTE_ADDR']))
253
	{
254
		$_SERVER['REMOTE_ADDR'] = '';
255
		// A new magic variable to indicate we think this is command line.
256
		$_SERVER['is_cli'] = true;
257
	}
258
	// Perhaps we have a IPv6 address.
259
	elseif (isValidIP($_SERVER['REMOTE_ADDR']))
260
	{
261
		$_SERVER['REMOTE_ADDR'] = preg_replace('~^::ffff:(\d+\.\d+\.\d+\.\d+)~', '\1', $_SERVER['REMOTE_ADDR']);
262
	}
263
264
	// Try to calculate their most likely IP for those people behind proxies (And the like).
265
	$_SERVER['BAN_CHECK_IP'] = $_SERVER['REMOTE_ADDR'];
266
267
	// If we haven't specified how to handle Reverse Proxy IP headers, lets do what we always used to do.
268
	if (!isset($modSettings['proxy_ip_header']))
269
		$modSettings['proxy_ip_header'] = 'autodetect';
270
271
	// Which headers are we going to check for Reverse Proxy IP headers?
272
	if ($modSettings['proxy_ip_header'] == 'disabled')
273
		$reverseIPheaders = array();
274
	elseif ($modSettings['proxy_ip_header'] == 'autodetect')
275
		$reverseIPheaders = array('HTTP_X_FORWARDED_FOR', 'HTTP_CLIENT_IP');
276
	else
277
		$reverseIPheaders = array($modSettings['proxy_ip_header']);
278
279
	// Find the user's IP address. (but don't let it give you 'unknown'!)
280
	foreach ($reverseIPheaders as $proxyIPheader)
281
	{
282
		// Ignore if this is not set.
283
		if (!isset($_SERVER[$proxyIPheader]))
284
			continue;
285
286
		if (!empty($modSettings['proxy_ip_servers']))
287
		{
288
			foreach (explode(',', $modSettings['proxy_ip_servers']) as $proxy)
289
				if ($proxy == $_SERVER['REMOTE_ADDR'] || matchIPtoCIDR($_SERVER['REMOTE_ADDR'], $proxy))
290
					continue;
291
		}
292
293
		// If there are commas, get the last one.. probably.
294
		if (strpos($_SERVER[$proxyIPheader], ',') !== false)
295
		{
296
			$ips = array_reverse(explode(', ', $_SERVER[$proxyIPheader]));
297
298
			// Go through each IP...
299
			foreach ($ips as $i => $ip)
300
			{
301
				// Make sure it's in a valid range...
302
				if (preg_match('~^((0|10|172\.(1[6-9]|2[0-9]|3[01])|192\.168|255|127)\.|unknown|::1|fe80::|fc00::)~', $ip) != 0 && preg_match('~^((0|10|172\.(1[6-9]|2[0-9]|3[01])|192\.168|255|127)\.|unknown|::1|fe80::|fc00::)~', $_SERVER['REMOTE_ADDR']) == 0)
303
				{
304 View Code Duplication
					if (!isValidIPv6($_SERVER[$proxyIPheader]) || preg_match('~::ffff:\d+\.\d+\.\d+\.\d+~', $_SERVER[$proxyIPheader]) !== 0)
305
					{
306
						$_SERVER[$proxyIPheader] = preg_replace('~^::ffff:(\d+\.\d+\.\d+\.\d+)~', '\1', $_SERVER[$proxyIPheader]);
307
308
						// Just incase we have a legacy IPv4 address.
309
						// @ TODO: Convert to IPv6.
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
310
						if (preg_match('~^((([1]?\d)?\d|2[0-4]\d|25[0-5])\.){3}(([1]?\d)?\d|2[0-4]\d|25[0-5])$~', $_SERVER[$proxyIPheader]) === 0)
311
							continue;
312
					}
313
314
					continue;
315
				}
316
317
				// Otherwise, we've got an IP!
318
				$_SERVER['BAN_CHECK_IP'] = trim($ip);
319
				break;
320
			}
321
		}
322
		// Otherwise just use the only one.
323
		elseif (preg_match('~^((0|10|172\.(1[6-9]|2[0-9]|3[01])|192\.168|255|127)\.|unknown|::1|fe80::|fc00::)~', $_SERVER[$proxyIPheader]) == 0 || preg_match('~^((0|10|172\.(1[6-9]|2[0-9]|3[01])|192\.168|255|127)\.|unknown|::1|fe80::|fc00::)~', $_SERVER['REMOTE_ADDR']) != 0)
324
			$_SERVER['BAN_CHECK_IP'] = $_SERVER[$proxyIPheader];
325 View Code Duplication
		elseif (!isValidIPv6($_SERVER[$proxyIPheader]) || preg_match('~::ffff:\d+\.\d+\.\d+\.\d+~', $_SERVER[$proxyIPheader]) !== 0)
326
		{
327
			$_SERVER[$proxyIPheader] = preg_replace('~^::ffff:(\d+\.\d+\.\d+\.\d+)~', '\1', $_SERVER[$proxyIPheader]);
328
329
			// Just incase we have a legacy IPv4 address.
330
			// @ TODO: Convert to IPv6.
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
331
			if (preg_match('~^((([1]?\d)?\d|2[0-4]\d|25[0-5])\.){3}(([1]?\d)?\d|2[0-4]\d|25[0-5])$~', $_SERVER[$proxyIPheader]) === 0)
332
				continue;
333
		}
334
	}
335
336
	// Make sure we know the URL of the current request.
337
	if (empty($_SERVER['REQUEST_URI']))
338
		$_SERVER['REQUEST_URL'] = $scripturl . (!empty($_SERVER['QUERY_STRING']) ? '?' . $_SERVER['QUERY_STRING'] : '');
339
	elseif (preg_match('~^([^/]+//[^/]+)~', $scripturl, $match) == 1)
340
		$_SERVER['REQUEST_URL'] = $match[1] . $_SERVER['REQUEST_URI'];
341
	else
342
		$_SERVER['REQUEST_URL'] = $_SERVER['REQUEST_URI'];
343
344
	// And make sure HTTP_USER_AGENT is set.
345
	$_SERVER['HTTP_USER_AGENT'] = isset($_SERVER['HTTP_USER_AGENT']) ? (isset($smcFunc['htmlspecialchars']) ? $smcFunc['htmlspecialchars']($smcFunc['db_unescape_string']($_SERVER['HTTP_USER_AGENT']), ENT_QUOTES) : htmlspecialchars($smcFunc['db_unescape_string']($_SERVER['HTTP_USER_AGENT']), ENT_QUOTES)) : '';
346
347
	// Some final checking.
348
	if (!isValidIP($_SERVER['BAN_CHECK_IP']))
349
		$_SERVER['BAN_CHECK_IP'] = '';
350
	if ($_SERVER['REMOTE_ADDR'] == 'unknown')
351
		$_SERVER['REMOTE_ADDR'] = '';
352
}
353
354
/**
355
 * Validates a IPv6 address. returns true if it is ipv6.
356
 *
357
 * @param string $ip The ip address to be validated
358
 * @return boolean Whether the specified IP is a valid IPv6 address
0 ignored issues
show
Documentation introduced by
Should the return type not be false|string?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
359
 */
360
function isValidIPv6($ip)
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $ip. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
361
{
362
	//looking for :
363
	if (strpos($ip, ':') === false)
364
		return false;
365
366
	//check valid address
367
	return inet_pton($ip);
368
}
369
370
/**
371
 * Converts IPv6s to numbers.  This makes ban checks much easier.
372
 *
373
 * @param string $ip The IP address to be converted
374
 * @return array An array containing the expanded IP parts
375
 */
376
function convertIPv6toInts($ip)
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $ip. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
377
{
378
	static $expanded = array();
379
380
	// Check if we have done this already.
381
	if (isset($expanded[$ip]))
382
		return $expanded[$ip];
383
384
	// Expand the IP out.
385
	$expanded_ip = explode(':', expandIPv6($ip));
386
387
	$new_ip = array();
388
	foreach ($expanded_ip as $int)
389
		$new_ip[] = hexdec($int);
390
391
	// Save this incase of repeated use.
392
	$expanded[$ip] = $new_ip;
393
394
	return $expanded[$ip];
395
}
396
397
/**
398
 * Expands a IPv6 address to its full form.
399
 *
400
 * @param string $addr The IPv6 address
401
 * @param bool $strict_check Whether to check the length of the expanded address for compliance
402
 * @return string|bool The expanded IPv6 address or false if $strict_check is true and the result isn't valid
403
 */
404
function expandIPv6($addr, $strict_check = true)
405
{
406
	static $converted = array();
407
408
	// Check if we have done this already.
409
	if (isset($converted[$addr]))
410
		return $converted[$addr];
411
412
	// Check if there are segments missing, insert if necessary.
413
	if (strpos($addr, '::') !== false)
414
	{
415
		$part = explode('::', $addr);
416
		$part[0] = explode(':', $part[0]);
417
		$part[1] = explode(':', $part[1]);
418
		$missing = array();
419
420
		for ($i = 0; $i < (8 - (count($part[0]) + count($part[1]))); $i++)
421
			array_push($missing, '0000');
422
423
		$part = array_merge($part[0], $missing, $part[1]);
424
	}
425
	else
426
		$part = explode(':', $addr);
427
428
	// Pad each segment until it has 4 digits.
429
	foreach ($part as &$p)
430
		while (strlen($p) < 4)
431
			$p = '0' . $p;
432
433
	unset($p);
434
435
	// Join segments.
436
	$result = implode(':', $part);
437
438
	// Save this incase of repeated use.
439
	$converted[$addr] = $result;
440
441
	// Quick check to make sure the length is as expected.
442
	if (!$strict_check || strlen($result) == 39)
443
		return $result;
444
	else
445
		return false;
446
}
447
448
449
/**
450
 * Detect if a IP is in a CIDR address
451
 * - returns true or false
452
 *
453
 * @param string $ip_address IP address to check
454
 * @param string $cidr_address CIDR address to verify
455
 * @return bool Whether the IP matches the CIDR
456
*/
457
function matchIPtoCIDR($ip_address, $cidr_address)
458
{
459
    list ($cidr_network, $cidr_subnetmask) = preg_split('/', $cidr_address);
460
    return (ip2long($ip_address) & (~((1 << (32 - $cidr_subnetmask)) - 1))) == ip2long($cidr_network);
461
}
462
463
/**
464
 * Adds slashes to the array/variable.
465
 * What it does:
466
 * - returns the var, as an array or string, with escapes as required.
467
 * - importantly escapes all keys and values!
468
 * - calls itself recursively if necessary.
469
 *
470
 * @param array|string $var A string or array of strings to escape
471
 * @return array|string The escaped string or array of escaped strings
472
 */
473 View Code Duplication
function escapestring__recursive($var)
0 ignored issues
show
Duplication introduced by
This function 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...
474
{
475
	global $smcFunc;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
476
477
	if (!is_array($var))
478
		return $smcFunc['db_escape_string']($var);
479
480
	// Reindex the array with slashes.
481
	$new_var = array();
482
483
	// Add slashes to every element, even the indexes!
484
	foreach ($var as $k => $v)
485
		$new_var[$smcFunc['db_escape_string']($k)] = escapestring__recursive($v);
486
487
	return $new_var;
488
}
489
490
/**
491
 * Adds html entities to the array/variable.  Uses two underscores to guard against overloading.
492
 * What it does:
493
 * - adds entities (&quot;, &lt;, &gt;) to the array or string var.
494
 * - importantly, does not effect keys, only values.
495
 * - calls itself recursively if necessary.
496
 *
497
 * @param array|string $var The string or array of strings to add entites to
498
 * @param int $level Which level we're at within the array (if called recursively)
499
 * @return array|string The string or array of strings with entities added
500
 */
501
function htmlspecialchars__recursive($var, $level = 0)
502
{
503
	global $smcFunc;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
504
505
	if (!is_array($var))
506
		return isset($smcFunc['htmlspecialchars']) ? $smcFunc['htmlspecialchars']($var, ENT_QUOTES) : htmlspecialchars($var, ENT_QUOTES);
507
508
	// Add the htmlspecialchars to every element.
509
	foreach ($var as $k => $v)
510
		$var[$k] = $level > 25 ? null : htmlspecialchars__recursive($v, $level + 1);
511
512
	return $var;
513
}
514
515
/**
516
 * Removes url stuff from the array/variable.  Uses two underscores to guard against overloading.
517
 * What it does:
518
 * - takes off url encoding (%20, etc.) from the array or string var.
519
 * - importantly, does it to keys too!
520
 * - calls itself recursively if there are any sub arrays.
521
 *
522
 * @param array|string $var The string or array of strings to decode
523
 * @param int $level Which level we're at within the array (if called recursively)
524
 * @return array|string The decoded string or array of decoded strings
525
 */
526 View Code Duplication
function urldecode__recursive($var, $level = 0)
0 ignored issues
show
Duplication introduced by
This function 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...
527
{
528
	if (!is_array($var))
529
		return urldecode($var);
530
531
	// Reindex the array...
532
	$new_var = array();
533
534
	// Add the htmlspecialchars to every element.
535
	foreach ($var as $k => $v)
536
		$new_var[urldecode($k)] = $level > 25 ? null : urldecode__recursive($v, $level + 1);
537
538
	return $new_var;
539
}
540
/**
541
 * Unescapes any array or variable.  Uses two underscores to guard against overloading.
542
 * What it does:
543
 * - unescapes, recursively, from the array or string var.
544
 * - effects both keys and values of arrays.
545
 * - calls itself recursively to handle arrays of arrays.
546
 *
547
 * @param array|string $var The string or array of strings to unescape
548
 * @return array|string The unescaped string or array of unescaped strings
549
 */
550 View Code Duplication
function unescapestring__recursive($var)
0 ignored issues
show
Duplication introduced by
This function 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...
551
{
552
	global $smcFunc;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
553
554
	if (!is_array($var))
555
		return $smcFunc['db_unescape_string']($var);
556
557
	// Reindex the array without slashes, this time.
558
	$new_var = array();
559
560
	// Strip the slashes from every element.
561
	foreach ($var as $k => $v)
562
		$new_var[$smcFunc['db_unescape_string']($k)] = unescapestring__recursive($v);
563
564
	return $new_var;
565
}
566
567
/**
568
 * Remove slashes recursively.  Uses two underscores to guard against overloading.
569
 * What it does:
570
 * - removes slashes, recursively, from the array or string var.
571
 * - effects both keys and values of arrays.
572
 * - calls itself recursively to handle arrays of arrays.
573
 *
574
 * @param array|string $var The string or array of strings to strip slashes from
575
 * @param int $level = 0 What level we're at within the array (if called recursively)
576
 * @return array|string The string or array of strings with slashes stripped
577
 */
578 View Code Duplication
function stripslashes__recursive($var, $level = 0)
0 ignored issues
show
Duplication introduced by
This function 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...
579
{
580
	if (!is_array($var))
581
		return stripslashes($var);
582
583
	// Reindex the array without slashes, this time.
584
	$new_var = array();
585
586
	// Strip the slashes from every element.
587
	foreach ($var as $k => $v)
588
		$new_var[stripslashes($k)] = $level > 25 ? null : stripslashes__recursive($v, $level + 1);
589
590
	return $new_var;
591
}
592
593
/**
594
 * Trim a string including the HTML space, character 160.  Uses two underscores to guard against overloading.
595
 * What it does:
596
 * - trims a string or an the var array using html characters as well.
597
 * - does not effect keys, only values.
598
 * - may call itself recursively if needed.
599
 *
600
 * @param array|string $var The string or array of strings to trim
601
 * @param int $level = 0 How deep we're at within the array (if called recursively)
602
 * @return array|string The trimmed string or array of trimmed strings
603
 */
604
function htmltrim__recursive($var, $level = 0)
605
{
606
	global $smcFunc;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
607
608
	// Remove spaces (32), tabs (9), returns (13, 10, and 11), nulls (0), and hard spaces. (160)
0 ignored issues
show
Unused Code Comprehensibility introduced by
52% 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...
609
	if (!is_array($var))
610
		return isset($smcFunc) ? $smcFunc['htmltrim']($var) : trim($var, ' ' . "\t\n\r\x0B" . '\0' . "\xA0");
611
612
	// Go through all the elements and remove the whitespace.
613
	foreach ($var as $k => $v)
614
		$var[$k] = $level > 25 ? null : htmltrim__recursive($v, $level + 1);
615
616
	return $var;
617
}
618
619
/**
620
 * Clean up the XML to make sure it doesn't contain invalid characters.
621
 * What it does:
622
 * - removes invalid XML characters to assure the input string being
623
 * - parsed properly.
624
 *
625
 * @param string $string The string to clean
626
 * @return string The cleaned string
627
 */
628
function cleanXml($string)
629
{
630
	global $context;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
631
632
	// https://www.w3.org/TR/2000/REC-xml-20001006#NT-Char
633
	return preg_replace('~[\x00-\x08\x0B\x0C\x0E-\x19' . ($context['utf8'] ? '\x{FFFE}\x{FFFF}' : '') . ']~' . ($context['utf8'] ? 'u' : ''), '', $string);
634
}
635
636
/**
637
 * Escapes (replaces) characters in strings to make them safe for use in javascript
638
 *
639
 * @param string $string The string to escape
640
 * @return string The escaped string
641
 */
642
function JavaScriptEscape($string)
643
{
644
	global $scripturl;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
645
646
	return '\'' . strtr($string, array(
647
		"\r" => '',
648
		"\n" => '\\n',
649
		"\t" => '\\t',
650
		'\\' => '\\\\',
651
		'\'' => '\\\'',
652
		'</' => '<\' + \'/',
653
		'<script' => '<scri\'+\'pt',
654
		'<body>' => '<bo\'+\'dy>',
655
		'<a href' => '<a hr\'+\'ef',
656
		$scripturl => '\' + smf_scripturl + \'',
657
	)) . '\'';
658
}
659
660
/**
661
 * Rewrite URLs to include the session ID.
662
 * What it does:
663
 * - rewrites the URLs outputted to have the session ID, if the user
664
 *   is not accepting cookies and is using a standard web browser.
665
 * - handles rewriting URLs for the queryless URLs option.
666
 * - can be turned off entirely by setting $scripturl to an empty
667
 *   string, ''. (it wouldn't work well like that anyway.)
668
 * - because of bugs in certain builds of PHP, does not function in
669
 *   versions lower than 4.3.0 - please upgrade if this hurts you.
670
 *
671
 * @param string $buffer The unmodified output buffer
672
 * @return string The modified buffer
673
 */
674
function ob_sessrewrite($buffer)
675
{
676
	global $scripturl, $modSettings, $context;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
677
678
	// If $scripturl is set to nothing, or the SID is not defined (SSI?) just quit.
679
	if ($scripturl == '' || !defined('SID'))
680
		return $buffer;
681
682
	// Do nothing if the session is cookied, or they are a crawler - guests are caught by redirectexit().  This doesn't work below PHP 4.3.0, because it makes the output buffer bigger.
683
	// @todo smflib
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
684
	if (empty($_COOKIE) && SID != '' && !isBrowser('possibly_robot'))
685
		$buffer = preg_replace('/(?<!<link rel="canonical" href=)"' . preg_quote($scripturl, '/') . '(?!\?' . preg_quote(SID, '/') . ')\\??/', '"' . $scripturl . '?' . SID . '&amp;', $buffer);
686
	// Debugging templates, are we?
687 View Code Duplication
	elseif (isset($_GET['debug']))
688
		$buffer = preg_replace('/(?<!<link rel="canonical" href=)"' . preg_quote($scripturl, '/') . '\\??/', '"' . $scripturl . '?debug;', $buffer);
689
690
	// This should work even in 4.2.x, just not CGI without cgi.fix_pathinfo.
691
	if (!empty($modSettings['queryless_urls']) && (!$context['server']['is_cgi'] || ini_get('cgi.fix_pathinfo') == 1 || @get_cfg_var('cgi.fix_pathinfo') == 1) && ($context['server']['is_apache'] || $context['server']['is_lighttpd'] || $context['server']['is_litespeed']))
692
	{
693
		// Let's do something special for session ids!
694
		if (defined('SID') && SID != '')
695
			$buffer = preg_replace_callback('~"' . preg_quote($scripturl, '/') . '\?(?:' . SID . '(?:;|&|&amp;))((?:board|topic)=[^#"]+?)(#[^"]*?)?"~', function($m)
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $m. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
696
			{
697
				global $scripturl; return '"' . $scripturl . "/" . strtr("$m[1]", '&;=', '//,') . ".html?" . SID . (isset($m[2]) ? $m[2] : "") . '"';
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
Coding Style Comprehensibility introduced by
The string literal / does not require double quotes, as per coding-style, please use single quotes.

PHP provides two ways to mark string literals. Either with single quotes 'literal' or with double quotes "literal". The difference between these is that string literals in double quotes may contain variables with are evaluated at run-time as well as escape sequences.

String literals in single quotes on the other hand are evaluated very literally and the only two characters that needs escaping in the literal are the single quote itself (\') and the backslash (\\). Every other character is displayed as is.

Double quoted string literals may contain other variables or more complex escape sequences.

<?php

$singleQuoted = 'Value';
$doubleQuoted = "\tSingle is $singleQuoted";

print $doubleQuoted;

will print an indented: Single is Value

If your string literal does not contain variables or escape sequences, it should be defined using single quotes to make that fact clear.

For more information on PHP string literals and available escape sequences see the PHP core documentation.

Loading history...
Coding Style Best Practice introduced by
As per coding-style, please use concatenation or sprintf for the variable $m instead of interpolation.

It is generally a best practice as it is often more readable to use concatenation instead of interpolation for variables inside strings.

// Instead of
$x = "foo $bar $baz";

// Better use either
$x = "foo " . $bar . " " . $baz;
$x = sprintf("foo %s %s", $bar, $baz);
Loading history...
Coding Style Comprehensibility introduced by
The string literal .html? does not require double quotes, as per coding-style, please use single quotes.

PHP provides two ways to mark string literals. Either with single quotes 'literal' or with double quotes "literal". The difference between these is that string literals in double quotes may contain variables with are evaluated at run-time as well as escape sequences.

String literals in single quotes on the other hand are evaluated very literally and the only two characters that needs escaping in the literal are the single quote itself (\') and the backslash (\\). Every other character is displayed as is.

Double quoted string literals may contain other variables or more complex escape sequences.

<?php

$singleQuoted = 'Value';
$doubleQuoted = "\tSingle is $singleQuoted";

print $doubleQuoted;

will print an indented: Single is Value

If your string literal does not contain variables or escape sequences, it should be defined using single quotes to make that fact clear.

For more information on PHP string literals and available escape sequences see the PHP core documentation.

Loading history...
Coding Style Comprehensibility introduced by
The string literal does not require double quotes, as per coding-style, please use single quotes.

PHP provides two ways to mark string literals. Either with single quotes 'literal' or with double quotes "literal". The difference between these is that string literals in double quotes may contain variables with are evaluated at run-time as well as escape sequences.

String literals in single quotes on the other hand are evaluated very literally and the only two characters that needs escaping in the literal are the single quote itself (\') and the backslash (\\). Every other character is displayed as is.

Double quoted string literals may contain other variables or more complex escape sequences.

<?php

$singleQuoted = 'Value';
$doubleQuoted = "\tSingle is $singleQuoted";

print $doubleQuoted;

will print an indented: Single is Value

If your string literal does not contain variables or escape sequences, it should be defined using single quotes to make that fact clear.

For more information on PHP string literals and available escape sequences see the PHP core documentation.

Loading history...
Coding Style introduced by
It is generally recommended to place each PHP statement on a line by itself.

Let’s take a look at an example:

// Bad
$a = 5; $b = 6; $c = 7;

// Good
$a = 5;
$b = 6;
$c = 7;
Loading history...
698
			}, $buffer);
699 View Code Duplication
		else
700
			$buffer = preg_replace_callback('~"' . preg_quote($scripturl, '/') . '\?((?:board|topic)=[^#"]+?)(#[^"]*?)?"~', function($m)
701
			{
702
				global $scripturl; return '"' . $scripturl . '/' . strtr("$m[1]", '&;=', '//,') . '.html' . (isset($m[2]) ? $m[2] : "") . '"';
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
Coding Style Best Practice introduced by
As per coding-style, please use concatenation or sprintf for the variable $m instead of interpolation.

It is generally a best practice as it is often more readable to use concatenation instead of interpolation for variables inside strings.

// Instead of
$x = "foo $bar $baz";

// Better use either
$x = "foo " . $bar . " " . $baz;
$x = sprintf("foo %s %s", $bar, $baz);
Loading history...
Coding Style Comprehensibility introduced by
The string literal does not require double quotes, as per coding-style, please use single quotes.

PHP provides two ways to mark string literals. Either with single quotes 'literal' or with double quotes "literal". The difference between these is that string literals in double quotes may contain variables with are evaluated at run-time as well as escape sequences.

String literals in single quotes on the other hand are evaluated very literally and the only two characters that needs escaping in the literal are the single quote itself (\') and the backslash (\\). Every other character is displayed as is.

Double quoted string literals may contain other variables or more complex escape sequences.

<?php

$singleQuoted = 'Value';
$doubleQuoted = "\tSingle is $singleQuoted";

print $doubleQuoted;

will print an indented: Single is Value

If your string literal does not contain variables or escape sequences, it should be defined using single quotes to make that fact clear.

For more information on PHP string literals and available escape sequences see the PHP core documentation.

Loading history...
Coding Style introduced by
It is generally recommended to place each PHP statement on a line by itself.

Let’s take a look at an example:

// Bad
$a = 5; $b = 6; $c = 7;

// Good
$a = 5;
$b = 6;
$c = 7;
Loading history...
703
			}, $buffer);
704
	}
705
706
	// Return the changed buffer.
707
	return $buffer;
708
}
709
710
?>