<?php
/**
 * Project: 
 * Contenido Content Management System
 * 
 * Description: 
 * This class provides validation methods for HTTP parameters (GET and POST).
 * Originally based on work of kummer and started by discussion in contenido forum this class
 * is a little bit "re-writed" for better interaction with contenido.
 * Thanks to Andreas Kummer (aka kummer) for this great idea!
 * 
 * Requirements: 
 * @con_php_req 5.0
 * @con_notice ToDo: Error page re-direction?
 *
 *
 * @package    Contenido Backend classes
 * @version    1.1.2
 * @author     Andreas Kummer, Holger Librenz
 * @copyright  atelierQ Kummer, four for business AG <www.4fb.de>
 * @license    http://www.contenido.org/license/LIZENZ.txt
 * @link       http://www.4fb.de
 * @link       http://www.contenido.org
 * @since      file available since contenido release <= 4.6
 *
 * @TODO: Some features are the same as in Contenido_Security (see contenido/classes/class.security.php),
 *        merge them...
 *
 * {@internal 
 *   created 2008-02-06
 *   modified 2008-06-10, I. van Peeren, initially set $this->bLog as $bLog in config file
 *   modified 2008-07-02, Frederic Schneider, add security fix
 *
 *   $Id$:
 * }}
 * 
 */

if(!defined('CON_FRAMEWORK')) {
	die('Illegal call');
}

#### FORMAT CONSTANTS ####
define('CON_CHECK_INTEGER', '/^[0-9]*$/'); // integer value
define('CON_CHECK_PRIMITIVESTRING', '/^[a-zA-Z0-9 -_]*$/'); // simple string
define('CON_CHECK_STRING', '/^[\w0-9 -_]*$/'); // more complex string
define('CON_CHECK_HASH32', '/^[a-zA-Z0-9]{32}$/'); // 32-character hash


/**
 * This class is the extended version of excelent 
 * code made by kummer.
 * 
 * @version 1.0.1
 * @see http://contenido.org/forum/viewtopic.php?p=113492#113492
 */
class HttpInputValidator {
	
	/**
	 * Associative array with available POST parameter name as
	 * key and flag whether this parameter is "clean" or not.
	 *
	 * @var array
	 */
	var $aPostVariables = array();

	/**
	 * Path and filename of logfile
	 *
	 * @var string
	 */
	var $sLogPath = '';

	/**
	 * Flag whether to write log or not.
	 *
	 * @var boolean
	 */
	var $bLog = false;

	/**
	 * Path to config file.
	 *
	 * @var string
	 */
	var $sConfigPath = '';

	/**
	 * Array with all possible parameters and parameter formats.
	 * Structure has to be:
	 * 
	 * <code>
	 * $check['GET']['param1']	= VALIDATE_FORMAT;
	 * $check['POST']['param2']	= VALIDATE_FORMAT;
	 * </code> 
	 *
	 * Possible formats are defined as constants in top of these class file.
	 * 
	 * @var array
	 */
	var $aCheck = array();

	/**
	 * Contains first invalid parameter name.
	 *
	 * @var string
	 */
	var $sFailure = '';

	/**
	 * Current mode
	 *
	 * @var string
	 */
	var $sMode = 'training';

	/**
	 * Constructor
	 * 
	 * Configuration path $sConfigPath is mandatory and has to contain the complete
	 * path to configuration file with defined parameters.
	 * 
	 * The class provides two modes: training and arcade.
	 * Training mode only logs violations - if log path is given into log file otherwise
	 * as comment into HTML output. Arcade mode is made for killing - every violation will 
	 * cause an hard exit!
	 *
	 * @param string $sConfigPath
	 * @return HttpInputValidator
	 */
	function __construct($sConfigPath) {
		// check config and logging path
		if (!empty($sConfigPath) && file_exists($sConfigPath)) {
			$this->sConfigPath = realpath($sConfigPath);
		} else {
			die ('Could not load HttpInputValidator configuration! (invalid path)');
		}

		// include configuration
		require ($this->sConfigPath);

		// if custom config exists, include it also here
		if (file_exists(dirname($this->sConfigPath) . '/config.http_check.local.php')) {
			require (dirname($this->sConfigPath) . '/config.http_check.local.php');
		}
		
		$this->bLog = $bLog;

		if ($this->bLog === true) {
			if (!empty($sLogPath) && is_writable(dirname($sLogPath))) {
				$this->sLogPath = realpath($sLogPath);
			} else {
				die ('Could not log into not existing or empty log path!');
			}
		}
	
		$this->aCheck = $aCheck;

		// run GET check
		if ($this->checkGetParams()) {
			// logging is needed in both modes, training and arcade
			$this->logHackTrial();

			// stops here in case of arcade mode
			if ($sMode == 'arcade') {
				ob_end_clean();
				die ('Parameter check failed! (' . $this->sFailure . ')');
			}
		}
		
		// check POST params for further processings
		$this->checkPostParams();
	}
	
