Issues (1027)

Sources/QueryString.php (4 issues)

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 2019 Simple Machines and individual contributors
12
 * @license http://www.simplemachines.org/about/smf/license.php BSD
13
 *
14
 * @version 2.1 RC2
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;
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.');
0 ignored issues
show
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
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.');
0 ignored issues
show
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
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
		send_http_status(400);
68
		die;
0 ignored issues
show
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
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
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
		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
		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);
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.
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
			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 ...
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
		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
		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']));
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
					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.
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
		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.
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
359
 */
360
function isValidIPv6($ip)
361
{
362
	//looking for :
363
	if (strpos($ip, ':') === false)
364
		return false;
365
366
	//check valid address
367
	return filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6);
368
}
369
370
/**
371
 * Expands a IPv6 address to its full form.
372
 *
373
 * @param string $addr The IPv6 address
374
 * @param bool $strict_check Whether to check the length of the expanded address for compliance
375
 * @return string|bool The expanded IPv6 address or false if $strict_check is true and the result isn't valid
376
 */
377
function expandIPv6($addr, $strict_check = true)
378
{
379
	static $converted = array();
380
381
	// Check if we have done this already.
382
	if (isset($converted[$addr]))
383
		return $converted[$addr];
384
385
	// Check if there are segments missing, insert if necessary.
386
	if (strpos($addr, '::') !== false)
387
	{
388
		$part = explode('::', $addr);
389
		$part[0] = explode(':', $part[0]);
390
		$part[1] = explode(':', $part[1]);
391
		$missing = array();
392
393
		for ($i = 0; $i < (8 - (count($part[0]) + count($part[1]))); $i++)
394
			array_push($missing, '0000');
395
396
		$part = array_merge($part[0], $missing, $part[1]);
397
	}
398
	else
399
		$part = explode(':', $addr);
400
401
	// Pad each segment until it has 4 digits.
402
	foreach ($part as &$p)
403
		while (strlen($p) < 4)
404
			$p = '0' . $p;
405
406
	unset($p);
407
408
	// Join segments.
409
	$result = implode(':', $part);
410
411
	// Save this incase of repeated use.
412
	$converted[$addr] = $result;
413
414
	// Quick check to make sure the length is as expected.
415
	if (!$strict_check || strlen($result) == 39)
416
		return $result;
417
	else
418
		return false;
419
}
420
421
/**
422
 * Detect if a IP is in a CIDR address
423
 * - returns true or false
424
 *
425
 * @param string $ip_address IP address to check
426
 * @param string $cidr_address CIDR address to verify
427
 * @return bool Whether the IP matches the CIDR
428
 */
429
function matchIPtoCIDR($ip_address, $cidr_address)
430
{
431
	list ($cidr_network, $cidr_subnetmask) = preg_split('/', $cidr_address);
432
433
	//v6?
434
	if ((strpos($cidr_network, ':') !== false))
435
	{
436
		if (!filter_var($ip_address, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) || !filter_var($cidr_network, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6))
437
			return false;
438
439
		$ip_address = inet_pton($ip_address);
440
		$cidr_network = inet_pton($cidr_network);
441
		$binMask = str_repeat("f", $cidr_subnetmask / 4);
442
		switch ($cidr_subnetmask % 4)
443
		{
444
			case 0:
445
				break;
446
			case 1:
447
				$binMask .= "8";
448
				break;
449
			case 2:
450
				$binMask .= "c";
451
				break;
452
			case 3:
453
				$binMask .= "e";
454
				break;
455
		}
456
		$binMask = str_pad($binMask, 32, '0');
457
		$binMask = pack("H*", $binMask);
458
459
		return ($ip_address & $binMask) == $cidr_network;
0 ignored issues
show
Are you sure you want to use the bitwise & or did you mean &&?
Loading history...
460
	}
461
	else
462
		return (ip2long($ip_address) & (~((1 << (32 - $cidr_subnetmask)) - 1))) == ip2long($cidr_network);
463
}
464
465
/**
466
 * Adds slashes to the array/variable.
467
 * What it does:
468
 * - returns the var, as an array or string, with escapes as required.
469
 * - importantly escapes all keys and values!
470
 * - calls itself recursively if necessary.
471
 *
472
 * @param array|string $var A string or array of strings to escape
473
 * @return array|string The escaped string or array of escaped strings
474
 */
