Skip to content

Instantly share code, notes, and snippets.

@sprice
Created December 9, 2010 06:46
Show Gist options
  • Save sprice/734417 to your computer and use it in GitHub Desktop.
Save sprice/734417 to your computer and use it in GitHub Desktop.
? sites/all/modules
? sites/default/files
? sites/default/settings.php
Index: install.php
===================================================================
RCS file: /cvs/drupal/drupal/install.php,v
retrieving revision 1.113.2.12
diff -u -p -r1.113.2.12 install.php
--- install.php 9 May 2010 14:13:31 -0000 1.113.2.12
+++ install.php 7 Oct 2010 21:55:51 -0000
@@ -20,6 +20,14 @@ function install_main() {
require_once './includes/bootstrap.inc';
drupal_bootstrap(DRUPAL_BOOTSTRAP_CONFIGURATION);
+ // The user agent header is used to pass a database prefix in the request when
+ // running tests. However, for security reasons, it is imperative that no
+ // installation be permitted using such a prefix.
+ if (isset($_SERVER['HTTP_USER_AGENT']) && strpos($_SERVER['HTTP_USER_AGENT'], "simpletest") !== FALSE) {
+ header($_SERVER['SERVER_PROTOCOL'] . ' 403 Forbidden');
+ exit;
+ }
+
// This must go after drupal_bootstrap(), which unsets globals!
global $profile, $install_locale, $conf;
Index: includes/bootstrap.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/bootstrap.inc,v
retrieving revision 1.206.2.29
diff -u -p -r1.206.2.29 bootstrap.inc
--- includes/bootstrap.inc 6 Aug 2010 11:50:24 -0000 1.206.2.29
+++ includes/bootstrap.inc 7 Oct 2010 21:55:51 -0000
@@ -1112,7 +1112,7 @@ function drupal_bootstrap($phase) {
}
function _drupal_bootstrap($phase) {
- global $conf;
+ global $conf, $db_prefix;
switch ($phase) {
@@ -1138,6 +1138,19 @@ function _drupal_bootstrap($phase) {
break;
case DRUPAL_BOOTSTRAP_DATABASE:
+ // The user agent header is used to pass a database prefix in the request when
+ // running tests. However, for security reasons, it is imperative that we
+ // validate we ourselves made the request.
+ $GLOBALS['simpletest_installed'] = TRUE;
+ if (isset($_SERVER['HTTP_USER_AGENT']) && preg_match("/^(simpletest\d+);/", $_SERVER['HTTP_USER_AGENT'], $matches)) {
+ if (!drupal_valid_test_ua($_SERVER['HTTP_USER_AGENT'])) {
+ header($_SERVER['SERVER_PROTOCOL'] . ' 403 Forbidden');
+ exit;
+ }
+ $db_prefix_string = is_array($db_prefix) ? $db_prefix['default'] : $db_prefix;
+ $db_prefix = $db_prefix_string . $matches[1];
+ }
+
// Initialize the default database.
require_once './includes/database.inc';
db_set_active();
@@ -1331,3 +1344,46 @@ function ip_address() {
return $ip_address;
}
+
+/**
+ * Validate the HMAC and timestamp of a user agent header from simpletest.
+ */
+function drupal_valid_test_ua($user_agent) {
+// global $dbatabases;
+ global $db_url;
+
+ list($prefix, $time, $salt, $hmac) = explode(';', $user_agent);
+ $check_string = $prefix . ';' . $time . ';' . $salt;
+ // We use the database credentials from settings.php to make the HMAC key, since
+ // the database is not yet initialized and we can't access any Drupal variables.
+ // The file properties add more entropy not easily accessible to others.
+// $filepath = DRUPAL_ROOT . '/includes/bootstrap.inc';
+ $filepath = './includes/bootstrap.inc';
+// $key = sha1(serialize($databases) . filectime($filepath) . fileinode($filepath), TRUE);
+ $key = sha1(serialize($db_url) . filectime($filepath) . fileinode($filepath), TRUE);
+ // The HMAC must match.
+ return $hmac == base64_encode(hash_hmac('sha1', $check_string, $key, TRUE));
+}
+
+/**
+ * Generate a user agent string with a HMAC and timestamp for simpletest.
+ */
+function drupal_generate_test_ua($prefix) {
+// global $dbatabases;
+ global $db_url;
+ static $key;
+
+ if (!isset($key)) {
+ // We use the database credentials to make the HMAC key, since we
+ // check the HMAC before the database is initialized. filectime()
+ // and fileinode() are not easily determined from remote.
+// $filepath = DRUPAL_ROOT . '/includes/bootstrap.inc';
+ $filepath = './includes/bootstrap.inc';
+// $key = sha1(serialize($databases) . filectime($filepath) . fileinode($filepath), TRUE);
+ $key = sha1(serialize($db_url) . filectime($filepath) . fileinode($filepath), TRUE);
+ }
+ // Generate a moderately secure HMAC based on the database credentials.
+ $salt = uniqid('', TRUE);
+ $check_string = $prefix . ';' . time() . ';' . $salt;
+ return $check_string . ';' . base64_encode(hash_hmac('sha1', $check_string, $key, TRUE));
+}
Index: includes/common.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/common.inc,v
retrieving revision 1.756.2.98
diff -u -p -r1.756.2.98 common.inc
--- includes/common.inc 6 Sep 2010 11:13:27 -0000 1.756.2.98
+++ includes/common.inc 7 Oct 2010 21:55:51 -0000
@@ -531,7 +531,7 @@ function drupal_http_request($url, $head
// same time won't interfere with each other as they would if the database
// prefix were stored statically in a file or database variable.
if (is_string($db_prefix) && preg_match("/^simpletest\d+$/", $db_prefix, $matches)) {
- $defaults['User-Agent'] = 'User-Agent: ' . $matches[0];
+ $defaults['User-Agent'] = 'User-Agent: ' . drupal_generate_test_ua($matches[0]);
}
foreach ($headers as $header => $value) {
@@ -2661,14 +2661,24 @@ function _drupal_bootstrap_full() {
require_once './includes/mail.inc';
require_once './includes/actions.inc';
// Set the Drupal custom error handler.
- set_error_handler('drupal_error_handler');
+ set_error_handler('_drupal_error_handler');
+ set_exception_handler('_drupal_exception_handler');
// Emit the correct charset HTTP header.
drupal_set_header('Content-Type: text/html; charset=utf-8');
// Detect string handling method
unicode_check();
// Undo magic quotes
fix_gpc_magic();
- // Load all enabled modules
+
+ if (isset($_SERVER['HTTP_USER_AGENT']) && strpos($_SERVER['HTTP_USER_AGENT'], 'simpletest') !== FALSE) {
+ // Valid SimpleTest user-agent, log fatal errors to test specific file
+ // directory. The user-agent is validated in DRUPAL_BOOTSTRAP_DATABASE
+ // phase so as long as it is a SimpleTest user-agent it is valid.
+ ini_set('log_errors', 1);
+ ini_set('error_log', file_directory_path() . '/error.log');
+ }
+
+// Load all enabled modules
module_load_all();
// Let all modules take action before menu system handles the request
// We do not want this while running update.php.
@@ -3789,3 +3799,264 @@ function _drupal_flush_css_js() {
}
variable_set('css_js_query_string', $new_character . substr($string_history, 0, 19));
}
+
+/**
+ * Error reporting level: display no errors.
+ */
+define('ERROR_REPORTING_HIDE', 0);
+
+/**
+ * Error reporting level: display errors and warnings.
+ */
+define('ERROR_REPORTING_DISPLAY_SOME', 1);
+
+/**
+ * Error reporting level: display all messages.
+ */
+define('ERROR_REPORTING_DISPLAY_ALL', 2);
+
+/**
+ * Custom PHP error handler.
+ *
+ * @param $error_level
+ * The level of the error raised.
+ * @param $message
+ * The error message.
+ * @param $filename
+ * The filename that the error was raised in.
+ * @param $line
+ * The line number the error was raised at.
+ * @param $context
+ * An array that points to the active symbol table at the point the error occurred.
+ */
+function _drupal_error_handler($error_level, $message, $filename, $line, $context) {
+ if ($error_level & error_reporting()) {
+ // All these constants are documented at http://php.net/manual/en/errorfunc.constants.php
+ $types = array(
+ E_ERROR => 'Error',
+ E_WARNING => 'Warning',
+ E_PARSE => 'Parse error',
+ E_NOTICE => 'Notice',
+ E_CORE_ERROR => 'Core error',
+ E_CORE_WARNING => 'Core warning',
+ E_COMPILE_ERROR => 'Compile error',
+ E_COMPILE_WARNING => 'Compile warning',
+ E_USER_ERROR => 'User error',
+ E_USER_WARNING => 'User warning',
+ E_USER_NOTICE => 'User notice',
+ E_STRICT => 'Strict warning',
+ E_RECOVERABLE_ERROR => 'Recoverable fatal error'
+ );
+ $caller = _drupal_get_last_caller(debug_backtrace());
+
+ // We treat recoverable errors as fatal.
+ _drupal_log_error(array(
+ '%type' => isset($types[$error_level]) ? $types[$error_level] : 'Unknown error',
+ '%message' => $message,
+ '%function' => $caller['function'],
+ '%file' => $caller['file'],
+ '%line' => $caller['line'],
+ ), $error_level == E_RECOVERABLE_ERROR);
+ }
+}
+
+/**
+ * Custom PHP exception handler.
+ *
+ * Uncaught exceptions are those not enclosed in a try/catch block. They are
+ * always fatal: the execution of the script will stop as soon as the exception
+ * handler exits.
+ *
+ * @param $exception
+ * The exception object that was thrown.
+ */
+function _drupal_exception_handler($exception) {
+ // Log the message to the watchdog and return an error page to the user.
+ _drupal_log_error(_drupal_decode_exception($exception), TRUE);
+}
+
+/**
+ * Decode an exception, especially to retrive the correct caller.
+ *
+ * @param $exception
+ * The exception object that was thrown.
+ * @return An error in the format expected by _drupal_log_error().
+ */
+function _drupal_decode_exception($exception) {
+ $message = $exception->getMessage();
+
+ $backtrace = $exception->getTrace();
+ // Add the line throwing the exception to the backtrace.
+ array_unshift($backtrace, array('line' => $exception->getLine(), 'file' => $exception->getFile()));
+
+ // For PDOException errors, we try to return the initial caller,
+ // skipping internal functions of the database layer.
+ if (is_a($exception, 'PDOException')) {
+ // The first element in the stack is the call, the second element gives us the caller.
+ // We skip calls that occurred in one of the classes of the database layer
+ // or in one of its global functions.
+// $db_functions = array('db_query', 'pager_query', 'db_query_range', 'db_query_temporary', 'update_sql');
+ $db_functions = array('db_query', '_db_query', 'pager_query', 'db_query_range', 'db_query_temporary', 'update_sql');
+ while (!empty($backtrace[1]) && ($caller = $backtrace[1]) &&
+ ((isset($caller['class']) && (strpos($caller['class'], 'Query') !== FALSE || strpos($caller['class'], 'Database') !== FALSE || strpos($caller['class'], 'PDO') !== FALSE)) ||
+ in_array($caller['function'], $db_functions))) {
+ // We remove that call.
+ array_shift($backtrace);
+ }
+ if (isset($exception->query_string, $exception->args)) {
+ $message .= ": " . $exception->query_string . "; " . print_r($exception->args, TRUE);
+ }
+ }
+ $caller = _drupal_get_last_caller($backtrace);
+
+ return array(
+ '%type' => get_class($exception),
+ '%message' => $message,
+ '%function' => $caller['function'],
+ '%file' => $caller['file'],
+ '%line' => $caller['line'],
+ );
+}
+
+/**
+ * Log a PHP error or exception, display an error page in fatal cases.
+ *
+ * @param $error
+ * An array with the following keys: %type, %message, %function, %file, %line.
+ * @param $fatal
+ * TRUE if the error is fatal.
+ */
+function _drupal_log_error($error, $fatal = FALSE) {
+ // Initialize a maintenance theme if the boostrap was not complete.
+ // Do it early because drupal_set_message() triggers a drupal_theme_initialize().
+// if ($fatal && (drupal_get_bootstrap_phase() != DRUPAL_BOOTSTRAP_FULL)) {
+ if ($fatal) { // Assumed full bootstrap since common.inc loaded.
+ unset($GLOBALS['theme']);
+ if (!defined('MAINTENANCE_MODE')) {
+ define('MAINTENANCE_MODE', 'error');
+ }
+ drupal_maintenance_theme();
+ }
+
+ // When running inside the testing framework, we relay the errors
+ // to the tested site by the way of HTTP headers.
+ if (isset($_SERVER['HTTP_USER_AGENT']) && preg_match("/^simpletest\d+;/", $_SERVER['HTTP_USER_AGENT']) && !headers_sent() && (!defined('SIMPLETEST_COLLECT_ERRORS') || SIMPLETEST_COLLECT_ERRORS)) {
+ // $number does not use drupal_static as it should not be reset
+ // as it uniquely identifies each PHP error.
+ static $number = 0;
+ $assertion = array(
+ $error['%message'],
+ $error['%type'],
+ array(
+ 'function' => $error['%function'],
+ 'file' => $error['%file'],
+ 'line' => $error['%line'],
+ ),
+ );
+ header('X-Drupal-Assertion-' . $number . ': ' . rawurlencode(serialize($assertion)));
+ $number++;
+ }
+
+ try {
+ watchdog('php', '%type: %message in %function (line %line of %file).', $error, WATCHDOG_ERROR);
+ }
+ catch (Exception $e) {
+ // Ignore any additional watchdog exception, as that probably means
+ // that the database was not initialized correctly.
+ }
+
+ if ($fatal) {
+ drupal_set_header('500 Service unavailable (with message)');
+ }
+
+ if (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest') {
+ if ($fatal) {
+ // When called from JavaScript, simply output the error message.
+ print t('%type: %message in %function (line %line of %file).', $error);
+ exit;
+ }
+ }
+ else {
+ // Display the message if the current error reporting level allows this type
+ // of message to be displayed, and unconditionnaly in update.php.
+ $error_level = variable_get('error_level', ERROR_REPORTING_DISPLAY_ALL);
+ $display_error = $error_level == ERROR_REPORTING_DISPLAY_ALL || ($error_level == ERROR_REPORTING_DISPLAY_SOME && $error['%type'] != 'Notice');
+ if ($display_error || (defined('MAINTENANCE_MODE') && MAINTENANCE_MODE == 'update')) {
+ $class = 'error';
+
+ // If error type is 'User notice' then treat it as debug information
+ // instead of an error message, see dd().
+ if ($error['%type'] == 'User notice') {
+ $error['%type'] = 'Debug';
+ $class = 'status';
+ }
+
+ drupal_set_message(t('%type: %message in %function (line %line of %file).', $error), $class);
+ }
+
+ if ($fatal) {
+ drupal_set_title(t('Error'));
+ // We fallback to a maintenance page at this point, because the page generation
+ // itself can generate errors.
+ print theme('maintenance_page', t('The website encountered an unexpected error. Please try again later.'));
+ exit;
+ }
+ }
+}
+
+/**
+ * Gets the last caller from a backtrace.
+ *
+ * @param $backtrace
+ * A standard PHP backtrace.
+ * @return
+ * An associative array with keys 'file', 'line' and 'function'.
+ */
+function _drupal_get_last_caller($backtrace) {
+ // Errors that occur inside PHP internal functions do not generate
+ // information about file and line. Ignore black listed functions.
+ $blacklist = array('debug');
+ while (($backtrace && !isset($backtrace[0]['line'])) ||
+ (isset($backtrace[1]['function']) && in_array($backtrace[1]['function'], $blacklist))) {
+ array_shift($backtrace);
+ }
+
+ // The first trace is the call itself.
+ // It gives us the line and the file of the last call.
+ $call = $backtrace[0];
+
+ // The second call give us the function where the call originated.
+ if (isset($backtrace[1])) {
+ if (isset($backtrace[1]['class'])) {
+ $call['function'] = $backtrace[1]['class'] . $backtrace[1]['type'] . $backtrace[1]['function'] . '()';
+ }
+ else {
+ $call['function'] = $backtrace[1]['function'] . '()';
+ }
+ }
+ else {
+ $call['function'] = 'main()';
+ }
+ return $call;
+}
+
+/**
+ * Debug function used for outputting debug information.
+ *
+ * The debug information is passed on to trigger_error() after being converted
+ * to a string using _drupal_debug_message().
+ *
+ * @param $data
+ * Data to be output.
+ * @param $label
+ * Label to prefix the data.
+ * @param $print_r
+ * Flag to switch between print_r() and var_export() for data conversion to
+ * string. Set $print_r to TRUE when dealing with a recursive data structure
+ * as var_export() will generate an error.
+ */
+function debug($data, $label = NULL, $print_r = FALSE) {
+ // Print $data contents to string.
+ $string = $print_r ? print_r($data, TRUE) : var_export($data, TRUE);
+ trigger_error(trim($label ? "$label: $string" : $string));
+}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment