Index: branches/5.2.x/core/kernel/utility/http_query.php
===================================================================
--- branches/5.2.x/core/kernel/utility/http_query.php	(revision 16781)
+++ branches/5.2.x/core/kernel/utility/http_query.php	(revision 16782)
@@ -1,815 +1,817 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 defined('FULL_PATH') or die('restricted access!');
 
 class kHTTPQuery extends Params {
 
 	/**
 	 * Cache of QueryString parameters
 	 * from config, that are represented
 	 * in environment variable
 	 *
 	 * @var Array
 	 */
 	protected $discoveredUnits = Array ();
 
 	/**
 	 * $_POST vars
 	 *
 	 * @var Array
 	 * @access private
 	 */
 	var $Post;
 
 	/**
 	 * $_GET vars
 	 *
 	 * @var Array
 	 * @access private
 	 */
 	var $Get;
 
 	/**
 	 * $_COOKIE vars
 	 *
 	 * @var Array
 	 * @access private
 	 */
 	var $Cookie = array();
 
 	/**
 	 * $_SERVER vars
 	 *
 	 * @var Array
 	 * @access private
 	 */
 	var $Server;
 
 	/**
 	 * $_ENV vars
 	 *
 	 * @var Array
 	 * @access private
 	 */
 	var $Env;
 
 	/**
 	 * Order in what write
 	 * all vars together in
 	 * the same array
 	 *
 	 * @var string
 	 */
 	var $Order;
 
 	/**
 	 * Uploaded files info
 	 *
 	 * @var Array
 	 * @access private
 	 */
 	var $Files;
 
 	var $specialsToRemove = Array();
 
 	/**
 	 * SessionID is given via "sid" variable in query string
 	 *
 	 * @var bool
 	 */
 	var $_sidInQueryString = false;
 
 	/**
 	 * Trust information, provided by proxy
 	 *
 	 * @var bool
 	 */
 	protected $_trustProxy = false;
 
 	/**
 	 * Loads info from $_POST, $_GET and related arrays into common place.
 	 *
 	 * @param string $order Order.
 	 *
 	 * @throws RuntimeException When "Proxy" header is present in Web Request.
 	 */
 	public function __construct($order = 'CGPF')
 	{
 		parent::__construct();
 
 		// Don't process cookies from CLI.
 		if ( PHP_SAPI === 'cli' ) {
 			$order = str_replace('C', '', $order);
 		}
 
 		$this->Order = $order;
 
 		if ( isset($_SERVER['HTTP_PROXY']) && PHP_SAPI !== 'cli' ) {
 			throw new RuntimeException('Web Requests with "Proxy" header are forbidden.');
 		}
 
 		if ( isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest' ) {
 			// When AJAX request is made from jQuery, then create ajax variable,
 			// so any logic based in it (like redirects) will not break down.
 			$_GET['ajax'] = 'yes';
 		}
 
 		$this->_trustProxy = kUtil::getSystemConfig()->get('TrustProxy');
 	}
 
 	/**
 	 * Discovers unit form request and returns it's QueryString option on success
 	 *
 	 * @param string $prefix_special
 	 *
 	 * @return Array|bool
 	 * @access public
 	 */
 	public function discoverUnit($prefix_special)
 	{
 		list($prefix) = explode('.', $prefix_special);
 
 		$query_string = $this->getQueryString($prefix);
 
 		if ($query_string) {
 			// only units with QueryString option can be discovered
 			$this->discoveredUnits[$prefix_special] = $query_string;
 
 			return $query_string;
 		}
 
 		unset( $this->discoveredUnits[$prefix] );
 
 		return false;
 	}
 
 	/**
 	 * Returns units, passed in request
 	 *
 	 * @param bool $prefix_special_only
 	 * @return Array
 	 * @access protected
 	 */
 	public function getDiscoveredUnits($prefix_special_only = true)
 	{
 		return $prefix_special_only ? array_keys( $this->discoveredUnits ) : $this->discoveredUnits;
 	}
 
 	/**
 	 * Returns QueryMap for requested unit config.
 	 * In case if unit config is a clone, then get parent item's (from prefix) config to create clone
 	 *
 	 * @param string $prefix
 	 * @return Array
 	 * @access protected
 	 */
 	protected function getQueryString($prefix)
 	{
 		$ret = $this->Application->getUnitOption($prefix, 'QueryString', Array ());
 
 		if ( !$ret && preg_match('/(.*?)-(.*)/', $prefix, $regs) ) {
 			// "#prefix" (new format), "prefix" (old format)
 			return $this->_getQueryString('#' . $regs[2]);
 		}
 
 		return $ret;
 	}
 
 	/**
 	 * Returns query string (with safety check against missing prefixes)
 	 *
 	 * @param string $prefix
 	 * @return Array
 	 */
 	private function _getQueryString($prefix)
 	{
 		if ( $this->Application->prefixRegistred($prefix) ) {
 			return $this->Application->getUnitOption($prefix, 'QueryString');
 		}
 
 		return substr($prefix, 0, 1) == '#' ? $this->_getQueryString( substr($prefix, 1) ) : Array ();
 	}
 
 	/**
 	 * Removes specials from request
 	 *
 	 * @param Array $array
 	 * @return Array
 	 * @access protected
 	 */
 	protected function _removeSpecials($array)
 	{
 		$ret = Array ();
 		$removed = false;
 
 		foreach ($this->specialsToRemove as $prefix_special => $flag) {
 			if ( $flag ) {
 				$removed = true;
 				list ($prefix, $special) = explode('.', $prefix_special, 2);
 
 				foreach ($array as $key => $val) {
 					$new_key = preg_match("/^" . $prefix . "[._]{1}" . $special . "(.*)/", $key, $regs) ? $prefix . $regs[1] : $key;
 					$ret[$new_key] = is_array($val) ? $this->_removeSpecials($val) : $val;
 				}
 			}
 		}
 
 		return $removed ? $ret : $array;
 	}
 
 	public function process()
 	{
 		$this->AddAllVars();
 		$this->removeSpecials();
 		ini_set('magic_quotes_gpc', 0);
 
 		$this->Application->UrlManager->LoadStructureTemplateMapping();
 
 		$this->AfterInit();
 	}
 
 	/**
 	 * All all requested vars to
 	 * common storage place
 	 *
 	 * @return void
 	 * @access protected
 	 */
 	protected function AddAllVars()
 	{
 		for ($i = 0; $i < strlen($this->Order); $i++) {
 			switch ($this->Order[$i]) {
 				case 'G':
 					$this->Get = $this->AddVars($_GET);
 					if ( array_key_exists('sid', $_GET) ) {
 						$this->_sidInQueryString = true;
 					}
 
 					$vars = $this->Application->processQueryString($this->Get(ENV_VAR_NAME));
 
 					if ( array_key_exists('sid', $vars) ) {
 						// used by Session::GetPassedSIDValue
 						$this->Get['sid'] = $vars['sid'];
 					}
 
 					$this->AddParams($vars);
 					break;
 
 				case 'P':
 					$this->Post = $this->AddVars($_POST);
 					$this->convertPostEvents();
 					$this->_processPostEnvVariables();
 					break;
 
 				case 'C':
 					$this->Cookie = $this->AddVars($_COOKIE);
 					break;
 
 				/*case 'E';
 					$this->Env = $this->AddVars($_ENV, false); //do not strip slashes!
 					break;
 
 				case 'S';
 					$this->Server = $this->AddVars($_SERVER, false); //do not strip slashes!
 					break;*/
 
 				case 'F';
 					$this->convertFiles();
 					$this->Files = $this->MergeVars($_FILES); // , false); //do not strip slashes!
 					break;
 			}
 		}
 	}
 
 	/**
 	 * Allow POST variables, that names were transformed by PHP ("." replaced with "_") to
 	 * override variables, that were virtually created through environment variable parsing
 	 *
 	 */
 	function _processPostEnvVariables()
 	{
 		$passed = $this->Get('passed');
 		if ( !$passed ) {
 			return;
 		}
 
 		$passed = explode(',', $passed);
 		foreach ($passed as $prefix_special) {
 			if ( strpos($prefix_special, '.') === false ) {
 				continue;
 			}
 
 			list ($prefix, $special) = explode('.', $prefix_special);
 			$query_map = $this->getQueryString($prefix);
 			$post_prefix_special = $prefix . '_' . $special;
 
 			foreach ($query_map as $var_name) {
 				if ( array_key_exists($post_prefix_special . '_' . $var_name, $this->Post) ) {
 					$this->Set($prefix_special . '_' . $var_name, $this->Post[$post_prefix_special . '_' . $var_name]);
 				}
 			}
 		}
 	}
 
 	/**
 	 * Removes requested specials from all request variables
 	 *
 	 * @return void
 	 * @access protected
 	 */
 	protected function removeSpecials()
 	{
 		$this->specialsToRemove = $this->Get('remove_specials');
 
 		if ( $this->specialsToRemove ) {
 			foreach ($this->specialsToRemove as $prefix_special => $flag) {
 				if ( $flag && strpos($prefix_special, '.') === false ) {
 					unset($this->specialsToRemove[$prefix_special]);
 					trigger_error('Incorrect usage of "<strong>remove_specials[' . $prefix_special . ']</strong>" field (no special found)', E_USER_NOTICE);
 				}
 			}
 
 			$this->_Params = $this->_removeSpecials($this->_Params);
 		}
 	}
 
 	/**
 	 * Finishes initialization of kHTTPQuery class
 	 *
 	 * @return void
 	 * @access protected
 	 * @todo: only uses build-in rewrite listeners, when cache is build for the first time
 	 */
 	protected function AfterInit()
 	{
 		$rewrite_url = $this->Get('_mod_rw_url_');
 
 		if ( $this->Application->RewriteURLs() || $rewrite_url ) {
 			// maybe call onafterconfigread here
 
 			$this->Application->UrlManager->initRewrite();
 
 			if ( defined('DEBUG_MODE') && $this->Application->isDebugMode() ) {
 				$this->Application->Debugger->profileStart('url_parsing', 'Parsing <b>MOD_REWRITE</b> url');
 				$this->Application->UrlManager->rewrite->parseRewriteURL();
 				$description = 'Parsing <b>MOD_REWRITE</b> url (template: <b>' . $this->Get('t') . '</b>)';
 				$this->Application->Debugger->profileFinish('url_parsing', $description);
 			}
 			else {
 				$this->Application->UrlManager->rewrite->parseRewriteURL();
 			}
 
 			if ( !$rewrite_url && $this->rewriteRedirectRequired() ) {
 				// rewrite url is missing (e.g. not a script from tools folder)
 				$url_params = $this->getRedirectParams();
 
 				// no idea about how to check, that given template require category to be passed with it, so pass anyway
 				$url_params['pass_category'] = 1;
 				$url_params['response_code'] = 301; // Moved Permanently
 
 				trigger_error('Non mod-rewrite url "<strong>' . $_SERVER['REQUEST_URI'] . '</strong>" used', E_USER_NOTICE);
 				$this->Application->Redirect('', $url_params);
 			}
 
 			if ( $this->Application->GetVar('is_friendly_url') ) {
 				$url_params = $this->getRedirectParams();
 
 				// No idea about how to check, that given template
 				// require category to be passed with it, so pass anyway.
 				$url_params['pass_category'] = 1;
 
 				$this->Application->Redirect('', $url_params);
 			}
 		}
 		else {
 			$this->Application->VerifyThemeId();
 			$this->Application->VerifyLanguageId();
 		}
 
 		$virtual_template = $this->Application->getVirtualPageTemplate($this->Get('m_cat_id'));
 
 		if ( ($virtual_template !== false) && preg_match('/external:(.*)/', $virtual_template) ) {
 			trigger_error('URL of page, that has "External URL" set was accessed: "<strong>' . $_SERVER['REQUEST_URI'] . '</strong>"', E_USER_NOTICE);
 			$this->Application->Redirect($virtual_template);
 		}
 	}
 
 	/**
 	 * Checks, that non-rewrite url was visited and it's automatic rewrite is required
 	 *
 	 * @return bool
 	 */
 	function rewriteRedirectRequired()
 	{
 		$redirect_conditions = Array (
 			!$this->IsHTTPSRedirect(), // not https <-> http redirect
 			!$this->refererIsOurSite(), // referer doesn't match ssl path or non-ssl domain (same for site domains)
 			!defined('GW_NOTIFY'), // not in payment gateway notification script
 			preg_match('/[\/]{0,1}index.php[\/]{0,1}/', $_SERVER['PHP_SELF']), // "index.php" was visited
 			$this->Get('t') != 'index', // not on index page
 		);
 
 		$perform_redirect = true;
 
 		foreach ($redirect_conditions as $redirect_condition) {
 			$perform_redirect = $perform_redirect && $redirect_condition;
 
 			if (!$perform_redirect) {
 				return false;
 			}
 		}
 
 		return true;
 	}
 
 	/**
 	 * This is redirect from https to http or via versa
 	 *
 	 * @return bool
 	 */
 	function IsHTTPSRedirect()
 	{
 		$http_referer = array_key_exists('HTTP_REFERER', $_SERVER) ? $_SERVER['HTTP_REFERER'] : false;
 
 		return (
 			( PROTOCOL == 'https://' && preg_match('#http:\/\/#', $http_referer) )
 			||
 			( PROTOCOL == 'http://' && preg_match('#https:\/\/#', $http_referer) )
 		);
 	}
 
 	/**
 	 * Checks, that referer is out site
 	 *
 	 * @return bool
 	 */
 	function refererIsOurSite()
 	{
 		if ( !array_key_exists('HTTP_REFERER', $_SERVER) ) {
 			// no referer -> don't care what happens
 			return false;
 		}
 
 		/** @var SiteHelper $site_helper */
 		$site_helper = $this->Application->recallObject('SiteHelper');
 
 		$found = false;
 		$http_referer = $_SERVER['HTTP_REFERER'];
 		preg_match('/^(.*?):\/\/(.*?)(\/|$)/', $http_referer, $regs); // 1 - protocol, 2 - domain
 
 		if ($regs[1] == 'https') {
 			$found = $site_helper->getDomainByName('SSLUrl', $http_referer) > 0;
 
 			if (!$found) {
 				// check if referer starts with our ssl url
 				$ssl_url = $this->Application->ConfigValue('SSL_URL');
 				$found = $ssl_url && preg_match('/^' . preg_quote($ssl_url, '/') . '/', $http_referer);
 			}
 		}
 		else {
 			$found = $site_helper->getDomainByName('DomainName', $regs[2]) > 0;
 
 			if (!$found) {
 				$found = $regs[2] == DOMAIN;
 			}
 		}
 
 		return $found;
 	}
 
 	function convertFiles()
 	{
 		if ( !$_FILES ) {
 			return ;
 		}
 
 		$tmp = Array ();
 		$file_keys = Array ('error', 'name', 'size', 'tmp_name', 'type');
 
 		foreach ($_FILES as $file_name => $file_info) {
 			if ( is_array($file_info['error']) ) {
 				$tmp[$file_name] = $this->getArrayLevel($file_info['error'], $file_name);
 			}
 			else {
 				$normal_files[$file_name] = $file_info;
 			}
 		}
 
 		if ( !$tmp ) {
 			return ;
 		}
 
 		$files = $_FILES;
 		$_FILES = Array ();
 
 		foreach ($tmp as $prefix => $prefix_files) {
 			$anchor =& $_FILES;
 			foreach ($prefix_files['keys'] as $key) {
 				$anchor =& $anchor[$key];
 			}
 
 			foreach ($prefix_files['value'] as $field_name) {
 				unset($inner_anchor, $copy);
 				$work_copy = $prefix_files['keys'];
 
 				foreach ($file_keys as $file_key) {
 					$inner_anchor =& $files[$prefix][$file_key];
 
 					if ( isset($copy) ) {
 						$work_copy = $copy;
 					}
 					else {
 						$copy = $work_copy;
 					}
 
 					array_shift($work_copy);
 					foreach ($work_copy as $prefix_file_key) {
 						$inner_anchor =& $inner_anchor[$prefix_file_key];
 					}
 
 					$anchor[$field_name][$file_key] = $inner_anchor[$field_name];
 				}
 			}
 		}
 		// keys: img_temp, 0, values: LocalPath, ThumbPath
 	}
 
 	function getArrayLevel(&$level, $prefix='')
 	{
 		$ret['keys'] = $prefix ? Array($prefix) : Array();
 		$ret['value'] = Array();
 
 		foreach($level as $level_key => $level_value)
 		{
 			if( is_array($level_value) )
 			{
 				$ret['keys'][] = $level_key;
 				$tmp = $this->getArrayLevel($level_value);
 
 				$ret['keys'] = array_merge($ret['keys'], $tmp['keys']);
 				$ret['value'] = array_merge($ret['value'], $tmp['value']);
 			}
 			else
 			{
 				$ret['value'][] = $level_key;
 			}
 		}
 
 		return $ret;
 	}
 
 	/**
 	 * Overwrites GET events with POST events in case if they are set and not empty
 	 *
 	 * @return void
 	 * @access protected
 	 */
 	protected function convertPostEvents()
 	{
 		/** @var Array $events */
 		$events = $this->Get('events', Array ());
 
 		if ( is_array($events) ) {
 			$events = array_filter($events);
 
 			foreach ($events as $prefix_special => $event_name) {
 				$this->Set($prefix_special . '_event', $event_name);
 			}
 		}
 	}
 
 	function finalizeParsing($passed = Array())
 	{
 		if (!$passed) {
 			return;
 		}
 
 		foreach ($passed as $passed_prefix) {
 			$this->discoverUnit($passed_prefix); // from mod-rewrite url parsing
 		}
 
 		$this->Set('passed', implode(',', $this->getDiscoveredUnits()));
 	}
 
 	/**
 	 * Saves variables from array specified
 	 * into common variable storage place
 	 *
 	 * @param Array $array
 	 * @param bool $strip_slashes
 	 * @return Array
 	 * @access private
 	 */
 	function AddVars($array, $strip_slashes = true)
 	{
 		if ( $strip_slashes ) {
 			$array = $this->StripSlashes($array);
 		}
 
 		foreach ($array as $key => $value) {
 			$this->Set($key, $value);
 		}
 
 		return $array;
 	}
 
 	function MergeVars($array, $strip_slashes = true)
 	{
 		if ( $strip_slashes ) {
 			$array = $this->StripSlashes($array);
 		}
 
 		foreach ($array as $key => $value_array) {
 			// $value_array is an array too
 			$this->_Params = kUtil::array_merge_recursive($this->_Params, Array ($key => $value_array));
 		}
 
 		return $array;
 	}
 
 	function StripSlashes($array)
 	{
 		foreach ($array as $key => $value) {
 			if (is_array($value)) {
 				$array[$key] = $this->StripSlashes($value);
 			}
 			else {
 				if (!$this->Application->isAdmin) {
 					// TODO: always escape output instead of input
 					$value = kUtil::escape($value, kUtil::ESCAPE_HTML);
 				}
 
 				$array[$key] = $value;
 			}
 		}
 
 		return $array;
 	}
 
 	/**
 	 * Removes forceful escaping done to the variable upon Front-End submission.
 	 *
 	 * @param string|array $value Value.
 	 *
 	 * @return string|array
 	 * @see    StripSlashes
 	 */
 	public function unescapeRequestVariable($value)
 	{
 		if ( $this->Application->isAdmin ) {
 			return $value;
 		}
 
 		// This allows to revert kUtil::escape() call for each field submitted on front-end.
 		if ( is_array($value) ) {
 			foreach ( $value as $param_name => $param_value ) {
 				$value[$param_name] = $this->unescapeRequestVariable($param_value);
 			}
 
 			return $value;
 		}
 
 		return kUtil::unescape($value, kUtil::ESCAPE_HTML);
 	}
 
 	/**
 	 * Returns all $_GET array excluding system parameters, that are not allowed to be passed through generated urls
 	 *
 	 * @param bool $access_error Method is called during no_permission, require login, session expiration link preparation
 	 * @return Array
 	 */
 	function getRedirectParams($access_error = false)
 	{
 		$vars = $this->Get;
 		$unset_vars = Array (ENV_VAR_NAME, 'rewrite', '_mod_rw_url_', 'Action');
 
 		if (!$this->_sidInQueryString) {
 			$unset_vars[] = 'sid';
 		}
 
 		// remove system variables
 		foreach ($unset_vars as $var_name) {
 			if (array_key_exists($var_name, $vars)) {
 				unset($vars[$var_name]);
 			}
 		}
 
 		if ($access_error) {
 			// place 1 of 2 (also in UsersEventHandler::OnSessionExpire)
 			$vars = $this->_removePassThroughVariables($vars);
 		}
 
 		return $vars;
 	}
 
 	/**
 	 * Removes all pass_though variables from redirect params
 	 *
 	 * @param Array $url_params
 	 * @return Array
 	 */
 	function _removePassThroughVariables($url_params)
 	{
 		$pass_through = array_key_exists('pass_through', $url_params) ? $url_params['pass_through'] : '';
 		if (!$pass_through) {
 			return $url_params;
 		}
 
 		$pass_through = explode(',', $pass_through . ',pass_through');
 		foreach ($pass_through as $pass_through_var) {
 			unset($url_params[$pass_through_var]);
 		}
 
 		$url_params['no_pass_through'] = 1; // this way kApplication::HREF won't add them again
 
 		return $url_params;
 	}
 
 	/**
 	 * Checks, that url is empty
 	 *
 	 * @return bool
 	 * @access public
 	 */
 	public function isEmptyUrl()
 	{
 		if ( $this->Application->RewriteURLs() ) {
 			return !$this->Get('_mod_rw_url_');
 		}
 
 		return !count($this->Get);
 	}
 
 	/**
 	 * Returns the client IP address.
 	 *
 	 * @return string The client IP address
 	 * @access public
 	 */
 	public function getClientIp()
 	{
 		if ( $this->_trustProxy ) {
 			if ( array_key_exists('HTTP_CLIENT_IP', $_SERVER) ) {
 				return $_SERVER['HTTP_CLIENT_IP'];
 			}
 
 			if ( array_key_exists('HTTP_X_FORWARDED_FOR', $_SERVER) ) {
 				$client_ip = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
 
 				foreach ($client_ip as $ip_address) {
 					$clean_ip_address = trim($ip_address);
 
 					if ( false !== filter_var($clean_ip_address, FILTER_VALIDATE_IP) ) {
 						return $clean_ip_address;
 					}
 				}
 
 				return '';
 			}
 		}
 
 		return isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '';
 	}
 
 	/**
 	 * Returns headers
 	 *
 	 * @return array
 	 * @access public
 	 */
 	public function getHeaders()
 	{
 		if ( function_exists('apache_request_headers') ) {
 			// If apache_request_headers() exists...
 			$headers = apache_request_headers();
 
-			if ( $headers ) {
-				return $headers; // And works... Use it
+			if ( $headers !== false ) {
+				return $headers; // And works... Use it.
 			}
 		}
 
 		$headers = array();
 
-		foreach ( array_keys($_SERVER) as $server_key ) {
-			if ( substr($server_key, 0, 5) == 'HTTP_' ) {
-				$header_name = str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($server_key, 0, 5)))));
-				$headers[$header_name] = $_SERVER[$server_key];
-			}
+		$header_server_keys = array_filter(array_keys($_SERVER), function ($server_key) {
+			return strpos($server_key, 'HTTP_') === 0;
+		});
+
+		foreach ( $header_server_keys as $server_key ) {
+			$header_name = str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($server_key, 5)))));
+			$headers[$header_name] = $_SERVER[$server_key];
 		}
 
 		return $headers;
 	}
 
 }