475
function escapestring__recursive($var)
476
{
477
	global $smcFunc;
478
479
	if (!is_array($var))
480
		return $smcFunc['db_escape_string']($var);
481
482
	// Reindex the array with slashes.
483
	$new_var = array();
484
485
	// Add slashes to every element, even the indexes!
486
	foreach ($var as $k => $v)
487
		$new_var[$smcFunc['db_escape_string']($k)] = escapestring__recursive($v);
488
489
	return $new_var;
490
}
491
492
/**
493
 * Adds html entities to the array/variable.  Uses two underscores to guard against overloading.
494
 * What it does:
495
 * - adds entities (&quot;, &lt;, &gt;) to the array or string var.
496
 * - importantly, does not effect keys, only values.
497
 * - calls itself recursively if necessary.
498
 *
499
 * @param array|string $var The string or array of strings to add entites to
500
 * @param int $level Which level we're at within the array (if called recursively)
501
 * @return array|string The string or array of strings with entities added
502
 */
503
function htmlspecialchars__recursive($var, $level = 0)
504
{
505
	global $smcFunc;
506
507
	if (!is_array($var))
508
		return isset($smcFunc['htmlspecialchars']) ? $smcFunc['htmlspecialchars']($var, ENT_QUOTES) : htmlspecialchars($var, ENT_QUOTES);
509
510
	// Add the htmlspecialchars to every element.
511
	foreach ($var as $k => $v)
512
		$var[$k] = $level > 25 ? null : htmlspecialchars__recursive($v, $level + 1);
513
514
	return $var;
515
}
516
517
/**
518
 * Removes url stuff from the array/variable.  Uses two underscores to guard against overloading.
519
 * What it does:
520
 * - takes off url encoding (%20, etc.) from the array or string var.
521
 * - importantly, does it to keys too!
522
 * - calls itself recursively if there are any sub arrays.
523
 *
524
 * @param array|string $var The string or array of strings to decode
525
 * @param int $level Which level we're at within the array (if called recursively)
526
 * @return array|string The decoded string or array of decoded strings
527
 */
528
function urldecode__recursive($var, $level = 0)
529
{
530
	if (!is_array($var))
531
		return urldecode($var);
532
533
	// Reindex the array...
534
	$new_var = array();
535
536
	// Add the htmlspecialchars to every element.
537
	foreach ($var as $k => $v)
538
		$new_var[urldecode($k)] = $level > 25 ? null : urldecode__recursive($v, $level + 1);
539
540
	return $new_var;
541
}
542
543
/**
544
 * Unescapes any array or variable.  Uses two underscores to guard against overloading.
545
 * What it does:
546
 * - unescapes, recursively, from the array or string var.
547
 * - effects both keys and values of arrays.
548
 * - calls itself recursively to handle arrays of arrays.
549
 *
550
 * @param array|string $var The string or array of strings to unescape
551
 * @return array|string The unescaped string or array of unescaped strings
552
 */
553
function unescapestring__recursive($var)
554
{
555
	global $smcFunc;
556
557
	if (!is_array($var))
558
		return $smcFunc['db_unescape_string']($var);
559
560
	// Reindex the array without slashes, this time.
561
	$new_var = array();
562
563
	// Strip the slashes from every element.
564
	foreach ($var as $k => $v)
565
		$new_var[$smcFunc['db_unescape_string']($k)] = unescapestring__recursive($v);
566
567
	return $new_var;
568
}
569
570
/**
571
 * Remove slashes recursively.  Uses two underscores to guard against overloading.
572
 * What it does:
573
 * - removes slashes, recursively, from the array or string var.
574
 * - effects both keys and values of arrays.
575
 * - calls itself recursively to handle arrays of arrays.
576
 *
577
 * @param array|string $var The string or array of strings to strip slashes from
578
 * @param int $level = 0 What level we're at within the array (if called recursively)
579
 * @return array|string The string or array of strings with slashes stripped
580
 */
581
function stripslashes__recursive($var, $level = 0)
582
{
583
	if (!is_array($var))
584
		return stripslashes($var);
585
586
	// Reindex the array without slashes, this time.
587
	$new_var = array();
588
589
	// Strip the slashes from every element.
590
	foreach ($var as $k => $v)
591
		$new_var[stripslashes($k)] = $level > 25 ? null : stripslashes__recursive($v, $level + 1);
592
593
	return $new_var;
594
}
595
596
/**
597
 * Trim a string including the HTML space, character 160.  Uses two underscores to guard against overloading.
598
 * What it does:
599
 * - trims a string or an the var array using html characters as well.
600
 * - does not effect keys, only values.
601
 * - may call itself recursively if needed.
602
 *
603
 * @param array|string $var The string or array of strings to trim
604
 * @param int $level = 0 How deep we're at within the array (if called recursively)
605
 * @return array|string The trimmed string or array of trimmed strings
606
 */
