From 90d6fb15edf7f3df88cb38de22db25d8f36dd1b3 Mon Sep 17 00:00:00 2001
From: Alex Pott <alex.a.pott@googlemail.com>
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 c8ba628ac6cb..815f7f3387a1 100644
--- a/core/lib/Drupal/Core/DrupalKernel.php
+++ b/core/lib/Drupal/Core/DrupalKernel.php
@@ -35,6 +35,7 @@
 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 @@ public static function findSitePath(Request $request, $require_settings = TRUE)
    * {@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 @@ public function terminate(Request $request, Response $response) {
    * {@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 <a href="https://www.drupal.org/documentation/rebuild">https://www.drupal.org/documentation/rebuild</a>');
+        if ($message && Settings::get('rebuild_access', FALSE)) {
+          $rebuild_path = $GLOBALS['base_url'] . '/rebuild.php';
+          $message .= " or run the <a href=\"$rebuild_path\">rebuild script</a>";
+        }
+        print $message;
+      }
+      return new Response(ob_get_clean(), 500);
+    }
   }
 
   /**
@@ -796,6 +825,10 @@ public static function bootEnvironment() {
       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 @@ public static function bootEnvironment() {
     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 ab17b77ff09d..c7eb93f9d342 100644
--- a/core/lib/Drupal/Core/DrupalKernelInterface.php
+++ b/core/lib/Drupal/Core/DrupalKernelInterface.php
@@ -60,8 +60,11 @@ public function getContainer();
   /**
    * 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 bff37e6e5dc9..55294caff8c9 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 fa9fd51f20e9..f19afb3bf924 100644
--- a/core/lib/Drupal/Core/Test/TestRunnerKernel.php
+++ b/core/lib/Drupal/Core/Test/TestRunnerKernel.php
@@ -42,8 +42,8 @@ public function __construct($environment, $class_loader) {
       '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 ab6c224952de..84178a1f23be 100644
--- a/core/modules/system/src/Tests/DrupalKernel/DrupalKernelTest.php
+++ b/core/modules/system/src/Tests/DrupalKernel/DrupalKernelTest.php
@@ -166,7 +166,7 @@ public function testCompileDIC() {
   }
 
   /**
-   * 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 @@ public function testRepeatedBootWithDifferentEnvironment() {
     $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 01856361eee6..6d822ec88f08 100644
--- a/core/modules/system/tests/http.php
+++ b/core/modules/system/tests/http.php
@@ -20,10 +20,10 @@
   $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 95ebed862ecc..827dd7a197f3 100644
--- a/core/modules/system/tests/https.php
+++ b/core/modules/system/tests/https.php
@@ -22,10 +22,10 @@
   $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 654163fd549e..750dc282dc20 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 <a href="https://www.drupal.org/documentation/rebuild">https://www.drupal.org/documentation/rebuild</a>';
-  if (Settings::get('rebuild_access', FALSE)) {
-    $rebuild_path = $GLOBALS['base_url'] . '/rebuild.php';
-    $message .= " or run the <a href=\"$rebuild_path\">rebuild script</a>";
-  }
+$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);
-- 
GitLab