2
0

QueryString.php 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625
  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 2012 Simple Machines
  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('Hacking attempt...');
  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']) ? 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. * @param string $ip ip address to be validated
  254. * @return bool true|false
  255. */
  256. function isValidIPv6($ip)
  257. {
  258. 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)
  259. return false;
  260. return true;
  261. }
  262. /**
  263. * Converts IPv6s to numbers. This makes ban checks much easier.
  264. * @param string $ip ip address to be converted
  265. * @return array
  266. */
  267. function convertIPv6toInts($ip)
  268. {
  269. static $expanded = array();
  270. // Check if we have done this already.
  271. if (isset($expanded[$ip]))
  272. return $expanded[$ip];
  273. // Expand the IP out.
  274. $expanded_ip = explode(':', expandIPv6($ip));
  275. $new_ip = array();
  276. foreach ($expanded_ip as $int)
  277. $new_ip[] = hexdec($int);
  278. // Save this incase of repeated use.
  279. $expanded[$ip] = $new_ip;
  280. return $expanded[$ip];
  281. }
  282. /**
  283. * Expands a IPv6 address to its full form.
  284. * @param string $ip ip address to be converted
  285. * @return bool/string expanded ipv6 address.
  286. */
  287. function expandIPv6($addr, $strict_check = true)
  288. {
  289. static $converted = array();
  290. // Check if we have done this already.
  291. if (isset($converted[$addr]))
  292. return $converted[$addr];
  293. // Check if there are segments missing, insert if necessary.
  294. if (strpos($addr, '::') !== false)
  295. {
  296. $part = explode('::', $addr);
  297. $part[0] = explode(':', $part[0]);
  298. $part[1] = explode(':', $part[1]);
  299. $missing = array();
  300. for ($i = 0; $i < (8 - (count($part[0]) + count($part[1]))); $i++)
  301. array_push($missing, '0000');
  302. $part = array_merge($part[0], $missing, $part[1]);
  303. }
  304. else
  305. $part = explode(':', $addr);
  306. // Pad each segment until it has 4 digits.
  307. foreach ($part as &$p)
  308. while (strlen($p) < 4)
  309. $p = '0' . $p;
  310. unset($p);
  311. // Join segments.
  312. $result = implode(':', $part);
  313. // Save this incase of repeated use.
  314. $converted[$addr] = $result;
  315. // Quick check to make sure the length is as expected.
  316. if (!$strict_check || strlen($result) == 39)
  317. return $result;
  318. else
  319. return false;
  320. }
  321. /**
  322. * Adds slashes to the array/variable.
  323. * What it does:
  324. * - returns the var, as an array or string, with escapes as required.
  325. * - importantly escapes all keys and values!
  326. * - calls itself recursively if necessary.
  327. *
  328. * @param array|string $var
  329. * @return array|string
  330. */
  331. function escapestring__recursive($var)
  332. {
  333. global $smcFunc;
  334. if (!is_array($var))
  335. return $smcFunc['db_escape_string']($var);
  336. // Reindex the array with slashes.
  337. $new_var = array();
  338. // Add slashes to every element, even the indexes!
  339. foreach ($var as $k => $v)
  340. $new_var[$smcFunc['db_escape_string']($k)] = escapestring__recursive($v);
  341. return $new_var;
  342. }
  343. /**
  344. * Adds html entities to the array/variable. Uses two underscores to guard against overloading.
  345. * What it does:
  346. * - adds entities (&quot;, &lt;, &gt;) to the array or string var.
  347. * - importantly, does not effect keys, only values.
  348. * - calls itself recursively if necessary.
  349. *
  350. * @param array|string $var
  351. * @param int $level = 0
  352. * @return array|string
  353. */
  354. function htmlspecialchars__recursive($var, $level = 0)
  355. {
  356. global $smcFunc;
  357. if (!is_array($var))
  358. return isset($smcFunc['htmlspecialchars']) ? $smcFunc['htmlspecialchars']($var, ENT_QUOTES) : htmlspecialchars($var, ENT_QUOTES);
  359. // Add the htmlspecialchars to every element.
  360. foreach ($var as $k => $v)
  361. $var[$k] = $level > 25 ? null : htmlspecialchars__recursive($v, $level + 1);
  362. return $var;
  363. }
  364. /**
  365. * Removes url stuff from the array/variable. Uses two underscores to guard against overloading.
  366. * What it does:
  367. * - takes off url encoding (%20, etc.) from the array or string var.
  368. * - importantly, does it to keys too!
  369. * - calls itself recursively if there are any sub arrays.
  370. *
  371. * @param array|string $var
  372. * @param int $level = 0
  373. * @return array|string
  374. */
  375. function urldecode__recursive($var, $level = 0)
  376. {
  377. if (!is_array($var))
  378. return urldecode($var);
  379. // Reindex the array...
  380. $new_var = array();
  381. // Add the htmlspecialchars to every element.
  382. foreach ($var as $k => $v)
  383. $new_var[urldecode($k)] = $level > 25 ? null : urldecode__recursive($v, $level + 1);
  384. return $new_var;
  385. }
  386. /**
  387. * Unescapes any array or variable. Uses two underscores to guard against overloading.
  388. * What it does:
  389. * - unescapes, recursively, from the array or string var.
  390. * - effects both keys and values of arrays.
  391. * - calls itself recursively to handle arrays of arrays.
  392. *
  393. * @param array|string $var
  394. * @return array|string
  395. */
  396. function unescapestring__recursive($var)
  397. {
  398. global $smcFunc;
  399. if (!is_array($var))
  400. return $smcFunc['db_unescape_string']($var);
  401. // Reindex the array without slashes, this time.
  402. $new_var = array();
  403. // Strip the slashes from every element.
  404. foreach ($var as $k => $v)
  405. $new_var[$smcFunc['db_unescape_string']($k)] = unescapestring__recursive($v);
  406. return $new_var;
  407. }
  408. /**
  409. * Remove slashes recursively. Uses two underscores to guard against overloading.
  410. * What it does:
  411. * - removes slashes, recursively, from the array or string var.
  412. * - effects both keys and values of arrays.
  413. * - calls itself recursively to handle arrays of arrays.
  414. *
  415. * @param array|string $var
  416. * @param int $level = 0
  417. * @return array|string
  418. */
  419. function stripslashes__recursive($var, $level = 0)
  420. {
  421. if (!is_array($var))
  422. return stripslashes($var);
  423. // Reindex the array without slashes, this time.
  424. $new_var = array();
  425. // Strip the slashes from every element.
  426. foreach ($var as $k => $v)
  427. $new_var[stripslashes($k)] = $level > 25 ? null : stripslashes__recursive($v, $level + 1);
  428. return $new_var;
  429. }
  430. /**
  431. * Trim a string including the HTML space, character 160. Uses two underscores to guard against overloading.
  432. * What it does:
  433. * - trims a string or an the var array using html characters as well.
  434. * - does not effect keys, only values.
  435. * - may call itself recursively if needed.
  436. *
  437. * @param array|string $var
  438. * @param int $level = 0
  439. * @return array|string
  440. */
  441. function htmltrim__recursive($var, $level = 0)
  442. {
  443. global $smcFunc;
  444. // Remove spaces (32), tabs (9), returns (13, 10, and 11), nulls (0), and hard spaces. (160)
  445. if (!is_array($var))
  446. return isset($smcFunc) ? $smcFunc['htmltrim']($var) : trim($var, ' ' . "\t\n\r\x0B" . '\0' . "\xA0");
  447. // Go through all the elements and remove the whitespace.
  448. foreach ($var as $k => $v)
  449. $var[$k] = $level > 25 ? null : htmltrim__recursive($v, $level + 1);
  450. return $var;
  451. }
  452. /**
  453. * Clean up the XML to make sure it doesn't contain invalid characters.
  454. * What it does:
  455. * - removes invalid XML characters to assure the input string being
  456. * - parsed properly.
  457. *
  458. * @param string $string
  459. * @return string
  460. */
  461. function cleanXml($string)
  462. {
  463. global $context;
  464. // http://www.w3.org/TR/2000/REC-xml-20001006#NT-Char
  465. return preg_replace('~[\x00-\x08\x0B\x0C\x0E-\x19' . ($context['utf8'] ? '\x{FFFE}\x{FFFF}' : '') . ']~' . ($context['utf8'] ? 'u' : ''), '', $string);
  466. }
  467. /**
  468. * @todo needs a description
  469. *
  470. * @param string $string
  471. * @return string
  472. */
  473. function JavaScriptEscape($string)
  474. {
  475. global $scripturl;
  476. return '\'' . strtr($string, array(
  477. "\r" => '',
  478. "\n" => '\\n',
  479. "\t" => '\\t',
  480. '\\' => '\\\\',
  481. '\'' => '\\\'',
  482. '</' => '<\' + \'/',
  483. '<script' => '<scri\'+\'pt',
  484. '<body>' => '<bo\'+\'dy>',
  485. '<a href' => '<a hr\'+\'ef',
  486. $scripturl => '\' + smf_scripturl + \'',
  487. )) . '\'';
  488. }
  489. /**
  490. * Rewrite URLs to include the session ID.
  491. * What it does:
  492. * - rewrites the URLs outputted to have the session ID, if the user
  493. * is not accepting cookies and is using a standard web browser.
  494. * - handles rewriting URLs for the queryless URLs option.
  495. * - can be turned off entirely by setting $scripturl to an empty
  496. * string, ''. (it wouldn't work well like that anyway.)
  497. * - because of bugs in certain builds of PHP, does not function in
  498. * versions lower than 4.3.0 - please upgrade if this hurts you.
  499. *
  500. * @param string $buffer
  501. * @return string
  502. */
  503. function ob_sessrewrite($buffer)
  504. {
  505. global $scripturl, $modSettings, $user_info, $context;
  506. // If $scripturl is set to nothing, or the SID is not defined (SSI?) just quit.
  507. if ($scripturl == '' || !defined('SID'))
  508. return $buffer;
  509. // 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.
  510. // @todo smflib
  511. if (empty($_COOKIE) && SID != '' && !isBrowser('possibly_robot'))
  512. $buffer = preg_replace('/"' . preg_quote($scripturl, '/') . '(?!\?' . preg_quote(SID, '/') . ')\\??/', '"' . $scripturl . '?' . SID . '&amp;', $buffer);
  513. // Debugging templates, are we?
  514. elseif (isset($_GET['debug']))
  515. $buffer = preg_replace('/(?<!<link rel="canonical" href=)"' . preg_quote($scripturl, '/') . '\\??/', '"' . $scripturl . '?debug;', $buffer);
  516. // This should work even in 4.2.x, just not CGI without cgi.fix_pathinfo.
  517. 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']))
  518. {
  519. // Let's do something special for session ids!
  520. if (defined('SID') && SID != '')
  521. $buffer = preg_replace('/"' . preg_quote($scripturl, '/') . '\?(?:' . SID . '(?:;|&|&amp;))((?:board|topic)=[^#"]+?)(#[^"]*?)?"/e', "'\"' . \$scripturl . '/' . strtr('\$1', '&;=', '//,') . '.html?' . SID . '\$2\"'", $buffer);
  522. else
  523. $buffer = preg_replace('/"' . preg_quote($scripturl, '/') . '\?((?:board|topic)=[^#"]+?)(#[^"]*?)?"/e', "'\"' . \$scripturl . '/' . strtr('\$1', '&;=', '//,') . '.html\$2\"'", $buffer);
  524. }
  525. // Return the changed buffer.
  526. return $buffer;
  527. }
  528. ?>