607
function htmltrim__recursive($var, $level = 0)
608
{
609
	global $smcFunc;
610
611
	// Remove spaces (32), tabs (9), returns (13, 10, and 11), nulls (0), and hard spaces. (160)
612
	if (!is_array($var))
613
		return isset($smcFunc) ? $smcFunc['htmltrim']($var) : trim($var, ' ' . "\t\n\r\x0B" . '\0' . "\xA0");
614
615
	// Go through all the elements and remove the whitespace.
616
	foreach ($var as $k => $v)
617
		$var[$k] = $level > 25 ? null : htmltrim__recursive($v, $level + 1);
618
619
	return $var;
620
}
621
622
/**
623
 * Clean up the XML to make sure it doesn't contain invalid characters.
624
 * What it does:
625
 * - removes invalid XML characters to assure the input string being
626
 * - parsed properly.
627
 *
628
 * @param string $string The string to clean
629
 * @return string The cleaned string
630
 */
631
function cleanXml($string)
632
{
633
	global $context;
634
635
	// https://www.w3.org/TR/2000/REC-xml-20001006#NT-Char
636
	return preg_replace('~[\x00-\x08\x0B\x0C\x0E-\x19' . ($context['utf8'] ? '\x{FFFE}\x{FFFF}' : '') . ']~' . ($context['utf8'] ? 'u' : ''), '', $string);
637
}
638
639
/**
640
 * Escapes (replaces) characters in strings to make them safe for use in javascript
641
 *
642
 * @param string $string The string to escape
643
 * @return string The escaped string
644
 */
645
function JavaScriptEscape($string)
646
{
647
	global $scripturl;
648
649
	return '\'' . strtr($string, array(
650
		"\r" => '',
651
		"\n" => '\\n',
652
		"\t" => '\\t',
653
		'\\' => '\\\\',
654
		'\'' => '\\\'',
655
		'</' => '<\' + \'/',
656
		'<script' => '<scri\'+\'pt',
657
		'<body>' => '<bo\'+\'dy>',
658
		'<a href' => '<a hr\'+\'ef',
659
		$scripturl => '\' + smf_scripturl + \'',
660
	)) . '\'';
661
}
662
663
/**
664
 * Rewrite URLs to include the session ID.
665
 * What it does:
666
 * - rewrites the URLs outputted to have the session ID, if the user
667
 *   is not accepting cookies and is using a standard web browser.
668
 * - handles rewriting URLs for the queryless URLs option.
669
 * - can be turned off entirely by setting $scripturl to an empty
670
 *   string, ''. (it wouldn't work well like that anyway.)
671
 * - because of bugs in certain builds of PHP, does not function in
672
 *   versions lower than 4.3.0 - please upgrade if this hurts you.
673
 *
674
 * @param string $buffer The unmodified output buffer
675
 * @return string The modified buffer
676
 */
677
function ob_sessrewrite($buffer)
678
{
679
	global $scripturl, $modSettings, $context;
680
681
	// If $scripturl is set to nothing, or the SID is not defined (SSI?) just quit.
682
	if ($scripturl == '' || !defined('SID'))
683
		return $buffer;
684
685
	// 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.
686
	// @todo smflib
687
	if (empty($_COOKIE) && SID != '' && !isBrowser('possibly_robot'))
688
		$buffer = preg_replace('/(?<!<link rel="canonical" href=)"' . preg_quote($scripturl, '/') . '(?!\?' . preg_quote(SID, '/') . ')\\??/', '"' . $scripturl . '?' . SID . '&amp;', $buffer);
689
	// Debugging templates, are we?
690
	elseif (isset($_GET['debug']))
691
		$buffer = preg_replace('/(?<!<link rel="canonical" href=)"' . preg_quote($scripturl, '/') . '\\??/', '"' . $scripturl . '?debug;', $buffer);
692
693
	// This should work even in 4.2.x, just not CGI without cgi.fix_pathinfo.
694
	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']))
695
	{
696
		// Let's do something special for session ids!
697
		if (defined('SID') && SID != '')
698
			$buffer = preg_replace_callback('~"' . preg_quote($scripturl, '~') . '\?(?:' . SID . '(?:;|&|&amp;))((?:board|topic)=[^#"]+?)(#[^"]*?)?"~', function($m)
699
			{
700
				global $scripturl;
701
				return '"' . $scripturl . "/" . strtr("$m[1]", '&;=', '//,') . ".html?" . SID . (isset($m[2]) ? $m[2] : "") . '"';
702
			}, $buffer);
703
		else
704
			$buffer = preg_replace_callback('~"' . preg_quote($scripturl, '~') . '\?((?:board|topic)=[^#"]+?)(#[^"]*?)?"~', function($m)
705
			{
706
				global $scripturl;
707
				return '"' . $scripturl . '/' . strtr("$m[1]", '&;=', '//,') . '.html' . (isset($m[2]) ? $m[2] : "") . '"';
708
			}, $buffer);
709
	}
710
711
	// Return the changed buffer.
712
	return $buffer;
713
}
714
715
?>