Location: PHPKode > scripts > PHON > phon/phon/PHONValidator.php
<?php
/**
 * File the for PHONValidator class.
 * @package PHON
 */

/**
 * An utility class that validates PHON, preventing possible
 * security threats that could come from executing unvalidated
 * PHON data. It won't check that the PHON represents valid PHP
 * code, just that there is no harmful code.
 * @package PHON
 */
final class PHONValidator {
	/**
	 * Singleton instance.
	 * @var PHONValidator
	 */
	private static $instance;
	
	/**
	 * Singleton access.
	 * @return PHONValidator Singleton instance.
	 */
	public static function getInstance() {
		if (!self::$instance) {
			self::$instance = new PHONValidator();
		}
		return self::$instance;
	}
	
	/**
	 * An array of classes associated with a boolean flag which
	 * indicates if this value can be safely created from PHON data.
	 * If the class is not in the array it must be checked through
	 * Reflection.
	 * @var array
	 */
	private $classes;
	
	/**
	 * Constructor.
	 */
	private function __construct() {
		$this->classes = array();
	}
	
	/**
	 * Indicates if a class can be safely created from PHON data.
	 * @param string $classname The class name.
	 * @return bool True if the class can be safely created from PHON data.
	 */
	public function isSecureClass($classname) {
		if (!isset($this->classes[$classname])) {
			$class = new ReflectionClass($classname);
			$this->classes[$classname] = $class->implementsInterface('SecurePHONClass');
		}
		return $this->classes[$classname];
	}
	
	/**
	 * Checks if PHON data can be safely executed as PHP code.
	 * @param string $phon The PHON data.
	 * @return bool True if the PHON data is secure.
	 */
	public function isSecure($phon) {
		$tokens = token_get_all("<?php $phon");
		// we were forced to add the first token, so we remove it
		array_shift($tokens);
		for ($i = 0; $i < count($tokens); $i++) {
			$token = $tokens[$i];
			// check one char secure tokens
			if ($token === ',') continue;
			if ($token === '(') continue;
			if ($token === ')') continue;
			// no other one char token is secure
			if (!is_array($token)) return false;
			// whitespace and comments are ok
			if ($token[0] === T_WHITESPACE) continue;
			if ($token[0] === T_COMMENT) continue;
			if ($token[0] === T_DOC_COMMENT) continue;
			// literals are ok
			if ($token[0] === T_CONSTANT_ENCAPSED_STRING) continue;
			if ($token[0] === T_LNUMBER) continue;
			if ($token[0] === T_DNUMBER) continue;
			// array and => are ok
			if ($token[0] === T_ARRAY) continue;
			if ($token[0] === T_DOUBLE_ARROW) continue;
			// T_STRING must be handled carefully
			if ($token[0] === T_STRING) {
				// T_STRING is ok if followed by ::__set_state
				// and is the name of a secure class
				if ($this->lookAheadSetStateCall($tokens, $i)) {
					if ($this->isSecureClass($token[1])) continue;
					return false;
				}
				// T_STRING is ok if represents a boolean literal
				if ($token[1] === 'true') continue;
				if ($token[1] === 'false') continue;
				return false;
			}
			// otherwise, we found something we shouldn't
			return false; 
		}
		// nothing suspicious to report
		return true;
	}
	
	/**
	 * Checks subsequent tokens for the pattern ::__set_state.
	 * @param array $tokens The list of tokens.
	 * @param int $i The index of the current token.
	 *               If the pattern is found, this variable will point
	 *               to the place where the __set_state was found.
	 * @return bool True if the pattern was found.
	 */
	private function lookAheadSetStateCall($tokens, &$i) {
		$foundDoubleColon = false;
		for ($j = $i+1; $j < count($tokens); $j++) {
			$token = $tokens[$j];
			// we do not expect one char tokens
			if (!is_array($tokens)) return false;
			// ignore whitespace and comments
			if ($token[0] === T_WHITESPACE) continue;
			if ($token[0] === T_COMMENT) continue;
			if ($token[0] === T_DOC_COMMENT) continue;
			// look for double colon
			if (!$foundDoubleColon) {
				if ($token[0] === T_DOUBLE_COLON) {
					$foundDoubleColon = true;
					continue;
				}
				return false;
			}
			// look for __set_state
			if ($token[0] === T_STRING) {
				if ($token[1] === '__set_state') {
					$i = $j;
					return true;
				}
			}
			// we didn't find what we were looking for
			return false;
		}
		// we ran out of tokens
		return false;
	}
}
Return current item: PHON