diff --git a/.htaccess b/.htaccess
index a69bdd4..725897e 100644
--- a/.htaccess
+++ b/.htaccess
@@ -109,7 +109,7 @@ DirectoryIndex index.php index.html index.htm
   RewriteCond %{REQUEST_FILENAME} !-f
   RewriteCond %{REQUEST_FILENAME} !-d
   RewriteCond %{REQUEST_URI} !=/favicon.ico
-  RewriteRule ^ index.php [L]
+  RewriteRule ^(.*)$ index.php [L]
 
   # Rules to correctly serve gzip compressed CSS and JS files.
   # Requires both mod_rewrite and mod_headers to be enabled.
diff --git a/core/includes/common.inc b/core/includes/common.inc
index a18e1e4..e588ff8 100644
--- a/core/includes/common.inc
+++ b/core/includes/common.inc
@@ -1,5 +1,7 @@
 <?php
 
+use Symfony\Component\HttpFoundation\File\Exception\AccessDeniedException;
+use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
 use Drupal\Core\Database\Database;
 
 /**
@@ -711,7 +713,10 @@ function drupal_site_offline() {
  * bubble up to menu_execute_active_handler() should call drupal_not_found().
  */
 function drupal_not_found() {
-  drupal_deliver_page(MENU_NOT_FOUND);
+
+  throw new NotFoundHttpException();
+
+  //drupal_deliver_page(MENU_NOT_FOUND);
 }
 
 /**
@@ -724,7 +729,10 @@ function drupal_not_found() {
  * drupal_access_denied().
  */
 function drupal_access_denied() {
-  drupal_deliver_page(MENU_ACCESS_DENIED);
+
+  throw new AccessDeniedException();
+
+  //drupal_deliver_page(MENU_ACCESS_DENIED);
 }
 
 /**
@@ -2200,31 +2208,20 @@ function url($path = NULL, array $options = array()) {
   $base = $options['absolute'] ? $options['base_url'] . '/' : base_path();
   $prefix = empty($path) ? rtrim($options['prefix'], '/') : $options['prefix'];
 
-  // With Clean URLs.
-  if (!empty($GLOBALS['conf']['clean_url'])) {
-    $path = drupal_encode_path($prefix . $path);
-    if ($options['query']) {
-      return $base . $path . '?' . drupal_http_build_query($options['query']) . $options['fragment'];
-    }
-    else {
-      return $base . $path . $options['fragment'];
-    }
+  // If Clean URLs are not enabled, we need to prefix the script name onto
+  // the link.
+  // @todo: Make this dynamic based on the request object without using a global
+  // request object.
+  if (empty($GLOBALS['conf']['clean_url'])) {
+    $base .= 'index.php/';
+  }
+
+  $path = drupal_encode_path($prefix . $path);
+  if ($options['query']) {
+    return $base . $path . '?' . drupal_http_build_query($options['query']) . $options['fragment'];
   }
-  // Without Clean URLs.
   else {
-    $path = $prefix . $path;
-    $query = array();
-    if (!empty($path)) {
-      $query['q'] = $path;
-    }
-    if ($options['query']) {
-      // We do not use array_merge() here to prevent overriding $path via query
-      // parameters.
-      $query += $options['query'];
-    }
-    $query = $query ? ('?' . drupal_http_build_query($query)) : '';
-    $script = isset($options['script']) ? $options['script'] : '';
-    return $base . $script . $query . $options['fragment'];
+    return $base . $path . $options['fragment'];
   }
 }
 
diff --git a/core/includes/menu.inc b/core/includes/menu.inc
index 511c5d0..6dc75ff 100644
--- a/core/includes/menu.inc
+++ b/core/includes/menu.inc
@@ -462,11 +462,9 @@ function menu_get_item($path = NULL, $router_item = NULL) {
         $router_items[$path] = FALSE;
         return FALSE;
       }
-      if ($router_item['access']) {
-        $router_item['map'] = $map;
-        $router_item['page_arguments'] = array_merge(menu_unserialize($router_item['page_arguments'], $map), array_slice($map, $router_item['number_parts']));
-        $router_item['theme_arguments'] = array_merge(menu_unserialize($router_item['theme_arguments'], $map), array_slice($map, $router_item['number_parts']));
-      }
+      $router_item['map'] = $map;
+      $router_item['page_arguments'] = array_merge(menu_unserialize($router_item['page_arguments'], $map), array_slice($map, $router_item['number_parts']));
+      $router_item['theme_arguments'] = array_merge(menu_unserialize($router_item['theme_arguments'], $map), array_slice($map, $router_item['number_parts']));
     }
     $router_items[$path] = $router_item;
   }
diff --git a/core/lib/Drupal/Core/DrupalKernel.php b/core/lib/Drupal/Core/DrupalKernel.php
new file mode 100644
index 0000000..32ba880
--- /dev/null
+++ b/core/lib/Drupal/Core/DrupalKernel.php
@@ -0,0 +1,110 @@
+<?php
+
+namespace Drupal\Core;
+
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\Routing\RequestContext;
+use Symfony\Component\HttpKernel\HttpKernelInterface;
+use Symfony\Component\HttpKernel\HttpKernel;
+use Symfony\Component\HttpKernel\KernelEvents;
+use Symfony\Component\HttpKernel\Controller\ControllerResolver;
+use Symfony\Component\EventDispatcher\EventDispatcher;
+use Symfony\Component\EventDispatcher\Event;
+use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
+use Symfony\Component\HttpKernel\EventListener\RouterListener;
+
+use Drupal\Core\EventSubscriber\HtmlSubscriber;
+use Drupal\Core\EventSubscriber\JsonSubscriber;
+use Drupal\Core\EventSubscriber\AccessSubscriber;
+use Drupal\Core\EventSubscriber\PathSubscriber;
+use Drupal\Core\EventSubscriber\LegacyControllerSubscriber;
+
+use Exception;
+
+/**
+ * @file
+ *
+ * Definition of Drupal\Core\DrupalKernel.
+ */
+
+/**
+ * The DrupalApp class is the core of Drupal itself.
+ */
+class DrupalKernel implements HttpKernelInterface {
+
+  /**
+   *
+   * @param Request $request
+   *   The request to process.
+   * @return Response
+   *   The response object to return to the requesting user agent.
+   */
+  function handle(Request $request, $type = self::MASTER_REQUEST, $catch = true) {
+    try {
+
+      $dispatcher = $this->getDispatcher();
+
+      $matcher = $this->getMatcher($request);
+      $dispatcher->addSubscriber(new RouterListener($matcher));
+      $dispatcher->addSubscriber(new AccessSubscriber());
+      $dispatcher->addSubscriber(new PathSubscriber());
+      $dispatcher->addSubscriber(new LegacyControllerSubscriber());
+
+      $resolver = new ControllerResolver();
+
+      $kernel = new HttpKernel($dispatcher, $resolver);
+      $response = $kernel->handle($request);
+    }
+    catch (Exception $e) {
+      // Some other form of error occured that wasn't handled by another kernel
+      // listener.  That could mean that it's a method/mime-type/error
+      // combination that is not accounted for, or some other type of error.
+      // Either way, treat it as a server-level error and return an HTTP 500.
+      // By default, this will be an HTML-type response because that's a decent
+      // best guess if we don't know otherwise.
+      $response = new Response('A fatal error occurred: ' . $e->getMessage(), 500);
+    }
+
+    return $response;
+  }
+
+  /**
+   * Returns an EventDispatcher for the Kernel to use.
+   *
+   * The EventDispatcher is pre-wired with some event listeners/subscribers.
+   *
+   * @todo Make the listeners that get attached extensible, but without using
+   * hooks.
+   *
+   * @return EventDispatcher
+   */
+  protected function getDispatcher() {
+    $dispatcher = new EventDispatcher();
+
+    // @todo Make this extensible rather than just hard coding some.
+    // @todo Add a subscriber to handle other things, too, like our Ajax
+    // replacement system.
+    $dispatcher->addSubscriber(new HtmlSubscriber());
+    $dispatcher->addSubscriber(new JsonSubscriber());
+
+    return $dispatcher;
+  }
+
+  /**
+   * Returns a UrlMatcher object for the specified request.
+   *
+   * @param Request $request
+   *   The request object for this matcher to use.
+   * @return UrlMatcher
+   */
+  protected function getMatcher(Request $request) {
+    // Resolve a routing context(path, etc) using the routes object to a
+    // Set a routing context to translate.
+    $context = new RequestContext();
+    $context->fromRequest($request);
+    $matcher = new UrlMatcher($context);
+
+    return $matcher;
+  }
+}
diff --git a/core/lib/Drupal/Core/EventSubscriber/AccessSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/AccessSubscriber.php
new file mode 100644
index 0000000..77f827d
--- /dev/null
+++ b/core/lib/Drupal/Core/EventSubscriber/AccessSubscriber.php
@@ -0,0 +1,54 @@
+<?php
+
+namespace Drupal\Core\EventSubscriber;
+
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpKernel\KernelEvents;
+use Symfony\Component\HttpKernel\Event\GetResponseEvent;
+use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
+use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
+use Symfony\Component\Routing\Exception\ResourceNotFoundException;
+use Symfony\Component\Routing\Exception\MethodNotAllowedException;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+
+/**
+ * @file
+ *
+ * Definition of Drupal\Core\EventSubscriber\AccessSubscriber
+ */
+
+/**
+ * Access subscriber for controller requests.
+ */
+class AccessSubscriber implements EventSubscriberInterface {
+
+  /**
+   * Verifys that the current user can access the requested path.
+   *
+   * @todo This is a total hack to keep our current access system working. It
+   * should be replaced with something robust and injected at some point.
+   *
+   * @param GetResponseEvent $event
+   *   The Event to process.
+   */
+  public function onKernelRequestAccessCheck(GetResponseEvent $event) {
+
+    $router_item = $event->getRequest()->attributes->get('drupal_menu_item');
+
+    if (!$router_item['access']) {
+      throw new AccessDeniedHttpException();
+    }
+  }
+
+  /**
+   * Registers the methods in this class that should be listeners.
+   *
+   * @return array
+   *   An array of event listener definitions.
+   */
+  static function getSubscribedEvents() {
+    $events[KernelEvents::REQUEST][] = array('onKernelRequestAccessCheck', 30);
+
+    return $events;
+  }
+}
diff --git a/core/lib/Drupal/Core/EventSubscriber/HtmlSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/HtmlSubscriber.php
new file mode 100644
index 0000000..060888d
--- /dev/null
+++ b/core/lib/Drupal/Core/EventSubscriber/HtmlSubscriber.php
@@ -0,0 +1,185 @@
+<?php
+
+namespace Drupal\Core\EventSubscriber;
+
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpKernel\KernelEvents;
+use Symfony\Component\HttpKernel\Event\GetResponseEvent;
+use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
+use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
+use Symfony\Component\Routing\Exception\ResourceNotFoundException;
+use Symfony\Component\Routing\Exception\MethodNotAllowedException;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+
+use Drupal\Core\DrupalKernel;
+
+/**
+ * @file
+ *
+ * Definition of Drupal\Core\EventSubscriber\HtmlSubscriber;
+ */
+
+/**
+ * Main subscriber for HTML-type HTTP responses.
+ */
+class HtmlSubscriber implements EventSubscriberInterface {
+
+  /**
+   * Determines if we are dealing with an HTML-style response.
+   *
+   * @param GetResponseEvent $event
+   *   The Event to process.
+   * @return boolean
+   *   True if it is an event we should process as HTML, False otherwise.
+   */
+  protected function isHtmlRequestEvent(GetResponseEvent $event) {
+    $acceptable_content_types = $event->getRequest()->getAcceptableContentTypes();
+    return in_array('text/html', $acceptable_content_types) || in_array('*/*', $acceptable_content_types);
+  }
+
+  /**
+   * Processes an AccessDenied exception into an HTTP 403 response.
+   *
+   * @param GetResponseEvent $event
+   *   The Event to process.
+   */
+  public function onAccessDeniedException(GetResponseEvent $event) {
+    if ($this->isHtmlRequestEvent($event) && $event->getException() instanceof AccessDeniedHttpException) {
+      $system_path = $event->getRequest()->attributes->get('system_path');
+
+      watchdog('access denied', $system_path, NULL, WATCHDOG_WARNING);
+
+      // Keep the old path for reference, and to allow forms to redirect to it.
+      if (!isset($_GET['destination'])) {
+        $_GET['destination'] = $system_path;
+      }
+
+      $path = drupal_get_normal_path(variable_get('site_403', ''));
+      if ($path && $path != $system_path) {
+        // Custom 403 page.
+        // @todo: Same hack as in onNotFoundHttpException. Find a better way to
+        // override URLs.
+        $request = Request::create('/' . $path);
+
+        $kernel = new DrupalKernel();
+        $response = $kernel->handle($request, DrupalKernel::SUB_REQUEST);
+        $response->setStatusCode(403, 'Forbidden');
+      }
+      else {
+        drupal_set_title(t('Access denied'));
+        drupal_set_page_content(t('You are not authorized to access this page.'));
+        $page = element_info('page');
+        $content = drupal_render_page($page);
+
+        $response = new Response('Forbidden', 403);
+        $response->setContent($content);
+      }
+
+      $event->setResponse($response);
+    }
+  }
+
+  /**
+   * Processes a NotFound exception into an HTTP 404 response.
+   *
+   * @param GetResponseEvent $event
+   *   The Event to process.
+   */
+  public function onNotFoundHttpException(GetResponseEvent $event) {
+    if ($this->isHtmlRequestEvent($event) && $event->getException() instanceof NotFoundHttpException) {
+      $system_path = $event->getRequest()->attributes->get('system_path');
+
+      watchdog('page not found', $system_path, NULL, WATCHDOG_WARNING);
+
+      // Check for and return a fast 404 page if configured.
+      // @todo Inline this rather than using a function. If drupal_fast_404 is
+      // mainly for image styles maybe we should have a handler for that.
+      drupal_fast_404();
+
+
+      // Keep old path for reference, and to allow forms to redirect to it.
+      if (!isset($_GET['destination'])) {
+        $_GET['destination'] = $system_path;
+      }
+
+      $path = drupal_get_normal_path(variable_get('site_404', ''));
+      if ($path && $path != $system_path) {
+        // @TODO: Um, how do I specify an override URL again? Totally not clear.
+        // Do that and sub-call the kernel rather than using meah().
+        // Also the need to prefix / seams especially hacky for now.
+        $request = Request::create('/' . $path);
+
+        $kernel = new DrupalKernel();
+        $response = $kernel->handle($request, DrupalKernel::SUB_REQUEST);
+        $response->setStatusCode(404, 'Not Found');
+      }
+      else {
+        $response = new Response('Not Found', 404);
+
+        // @todo Replace this block with something cleaner.
+        $return = t('The requested page "@path" could not be found.', array('@path' => $event->getRequest()->getPathInfo()));
+        drupal_set_title(t('Page not found'));
+        drupal_set_page_content($return);
+        $page = element_info('page');
+        $content = drupal_render_page($page);
+
+        $response->setContent($content);
+      }
+
+      $event->setResponse($response);
+    }
+  }
+
+  /**
+   * Processes a MethodNotAllowed exception into an HTTP 405 response.
+   *
+   * @param GetResponseEvent $event
+   *   The Event to process.
+   */
+  public function onMethodAllowedException(GetResponseEvent $event) {
+    if ($this->isHtmlRequestEvent($event) && $event->getException() instanceof MethodNotAllowedException) {
+      $event->setResponse(new Response('Method Not Allowed', 405));
+    }
+  }
+
+  /**
+   * Processes a successful controller into an HTTP 200 response.
+   *
+   * Some controllers may not return a response object but simply the body of
+   * one.  The VIEW event is called in that case, to allow us to mutate that
+   * body into a Response object.  In particular we assume that the return
+   * from an HTML-type response is a render array from a legacy page callback
+   * and render it.
+   *
+   * @param GetResponseEvent $event
+   *   The Event to process.
+   */
+  public function onView(GetResponseEvent $event) {
+    if ($this->isHtmlRequestEvent($event)) {
+      $page_callback_result = $event->getControllerResult();
+      $event->setResponse(new Response(drupal_render_page($page_callback_result)));
+    }
+    else {
+      $event->setResponse(new Response('Unsupported Media Type', 415));
+    }
+  }
+
+  /**
+   * Registers the methods in this class that should be listeners.
+   *
+   * @return array
+   *   An array of event listener definitions.
+   */
+  static function getSubscribedEvents() {
+    // Since we want HTML to be our default, catch-all response type, give its
+    // listeners a very low priority so that they always check last.
+    $events[KernelEvents::EXCEPTION][] = array('onNotFoundHttpException', -5);
+    $events[KernelEvents::EXCEPTION][] = array('onAccessDeniedException', -5);
+    $events[KernelEvents::EXCEPTION][] = array('onMethodAllowedException', -5);
+
+    $events[KernelEvents::VIEW][] = array('onView', -5);
+
+    return $events;
+  }
+}
diff --git a/core/lib/Drupal/Core/EventSubscriber/JsonSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/JsonSubscriber.php
new file mode 100644
index 0000000..92ce1e8
--- /dev/null
+++ b/core/lib/Drupal/Core/EventSubscriber/JsonSubscriber.php
@@ -0,0 +1,124 @@
+<?php
+
+namespace Drupal\Core\EventSubscriber;
+
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpKernel\KernelEvents;
+use Symfony\Component\HttpKernel\Event\GetResponseEvent;
+use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
+use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
+use Symfony\Component\Routing\Exception\ResourceNotFoundException;
+use Symfony\Component\Routing\Exception\MethodNotAllowedException;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+
+/**
+ * @file
+ *
+ * Definition of Drupal\Core\EventSubscriber\HtmlSubscriber;
+ */
+
+/**
+ * Main subscriber for JSON-type HTTP responses.
+ */
+class JsonSubscriber implements EventSubscriberInterface {
+
+  /**
+   * Determines if we are dealing with an JSON-style response.
+   *
+   * @param GetResponseEvent $event
+   *   The Event to process.
+   * @return boolean
+   *   True if it is an event we should process as JSON, False otherwise.
+   */
+  protected function isJsonRequestEvent(GetResponseEvent $event) {
+    return in_array('application/json', $event->getRequest()->getAcceptableContentTypes());
+  }
+
+  protected function createJsonResponse() {
+    $response = new Response();
+    $response->headers->set('Content-Type', 'application/json; charset=utf-8');
+
+    return $response;
+  }
+
+  /**
+   * Processes an AccessDenied exception into an HTTP 403 response.
+   *
+   * @param GetResponseEvent $event
+   *   The Event to process.
+   */
+  public function onAccessDeniedException(GetResponseEvent $event) {
+    if ($this->isJsonRequestEvent($event) && $event->getException() instanceof AccessDeniedHttpException) {
+      $response = $this->createJsonResponse();
+      $response->setStatusCode(403, 'Access Denied');
+      $event->setResponse($response);
+    }
+  }
+
+  /**
+   * Processes a NotFound exception into an HTTP 404 response.
+   *
+   * @param GetResponseEvent $event
+   *   The Event to process.
+   */
+  public function onNotFoundHttpException(GetResponseEvent $event) {
+    if ($this->isJsonRequestEvent($event) && $event->getException() instanceof NotFoundHttpException) {
+      $response = $this->createJsonResponse();
+      $response->setStatusCode(404, 'Not Found');
+      $event->setResponse($response);
+    }
+  }
+
+  /**
+   * Processes a MethodNotAllowed exception into an HTTP 405 response.
+   *
+   * @param GetResponseEvent $event
+   *   The Event to process.
+   */
+  public function onMethodAllowedException(GetResponseEvent $event) {
+    if ($this->isJsonRequestEvent($event) && $event->getException() instanceof MethodNotAllowedException) {
+      $response = $this->createJsonResponse();
+      $response->setStatusCode(405, 'Method Not Allowed');
+      $event->setResponse($response);
+    }
+  }
+
+  /**
+   * Processes a successful controller into an HTTP 200 response.
+   *
+   * Some controllers may not return a response object but simply the body of
+   * one.  The VIEW event is called in that case, to allow us to mutate that
+   * body into a Response object.  In particular we assume that the return
+   * from an JSON-type response is a JSON string, so just wrap it into a
+   * Response object.
+   *
+   * @param GetResponseEvent $event
+   *   The Event to process.
+   */
+  public function onView(GetResponseEvent $event) {
+    if ($this->isJsonRequestEvent($event)) {
+      $page_callback_result = $event->getControllerResult();
+
+      $response = $this->createJsonResponse();
+      $response->setContent($page_callback_result);
+
+      $event->setResponse($response);
+    }
+  }
+
+  /**
+   * Registers the methods in this class that should be listeners.
+   *
+   * @return array
+   *   An array of event listener definitions.
+   */
+  static function getSubscribedEvents() {
+    $events[KernelEvents::EXCEPTION][] = array('onNotFoundHttpException');
+    $events[KernelEvents::EXCEPTION][] = array('onAccessDeniedException');
+    $events[KernelEvents::EXCEPTION][] = array('onMethodAllowedException');
+
+    $events[KernelEvents::VIEW][] = array('onView');
+
+    return $events;
+  }
+}
diff --git a/core/lib/Drupal/Core/EventSubscriber/LegacyControllerSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/LegacyControllerSubscriber.php
new file mode 100644
index 0000000..e85d89f
--- /dev/null
+++ b/core/lib/Drupal/Core/EventSubscriber/LegacyControllerSubscriber.php
@@ -0,0 +1,64 @@
+<?php
+
+namespace Drupal\Core\EventSubscriber;
+
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpKernel\KernelEvents;
+use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+
+/**
+ * @file
+ *
+ * Definition of Drupal\Core\EventSubscriber\LegacyControllerSubscriber
+ */
+
+/**
+ * Access subscriber for controller requests.
+ */
+class LegacyControllerSubscriber implements EventSubscriberInterface {
+
+  /**
+   * Wraps legacy controllers in a closure to handle old-style arguments.
+   *
+   * This is a backward compatibility layer only.  This is a rather ugly way
+   * to piggyback Drupal's existing menu router items onto the Symfony model,
+   * but it works for now.  If we did not do this, any menu router item with
+   * a variable number of arguments would fail to work.  This bypasses Symfony's
+   * controller argument handling entirely and lets the old-style approach work.
+   *
+   * @todo Convert Drupal to use the IETF-draft-RFC style {placeholders}. That
+   * will allow us to use the native Symfony conversion, including out-of-order
+   * argument mapping, name-based mapping, and with another listener
+   * auto-conversion of parameters to full objects.  That may necessitate not
+   * using func_get_args()-based controllers.  That is likely for the best,
+   * as those are quite hard to document anyway.
+   *
+   * @param GetResponseEvent $event
+   *   The Event to process.
+   */
+  public function onKernelControllerLegacy(FilterControllerEvent $event) {
+    $router_item = $event->getRequest()->attributes->get('drupal_menu_item');
+    $controller = $event->getController();
+
+    // This BC logic applies only to functions.  Otherwise, skip it.
+    if (function_exists($controller)) {
+      $new_controller = function() use ($router_item) {
+        return call_user_func_array($router_item['page_callback'], $router_item['page_arguments']);
+      };
+      $event->setController($new_controller);
+    }
+  }
+
+  /**
+   * Registers the methods in this class that should be listeners.
+   *
+   * @return array
+   *   An array of event listener definitions.
+   */
+  static function getSubscribedEvents() {
+    $events[KernelEvents::CONTROLLER][] = array('onKernelControllerLegacy', 30);
+
+    return $events;
+  }
+}
diff --git a/core/lib/Drupal/Core/EventSubscriber/PathSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/PathSubscriber.php
new file mode 100644
index 0000000..dc5059b
--- /dev/null
+++ b/core/lib/Drupal/Core/EventSubscriber/PathSubscriber.php
@@ -0,0 +1,68 @@
+<?php
+
+namespace Drupal\Core\EventSubscriber;
+
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpKernel\KernelEvents;
+use Symfony\Component\HttpKernel\Event\GetResponseEvent;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+
+/**
+ * @file
+ *
+ * Definition of Drupal\Core\EventSubscriber\AccessSubscriber
+ */
+
+/**
+ * Access subscriber for controller requests.
+ */
+class PathSubscriber implements EventSubscriberInterface {
+
+  /**
+   * Resolve the system path.
+   *
+   * @todo The path system should be objectified to remove the function calls
+   * in this method.
+   *
+   * @todo We're writing back to $_GET['q'] for temporary BC. All instances of
+   * $_GET['q'] should be removed and then this code eliminated.
+   *
+   * @param GetResponseEvent $event
+   *   The Event to process.
+   */
+  public function onKernelRequestPathResolve(GetResponseEvent $event) {
+
+    $request = $event->getRequest();
+
+    $path = ltrim($request->getPathInfo(), '/');
+
+    if (empty($path)) {
+      // @todo Temporary hack. Fix when configuration is injectable.
+      $path = variable_get('site_frontpage', 'user');
+    }
+    $system_path = drupal_get_normal_path($path);
+
+    $request->attributes->set('system_path', $system_path);
+
+    // @todo Remove this line.
+    // Drupal uses $_GET['q'] directly in over 100 places at present,
+    // including writing back to it at times. Those are all critical bugs,
+    // even by Drupal 7 standards, but as many of the places that it does so
+    // are slated to be rewritten anyway we will save time and include this
+    // temporary hack. Removal of this line is a critical, Drupal-release
+    // blocking bug.
+    $_GET['q'] = $system_path;
+  }
+
+  /**
+   * Registers the methods in this class that should be listeners.
+   *
+   * @return array
+   *   An array of event listener definitions.
+   */
+  static function getSubscribedEvents() {
+    $events[KernelEvents::REQUEST][] = array('onKernelRequestPathResolve', 100);
+
+    return $events;
+  }
+}
diff --git a/core/lib/Drupal/Core/UrlMatcher.php b/core/lib/Drupal/Core/UrlMatcher.php
new file mode 100644
index 0000000..366405a
--- /dev/null
+++ b/core/lib/Drupal/Core/UrlMatcher.php
@@ -0,0 +1,99 @@
+<?php
+
+namespace Drupal\Core;
+
+use Symfony\Component\Routing\Exception\MethodNotAllowedException;
+use Symfony\Component\Routing\Exception\ResourceNotFoundException;
+use Symfony\Component\Routing\Matcher\UrlMatcher as SymfonyUrlMatcher;
+use Symfony\Component\Routing\RequestContext;
+use Symfony\Component\Routing\Route;
+use Symfony\Component\Routing\RouteCollection;
+
+/**
+ * UrlMatcher matches URL based on a set of routes.
+ */
+class UrlMatcher extends SymfonyUrlMatcher {
+
+  protected $context;
+
+  /**
+   * Constructor.
+   *
+   * @param RequestContext  $context
+   *   The request context object.
+   */
+  public function __construct(RequestContext $context) {
+    $this->context = $context;
+  }
+
+  /**
+   * {@inheritDoc}
+   *
+   * @api
+   */
+  public function match($pathinfo) {
+
+    $this->allow = array();
+
+    // Symfony uses a prefixing / but we don't yet.
+    $dpathinfo = ltrim($pathinfo, '/');
+
+    // Do our fancy frontpage logic.
+    if (empty($dpathinfo)) {
+      $dpathinfo = variable_get('site_frontpage', 'user');
+      $pathinfo = '/' . $dpathinfo;
+    }
+
+    if ($router_item = $this->matchDrupalItem($dpathinfo)) {
+
+      $routes = new RouteCollection();
+      $routes->add(hash('sha256', $router_item['path']), $this->convertDrupalItem($router_item));
+
+      if ($ret = $this->matchCollection($pathinfo, $routes)) {
+        //drupal_set_message('<pre>' . var_export('test', TRUE) . '</pre>');
+        // Stash the router item in the attributes while we're transitioning.
+        $ret['drupal_menu_item'] = $router_item;
+
+        // Most legacy controllers (aka page callbacks) are in a separate file,
+        // so we have to include that.
+        if ($router_item['include_file']) {
+          require_once DRUPAL_ROOT . '/' . $router_item['include_file'];
+        }
+
+        return $ret;
+      }
+    }
+
+    throw 0 < count($this->allow)
+      ? new MethodNotAllowedException(array_unique(array_map('strtoupper', $this->allow)))
+      : new ResourceNotFoundException();
+  }
+
+  /**
+   * Get a drupal menu item.
+   *
+   * @todo Make this return multiple possible candidates for the resolver to
+   * consider.
+   *
+   * @param string $path
+   *   The path being looked up by
+   */
+  protected function matchDrupalItem($path) {
+    // For now we can just proxy our procedural method. At some point this will
+    // become more complicated because we'll need to get back candidates for a
+    // path and them resolve them based on things like method and scheme which
+    // we currently can't do.
+    return menu_get_item($path);
+  }
+
+  protected function convertDrupalItem($router_item) {
+    $route = array(
+      '_controller' => $router_item['page_callback']
+    );
+    // Place argument defaults on the route.
+    foreach ($router_item['page_arguments'] as $k => $v) {
+      $route[$k] = $v;
+    }
+    return new Route($router_item['href'], $route);
+  }
+}
diff --git a/index.php b/index.php
index b91fb1e..7609b3e 100644
--- a/index.php
+++ b/index.php
@@ -1,5 +1,8 @@
 <?php
 
+use Drupal\Core\DrupalKernel;
+use Symfony\Component\HttpFoundation\Request;
+
 /**
  * @file
  * The PHP page that serves all page requests on a Drupal installation.
@@ -16,6 +19,12 @@
  */
 define('DRUPAL_ROOT', getcwd());
 
+// Bootstrap the lowest level of what we need.
 require_once DRUPAL_ROOT . '/core/includes/bootstrap.inc';
 drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);
-menu_execute_active_handler();
+
+// A request object from the HTTPFoundation to tell us about the request.
+$request = Request::createFromGlobals();
+
+$kernel = new DrupalKernel();
+$kernel->handle($request)->send();
