2
0

QueryString.php 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629
  1. <?php
  2. /**
  3. * This file does a lot of important stuff. Mainly, this means it handles
  4. * the query string, request variables, and session management.
  5. *
  6. * Simple Machines Forum (SMF)
  7. *
  8. * @package SMF
  9. * @author Simple Machines http://www.simplemachines.org
  10. * @copyright 2014 Simple Machines and individual contributors
  11. * @license http://www.simplemachines.org/about/smf/license.php BSD
  12. *
  13. * @version 2.1 Alpha 1
  14. */
  15. if (!defined('SMF'))
  16. die('No direct access...');
  17. /**
  18. * Clean the request variables - add html entities to GET and slashes if magic_quotes_gpc is Off.
  19. *
  20. * What it does:
  21. * - cleans the request variables (ENV, GET, POST, COOKIE, SERVER) and
  22. * makes sure the query string was parsed correctly.
  23. * - handles the URLs passed by the queryless URLs option.
  24. * - makes sure, regardless of php.ini, everything has slashes.
  25. * - sets up $board, $topic, and $scripturl and $_REQUEST['start'].
  26. * - determines, or rather tries to determine, the client's IP.
  27. */
  28. function cleanRequest()
  29. {
  30. global $board, $topic, $boardurl, $scripturl, $modSettings, $smcFunc;
  31. // Makes it easier to refer to things this way.
  32. $scripturl = $boardurl . '/index.php';
  33. // What function to use to reverse magic quotes - if sybase is on we assume that the database sensibly has the right unescape function!
  34. $removeMagicQuoteFunction = ini_get('magic_quotes_sybase') || strtolower(ini_get('magic_quotes_sybase')) == 'on' ? 'unescapestring__recursive' : 'stripslashes__recursive';
  35. // Save some memory.. (since we don't use these anyway.)
  36. unset($GLOBALS['HTTP_POST_VARS'], $GLOBALS['HTTP_POST_VARS']);
  37. unset($GLOBALS['HTTP_POST_FILES'], $GLOBALS['HTTP_POST_FILES']);
  38. // These keys shouldn't be set...ever.
  39. if (isset($_REQUEST['GLOBALS']) || isset($_COOKIE['GLOBALS']))
  40. die('Invalid request variable.');
  41. // Same goes for numeric keys.
  42. foreach (array_merge(array_keys($_POST), array_keys($_GET), array_keys($_FILES)) as $key)
  43. if (is_numeric($key))
  44. die('Numeric request keys are invalid.');
  45. // Numeric keys in cookies are less of a problem. Just unset those.
  46. foreach ($_COOKIE as $key => $value)
  47. if (is_numeric($key))
  48. unset($_COOKIE[$key]);
  49. // Get the correct query string. It may be in an environment variable...
  50. if (!isset($_SERVER['QUERY_STRING']))
  51. $_SERVER['QUERY_STRING'] = getenv('QUERY_STRING');
  52. // It seems that sticking a URL after the query string is mighty common, well, it's evil - don't.
  53. if (strpos($_SERVER['QUERY_STRING'], 'http') === 0)
  54. {
  55. header('HTTP/1.1 400 Bad Request');
  56. die;
  57. }
  58. // Are we going to need to parse the ; out?
  59. if (strpos(ini_get('arg_separator.input'), ';') === false && !empty($_SERVER['QUERY_STRING']))
  60. {
  61. // Get rid of the old one! You don't know where it's been!
  62. $_GET = array();
  63. // Was this redirected? If so, get the REDIRECT_QUERY_STRING.
  64. // Do not urldecode() the querystring, unless you so much wish to break OpenID implementation. :)
  65. $_SERVER['QUERY_STRING'] = substr($_SERVER['QUERY_STRING'], 0, 5) === 'url=/' ? $_SERVER['REDIRECT_QUERY_STRING'] : $_SERVER['QUERY_STRING'];
  66. // Replace ';' with '&' and '&something&' with '&something=&'. (this is done for compatibility...)
  67. // @todo smflib
  68. parse_str(preg_replace('/&(\w+)(?=&|$)/', '&$1=', strtr($_SERVER['QUERY_STRING'], array(';?' => '&', ';' => '&', '%00' => '', "\0" => ''))), $_GET);
  69. // Magic quotes still applies with parse_str - so clean it up.
  70. if (function_exists('get_magic_quotes_gpc') && @get_magic_quotes_gpc() != 0 && empty($modSettings['integrate_magic_quotes']))
  71. $_GET = $removeMagicQuoteFunction($_GET);
  72. }
  73. elseif (strpos(ini_get('arg_separator.input'), ';') !== false)
  74. {
  75. if (function_exists('get_magic_quotes_gpc') && @get_magic_quotes_gpc() != 0 && empty($modSettings['integrate_magic_quotes']))
  76. $_GET = $removeMagicQuoteFunction($_GET);
  77. // Search engines will send action=profile%3Bu=1, which confuses PHP.
  78. foreach ($_GET as $k => $v)
  79. {
  80. if ((string) $v === $v && strpos($k, ';') !== false)
  81. {
  82. $temp = explode(';', $v);
  83. $_GET[$k] = $temp[0];
  84. for ($i = 1, $n = count($temp); $i < $n; $i++)
  85. {
  86. @list ($key, $val) = @explode('=', $temp[$i], 2);
  87. if (!isset($_GET[$key]))
  88. $_GET[$key] = $val;
  89. }
  90. }
  91. // This helps a lot with integration!
  92. if (strpos($k, '?') === 0)
  93. {
  94. $_GET[substr($k, 1)] = $v;
  95. unset($_GET[$k]);
  96. }
  97. }
  98. }
  99. // There's no query string, but there is a URL... try to get the data from there.
  100. if (!empty($_SERVER['REQUEST_URI']))
  101. {
  102. // Remove the .html, assuming there is one.
  103. if (substr($_SERVER['REQUEST_URI'], strrpos($_SERVER['REQUEST_URI'], '.'), 4) == '.htm')
  104. $request = substr($_SERVER['REQUEST_URI'], 0, strrpos($_SERVER['REQUEST_URI'], '.'));
  105. else
  106. $request = $_SERVER['REQUEST_URI'];
  107. // @todo smflib.
  108. // Replace 'index.php/a,b,c/d/e,f' with 'a=b,c&d=&e=f' and parse it into $_GET.
  109. if (strpos($request, basename($scripturl) . '/') !== false)
  110. {
  111. parse_str(substr(preg_replace('/&(\w+)(?=&|$)/', '&$1=', strtr(preg_replace('~/([^,/]+),~', '/$1=', substr($request, strpos($request, basename($scripturl)) + strlen(basename($scripturl)))), '/', '&')), 1), $temp);
  112. if (function_exists('get_magic_quotes_gpc') && @get_magic_quotes_gpc() != 0 && empty($modSettings['integrate_magic_quotes']))
  113. $temp = $removeMagicQuoteFunction($temp);
  114. $_GET += $temp;
  115. }
  116. }
  117. // If magic quotes is on we have some work...
  118. if (function_exists('get_magic_quotes_gpc') && @get_magic_quotes_gpc() != 0)
  119. {
  120. $_ENV = $removeMagicQuoteFunction($_ENV);
  121. $_POST = $removeMagicQuoteFunction($_POST);
  122. $_COOKIE = $removeMagicQuoteFunction($_COOKIE);
  123. foreach ($_FILES as $k => $dummy)
  124. if (isset($_FILES[$k]['name']))
  125. $_FILES[$k]['name'] = $removeMagicQuoteFunction($_FILES[$k]['name']);
  126. }
  127. // Add entities to GET. This is kinda like the slashes on everything else.
  128. $_GET = htmlspecialchars__recursive($_GET);
  129. // Let's not depend on the ini settings... why even have COOKIE in there, anyway?
  130. $_REQUEST = $_POST + $_GET;
  131. // Make sure $board and $topic are numbers.
  132. if (isset($_REQUEST['board']))
  133. {
  134. // Make sure its a string and not something else like an array
  135. $_REQUEST['board'] = (string) $_REQUEST['board'];
  136. // If there's a slash in it, we've got a start value! (old, compatible links.)
  137. if (strpos($_REQUEST['board'], '/') !== false)
  138. list ($_REQUEST['board'], $_REQUEST['start']) = explode('/', $_REQUEST['board']);
  139. // Same idea, but dots. This is the currently used format - ?board=1.0...
  140. elseif (strpos($_REQUEST['board'], '.') !== false)
  141. list ($_REQUEST['board'], $_REQUEST['start']) = explode('.', $_REQUEST['board']);
  142. // Now make absolutely sure it's a number.
  143. $board = (int) $_REQUEST['board'];
  144. $_REQUEST['start'] = isset($_REQUEST['start']) ? (int) $_REQUEST['start'] : 0;
  145. // This is for "Who's Online" because it might come via POST - and it should be an int here.
  146. $_GET['board'] = $board;
  147. }
  148. // Well, $board is going to be a number no matter what.
  149. else
  150. $board = 0;
  151. // If there's a threadid, it's probably an old YaBB SE link. Flow with it.
  152. if (isset($_REQUEST['threadid']) && !isset($_REQUEST['topic']))
  153. $_REQUEST['topic'] = $_REQUEST['threadid'];
  154. // We've got topic!
  155. if (isset($_REQUEST['topic']))
  156. {
  157. // Make sure its a string and not something else like an array
  158. $_REQUEST['topic'] = (string) $_REQUEST['topic'];
  159. // Slash means old, beta style, formatting. That's okay though, the link should still work.
  160. if (strpos($_REQUEST['topic'], '/') !== false)
  161. list ($_REQUEST['topic'], $_REQUEST['start']) = explode('/', $_REQUEST['topic']);
  162. // Dots are useful and fun ;). This is ?topic=1.15.
  163. elseif (strpos($_REQUEST['topic'], '.') !== false)
  164. list ($_REQUEST['topic'], $_REQUEST['start']) = explode('.', $_REQUEST['topic']);
  165. $topic = (int) $_REQUEST['topic'];
  166. // Now make sure the online log gets the right number.
  167. $_GET['topic'] = $topic;
  168. }
  169. else
  170. $topic = 0;
  171. // There should be a $_REQUEST['start'], some at least. If you need to default to other than 0, use $_GET['start'].
  172. if (empty($_REQUEST['start']) || $_REQUEST['start'] < 0 || (int) $_REQUEST['start'] > 2147473647)
  173. $_REQUEST['start'] = 0;
  174. // The action needs to be a string and not an array or anything else
  175. if (isset($_REQUEST['action']))
  176. $_REQUEST['action'] = (string) $_REQUEST['action'];
  177. if (isset($_GET['action']))
  178. $_GET['action'] = (string) $_GET['action'];
  179. // Make sure we have a valid REMOTE_ADDR.
  180. if (!isset($_SERVER['REMOTE_ADDR']))
  181. {
  182. $_SERVER['REMOTE_ADDR'] = '';
  183. // A new magic variable to indicate we think this is command line.
  184. $_SERVER['is_cli'] = true;
  185. }
  186. // Perhaps we have a IPv6 address.
  187. elseif (!isValidIPv6($_SERVER['REMOTE_ADDR']) || preg_match('~::ffff:\d+\.\d+\.\d+\.\d+~', $_SERVER['REMOTE_ADDR']) !== 0)
  188. {
  189. $_SERVER['REMOTE_ADDR'] = preg_replace('~^::ffff:(\d+\.\d+\.\d+\.\d+)~', '\1', $_SERVER['REMOTE_ADDR']);
  190. // Just incase we have a legacy IPv4 address.
  191. // @ TODO: Convert to IPv6.
  192. 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['REMOTE_ADDR']) === 0)
  193. $_SERVER['REMOTE_ADDR'] = 'unknown';
  194. }
  195. // Try to calculate their most likely IP for those people behind proxies (And the like).
  196. $_SERVER['BAN_CHECK_IP'] = $_SERVER['REMOTE_ADDR'];
  197. // Find the user's IP address. (but don't let it give you 'unknown'!)
  198. // @ TODO: IPv6 really doesn't need this.
  199. if (!empty($_SERVER['HTTP_X_FORWARDED_FOR']) && !empty($_SERVER['HTTP_CLIENT_IP']) && (preg_match('~^((0|10|172\.(1[6-9]|2[0-9]|3[01])|192\.168|255|127)\.|unknown|::1|fe80::|fc00::)~', $_SERVER['HTTP_CLIENT_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))
  200. {
  201. // We have both forwarded for AND client IP... check the first forwarded for as the block - only switch if it's better that way.
  202. if (strtok($_SERVER['HTTP_X_FORWARDED_FOR'], '.') != strtok($_SERVER['HTTP_CLIENT_IP'], '.') && '.' . strtok($_SERVER['HTTP_X_FORWARDED_FOR'], '.') == strrchr($_SERVER['HTTP_CLIENT_IP'], '.') && (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)~', $_SERVER['REMOTE_ADDR']) != 0))
  203. $_SERVER['BAN_CHECK_IP'] = implode('.', array_reverse(explode('.', $_SERVER['HTTP_CLIENT_IP'])));
  204. else
  205. $_SERVER['BAN_CHECK_IP'] = $_SERVER['HTTP_CLIENT_IP'];
  206. }
  207. if (!empty($_SERVER['HTTP_CLIENT_IP']) && (preg_match('~^((0|10|172\.(1[6-9]|2[0-9]|3[01])|192\.168|255|127)\.|unknown|::1|fe80::|fc00::)~', $_SERVER['HTTP_CLIENT_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))
  208. {
  209. // Since they are in different blocks, it's probably reversed.
  210. if (strtok($_SERVER['REMOTE_ADDR'], '.') != strtok($_SERVER['HTTP_CLIENT_IP'], '.'))
  211. $_SERVER['BAN_CHECK_IP'] = implode('.', array_reverse(explode('.', $_SERVER['HTTP_CLIENT_IP'])));
  212. else
  213. $_SERVER['BAN_CHECK_IP'] = $_SERVER['HTTP_CLIENT_IP'];
  214. }
  215. elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR']))
  216. {
  217. // If there are commas, get the last one.. probably.
  218. if (strpos($_SERVER['HTTP_X_FORWARDED_FOR'], ',') !== false)
  219. {
  220. $ips = array_reverse(explode(', ', $_SERVER['HTTP_X_FORWARDED_FOR']));
  221. // Go through each IP...
  222. foreach ($ips as $i => $ip)
  223. {
  224. // Make sure it's in a valid range...
  225. 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)
  226. continue;
  227. // Otherwise, we've got an IP!
  228. $_SERVER['BAN_CHECK_IP'] = trim($ip);
  229. break;
  230. }
  231. }
  232. // Otherwise just use the only one.
  233. elseif (preg_match('~^((0|10|172\.(1[6-9]|2[0-9]|3[01])|192\.168|255|127)\.|unknown|::1|fe80::|fc00::)~', $_SERVER['HTTP_X_FORWARDED_FOR']) == 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)
  234. $_SERVER['BAN_CHECK_IP'] = $_SERVER['HTTP_X_FORWARDED_FOR'];
  235. }
  236. // Make sure we know the URL of the current request.
  237. if (empty($_SERVER['REQUEST_URI']))
  238. $_SERVER['REQUEST_URL'] = $scripturl . (!empty($_SERVER['QUERY_STRING']) ? '?' . $_SERVER['QUERY_STRING'] : '');
  239. elseif (preg_match('~^([^/]+//[^/]+)~', $scripturl, $match) == 1)
  240. $_SERVER['REQUEST_URL'] = $match[1] . $_SERVER['REQUEST_URI'];
  241. else
  242. $_SERVER['REQUEST_URL'] = $_SERVER['REQUEST_URI'];
  243. // And make sure HTTP_USER_AGENT is set.
  244. $_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)) : '';
  245. // Some final checking.
  246. 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['BAN_CHECK_IP']) === 0 || !isValidIPv6($_SERVER['BAN_CHECK_IP']))
  247. $_SERVER['BAN_CHECK_IP'] = '';
  248. if ($_SERVER['REMOTE_ADDR'] == 'unknown')
  249. $_SERVER['REMOTE_ADDR'] = '';
  250. }
  251. /**
  252. * Validates a IPv6 address. returns true if it is ipv6.
  253. *
  254. * @param string $ip ip address to be validated
  255. * @return boolean true|false
  256. */
  257. function isValidIPv6($ip)
  258. {
  259. if (preg_match('~^((([0-9A-Fa-f]{1,4}:){7}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){6}:[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){5}:([0-9A-Fa-f]{1,4}:)?[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){4}:([0-9A-Fa-f]{1,4}:){0,2}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){3}:([0-9A-Fa-f]{1,4}:){0,3}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){2}:([0-9A-Fa-f]{1,4}:){0,4}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){6}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|(([0-9A-Fa-f]{1,4}:){0,5}:((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|(::([0-9A-Fa-f]{1,4}:){0,5}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|([0-9A-Fa-f]{1,4}::([0-9A-Fa-f]{1,4}:){0,5}[0-9A-Fa-f]{1,4})|(::([0-9A-Fa-f]{1,4}:){0,6}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){1,7}:))$~', $ip) === 0)
  260. return false;
  261. return true;
  262. }
  263. /**
  264. * Converts IPv6s to numbers. This makes ban checks much easier.
  265. *
  266. * @param string $ip ip address to be converted
  267. * @return array
  268. */
  269. function convertIPv6toInts($ip)
  270. {
  271. static $expanded = array();
  272. // Check if we have done this already.
  273. if (isset($expanded[$ip]))
  274. return $expanded[$ip];
  275. // Expand the IP out.
  276. $expanded_ip = explode(':', expandIPv6($ip));
  277. $new_ip = array();
  278. foreach ($expanded_ip as $int)
  279. $new_ip[] = hexdec($int);
  280. // Save this incase of repeated use.
  281. $expanded[$ip] = $new_ip;
  282. return $expanded[$ip];
  283. }
  284. /**
  285. * Expands a IPv6 address to its full form.
  286. *
  287. * @param type $addr
  288. * @param type $strict_check checks lenght to expaned address for compliance
  289. * @return boolean/string expanded ipv6 address.
  290. */
  291. function expandIPv6($addr, $strict_check = true)
  292. {
  293. static $converted = array();
  294. // Check if we have done this already.
  295. if (isset($converted[$addr]))
  296. return $converted[$addr];
  297. // Check if there are segments missing, insert if necessary.
  298. if (strpos($addr, '::') !== false)
  299. {
  300. $part = explode('::', $addr);
  301. $part[0] = explode(':', $part[0]);
  302. $part[1] = explode(':', $part[1]);
  303. $missing = array();
  304. for ($i = 0; $i < (8 - (count($part[0]) + count($part[1]))); $i++)
  305. array_push($missing, '0000');
  306. $part = array_merge($part[0], $missing, $part[1]);
  307. }
  308. else
  309. $part = explode(':', $addr);
  310. // Pad each segment until it has 4 digits.
  311. foreach ($part as &$p)
  312. while (strlen($p) < 4)
  313. $p = '0' . $p;
  314. unset($p);
  315. // Join segments.
  316. $result = implode(':', $part);
  317. // Save this incase of repeated use.
  318. $converted[$addr] = $result;
  319. // Quick check to make sure the length is as expected.
  320. if (!$strict_check || strlen($result) == 39)
  321. return $result;
  322. else
  323. return false;
  324. }
  325. /**
  326. * Adds slashes to the array/variable.
  327. * What it does:
  328. * - returns the var, as an array or string, with escapes as required.
  329. * - importantly escapes all keys and values!
  330. * - calls itself recursively if necessary.
  331. *
  332. * @param array|string $var
  333. * @return array|string
  334. */
  335. function escapestring__recursive($var)
  336. {
  337. global $smcFunc;
  338. if (!is_array($var))
  339. return $smcFunc['db_escape_string']($var);
  340. // Reindex the array with slashes.
  341. $new_var = array();
  342. // Add slashes to every element, even the indexes!
  343. foreach ($var as $k => $v)
  344. $new_var[$smcFunc['db_escape_string']($k)] = escapestring__recursive($v);
  345. return $new_var;
  346. }
  347. /**
  348. * Adds html entities to the array/variable. Uses two underscores to guard against overloading.
  349. * What it does:
  350. * - adds entities (&quot;, &lt;, &gt;) to the array or string var.
  351. * - importantly, does not effect keys, only values.
  352. * - calls itself recursively if necessary.
  353. *
  354. * @param array|string $var
  355. * @param int $level = 0
  356. * @return array|string
  357. */
  358. function htmlspecialchars__recursive($var, $level = 0)
  359. {
  360. global $smcFunc;
  361. if (!is_array($var))
  362. return isset($smcFunc['htmlspecialchars']) ? $smcFunc['htmlspecialchars']($var, ENT_QUOTES) : htmlspecialchars($var, ENT_QUOTES);
  363. // Add the htmlspecialchars to every element.
  364. foreach ($var as $k => $v)
  365. $var[$k] = $level > 25 ? null : htmlspecialchars__recursive($v, $level + 1);
  366. return $var;
  367. }
  368. /**
  369. * Removes url stuff from the array/variable. Uses two underscores to guard against overloading.
  370. * What it does:
  371. * - takes off url encoding (%20, etc.) from the array or string var.
  372. * - importantly, does it to keys too!
  373. * - calls itself recursively if there are any sub arrays.
  374. *
  375. * @param array|string $var
  376. * @param int $level = 0
  377. * @return array|string
  378. */
  379. function urldecode__recursive($var, $level = 0)
  380. {
  381. if (!is_array($var))
  382. return urldecode($var);
  383. // Reindex the array...
  384. $new_var = array();
  385. // Add the htmlspecialchars to every element.
  386. foreach ($var as $k => $v)
  387. $new_var[urldecode($k)] = $level > 25 ? null : urldecode__recursive($v, $level + 1);
  388. return $new_var;
  389. }
  390. /**
  391. * Unescapes any array or variable. Uses two underscores to guard against overloading.
  392. * What it does:
  393. * - unescapes, recursively, from the array or string var.
  394. * - effects both keys and values of arrays.
  395. * - calls itself recursively to handle arrays of arrays.
  396. *
  397. * @param array|string $var
  398. * @return array|string
  399. */
  400. function unescapestring__recursive($var)
  401. {
  402. global $smcFunc;
  403. if (!is_array($var))
  404. return $smcFunc['db_unescape_string']($var);
  405. // Reindex the array without slashes, this time.
  406. $new_var = array();
  407. // Strip the slashes from every element.
  408. foreach ($var as $k => $v)
  409. $new_var[$smcFunc['db_unescape_string']($k)] = unescapestring__recursive($v);
  410. return $new_var;
  411. }
  412. /**
  413. * Remove slashes recursively. Uses two underscores to guard against overloading.
  414. * What it does:
  415. * - removes slashes, recursively, from the array or string var.
  416. * - effects both keys and values of arrays.
  417. * - calls itself recursively to handle arrays of arrays.
  418. *
  419. * @param array|string $var
  420. * @param int $level = 0
  421. * @return array|string
  422. */
  423. function stripslashes__recursive($var, $level = 0)
  424. {
  425. if (!is_array($var))
  426. return stripslashes($var);
  427. // Reindex the array without slashes, this time.
  428. $new_var = array();
  429. // Strip the slashes from every element.
  430. foreach ($var as $k => $v)
  431. $new_var[stripslashes($k)] = $level > 25 ? null : stripslashes__recursive($v, $level + 1);
  432. return $new_var;
  433. }
  434. /**
  435. * Trim a string including the HTML space, character 160. Uses two underscores to guard against overloading.
  436. * What it does:
  437. * - trims a string or an the var array using html characters as well.
  438. * - does not effect keys, only values.
  439. * - may call itself recursively if needed.
  440. *
  441. * @param array|string $var
  442. * @param int $level = 0
  443. * @return array|string
  444. */
  445. function htmltrim__recursive($var, $level = 0)
  446. {
  447. global $smcFunc;
  448. // Remove spaces (32), tabs (9), returns (13, 10, and 11), nulls (0), and hard spaces. (160)
  449. if (!is_array($var))
  450. return isset($smcFunc) ? $smcFunc['htmltrim']($var) : trim($var, ' ' . "\t\n\r\x0B" . '\0' . "\xA0");
  451. // Go through all the elements and remove the whitespace.
  452. foreach ($var as $k => $v)
  453. $var[$k] = $level > 25 ? null : htmltrim__recursive($v, $level + 1);
  454. return $var;
  455. }
  456. /**
  457. * Clean up the XML to make sure it doesn't contain invalid characters.
  458. * What it does:
  459. * - removes invalid XML characters to assure the input string being
  460. * - parsed properly.
  461. *
  462. * @param string $string
  463. * @return string
  464. */
  465. function cleanXml($string)
  466. {
  467. global $context;
  468. // http://www.w3.org/TR/2000/REC-xml-20001006#NT-Char
  469. return preg_replace('~[\x00-\x08\x0B\x0C\x0E-\x19' . ($context['utf8'] ? '\x{FFFE}\x{FFFF}' : '') . ']~' . ($context['utf8'] ? 'u' : ''), '', $string);
  470. }
  471. /**
  472. * Escapes (replaces) characters in strings to make them safe for use in javascript
  473. *
  474. * @param string $string
  475. * @return string
  476. */
  477. function JavaScriptEscape($string)
  478. {
  479. global $scripturl;
  480. return '\'' . strtr($string, array(
  481. "\r" => '',
  482. "\n" => '\\n',
  483. "\t" => '\\t',
  484. '\\' => '\\\\',
  485. '\'' => '\\\'',
  486. '</' => '<\' + \'/',
  487. '<script' => '<scri\'+\'pt',
  488. '<body>' => '<bo\'+\'dy>',
  489. '<a href' => '<a hr\'+\'ef',
  490. $scripturl => '\' + smf_scripturl + \'',
  491. )) . '\'';
  492. }
  493. /**
  494. * Rewrite URLs to include the session ID.
  495. * What it does:
  496. * - rewrites the URLs outputted to have the session ID, if the user
  497. * is not accepting cookies and is using a standard web browser.
  498. * - handles rewriting URLs for the queryless URLs option.
  499. * - can be turned off entirely by setting $scripturl to an empty
  500. * string, ''. (it wouldn't work well like that anyway.)
  501. * - because of bugs in certain builds of PHP, does not function in
  502. * versions lower than 4.3.0 - please upgrade if this hurts you.
  503. *
  504. * @param string $buffer
  505. * @return string
  506. */
  507. function ob_sessrewrite($buffer)
  508. {
  509. global $scripturl, $modSettings, $context;
  510. // If $scripturl is set to nothing, or the SID is not defined (SSI?) just quit.
  511. if ($scripturl == '' || !defined('SID'))
  512. return $buffer;
  513. // 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.
  514. // @todo smflib
  515. if (empty($_COOKIE) && SID != '' && !isBrowser('possibly_robot'))
  516. $buffer = preg_replace('/(?<!<link rel="canonical" href=)"' . preg_quote($scripturl, '/') . '(?!\?' . preg_quote(SID, '/') . ')\\??/', '"' . $scripturl . '?' . SID . '&amp;', $buffer);
  517. // Debugging templates, are we?
  518. elseif (isset($_GET['debug']))
  519. $buffer = preg_replace('/(?<!<link rel="canonical" href=)"' . preg_quote($scripturl, '/') . '\\??/', '"' . $scripturl . '?debug;', $buffer);
  520. // This should work even in 4.2.x, just not CGI without cgi.fix_pathinfo.
  521. 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']))
  522. {
  523. // Let's do something special for session ids!
  524. if (defined('SID') && SID != '')
  525. $buffer = preg_replace_callback('~"' . preg_quote($scripturl, '/') . '\?(?:' . SID . '(?:;|&|&amp;))((?:board|topic)=[^#"]+?)(#[^"]*?)?"~', create_function('$m', 'global $scripturl; return \'"\' . $scripturl . "/" . strtr("$m[1]", \'&;=\', \'//,\') . ".html?" . SID . (isset($m[2]) ? $m[2] : "") . \'"\';'), $buffer);
  526. else
  527. $buffer = preg_replace_callback('~"' . preg_quote($scripturl, '/') . '\?((?:board|topic)=[^#"]+?)(#[^"]*?)?"~', create_function('$m', 'global $scripturl; return \'"\' . $scripturl . \'/\' . strtr("$m[1]", \'&;=\', \'//,\') . \'.html\' . (isset($m[2]) ? $m[2] : "") . \'"\';'), $buffer );
  528. }
  529. // Return the changed buffer.
  530. return $buffer;
  531. }
  532. ?>