From 90d6fb15edf7f3df88cb38de22db25d8f36dd1b3 Mon Sep 17 00:00:00 2001 From: Alex Pott Date: Wed, 3 Jun 2015 18:06:46 +0100 Subject: [PATCH] Issue #2389811 by znerol, mpdonadio, alexpott, hussainweb, neclimdul: Move all the logic out of index.php (again) --- core/lib/Drupal/Core/DrupalKernel.php | 164 +++++++++++++----- .../lib/Drupal/Core/DrupalKernelInterface.php | 5 +- core/lib/Drupal/Core/Test/TestKernel.php | 7 +- .../lib/Drupal/Core/Test/TestRunnerKernel.php | 4 +- .../Tests/DrupalKernel/DrupalKernelTest.php | 24 ++- core/modules/system/tests/http.php | 10 +- core/modules/system/tests/https.php | 10 +- index.php | 34 +--- 8 files changed, 165 insertions(+), 93 deletions(-) diff --git a/core/lib/Drupal/Core/DrupalKernel.php b/core/lib/Drupal/Core/DrupalKernel.php index c8ba628ac6c..815f7f3387a 100644 --- a/core/lib/Drupal/Core/DrupalKernel.php +++ b/core/lib/Drupal/Core/DrupalKernel.php @@ -35,6 +35,7 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; +use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface; use Symfony\Component\HttpKernel\TerminableInterface; use Symfony\Component\Routing\Route; @@ -213,53 +214,9 @@ class DrupalKernel implements DrupalKernelInterface, TerminableInterface { * In case the host name in the request is not trusted. */ public static function createFromRequest(Request $request, $class_loader, $environment, $allow_dumping = TRUE) { - // Include our bootstrap file. - $core_root = dirname(dirname(dirname(__DIR__))); - require_once $core_root . '/includes/bootstrap.inc'; - $class_loader_class = get_class($class_loader); - $kernel = new static($environment, $class_loader, $allow_dumping); - - // Ensure sane php environment variables.. static::bootEnvironment(); - - // Get our most basic settings setup. - $site_path = static::findSitePath($request); - $kernel->setSitePath($site_path); - Settings::initialize(dirname($core_root), $site_path, $class_loader); - - // Initialize our list of trusted HTTP Host headers to protect against - // header attacks. - $host_patterns = Settings::get('trusted_host_patterns', array()); - if (PHP_SAPI !== 'cli' && !empty($host_patterns)) { - if (static::setupTrustedHosts($request, $host_patterns) === FALSE) { - throw new BadRequestHttpException('The provided host name is not valid for this server.'); - } - } - - // Redirect the user to the installation script if Drupal has not been - // installed yet (i.e., if no $databases array has been defined in the - // settings.php file) and we are not already installing. - if (!Database::getConnectionInfo() && !drupal_installation_attempted() && PHP_SAPI !== 'cli') { - $response = new RedirectResponse($request->getBasePath() . '/core/install.php'); - $response->prepare($request)->send(); - } - - // If the class loader is still the same, possibly upgrade to the APC class - // loader. - if ($class_loader_class == get_class($class_loader) - && Settings::get('class_loader_auto_detect', TRUE) - && function_exists('apc_fetch')) { - $prefix = Settings::getApcuPrefix('class_loader', $core_root); - $apc_loader = new \Symfony\Component\ClassLoader\ApcClassLoader($prefix, $class_loader); - $class_loader->unregister(); - $apc_loader->register(); - $class_loader = $apc_loader; - } - - // Ensure that the class loader reference is up-to-date. - $kernel->classLoader = $class_loader; - + $kernel->initializeSettings($request); return $kernel; } @@ -379,6 +336,9 @@ class DrupalKernel implements DrupalKernelInterface, TerminableInterface { * {@inheritdoc} */ public function setSitePath($path) { + if ($this->booted) { + throw new \LogicException('Site path cannot be changed after calling boot()'); + } $this->sitePath = $path; } @@ -595,8 +555,77 @@ class DrupalKernel implements DrupalKernelInterface, TerminableInterface { * {@inheritdoc} */ public function handle(Request $request, $type = self::MASTER_REQUEST, $catch = TRUE) { - $this->boot(); - return $this->getHttpKernel()->handle($request, $type, $catch); + // Ensure sane PHP environment variables. + static::bootEnvironment(); + + try { + $this->initializeSettings($request); + + // Redirect the user to the installation script if Drupal has not been + // installed yet (i.e., if no $databases array has been defined in the + // settings.php file) and we are not already installing. + if (!Database::getConnectionInfo() && !drupal_installation_attempted() && PHP_SAPI !== 'cli') { + $response = new RedirectResponse($request->getBasePath() . '/core/install.php'); + } + else { + $this->boot(); + $response = $this->getHttpKernel()->handle($request, $type, $catch); + } + } + catch (\Exception $e) { + if ($catch === FALSE) { + throw $e; + } + + $response = $this->handleException($e, $request, $type); + } + + // Adapt response headers to the current request. + $response->prepare($request); + + return $response; + } + + /** + * Converts an exception into a response. + * + * @param \Exception $e + * An exception + * @param Request $request + * A Request instance + * @param int $type + * The type of the request (one of HttpKernelInterface::MASTER_REQUEST or + * HttpKernelInterface::SUB_REQUEST) + * + * @return Response + * A Response instance + */ + protected function handleException(\Exception $e, $request, $type) { + if ($e instanceof HttpExceptionInterface) { + $response = new Response($e->getMessage(), $e->getStatusCode()); + $response->headers->add($e->getHeaders()); + return $response; + } + else { + // @todo: _drupal_log_error() and thus _drupal_exception_handler() prints + // the message directly. Extract a function which generates and returns it + // instead, then remove the output buffer hack here. + ob_start(); + try { + // @todo: The exception handler prints the message directly. Extract a + // function which returns the message instead. + _drupal_exception_handler($e); + } + catch (\Exception $e) { + $message = Settings::get('rebuild_message', 'If you have just changed code (for example deployed a new module or moved an existing one) read https://www.drupal.org/documentation/rebuild'); + if ($message && Settings::get('rebuild_access', FALSE)) { + $rebuild_path = $GLOBALS['base_url'] . '/rebuild.php'; + $message .= " or run the rebuild script"; + } + print $message; + } + return new Response(ob_get_clean(), 500); + } } /** @@ -796,6 +825,10 @@ class DrupalKernel implements DrupalKernelInterface, TerminableInterface { return; } + // Include our bootstrap file. + $core_root = dirname(dirname(dirname(__DIR__))); + require_once $core_root . '/includes/bootstrap.inc'; + // Enforce E_STRICT, but allow users to set levels not part of E_STRICT. error_reporting(E_STRICT | E_ALL); @@ -847,6 +880,43 @@ class DrupalKernel implements DrupalKernelInterface, TerminableInterface { static::$isEnvironmentInitialized = TRUE; } + /** + * Locate site path and initialize settings singleton. + * + * @param \Symfony\Component\HttpFoundation\Request $request + * The current request. + * + * @throws \Symfony\Component\HttpKernel\Exception\BadRequestHttpException + * In case the host name in the request is not trusted. + */ + protected function initializeSettings(Request $request) { + $site_path = static::findSitePath($request); + $this->setSitePath($site_path); + $class_loader_class = get_class($this->classLoader); + Settings::initialize($this->root, $site_path, $this->classLoader); + + // Initialize our list of trusted HTTP Host headers to protect against + // header attacks. + $host_patterns = Settings::get('trusted_host_patterns', array()); + if (PHP_SAPI !== 'cli' && !empty($host_patterns)) { + if (static::setupTrustedHosts($request, $host_patterns) === FALSE) { + throw new BadRequestHttpException('The provided host name is not valid for this server.'); + } + } + + // If the class loader is still the same, possibly upgrade to the APC class + // loader. + if ($class_loader_class == get_class($this->classLoader) + && Settings::get('class_loader_auto_detect', TRUE) + && function_exists('apc_fetch')) { + $prefix = Settings::getApcuPrefix('class_loader', $this->root); + $apc_loader = new \Symfony\Component\ClassLoader\ApcClassLoader($prefix, $this->classLoader); + $this->classLoader->unregister(); + $apc_loader->register(); + $this->classLoader = $apc_loader; + } + } + /** * Bootstraps the legacy global request variables. * diff --git a/core/lib/Drupal/Core/DrupalKernelInterface.php b/core/lib/Drupal/Core/DrupalKernelInterface.php index ab17b77ff09..c7eb93f9d34 100644 --- a/core/lib/Drupal/Core/DrupalKernelInterface.php +++ b/core/lib/Drupal/Core/DrupalKernelInterface.php @@ -60,8 +60,11 @@ interface DrupalKernelInterface extends HttpKernelInterface { /** * Set the current site path. * - * @param $path + * @param string $path * The current site path. + * + * @throws \LogicException + * In case the kernel is already booted. */ public function setSitePath($path); diff --git a/core/lib/Drupal/Core/Test/TestKernel.php b/core/lib/Drupal/Core/Test/TestKernel.php index bff37e6e5dc..55294caff8c 100644 --- a/core/lib/Drupal/Core/Test/TestKernel.php +++ b/core/lib/Drupal/Core/Test/TestKernel.php @@ -8,7 +8,6 @@ namespace Drupal\Core\Test; use Drupal\Core\DrupalKernel; -use Symfony\Component\HttpFoundation\Request; /** * Kernel to mock requests to test simpletest. @@ -18,17 +17,17 @@ class TestKernel extends DrupalKernel { /** * {@inheritdoc} */ - public static function createFromRequest(Request $request, $class_loader, $environment, $allow_dumping = TRUE) { + public function __construct($environment, $class_loader, $allow_dumping = TRUE) { // Include our bootstrap file. require_once __DIR__ . '/../../../../includes/bootstrap.inc'; // Exit if we should be in a test environment but aren't. if (!drupal_valid_test_ua()) { - header($request->server->get('SERVER_PROTOCOL') . ' 403 Forbidden'); + header($_SERVER['SERVER_PROTOCOL'] . ' 403 Forbidden'); exit; } - return parent::createFromRequest($request, $class_loader, $environment, $allow_dumping); + parent::__construct($environment, $class_loader, $allow_dumping); } } diff --git a/core/lib/Drupal/Core/Test/TestRunnerKernel.php b/core/lib/Drupal/Core/Test/TestRunnerKernel.php index fa9fd51f20e..f19afb3bf92 100644 --- a/core/lib/Drupal/Core/Test/TestRunnerKernel.php +++ b/core/lib/Drupal/Core/Test/TestRunnerKernel.php @@ -42,8 +42,8 @@ class TestRunnerKernel extends DrupalKernel { 'simpletest' => 0, ); $this->moduleData = array( - 'system' => new Extension(DRUPAL_ROOT, 'module', 'core/modules/system/system.info.yml', 'system.module'), - 'simpletest' => new Extension(DRUPAL_ROOT, 'module', 'core/modules/simpletest/simpletest.info.yml', 'simpletest.module'), + 'system' => new Extension($this->root, 'module', 'core/modules/system/system.info.yml', 'system.module'), + 'simpletest' => new Extension($this->root, 'module', 'core/modules/simpletest/simpletest.info.yml', 'simpletest.module'), ); } diff --git a/core/modules/system/src/Tests/DrupalKernel/DrupalKernelTest.php b/core/modules/system/src/Tests/DrupalKernel/DrupalKernelTest.php index ab6c224952d..84178a1f23b 100644 --- a/core/modules/system/src/Tests/DrupalKernel/DrupalKernelTest.php +++ b/core/modules/system/src/Tests/DrupalKernel/DrupalKernelTest.php @@ -166,7 +166,7 @@ class DrupalKernelTest extends KernelTestBase { } /** - * Test repeated loading of compiled DIC with different environment. + * Tests repeated loading of compiled DIC with different environment. */ public function testRepeatedBootWithDifferentEnvironment() { $request = Request::createFromGlobals(); @@ -189,4 +189,26 @@ class DrupalKernelTest extends KernelTestBase { $this->pass('Repeatedly loaded compiled DIC with different environment'); } + /** + * Tests setting of site path after kernel boot. + */ + public function testPreventChangeOfSitePath() { + // @todo: write a memory based storage backend for testing. + $modules_enabled = array( + 'system' => 'system', + 'user' => 'user', + ); + + $request = Request::createFromGlobals(); + $kernel = $this->getTestKernel($request, $modules_enabled); + $pass = FALSE; + try { + $kernel->setSitePath('/dev/null'); + } + catch (\LogicException $e) { + $pass = TRUE; + } + $this->assertTrue($pass, 'Throws LogicException if DrupalKernel::setSitePath() is called after boot'); + } + } diff --git a/core/modules/system/tests/http.php b/core/modules/system/tests/http.php index 01856361eee..6d822ec88f0 100644 --- a/core/modules/system/tests/http.php +++ b/core/modules/system/tests/http.php @@ -20,10 +20,10 @@ foreach ($_SERVER as &$value) { $value = str_replace('https://', 'http://', $value); } +$kernel = new TestKernel('testing', $autoloader, TRUE); + $request = Request::createFromGlobals(); -$kernel = TestKernel::createFromRequest($request, $autoloader, 'testing', TRUE); -$response = $kernel - ->handle($request) - // Handle the response object. - ->prepare($request)->send(); +$response = $kernel->handle($request); +$response->send(); + $kernel->terminate($request, $response); diff --git a/core/modules/system/tests/https.php b/core/modules/system/tests/https.php index 95ebed862ec..827dd7a197f 100644 --- a/core/modules/system/tests/https.php +++ b/core/modules/system/tests/https.php @@ -22,10 +22,10 @@ foreach ($_SERVER as &$value) { $value = str_replace('http://', 'https://', $value); } +$kernel = new TestKernel('testing', $autoloader, TRUE); + $request = Request::createFromGlobals(); -$kernel = TestKernel::createFromRequest($request, $autoloader, 'testing', TRUE); -$response = $kernel - ->handle($request) - // Handle the response object. - ->prepare($request)->send(); +$response = $kernel->handle($request); +$response->send(); + $kernel->terminate($request, $response); diff --git a/index.php b/index.php index 654163fd549..750dc282dc2 100644 --- a/index.php +++ b/index.php @@ -9,36 +9,14 @@ */ use Drupal\Core\DrupalKernel; -use Drupal\Core\Site\Settings; -use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface; use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpFoundation\Response; $autoloader = require_once 'autoload.php'; -try { - $request = Request::createFromGlobals(); - $kernel = DrupalKernel::createFromRequest($request, $autoloader, 'prod'); - $response = $kernel - ->handle($request) - // Handle the response object. - ->prepare($request)->send(); - $kernel->terminate($request, $response); -} -catch (HttpExceptionInterface $e) { - $response = new Response($e->getMessage(), $e->getStatusCode()); - $response->prepare($request)->send(); -} -catch (Exception $e) { - $message = 'If you have just changed code (for example deployed a new module or moved an existing one) read https://www.drupal.org/documentation/rebuild'; - if (Settings::get('rebuild_access', FALSE)) { - $rebuild_path = $GLOBALS['base_url'] . '/rebuild.php'; - $message .= " or run the rebuild script"; - } +$kernel = new DrupalKernel('prod', $autoloader); - // Set the response code manually. Otherwise, this response will default to a - // 200. - http_response_code(500); - print $message; - throw $e; -} +$request = Request::createFromGlobals(); +$response = $kernel->handle($request); +$response->send(); + +$kernel->terminate($request, $response);