blob: db9529145421b58e0e398e357bd3b8ccb3653bd3 [file] [log] [blame]
<?php
/**
* Copyright 2007 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Handles the /shell.do url.
* It executes the received statement using the globals, constants and locals
* stored for this user. Statements declaring classes and functions are
* persisted and run every time.
*/
class Session {
/**
* Stores a serialized version of both local and global variables.
*/
private $globals = "";
private $locals = "";
private $statements = array();
private $use_statements = array();
private $constants = "";
function __construct() {
$this->locals = serialize(array());
$this->storeGlobals();
$this->functions = get_defined_functions();
$this->classes = get_declared_classes();
$this->storeConstants();
}
/** Stores a serialized version of the globals. */
function storeGlobals() {
$this->globals = serialize($GLOBALS);
}
/** Replace the globals with the serialized stored ones. */
function loadGlobals() {
$GLOBALS = unserialize($this->globals);
}
/** Stores a serialized version of the passed locals. */
function storeLocals($locals) {
foreach (array("_shell_statement",
"_shell_session") as $nonLocal) {
unset($locals[$nonLocal]);
}
$this->locals = serialize($locals);
}
/** Returns an array with the locals. */
function getLocals() {
return unserialize($this->locals);
}
/**
* Stores a statement if it declares a function, a class or if it is a
* use statment or a require/include statement.
*/
function storeStatementIfNeeded($statement) {
$nonSerializableTokens = array(T_CLASS, T_FUNCTION,
T_REQUIRE, T_REQUIRE_ONCE,
T_INCLUDE, T_INCLUDE_ONCE);
foreach(token_get_all("<?php $statement ?>") as $token) {
if (in_array($token[0], $nonSerializableTokens)) {
array_push($this->statements, $statement);
} else if ($token[0] == T_USE) {
array_push($this->use_statements, $statement);
}
}
}
/** Stores a serialized version of the constants. */
function storeConstants() {
$this->constants = serialize(get_defined_constants());
}
/** Replace the constants with the serialized stored ones. */
function loadConstants() {
$constants = unserialize($this->constants);
foreach(array_diff_key($constants, get_defined_constants()) as
$constant=>$value) {
define($constant, $value);
}
}
static function scrubOutput($output) {
return htmlentities($output);
}
/** Evaluate all saved statements.*/
function loadStatements() {
foreach ($this->statements as $statement) {
ob_start(['Session', 'scrubOutput']);
eval($statement);
ob_clean();
}
}
/** Prepend all the use statements to the given statement. */
function prependUseStatements($statement) {
return implode("\n", $this->use_statements) . "\n$statement";
}
/** Method to initialize user scope.*/
function start() {
// Must goes first, or otherwise the unserialized objects will be incomplete.
$this->loadStatements();
$this->loadGlobals();
$this->functions = get_defined_functions();
$this->classes = get_declared_classes();
$this->loadConstants();
}
/** Method to save user scope.*/
function end($statement, $locals) {
$this->storeGlobals();
$this->storeLocals($locals);
$this->storeStatementIfNeeded($statement);
$this->storeConstants();
}
}
/**
* Handler to catch exceptions raised when evaluation the code.
* We just return the error and not the line, as they are not meaningful in this
* context.
*/
function error_handler($errno, $errstr, $errfile, $errline) {
echo $errstr, "\n";
}
/**
* Handler to catch fatal errors (like function not defined) and print them
* nicely.
*/
function shutdown_handler() {
$error = error_get_last();
if($error !== NULL){
echo $error["message"], "\n";
}
}
/**
* Executes a statement for the given session.
* All locals must be prefixed with _shell_, to avoid messing up with the user's
* local.
*/
function shell($_shell_statement, $_shell_session) {
$_shell_session->start();
header("Content-Type: text/html; charset=utf-8");
extract($_shell_session->getLocals(), EXTR_SKIP);
// Disable all error reporting, otherwise it mess with the output.
error_reporting(0);
// Errors are handled with an error handler and a fatal error handler, because
// exceptions are not catchable when evaluating code.
register_shutdown_function('shutdown_handler');
set_error_handler('error_handler');
ob_start(['Session', 'scrubOutput']);
eval($_shell_session->prependUseStatements($_shell_statement));
ob_end_flush();
$_shell_session->end($_shell_statement, get_defined_vars());
}
session_start();
if (!isset($_SESSION["session"])) {
$_SESSION["session"] = new Session();
}
if (isset($_SESSION['token']) && ($_GET['token'] === $_SESSION['token'])) {
// Append a semi-colon just in case the statement doen't have one. An extra
// semi-colon makes no harm.
shell($_GET["statement"] . ";", $_SESSION["session"]);
} else if (!isset($_SESSION['token'])) {
syslog(LOG_ERR, 'Missing session token');
echo "Session token missing - Please reset your session.";
} else {
syslog(LOG_ERR, 'Mismatch session token.');
echo "Invalid session token - Please reset your session.";
}