Current Path : /var/www/alh/system/system/library/journal3/vendor/PhpConsole/ |
Current File : /var/www/alh/system/system/library/journal3/vendor/PhpConsole/EvalProvider.php |
<?php namespace PhpConsole; /** * Execute PHP code with some security & accessibility tweaks * * @package PhpConsole * @version 3.1 * @link http://consle.com * @author Sergey Barbushin http://linkedin.com/in/barbushin * @copyright © Sergey Barbushin, 2011-2013. All rights reserved. * @license http://www.opensource.org/licenses/BSD-3-Clause "The BSD 3-Clause License" */ class EvalProvider { protected $sharedVars = array(); protected $openBaseDirs = array(); protected $codeCallbackHandlers = array(); protected $globalsBackup; /** * Execute PHP code handling execution time, output & exception * @param string $code * @return EvalResult */ public function evaluate($code) { $code = $this->applyHandlersToCode($code); $code = $this->adaptCodeToEval($code); $this->backupGlobals(); $this->applyOpenBaseDirSetting(); $startTime = microtime(true); static::executeCode('', $this->sharedVars); $selfTime = microtime(true) - $startTime; ob_start(); $result = new EvalResult(); $startTime = microtime(true); try { $result->return = static::executeCode($code, $this->sharedVars); } catch(\Throwable $exception) { $result->exception = $exception; } catch(\Exception $exception) { $result->exception = $exception; } $result->time = abs(microtime(true) - $startTime - $selfTime); $result->output = ob_get_clean(); $this->restoreGlobals(); return $result; } /** * Add callback that will be called with &$code var reference before code execution * @param $callback * @throws \Exception */ public function addCodeHandler($callback) { if(!is_callable($callback)) { throw new \Exception('Argument is not callable'); } $this->codeCallbackHandlers[] = $callback; } /** * Call added code handlers * @param $code * @return mixed */ protected function applyHandlersToCode($code) { foreach($this->codeCallbackHandlers as $callback) { call_user_func_array($callback, array(&$code)); } return $code; } /** * Store global vars data in backup var */ protected function backupGlobals() { $this->globalsBackup = array(); foreach($GLOBALS as $key => $value) { if($key != 'GLOBALS') { $this->globalsBackup[$key] = $value; } } } /** * Restore global vars data from backup var */ protected function restoreGlobals() { foreach($this->globalsBackup as $key => $value) { $GLOBALS[$key] = $value; } foreach(array_diff(array_keys($GLOBALS), array_keys($this->globalsBackup)) as $newKey) { if($newKey != 'GLOBALS') { unset($GLOBALS[$newKey]); } } } /** * Execute code with shared vars * @param $_code * @param array $_sharedVars * @return mixed */ protected static function executeCode($_code, array $_sharedVars) { foreach($_sharedVars as $var => $value) { if(isset($GLOBALS[$var]) && $var[0] == '_') { // extract($this->sharedVars, EXTR_OVERWRITE) and $$var = $value do not overwrites global vars $GLOBALS[$var] = $value; } elseif(!isset($$var)) { $$var = $value; } } return eval($_code); } /** * Prepare code PHP tags be correctly passed to eval() function * @param string $code * @return string */ protected function trimPhpTags($code) { $replace = array( '~^(\s*)<\?=~s' => '\1echo ', '~^(\s*)<\?(php)?~is' => '\1', '~\?>\s*$~s' => '', '~<\?(php)?[\s;]*$~is' => '', ); return preg_replace(array_keys($replace), $replace, $code); } /** * Add semicolon to the end of code if it's required * @param string $code * @return string */ protected function forceEndingSemicolon($code) { $code = rtrim($code, "; \r\n"); return $code[strlen($code) - 1] != '}' ? $code . ';' : $code; } /** * Apply some default code handlers * @param string $code * @return string */ protected function adaptCodeToEval($code) { $code = $this->trimPhpTags($code); $code = $this->forceEndingSemicolon($code); return $code; } /** * Protect response code access only to specified directories using http://www.php.net/manual/en/ini.core.php#ini.open-basedir * IMPORTANT: classes autoload methods will work only for specified directories * @param array $openBaseDirs * @codeCoverageIgnore */ public function setOpenBaseDirs(array $openBaseDirs) { $this->openBaseDirs = $openBaseDirs; } /** * Autoload all PHP Console classes * @codeCoverageIgnore */ protected function forcePhpConsoleClassesAutoLoad() { foreach(new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator(__DIR__), \RecursiveIteratorIterator::LEAVES_ONLY) as $path) { /** @var $path \SplFileInfo */ if($path->isFile() && $path->getExtension() == 'php' && $path->getFilename() !== 'PsrLogger.php') { require_once($path->getPathname()); } } } /** * Set actual "open_basedir" PHP ini option * @throws \Exception * @codeCoverageIgnore */ protected function applyOpenBaseDirSetting() { if($this->openBaseDirs) { $value = implode(PATH_SEPARATOR, $this->openBaseDirs); if(ini_get('open_basedir') != $value) { $this->forcePhpConsoleClassesAutoLoad(); if(ini_set('open_basedir', $value) === false) { throw new \Exception('Unable to set "open_basedir" php.ini setting'); } } } } /** * Protect response code from reading/writing/including any files using http://www.php.net/manual/en/ini.core.php#ini.open-basedir * IMPORTANT: It does not protects from system(), exec(), passthru(), popen() & etc OS commands execution functions * IMPORTANT: Classes autoload methods will not work, so all required classes must be loaded before code evaluation * @codeCoverageIgnore */ public function disableFileAccessByOpenBaseDir() { $this->setOpenBaseDirs(array(__DIR__ . '/not_existed_dir' . mt_rand())); } /** * Add var that will be implemented in PHP code executed from PHP Console debug panel (will be implemented in PHP Console > v3.0) * @param $name * @param $var * @throws \Exception */ public function addSharedVar($name, $var) { $this->addSharedVarReference($name, $var); } /** * Add var that will be implemented in PHP code executed from PHP Console debug panel (will be implemented in PHP Console > v3.0) * @param $name * @param $var * @throws \Exception */ public function addSharedVarReference($name, &$var) { if(isset($this->sharedVars[$name])) { throw new \Exception('Var with name "' . $name . '" already added'); } $this->sharedVars[$name] =& $var; } } class EvalResult { public $return; public $output; public $time; /** @var \Exception|null */ public $exception; }