	/**
	 * Checks all GET params and returns true in case of a violation, otherwise false.
	 *
	 * @return bool
	 */
	function checkGetParams () {
		$bResult = false;

		foreach ($_GET as $sKey => $mValue) {
			if (!$this->checkParameter('GET', $sKey, $mValue)) {
				$this->sFailure = $sKey;
				$bResult = true;
				break;
			}
		}

		return $bResult;
	}

	/**
	 * Fills status-array $aPostVariables. Is POST-param known and valid mapped value is true.
	 *
	 */
	function checkPostParams () {
		foreach ($_POST as $sKey => $mValue) {
			$this->aPostVariables[$sKey] = ($this->checkParameter('POST', $sKey, $mValue)) ? (true) : (false);
		}
	}

	/**
	 * This method checks parameter of type $sType (currently GET and POST
	 * are supported) and name $sKey has valid value $mValue. In this case or
	 * in case of unknown but empty params, the method will also return true.
	 * 
	 * @param string $sType
	 * @param string $sKey
	 * @param mixed $mValue
	 * @return bool
	 */
	function checkParameter ($sType, $sKey, $mValue) {
		$bResult = false;

		if (in_array(strtoupper($sType), array('GET', 'POST'))) {
			if (!isset($this->aCheck[$sType][$sKey]) && (is_null($mValue) || empty($mValue))) {
				// if unknown but empty the value is unaesthetic but ok
				$bResult = true;
			} elseif (isset($this->aCheck[$sType][$sKey])) {
				// parameter is known, check it...
				$bResult = preg_match($this->aCheck[$sType][$sKey], $mValue);
			}
		}

		return $bResult;
	}
	
	/**
	 * Tries to log date, remote ip and the requested URI into log file.
	 */
	function logHackTrial() {
		if ($this->bLog === true && !empty($this->sLogPath)) { 
			if (($rLogFile = @fopen($this->sLogPath, 'a')) !== false) {
				fwrite($rLogFile, date('Y-m-d H:i:s'));
				fwrite($rLogFile, '  ');
				fwrite($rLogFile, $_SERVER['REMOTE_ADDR'] . str_repeat(' ', 17 - strlen($_SERVER['REMOTE_ADDR'])));
				fwrite($rLogFile, $_SERVER['QUERY_STRING']);
				fwrite($rLogFile, "\n");
				fclose($rLogFile);
			}
		} elseif ($this->sMode == 'training') {
			echo "\n<br />VIOLATION: URL contains invalid or undefined paramaters! URL: '" . 
				clHtmlEntities($_SERVER['QUERY_STRING']) . "' <br />\n";
		}
	}
	
	/**
	 * Print html comment or returns (depending on flag $bReturn) all POST params.
	 * 
	 * @return string
	 */
	function showPosts($bReturn = false) {
		$sResult = '';

		foreach ($_POST as $sKey => $sValue) {
			$sResult .= $key . ": ";
			
			if (is_null($this->isRegularPost($sKey))) {
				$sResult .= "not defined";
			} else
				if ($this->isRegularPost($key) === false) {
					$sResult .= "iregular";
				} else {
					$sResult .= "ok";
				}
			$sResult .= "\n";
		}
		
		if ($bReturn === false) {
			$sResult = "\n\n<!--\n*** POST PARAMETER CHECK ***\n\n" . $sResult . "\n-->\n\n";
			echo $sResult;
		} else {
			return $sResult;
		}
	}

	/**
	 * Checks POST param $sKey is unknown (result is null), known but invalid (result is false)
	 * or it is known and valid (result is true).
	 * 
	 * @param string $sKey
	 * @return mixed
	 */
	function isRegularPost($sKey) {
		$mResult = null;

		if (isset ($this->postVariable[$sKey])) {
			$mResult = $this->postVariable[$sKey]; 
		}

		return $mResult;
	}

}
?>