diff --git a/core/includes/file.inc b/core/includes/file.inc
index 58644ff..c26615a 100644
--- a/core/includes/file.inc
+++ b/core/includes/file.inc
@@ -7,9 +7,6 @@
 
 use Drupal\Core\StreamWrapper\LocalStream;
 use Drupal\Component\PhpStorage\MTimeProtectedFastFileStorage;
-use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
-use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
-use Symfony\Component\HttpFoundation\BinaryFileResponse;
 
 /**
  * Stream wrapper bit flags that are the basis for composite types.
@@ -1332,42 +1329,6 @@ function file_unmanaged_save_data($data, $destination = NULL, $replace = FILE_EX
 }
 
 /**
- * Page callback: Handles private file transfers.
- *
- * Call modules that implement hook_file_download() to find out if a file is
- * accessible and what headers it should be transferred with. If one or more
- * modules returned headers the download will start with the returned headers.
- * If a module returns -1 an AccessDeniedHttpException will be thrown.
- * If the file exists but no modules responded an AccessDeniedHttpException will
- * be thrown.If the file does not exist a NotFoundHttpException will be thrown.
- *
- * @see hook_file_download()
- * @see system_menu()
- */
-function file_download() {
-  // Merge remaining path arguments into relative file path.
-  $args = func_get_args();
-  $scheme = array_shift($args);
-  $target = implode('/', $args);
-  $uri = $scheme . '://' . $target;
-  if (file_stream_wrapper_valid_scheme($scheme) && file_exists($uri)) {
-    // Let other modules provide headers and controls access to the file.
-    $headers = module_invoke_all('file_download', $uri);
-    foreach ($headers as $result) {
-      if ($result == -1) {
-        throw new AccessDeniedHttpException();
-      }
-    }
-    if (count($headers)) {
-      return new BinaryFileResponse($uri, 200, $headers);
-    }
-    throw new AccessDeniedHttpException();
-  }
-  throw new NotFoundHttpException();
-}
-
-
-/**
  * Finds all files that match a given mask in a given directory.
  *
  * Directories and files beginning with a period are excluded; this
diff --git a/core/modules/config/lib/Drupal/config/Controller/ConfigController.php b/core/modules/config/lib/Drupal/config/Controller/ConfigController.php
index 34366fc..a84931c 100644
--- a/core/modules/config/lib/Drupal/config/Controller/ConfigController.php
+++ b/core/modules/config/lib/Drupal/config/Controller/ConfigController.php
@@ -10,9 +10,10 @@
 use Drupal\Core\Controller\ControllerInterface;
 use Drupal\Core\Config\StorageInterface;
 use Drupal\Component\Archiver\ArchiveTar;
-use Drupal\Core\Ajax\AjaxResponse;
-use Drupal\Core\Ajax\OpenModalDialogCommand;
+use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\system\FileDownloadController;
 use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\HttpFoundation\Request;
 
 /**
  * Returns responses for config module routes.
@@ -34,10 +35,21 @@ class ConfigController implements ControllerInterface {
   protected $sourceStorage;
 
   /**
+   * The module handler.
+   *
+   * @var \Drupal\Core\Extension\ModuleHandlerInterface
+   */
+  protected $moduleHandler;
+
+  /**
    * {@inheritdoc}
    */
   public static function create(ContainerInterface $container) {
-    return new static($container->get('config.storage'), $container->get('config.storage.staging'));
+    return new static(
+      $container->get('config.storage'),
+      $container->get('config.storage.staging'),
+      $container->get('module_handler')
+    );
   }
 
   /**
@@ -47,10 +59,13 @@ public static function create(ContainerInterface $container) {
    *   The target storage.
    * @param \Drupal\Core\Config\StorageInterface $source_storage
    *   The source storage
+   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
+   *   The module handler.
    */
-  public function __construct(StorageInterface $target_storage, StorageInterface $source_storage) {
+  public function __construct(StorageInterface $target_storage, StorageInterface $source_storage, ModuleHandlerInterface $module_handler) {
     $this->targetStorage = $target_storage;
     $this->sourceStorage = $source_storage;
+    $this->moduleHandler = $module_handler;
   }
 
   /**
@@ -64,7 +79,10 @@ public function downloadExport() {
       $config_files[] = $config_dir . '/' . $config_name . '.yml';
     }
     $archiver->createModify($config_files, '', config_get_config_directory());
-    return file_download('temporary', 'config.tar.gz');
+
+    $file_controller = new FileDownloadController($this->moduleHandler);
+    $request = new Request(array('file' => 'config.tar.gz'));
+    return $file_controller->fileDownload($request, 'temporary');
   }
 
   /**
diff --git a/core/modules/file/file.module b/core/modules/file/file.module
index bcdb460..e542769 100644
--- a/core/modules/file/file.module
+++ b/core/modules/file/file.module
@@ -1578,7 +1578,7 @@ function file_get_file_references(File $file, $field = NULL, $age = FIELD_LOAD_R
       // for every revision or the entity does not support revisions then
       // every usage is already a match.
       $match_entity_type = $age == FIELD_LOAD_REVISION || !isset($entity_info['entity_keys']['revision']);
-      $entities = entity_load_multiple($entity_type, $entity_ids);
+      $entities = entity_load_multiple($entity_type, array_keys($entity_ids));
       foreach ($entities as $entity) {
         $bundle = $entity->bundle();
         // We need to find file fields for this entity type and bundle.
diff --git a/core/modules/image/image.module b/core/modules/image/image.module
index 9a3dd64..f57b1a8 100644
--- a/core/modules/image/image.module
+++ b/core/modules/image/image.module
@@ -99,28 +99,6 @@ function image_style_entity_uri(ImageStyle $style) {
 function image_menu() {
   $items = array();
 
-  // Generate image derivatives of publicly available files.
-  // If clean URLs are disabled, image derivatives will always be served
-  // through the menu system.
-  // If clean URLs are enabled and the image derivative already exists,
-  // PHP will be bypassed.
-  $directory_path = file_stream_wrapper_get_instance_by_scheme('public')->getDirectoryPath();
-  $items[$directory_path . '/styles/%image_style'] = array(
-    'title' => 'Generate image style',
-    'page callback' => 'image_style_deliver',
-    'page arguments' => array(count(explode('/', $directory_path)) + 1),
-    'access callback' => TRUE,
-    'type' => MENU_CALLBACK,
-  );
-  // Generate and deliver image derivatives of private files.
-  // These image derivatives are always delivered through the menu system.
-  $items['system/files/styles/%image_style'] = array(
-    'title' => 'Generate image style',
-    'page callback' => 'image_style_deliver',
-    'page arguments' => array(3),
-    'access callback' => TRUE,
-    'type' => MENU_CALLBACK,
-  );
   $items['admin/config/media/image-styles'] = array(
     'title' => 'Image styles',
     'description' => 'Configure styles that can be used for resizing or adjusting images on display.',
@@ -399,98 +377,6 @@ function image_style_options($include_empty = TRUE) {
 }
 
 /**
- * Page callback: Generates a derivative, given a style and image path.
- *
- * After generating an image, transfer it to the requesting agent.
- *
- * @param $style
- *   The image style
- */
-function image_style_deliver($style, $scheme) {
-  $args = func_get_args();
-  array_shift($args);
-  array_shift($args);
-  $target = implode('/', $args);
-
-  // Check that the style is defined, the scheme is valid, and the image
-  // derivative token is valid. (Sites which require image derivatives to be
-  // generated without a token can set the
-  // 'image.settings:allow_insecure_derivatives' configuration to TRUE to bypass
-  // the latter check, but this will increase the site's vulnerability to
-  // denial-of-service attacks.)
-  $valid = !empty($style) && file_stream_wrapper_valid_scheme($scheme);
-  if (!config('image.settings')->get('allow_insecure_derivatives')) {
-    $image_derivative_token = Drupal::request()->query->get(IMAGE_DERIVATIVE_TOKEN);
-    $valid = $valid && isset($image_derivative_token) && $image_derivative_token === image_style_path_token($style->name, $scheme . '://' . $target);
-  }
-  if (!$valid) {
-    throw new AccessDeniedHttpException();
-  }
-
-  $image_uri = $scheme . '://' . $target;
-  $derivative_uri = image_style_path($style->id(), $image_uri);
-
-  // If using the private scheme, let other modules provide headers and
-  // control access to the file.
-  if ($scheme == 'private') {
-    if (file_exists($derivative_uri)) {
-      file_download($scheme, file_uri_target($derivative_uri));
-    }
-    else {
-      $headers = module_invoke_all('file_download', $image_uri);
-      if (in_array(-1, $headers) || empty($headers)) {
-        throw new AccessDeniedHttpException();
-      }
-      if (count($headers)) {
-        foreach ($headers as $name => $value) {
-          drupal_add_http_header($name, $value);
-        }
-      }
-    }
-  }
-
-  // Don't try to generate file if source is missing.
-  if (!file_exists($image_uri)) {
-    watchdog('image', 'Source image at %source_image_path not found while trying to generate derivative image at %derivative_path.',  array('%source_image_path' => $image_uri, '%derivative_path' => $derivative_uri));
-    return new Response(t('Error generating image, missing source file.'), 404);
-  }
-
-  // Don't start generating the image if the derivative already exists or if
-  // generation is in progress in another thread.
-  $lock_name = 'image_style_deliver:' . $style->id() . ':' . Crypt::hashBase64($image_uri);
-  if (!file_exists($derivative_uri)) {
-    $lock_acquired = lock()->acquire($lock_name);
-    if (!$lock_acquired) {
-      // Tell client to retry again in 3 seconds. Currently no browsers are known
-      // to support Retry-After.
-      throw new ServiceUnavailableHttpException(3, t('Image generation in progress. Try again shortly.'));
-    }
-  }
-
-  // Try to generate the image, unless another thread just did it while we were
-  // acquiring the lock.
-  $success = file_exists($derivative_uri) || image_style_create_derivative($style, $image_uri, $derivative_uri);
-
-  if (!empty($lock_acquired)) {
-    lock()->release($lock_name);
-  }
-
-  if ($success) {
-    $image = image_load($derivative_uri);
-    $uri = $image->source;
-    $headers = array(
-      'Content-Type' => $image->info['mime_type'],
-      'Content-Length' => $image->info['file_size'],
-    );
-    return new BinaryFileResponse($uri, 200, $headers);
-  }
-  else {
-    watchdog('image', 'Unable to generate the derived image located at %path.', array('%path' => $derivative_uri));
-    return new Response(t('Error generating image.'), 500);
-  }
-}
-
-/**
  * Creates a new image derivative based on an image style.
  *
  * Generates an image derivative by creating the destination folder (if it does
@@ -648,9 +534,10 @@ function image_style_url($style_name, $path, $clean_urls = NULL) {
   }
 
   $file_url = file_create_url($uri);
+
   // Append the query string with the token, if necessary.
   if ($token_query) {
-    $file_url .= (strpos($file_url, '?') !== FALSE ? '&' : '?') . drupal_http_build_query($token_query);
+    $file_url .= (strpos($file_url, '?') !== FALSE ? '&' : '?') . Drupal::urlGenerator()->httpBuildQuery($token_query);
   }
 
   return $file_url;
diff --git a/core/modules/image/image.routing.yml b/core/modules/image/image.routing.yml
index b178d33..46a79f1 100644
--- a/core/modules/image/image.routing.yml
+++ b/core/modules/image/image.routing.yml
@@ -11,3 +11,11 @@ image_effect_delete:
     _form: '\Drupal\image\Form\ImageEffectDeleteForm'
   requirements:
     _permission: 'administer image styles'
+
+image_style_private:
+  pattern: '/system/files/styles/{image_style}/{scheme}'
+  defaults:
+    _controller:  '\Drupal\image\Controller\ImageStyleDownloadController::deliver'
+  requirements:
+    _access: 'TRUE'
+
diff --git a/core/modules/image/image.services.yml b/core/modules/image/image.services.yml
new file mode 100644
index 0000000..2661593
--- /dev/null
+++ b/core/modules/image/image.services.yml
@@ -0,0 +1,9 @@
+services:
+  image.route_subscriber:
+    class: Drupal\image\EventSubscriber\RouteSubscriber
+    tags:
+      - { name: 'event_subscriber' }
+  path_processor.image_styles:
+    class: Drupal\image\PathProcessor\PathProcessorImageStyles
+    tags:
+      - { name: path_processor_inbound, priority: 300 }
diff --git a/core/modules/image/lib/Drupal/image/Controller/ImageStyleDownloadController.php b/core/modules/image/lib/Drupal/image/Controller/ImageStyleDownloadController.php
new file mode 100644
index 0000000..c95eeab
--- /dev/null
+++ b/core/modules/image/lib/Drupal/image/Controller/ImageStyleDownloadController.php
@@ -0,0 +1,181 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\image\Controller\ImageStyleDownloadController.
+ */
+
+namespace Drupal\image\Controller;
+
+use Drupal\Component\Utility\Crypt;
+use Drupal\Core\Config\ConfigFactory;
+use Drupal\Core\Controller\ControllerInterface;
+use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Lock\LockBackendInterface;
+use Drupal\image\ImageStyleInterface;
+use Drupal\system\FileDownloadController;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\HttpFoundation\BinaryFileResponse;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
+
+/**
+ * Defines a controller to serve image styles.
+ */
+class ImageStyleDownloadController extends FileDownloadController implements ControllerInterface {
+
+  /**
+   * The config factory.
+   *
+   * @var \Drupal\Core\config\ConfigFactory
+   */
+  protected $configFactory;
+
+  /**
+   * The module handler.
+   *
+   * @var \Drupal\Core\Extension\ModuleHandlerInterface
+   */
+  protected $moduleHandler;
+
+  /**
+   * The lock backend.
+   *
+   * @var \Drupal\Core\Lock\LockBackendInterface
+   */
+  protected $lock;
+
+  /**
+   * Constructs a ImageStyleDownloadController object.
+   *
+   * @param \Drupal\Core\Config\ConfigFactory $config_factory
+   *   The config factory.
+   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
+   *   The module handler.
+   * @param \Drupal\Core\Lock\LockBackendInterface $lock
+   *   The lock backend.
+   */
+  public function __construct(ConfigFactory $config_factory, ModuleHandlerInterface $module_handler, LockBackendInterface $lock) {
+    $this->configFactory = $config_factory;
+    $this->moduleHandler = $module_handler;
+    $this->lock = $lock;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('config.factory'),
+      $container->get('module_handler'),
+      $container->get('lock')
+    );
+  }
+
+  /**
+   * Generates a derivative, given a style and image path.
+   *
+   * After generating an image, transfer it to the requesting agent.
+   *
+   * @param \Symfony\Component\HttpFoundation\Request $request
+   *   The request object.
+   * @param string $scheme
+   *   The file scheme, defaults to 'private'.
+   * @param \Drupal\image\ImageStyleInterface $image_style
+   *   The image style to deliver.
+   *
+   * @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
+   *   Thrown when the user does not have access to the file.
+   *
+   * @return \Symfony\Component\HttpFoundation\BinaryFileResponse|\Symfony\Component\HttpFoundation\Response
+   *   The transferred file as response or some error response.
+   */
+  public function deliver(Request $request, $scheme, ImageStyleInterface $image_style) {
+    $target = $request->query->get('file');
+
+    // Check that the style is defined, the scheme is valid, and the image
+    // derivative token is valid. (Sites which require image derivatives to be
+    // generated without a token can set the
+    // 'image.settings:allow_insecure_derivatives' configuration to TRUE to bypass
+    // the latter check, but this will increase the site's vulnerability to
+    // denial-of-service attacks.)
+    $valid = !empty($image_style) && file_stream_wrapper_valid_scheme($scheme);
+    if (!$this->configFactory->get('image.settings')->get('allow_insecure_derivatives')) {
+      $valid = $valid && $request->query->get(IMAGE_DERIVATIVE_TOKEN) === image_style_path_token($image_style->name, $scheme . '://' . $target);
+    }
+    if (!$valid) {
+      throw new AccessDeniedHttpException();
+    }
+
+    $image_uri = $scheme . '://' . $target;
+    $derivative_uri = image_style_path($image_style->id(), $image_uri);
+
+    $response = new Response('');
+
+    // If using the private scheme, let other modules provide headers and
+    // control access to the file.
+    if ($scheme == 'private') {
+      if (file_exists($derivative_uri)) {
+        return parent::deliver($request, $scheme);
+      }
+      else {
+        $headers = $this->moduleHandler->invokeAll('file_download', array($image_uri));
+        if (in_array(-1, $headers) || empty($headers)) {
+          throw new AccessDeniedHttpException();
+        }
+        if (count($headers)) {
+          foreach ($headers as $name => $value) {
+            $response->headers->set($name, $value);
+          }
+        }
+      }
+    }
+
+    // Don't try to generate file if source is missing.
+    if (!file_exists($image_uri)) {
+      watchdog('image', 'Source image at %source_image_path not found while trying to generate derivative image at %derivative_path.',  array('%source_image_path' => $image_uri, '%derivative_path' => $derivative_uri));
+      $response->setContent(t('Error generating image, missing source file.'));
+      $response->setStatusCode(404);
+      return $response;
+    }
+
+    // Don't start generating the image if the derivative already exists or if
+    // generation is in progress in another thread.
+    $lock_name = 'image_style_deliver:' . $image_style->id() . ':' . Crypt::hashBase64($image_uri);
+    if (!file_exists($derivative_uri)) {
+      $lock_acquired = $this->lock->acquire($lock_name);
+      if (!$lock_acquired) {
+        // Tell client to retry again in 3 seconds. Currently no browsers are known
+        // to support Retry-After.
+        $response->headers->set('Status', '503 Service Unavailable');
+        $response->headers->set('Retry-After', 3);
+        $response->setContent(t('Image generation in progress. Try again shortly.'));
+        return $response;
+      }
+    }
+
+    // Try to generate the image, unless another thread just did it while we were
+    // acquiring the lock.
+    $success = file_exists($derivative_uri) || image_style_create_derivative($image_style, $image_uri, $derivative_uri);
+
+    if (!empty($lock_acquired)) {
+      $this->lock->release($lock_name);
+    }
+
+    if ($success) {
+      $image = image_load($derivative_uri);
+      $uri = $image->source;
+      $response->headers->set('Content-Type', $image->info['mime_type']);
+      $response->headers->set('Content-Length', $image->info['file_size']);
+      return new BinaryFileResponse($uri, 200, $response->headers->all());
+    }
+    else {
+      watchdog('image', 'Unable to generate the derived image located at %path.', array('%path' => $derivative_uri));
+      $response->setContent(t('Error generating image.'));
+      $response->setStatusCode(500);
+      return $response;
+    }
+  }
+
+}
diff --git a/core/modules/image/lib/Drupal/image/EventSubscriber/RouteSubscriber.php b/core/modules/image/lib/Drupal/image/EventSubscriber/RouteSubscriber.php
new file mode 100644
index 0000000..37f7047
--- /dev/null
+++ b/core/modules/image/lib/Drupal/image/EventSubscriber/RouteSubscriber.php
@@ -0,0 +1,55 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\image\EventSubscriber\RouteSubscriber.
+ */
+
+namespace Drupal\image\EventSubscriber;
+
+use Drupal\Core\Routing\RouteBuildEvent;
+use Drupal\Core\Routing\RoutingEvents;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+use Symfony\Component\Routing\Route;
+
+/**
+ * Defines a route subscriber to register a url for serving image styles.
+ */
+class RouteSubscriber implements EventSubscriberInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getSubscribedEvents() {
+    $events[RoutingEvents::DYNAMIC] = 'dynamicRoutes';
+    return $events;
+  }
+
+  /**
+   * Registers dynamic routes for image styles.
+   *
+   * Generate image derivatives of publicly available files. If clean URLs are
+   * disabled, image derivatives will always be served through the menu system.
+   * If clean URLs are enabled and the image derivative already exists, PHP will
+   * be bypassed.
+   *
+   * @param \Drupal\Core\Routing\RouteBuildEvent $event
+   *   The route building event.
+   */
+  public function dynamicRoutes(RouteBuildEvent $event) {
+    $collection = $event->getRouteCollection();
+
+    $directory_path = file_stream_wrapper_get_instance_by_scheme('public')->getDirectoryPath();
+
+    $route = new Route('/' . $directory_path . '/styles/{image_style}/{scheme}',
+      array(
+      '_controller' => 'Drupal\image\Controller\ImageStyleFileService::deliver',
+      ),
+      array(
+        '_access' => 'TRUE',
+      )
+    );
+    $collection->add('image_style_public', $route);
+  }
+
+}
diff --git a/core/modules/image/lib/Drupal/image/PathProcessor/PathProcessorImageStyles.php b/core/modules/image/lib/Drupal/image/PathProcessor/PathProcessorImageStyles.php
new file mode 100644
index 0000000..ea26f2f
--- /dev/null
+++ b/core/modules/image/lib/Drupal/image/PathProcessor/PathProcessorImageStyles.php
@@ -0,0 +1,77 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\image\PathProcessor\PathProcessorImageStyles.
+ */
+
+namespace Drupal\image\PathProcessor;
+
+use Drupal\Core\PathProcessor\InboundPathProcessorInterface;
+use Symfony\Component\HttpFoundation\Request;
+
+/**
+ * Defines a path processor to rewrite image styles URLs.
+ *
+ * As the route system does not allow arbitrary amount of parameters convert
+ * the file path to a query parameter on the request.
+ *
+ * This processor handles two different cases:
+ *   - public image styles: In order to allow apache to serve this files
+ *     directly, the route is registered under the same path as the image style
+ *     so it took over the first generation. Therefore the path processor
+ *     converts the file path to a query parameter and a request on
+ *     directory/styles/$image_style/public.
+ *   - private image styles: In contrast to public image styles, private one
+ *     already use system/files/styles. Similar to public image styles, it
+ *     also converts the file path to a query parameter.
+ */
+class PathProcessorImageStyles implements InboundPathProcessorInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function processInbound($path, Request $request) {
+    $directory_path = file_stream_wrapper_get_instance_by_scheme('public')->getDirectoryPath();
+    if (strpos($path, $directory_path . '/styles/') === 0) {
+      $rest = str_replace($directory_path .'/styles/', '', $path);
+
+      // Get the image style, schema and actual url.
+      list($image_style, $scheme, $rest) = explode('/', $rest, 3);
+
+      // Set file as query parameter on the route. There might be additional
+      // keys like itok, set them as query parameter as well.
+      $query = drupal_get_query_array($rest);
+      foreach ($query as $key => $value) {
+        if (!isset($value) && !$request->query->has('file')) {
+          $request->query->set('file', $key);
+        }
+        else {
+          $request->query->set($key, $value);
+        }
+      }
+      $path = $directory_path . '/styles/' . $image_style .'/' . $scheme;
+    }
+    elseif (strpos($path, 'system/files/styles/') === 0) {
+      $rest = str_replace('system/files/styles/', '', $path);
+
+      // Get the image style, schema and actual url.
+      list($image_style, $scheme, $file_path) = explode('/', $rest);
+
+      // Set file as query parameter on the route. There might be additional
+      // keys like itok, set them as query parameter as well.
+      $query = drupal_get_query_array($file_path);
+      foreach ($query as $key => $value) {
+        if (!isset($value) && !$request->query->has('file')) {
+          $request->query->set('file', $key);
+        }
+        else {
+          $request->query->set($key, $value);
+        }
+      }
+      $path = 'system/files/styles/' . $image_style . '/' . $scheme;
+    }
+    return $path;
+  }
+
+}
diff --git a/core/modules/system/lib/Drupal/system/FileDownloadController.php b/core/modules/system/lib/Drupal/system/FileDownloadController.php
new file mode 100644
index 0000000..15bc4eb
--- /dev/null
+++ b/core/modules/system/lib/Drupal/system/FileDownloadController.php
@@ -0,0 +1,99 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\system\FileDownloadController.
+ */
+
+namespace Drupal\system;
+
+use Drupal\Core\Controller\ControllerInterface;
+use Drupal\Core\Extension\ModuleHandlerInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
+use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
+use Symfony\Component\HttpFoundation\BinaryFileResponse;
+
+/**
+ * System file controller.
+ */
+class FileDownloadController implements ControllerInterface {
+
+  /**
+   * The module handler.
+   *
+   * @var \Drupal\Core\Extension\ModuleHandlerInterface
+   */
+  protected $moduleHandler;
+
+  /**
+   * Constructs a FileDownloadController object.
+   *
+   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
+   *   The module handler.
+   */
+  public function __construct(ModuleHandlerInterface $module_handler) {
+    $this->moduleHandler = $module_handler;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('module_handler')
+    );
+  }
+
+  /**
+   * Handles private file transfers.
+   *
+   * Call modules that implement hook_file_download() to find out if a file is
+   * accessible and what headers it should be transferred with. If one or more
+   * modules returned headers the download will start with the returned headers.
+   * If a module returns -1 an AccessDeniedHttpException will be thrown. If the
+   * file exists but no modules responded an AccessDeniedHttpException will be
+   * thrown. If the file does not exist a NotFoundHttpException will be thrown.
+   *
+   * @see hook_file_download()
+   *
+   * @param \Symfony\Component\HttpFoundation\Request $request
+   *   The request object.
+   * @param int|string $scheme
+   *   The file scheme, defaults to 'private'.
+   *
+   * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
+   *   Thrown when the requested file does not exist.
+   * @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
+   *   Thrown when the user does not have access to the file.
+   *
+   * @return \Symfony\Component\HttpFoundation\BinaryFileResponse
+   *   The transferred file as response.
+   */
+  public function deliver(Request $request, $scheme = 'private') {
+    $target = $request->query->get('file');
+    // Merge remaining path arguments into relative file path.
+    $uri = $scheme . '://' . $target;
+
+    if (file_stream_wrapper_valid_scheme($scheme) && file_exists($uri)) {
+      // Let other modules provide headers and controls access to the file.
+      $headers = $this->moduleHandler->invokeAll('file_download', array($uri));
+
+      foreach ($headers as $result) {
+        if ($result == -1) {
+          throw new AccessDeniedHttpException();
+        }
+      }
+
+      if (count($headers)) {
+        return new BinaryFileResponse($uri, 200, $headers);
+      }
+
+      throw new AccessDeniedHttpException();
+    }
+
+    throw new NotFoundHttpException();
+  }
+
+}
diff --git a/core/modules/system/lib/Drupal/system/PathProcessor/PathProcessorFiles.php b/core/modules/system/lib/Drupal/system/PathProcessor/PathProcessorFiles.php
new file mode 100644
index 0000000..d9cf07a
--- /dev/null
+++ b/core/modules/system/lib/Drupal/system/PathProcessor/PathProcessorFiles.php
@@ -0,0 +1,33 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\system\PathProcessor\PathProcessorPrivateFiles.
+ */
+
+namespace Drupal\system\PathProcessor;
+
+use Drupal\Core\PathProcessor\InboundPathProcessorInterface;
+use Symfony\Component\HttpFoundation\Request;
+
+/**
+ * Defines a path processor to rewrite file URLs.
+ *
+ * As the route system does not allow arbitrary amount of parameters convert
+ * the file path to a query parameter on the request.
+ */
+class PathProcessorFiles implements InboundPathProcessorInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function processInbound($path, Request $request) {
+    if (strpos($path, 'system/files/') === 0 && !$request->query->has('file')) {
+      $file_path = str_replace('system/files/', '', $path);
+      $request->query->set('file', $file_path);
+      return 'system/files';
+    }
+    return $path;
+  }
+
+}
diff --git a/core/modules/system/system.module b/core/modules/system/system.module
index 0df34a0..52fc9b3 100644
--- a/core/modules/system/system.module
+++ b/core/modules/system/system.module
@@ -613,13 +613,6 @@ function system_element_info() {
  * Implements hook_menu().
  */
 function system_menu() {
-  $items['system/files'] = array(
-    'title' => 'File download',
-    'page callback' => 'file_download',
-    'page arguments' => array('private'),
-    'access callback' => TRUE,
-    'type' => MENU_CALLBACK,
-  );
   $items['system/temporary'] = array(
     'title' => 'Temporary files',
     'page callback' => 'file_download',
diff --git a/core/modules/system/system.routing.yml b/core/modules/system/system.routing.yml
index 1e09f79..a1c7ae1 100644
--- a/core/modules/system/system.routing.yml
+++ b/core/modules/system/system.routing.yml
@@ -144,6 +144,14 @@ system_admin_index:
   requirements:
     _permission: 'access administration pages'
 
+system_files:
+  pattern: '/system/files/{scheme}'
+  defaults:
+    _controller: 'Drupal\system\FileDownloadController::deliver'
+    scheme: private
+  requirements:
+    _access: 'TRUE'
+
 system_theme_settings:
   pattern: '/admin/appearance/settings'
   defaults:
diff --git a/core/modules/system/system.services.yml b/core/modules/system/system.services.yml
index 6aefa00..d9f29f7 100644
--- a/core/modules/system/system.services.yml
+++ b/core/modules/system/system.services.yml
@@ -13,6 +13,10 @@ services:
     class: Drupal\system\LegacyBreadcrumbBuilder
     tags:
       - {name: breadcrumb_builder, priority: 500}
+  path_processor.files:
+    class: Drupal\system\PathProcessor\PathProcessorFiles
+    tags:
+      - { name: path_processor_inbound, priority: 200 }
   system.route_subscriber:
     class: Drupal\system\Routing\RouteSubscriber
     tags:
