كود PHP:
class vB_Input_Cleaner
{
/**
* Translation table for short name to long name
*
* @var array
*/
var $shortvars = array(
'f' => 'forumid',
't' => 'threadid',
'p' => 'postid',
'u' => 'userid',
'a' => 'announcementid',
'c' => 'calendarid',
'e' => 'eventid',
'q' => 'query',
'pp' => 'perpage',
'page' => 'pagenumber',
'sort' => 'sortfield',
'order' => 'sortorder',
);
/**
* Translation table for short superglobal name to long superglobal name
*
* @var array
*/
var $superglobal_lookup = array(
'g' => '_GET',
'p' => '_POST',
'r' => '_REQUEST',
'c' => '_COOKIE',
's' => '_SERVER',
'e' => '_ENV',
'f' => '_FILES'
);
/**
* System state. The complete URL of the current page, without sessionhash
*
* @var string
*/
var $scriptpath = '';
/**
* Reload URL. Complete URL of the current page including sessionhash
*
* @var string
*/
var $reloadurl = '';
/**
* System state. The complete URL of the page for Who's Online purposes
*
* @var string
*/
var $wolpath = '';
/**
* System state. The complete URL of the referring page
*
* @var string
*/
var $url = '';
/**
* System state. The IP address of the current visitor
*
* @var string
*/
var $ipaddress = '';
/**
* System state. An attempt to find a second IP for the current visitor (proxy etc)
*
* @var string
*/
var $alt_ip = '';
/**
* A reference to the main registry object
*
* @var vB_Registry
*/
var $registry = null;
/**
* Constructor
*
* First, reverses the effects of magic quotes on GPC
* Second, translates short variable names to long (u --> userid)
* Third, deals with $_COOKIE[userid] conflicts
*
* @param vB_Registry The instance of the vB_Registry object
*/
function vB_Input_Cleaner(&$registry)
{
$this->registry =& $registry;
if (!is_array($GLOBALS))
{
die('<strong>Fatal Error:</strong> Invalid URL.');
}
// overwrite GET[x] and REQUEST[x] with POST[x] if it exists (overrides server's GPC order preference)
if ($_SERVER['REQUEST_METHOD'] == 'POST')
{
foreach (array_keys($_POST) AS $key)
{
if (isset($_GET["$key"]))
{
$_GET["$key"] = $_REQUEST["$key"] = $_POST["$key"];
}
}
}
// deal with session bypass situation
if (!defined('SESSION_BYPASS'))
{
define('SESSION_BYPASS', !empty($_REQUEST['bypass']));
}
// reverse the effects of magic quotes if necessary
if (function_exists('get_magic_quotes_gpc') AND get_magic_quotes_gpc())
{
$this->stripslashes_deep($_REQUEST); // needed for some reason (at least on php5 - not tested on php4)
$this->stripslashes_deep($_GET);
$this->stripslashes_deep($_POST);
$this->stripslashes_deep($_COOKIE);
if (is_array($_FILES))
{
foreach ($_FILES AS $key => $val)
{
$_FILES["$key"]['tmp_name'] = str_replace('\\', '\\\\', $val['tmp_name']);
}
$this->stripslashes_deep($_FILES);
}
}
set_magic_quotes_runtime(0);
@ini_set('magic_quotes_sybase', 0);
foreach (array('_GET', '_POST') AS $arrayname)
{
if (isset($GLOBALS["$arrayname"]['do']))
{
$GLOBALS["$arrayname"]['do'] = trim($GLOBALS["$arrayname"]['do']);
}
$this->convert_shortvars($GLOBALS["$arrayname"]);
}
// set the AJAX flag if we have got an AJAX submission
if ($_SERVER['REQUEST_METHOD'] == 'POST' AND $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest')
{
$_POST['ajax'] = $_REQUEST['ajax'] = 1;
}
// reverse the effects of register_globals if necessary
if (@ini_get('register_globals') OR !@ini_get('gpc_order'))
{
foreach ($this->superglobal_lookup AS $arrayname)
{
$registry->superglobal_size["$arrayname"] = sizeof($GLOBALS["$arrayname"]);
foreach (array_keys($GLOBALS["$arrayname"]) AS $varname)
{
// make sure we dont unset any global arrays like _SERVER
if (!in_array($varname, $this->superglobal_lookup))
{
unset($GLOBALS["$varname"]);
}
}
}
}
else
{
foreach ($this->superglobal_lookup AS $arrayname)
{
$registry->superglobal_size["$arrayname"] = sizeof($GLOBALS["$arrayname"]);
}
}
// deal with cookies that may conflict with _GET and _POST data, and create our own _REQUEST with no _COOKIE input
foreach (array_keys($_COOKIE) AS $varname)
{
unset($_REQUEST["$varname"]);
if (isset($_POST["$varname"]))
{
$_REQUEST["$varname"] =& $_POST["$varname"];
}
else if (isset($_GET["$varname"]))
{
$_REQUEST["$varname"] =& $_GET["$varname"];
}
}
// fetch client IP address
$registry->ipaddress = $this->fetch_ip();
define('IPADDRESS', $registry->ipaddress);
// attempt to fetch IP address from behind proxies - useful, but don't rely on it...
$registry->alt_ip = $this->fetch_alt_ip();
define('ALT_IP', $registry->alt_ip);
// defines if the current page was visited via SSL or not
define('REQ_PROTOCOL', (($_SERVER['HTTPS'] == 'on' OR $_SERVER['HTTPS'] == '1') ? 'https' : 'http'));
// fetch complete url of current page
$registry->scriptpath = $this->fetch_scriptpath();
define('SCRIPTPATH', $registry->scriptpath);
// fetch url of current page without the variable string
$quest_pos = strpos($registry->scriptpath, '?');
if ($quest_pos !== false)
{
$registry->script = substr($registry->scriptpath, 0, $quest_pos);
}
else
{
$registry->script = $registry->scriptpath;
}
define('SCRIPT', $registry->script);
// fetch url of current page for Who's Online
$registry->wolpath = $this->fetch_wolpath();
define('WOLPATH', $registry->wolpath);
// define session constants
define('SESSION_HOST', substr($registry->ipaddress, 0, 15));
// define some useful contants related to environment
define('USER_AGENT', $_SERVER['HTTP_USER_AGENT']);
define('REFERRER', $_SERVER['HTTP_REFERER']);
}
/**
* Makes data in an array safe to use
*
* @param array The source array containing the data to be cleaned
* @param array Array of variable names and types we want to extract from the source array
*
* @return array
*/
function &clean_array(&$source, $variables)
{
$return = array();
foreach ($variables AS $varname => $vartype)
{
$return["$varname"] =& $this->clean($source["$varname"], $vartype, isset($source["$varname"]));
}
return $return;
}
/**
* Makes GPC variables safe to use
*
* @param string Either, g, p, c, r or f (corresponding to get, post, cookie, request and files)
* @param array Array of variable names and types we want to extract from the source array
*
* @return array
*/
function clean_array_gpc($source, $variables)
{
$sg =& $GLOBALS[$this->superglobal_lookup["$source"]];
foreach ($variables AS $varname => $vartype)
{
if (!isset($this->registry->GPC["$varname"])) // limit variable to only being "cleaned" once to avoid potential corruption
{
$this->registry->GPC_exists["$varname"] = isset($sg["$varname"]);
$this->registry->GPC["$varname"] =& $this->clean(
$sg["$varname"],
$vartype,
isset($sg["$varname"])
);
}
}
}
/**
* Makes a single GPC variable safe to use and returns it
*
* @param array The source array containing the data to be cleaned
* @param string The name of the variable in which we are interested
* @param integer The type of the variable in which we are interested
*
* @return mixed
*/
function &clean_gpc($source, $varname, $vartype = TYPE_NOCLEAN)
{
if (!isset($this->registry->GPC["$varname"])) // limit variable to only being "cleaned" once to avoid potential corruption
{
$sg =& $GLOBALS[$this->superglobal_lookup["$source"]];
$this->registry->GPC_exists["$varname"] = isset($sg["$varname"]);
$this->registry->GPC["$varname"] =& $this->clean(
$sg["$varname"],
$vartype,
isset($sg["$varname"])
);
}
return $this->registry->GPC["$varname"];
}
/**
* Makes a single variable safe to use and returns it
*
* @param mixed The variable to be cleaned
* @param integer The type of the variable in which we are interested
* @param boolean Whether or not the variable to be cleaned actually is set
*
* @return mixed The cleaned value
*/
function &clean(&$var, $vartype = TYPE_NOCLEAN, $exists = true)
{
if ($exists)
{
if ($vartype < TYPE_CONVERT_SINGLE)
{
$this->do_clean($var, $vartype);
}
else if (is_array($var))
{
if ($vartype >= TYPE_CONVERT_KEYS)
{
$var = array_keys($var);
$vartype -= TYPE_CONVERT_KEYS;
}
else
{
$vartype -= TYPE_CONVERT_SINGLE;
}
foreach (array_keys($var) AS $key)
{
$this->do_clean($var["$key"], $vartype);
}
}
else
{
$var = array();
}
return $var;
}
else
{
if ($vartype < TYPE_CONVERT_SINGLE)
{
switch ($vartype)
{
case TYPE_INT:
case TYPE_UINT:
case TYPE_NUM:
case TYPE_UNUM:
case TYPE_UNIXTIME:
{
$var = 0;
break;
}
case TYPE_STR:
case TYPE_NOHTML:
case TYPE_NOTRIM:
case TYPE_NOHTMLCOND:
{
$var = '';
break;
}
case TYPE_BOOL:
{
$var = 0;
break;
}
case TYPE_ARRAY:
case TYPE_FILE:
{
$var = array();
break;
}
case TYPE_NOCLEAN:
{
$var = null;
break;
}
default:
{
$var = null;
}
}
}
else
{
$var = array();
}
return $var;
}
}
/**
* Does the actual work to make a variable safe
*
* @param mixed The data we want to make safe
* @param integer The type of the data
*
* @return mixed
*/
function &do_clean(&$data, $type)
{
static $booltypes = array('1', 'yes', 'y', 'true');
switch ($type)
{
case TYPE_INT: $data = intval($data); break;
case TYPE_UINT: $data = ($data = intval($data)) < 0 ? 0 : $data; break;
case TYPE_NUM: $data = strval($data) + 0; break;
case TYPE_UNUM: $data = strval($data) + 0;
$data = ($data < 0) ? 0 : $data; break;
case TYPE_BINARY: $data = strval($data); break;
case TYPE_STR: $data = trim(strval($data)); break;
case TYPE_NOTRIM: $data = strval($data); break;
case TYPE_NOHTML: $data = htmlspecialchars_uni(trim(strval($data))); break;
case TYPE_BOOL: $data = in_array(strtolower($data), $booltypes) ? 1 : 0; break;
case TYPE_ARRAY: $data = (is_array($data)) ? $data : array(); break;
case TYPE_NOHTMLCOND:
{
$data = trim(strval($data));
if (strcspn($data, '<>"') < strlen($data) OR (strpos($data, '&') !== false AND !preg_match('/&(#[0-9]+|amp|lt|gt|quot);/si', $data)))
{
// data is not htmlspecialchars because it still has characters or entities it shouldn't
$data = htmlspecialchars_uni($data);
}
break;
}
case TYPE_FILE:
{
// perhaps redundant :p
if (is_array($data))
{
if (is_array($data['name']))
{
$files = count($data['name']);
for ($index = 0; $index < $files; $index++)
{
$data['name']["$index"] = trim(strval($data['name']["$index"]));
$data['type']["$index"] = trim(strval($data['type']["$index"]));
$data['tmp_name']["$index"] = trim(strval($data['tmp_name']["$index"]));
$data['error']["$index"] = intval($data['error']["$index"]);
$data['size']["$index"] = intval($data['size']["$index"]);
}
}
else
{
$data['name'] = trim(strval($data['name']));
$data['type'] = trim(strval($data['type']));
$data['tmp_name'] = trim(strval($data['tmp_name']));
$data['error'] = intval($data['error']);
$data['size'] = intval($data['size']);
}
}
else
{
$data = array(
'name' => '',
'type' => '',
'tmp_name' => '',
'error' => 0,
'size' => 4, // UPLOAD_ERR_NO_FILE
);
}
break;
}
case TYPE_UNIXTIME:
{
if (is_array($data))
{
$data = $this->clean($data, TYPE_ARRAY_UINT);
if ($data['month'] AND $data['day'] AND $data['year'])
{
require_once(DIR . '/includes/functions_misc.php');
$data = vbmktime($data['hour'], $data['minute'], $data['second'], $data['month'], $data['day'], $data['year']);
}
else
{
$data = 0;
}
}
else
{
$data = ($data = intval($data)) < 0 ? 0 : $data;
}
break;
}
// null actions should be deifned here so we can still catch typos below
case TYPE_NOCLEAN:
{
break;
}
default:
{
if ($this->registry->debug)
{
trigger_error('vB_Input_Cleaner::do_clean() Invalid data type specified', E_USER_WARNING);
}
}
}
// strip out characters that really have no business being in non-binary data
switch ($type)
{
case TYPE_STR:
case TYPE_NOTRIM:
case TYPE_NOHTML:
case TYPE_NOHTMLCOND:
$data = str_replace(chr(0), '', $data);
}
return $data;
}
/**
* Removes HTML characters and potentially unsafe scripting words from a string
*
* @param string The variable we want to make safe
*
* @return string
*/
function xss_clean($var)
{
static
$preg_find = array('#javascript#i', '#vbscript#i'),
$preg_replace = array('java script', 'vb script');
$var = preg_replace($preg_find, $preg_replace, htmlspecialchars_uni($var));
return $var;
}
/**
* Reverses the effects of magic_quotes on an entire array of variables
*
* @param array The array on which we want to work
*/
function stripslashes_deep(&$value, $depth = 0)
{
if (is_array($value))
{
foreach ($value AS $key => $val)
{
if (is_string($val))
{
$value["$key"] = stripslashes($val);
}
else if (is_array($val) AND $depth < 10)
{
$this->stripslashes_deep($value["$key"], $depth + 1);
}
}
}
}
/**
* Turns $_POST['t'] into $_POST['threadid'] etc.
*
* @param array The name of the array
*/
function convert_shortvars(&$array)
{
// extract long variable names from short variable names
foreach ($this->shortvars AS $shortname => $longname)
{
if (isset($array["$shortname"]) AND !isset($array["$longname"]))
{
$array["$longname"] =& $array["$shortname"];
$GLOBALS['_REQUEST']["$longname"] =& $array["$shortname"];
}
}
}
/**
* Strips out the s=gobbledygook& rubbish from URLs
*
* @param string The URL string from which to remove the session stuff
*
* @return string
*/
function strip_sessionhash($string)
{
$string = preg_replace('/(s|sessionhash)=[a-z0-9]{32}?&?/', '', $string);
return $string;
}
/**
* Fetches the 'scriptpath' variable - ie: the URI of the current page
*
* @return string
*/
function fetch_scriptpath()
{
if ($this->registry->scriptpath != '')
{
return $this->registry->scriptpath;
}
else
{
if ($_SERVER['REQUEST_URI'] OR $_ENV['REQUEST_URI'])
{
$scriptpath = $_SERVER['REQUEST_URI'] ? $_SERVER['REQUEST_URI'] : $_ENV['REQUEST_URI'];
}
else
{
if ($_SERVER['PATH_INFO'] OR $_ENV['PATH_INFO'])
{
$scriptpath = $_SERVER['PATH_INFO'] ? $_SERVER['PATH_INFO'] : $_ENV['PATH_INFO'];
}
else if ($_SERVER['REDIRECT_URL'] OR $_ENV['REDIRECT_URL'])
{
$scriptpath = $_SERVER['REDIRECT_URL'] ? $_SERVER['REDIRECT_URL'] : $_ENV['REDIRECT_URL'];
}
else
{
$scriptpath = $_SERVER['PHP_SELF'] ? $_SERVER['PHP_SELF'] : $_ENV['PHP_SELF'];
}
if ($_SERVER['QUERY_STRING'] OR $_ENV['QUERY_STRING'])
{
$scriptpath .= '?' . ($_SERVER['QUERY_STRING'] ? $_SERVER['QUERY_STRING'] : $_ENV['QUERY_STRING']);
}
}
// in the future we should set $registry->script here too
$quest_pos = strpos($scriptpath, '?');
if ($quest_pos !== false)
{
$script = urldecode(substr($scriptpath, 0, $quest_pos));
$scriptpath = $script . substr($scriptpath, $quest_pos);
}
else
{
$scriptpath = urldecode($scriptpath);
}
// store a version that includes the sessionhash
$this->registry->reloadurl = $this->xss_clean($scriptpath);
$scriptpath = $this->strip_sessionhash($scriptpath);
$scriptpath = $this->xss_clean($scriptpath);
$this->registry->scriptpath = $scriptpath;
return $scriptpath;
}
}
/**
* Fetches the 'wolpath' variable - ie: the same as 'scriptpath' but with a handler for the POST request method
*
* @return string
*/
function fetch_wolpath()
{
$wolpath = $this->fetch_scriptpath();
if ($_SERVER['REQUEST_METHOD'] == 'POST')
{
// Tag the variables back on to the filename if we are coming from POST so that WOL can access them.
$tackon = '';
if (is_array($_POST))
{
foreach ($_POST AS $varname => $value)
{
switch ($varname)
{
case 'forumid':
case 'threadid':
case 'postid':
case 'userid':
case 'eventid':
case 'calendarid':
case 'do':
case 'method': // postings.php
case 'dowhat': // private.php
{
$tackon .= ($tackon == '' ? '' : '&') . $varname . '=' . $value;
break;
}
}
}
}
if ($tackon != '')
{
$wolpath .= (strpos($wolpath, '?') !== false ? '&' : '?') . "$tackon";
}
}
return $wolpath;
}
/**
* Fetches the 'url' variable - usually the URL of the previous page in the history
*
* @return string
*/
function fetch_url()
{
$temp_url = $_REQUEST['url'];
$scriptpath = $this->fetch_scriptpath();
if (empty($temp_url))
{
$url = $_SERVER['HTTP_REFERER'];
}
else
{
if ($temp_url == $_SERVER['HTTP_REFERER'])
{
$url = 'index.php';
}
else
{
$url = $temp_url;
}
}
if ($url == $scriptpath OR empty($url))
{
$url = 'index.php';
}
// if $url is set to forum home page, check it against options
if ($url == 'index.php' AND $this->registry->options['forumhome'] != 'index')
{
$url = $this->registry->options['forumhome'] . '.php';
}
$url = $this->xss_clean($url);
return $url;
}
/**
* Fetches the IP address of the current visitor
*
* @return string
*/
function fetch_ip()
{
return $_SERVER['REMOTE_ADDR'];
}
/**
* Fetches an alternate IP address of the current visitor, attempting to detect proxies etc.
*
* @return string
*/
function fetch_alt_ip()
{
$alt_ip = $_SERVER['REMOTE_ADDR'];
if (isset($_SERVER['HTTP_CLIENT_IP']))
{
$alt_ip = $_SERVER['HTTP_CLIENT_IP'];
}
else if (isset($_SERVER['HTTP_X_FORWARDED_FOR']) AND preg_match_all('#\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}#s', $_SERVER['HTTP_X_FORWARDED_FOR'], $matches))
{
// make sure we dont pick up an internal IP defined by RFC1918
foreach ($matches[0] AS $ip)
{
if (!preg_match('#^(10|172\.16|192\.168)\.#', $ip))
{
$alt_ip = $ip;
break;
}
}
}
else if (isset($_SERVER['HTTP_FROM']))
{
$alt_ip = $_SERVER['HTTP_FROM'];
}
return $alt_ip;
}
}
حد فهم أي شيئ :app: