From 5163b993fd9f7855066b996d70d14669cbff84bc Mon Sep 17 00:00:00 2001
From: Claudiu Cristea <clau.cristea@gmail.com>
Date: Tue, 9 Jul 2013 15:14:18 +0300
Subject: [PATCH] Issue #1987712 by damiankloip, andypost, dawehner,
 claudiu.cristea | vijaycs85: Convert file_download() to a new style
 controller.

---
 core/includes/file.inc                             |  39 -----
 .../Drupal/config/Controller/ConfigController.php  |  28 +++-
 core/modules/file/file.module                      |   2 +-
 core/modules/image/image.module                    |  46 ------
 core/modules/image/image.routing.yml               |   8 +
 core/modules/image/image.services.yml              |   9 ++
 .../Controller/ImageStyleDownloadController.php    | 164 +++++++++++++++++++++
 .../image/EventSubscriber/RouteSubscriber.php      |  55 +++++++
 .../image/lib/Drupal/image/ImageStyleInterface.php |  24 ---
 .../PathProcessor/PathProcessorImageStyles.php     |  57 +++++++
 .../Drupal/image/Plugin/Core/Entity/ImageStyle.php |  87 +----------
 .../lib/Drupal/system/FileDownloadController.php   |  99 +++++++++++++
 .../system/PathProcessor/PathProcessorFiles.php    |  33 +++++
 core/modules/system/system.module                  |   7 -
 core/modules/system/system.routing.yml             |   8 +
 core/modules/system/system.services.yml            |   4 +
 16 files changed, 463 insertions(+), 207 deletions(-)
 create mode 100644 core/modules/image/image.services.yml
 create mode 100644 core/modules/image/lib/Drupal/image/Controller/ImageStyleDownloadController.php
 create mode 100644 core/modules/image/lib/Drupal/image/EventSubscriber/RouteSubscriber.php
 create mode 100644 core/modules/image/lib/Drupal/image/PathProcessor/PathProcessorImageStyles.php
 create mode 100644 core/modules/system/lib/Drupal/system/FileDownloadController.php
 create mode 100644 core/modules/system/lib/Drupal/system/PathProcessor/PathProcessorFiles.php

diff --git a/core/includes/file.inc b/core/includes/file.inc
index 1af50cc..e70fa98 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..3861ca8 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->download($request, 'temporary');
   }
 
   /**
diff --git a/core/modules/file/file.module b/core/modules/file/file.module
index 0d66adb..b5973dc 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 4e786bd..62aee35 100644
--- a/core/modules/image/image.module
+++ b/core/modules/image/image.module
@@ -95,28 +95,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.',
@@ -392,30 +370,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 \Drupal\image\ImageStyleInterface $style
- *   The image style.
- * @param string $scheme
- *   The scheme name of the original image file stream wrapper ('public',
- *   'private', 'temporary', etc.).
- *
- * @return \Symfony\Component\HttpFoundation\BinaryFileResponse|\Symfony\Component\HttpFoundation\Response
- *   The image to be delivered.
- *
- * @todo Remove this wrapper in https://drupal.org/node/1987712.
- */
-function image_style_deliver(ImageStyleInterface $style, $scheme) {
-  $args = func_get_args();
-  // Remove $style and $scheme from the arguments.
-  unset($args[0], $args[1]);
-  $target = implode('/', $args);
-  return $style->deliver($scheme, $target);
-}
-
-/**
  * Returns a set of image effects.
  *
  * These image effects are exposed by modules implementing
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..1d65270
--- /dev/null
+++ b/core/modules/image/lib/Drupal/image/Controller/ImageStyleDownloadController.php
@@ -0,0 +1,164 @@
+<?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;
+use Symfony\Component\HttpKernel\Exception\ServiceUnavailableHttpException;
+
+/**
+ * Defines a controller to serve image styles.
+ */
+class ImageStyleDownloadController extends FileDownloadController implements ControllerInterface {
+
+  /**
+   * The config factory.
+   *
+   * @var \Drupal\Core\config\ConfigFactory
+   */
+  protected $configFactory;
+
+  /**
+   * The lock backend.
+   *
+   * @var \Drupal\Core\Lock\LockBackendInterface
+   */
+  protected $lock;
+
+  /**
+   * Constructs a ImageStyleDownloadController object.
+   *
+   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
+   *   The module handler.
+   * @param \Drupal\Core\Config\ConfigFactory $config_factory
+   *   The config factory.
+   * @param \Drupal\Core\Lock\LockBackendInterface $lock
+   *   The lock backend.
+   */
+  public function __construct(ModuleHandlerInterface $module_handler, ConfigFactory $config_factory, LockBackendInterface $lock) {
+    parent::__construct($module_handler);
+    $this->configFactory = $config_factory;
+    $this->lock = $lock;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('module_handler'),
+      $container->get('config.factory'),
+      $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');
+    $image_uri = $scheme . '://' . $target;
+
+    // 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 &= $request->query->get(IMAGE_DERIVATIVE_TOKEN) === $image_style->getPathToken($image_uri);
+    }
+    if (!$valid) {
+      throw new AccessDeniedHttpException();
+    }
+
+    $derivative_uri = $image_style->buildUri($image_uri);
+    $headers = array();
+
+    // 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::download($request, $scheme);
+      }
+      else {
+        $headers = $this->moduleHandler->invokeAll('file_download', array($image_uri));
+        if (in_array(-1, $headers) || empty($headers)) {
+          throw new AccessDeniedHttpException();
+        }
+      }
+    }
+
+    // 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:' . $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.
+        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->createDerivative($image_uri, $derivative_uri);
+
+    if (!empty($lock_acquired)) {
+      $this->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);
+    }
+  }
+
+}
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..69f84f4
--- /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\ImageStyleDownloadController::deliver',
+      ),
+      array(
+        '_access' => 'TRUE',
+      )
+    );
+    $collection->add('image_style_public', $route);
+  }
+
+}
diff --git a/core/modules/image/lib/Drupal/image/ImageStyleInterface.php b/core/modules/image/lib/Drupal/image/ImageStyleInterface.php
index d7803cd..2532cc7 100644
--- a/core/modules/image/lib/Drupal/image/ImageStyleInterface.php
+++ b/core/modules/image/lib/Drupal/image/ImageStyleInterface.php
@@ -15,30 +15,6 @@
 interface ImageStyleInterface extends ConfigEntityInterface {
 
   /**
-   * Delivers an image derivative.
-   *
-   * Transfers a generated image derivative to the requesting agent. Modules may
-   * implement this method to set different serve different image derivatives
-   * from different stream wrappers or to customize different permissions on
-   * each image style.
-   *
-   * @param string $scheme
-   *   The scheme name of the original image file stream wrapper ('public',
-   *   'private', 'temporary', etc.).
-   * @param string $target
-   *   The target part of the uri.
-   *
-   * @return \Symfony\Component\HttpFoundation\BinaryFileResponse|\Symfony\Component\HttpFoundation\Response
-   *   The image to be delivered.
-   *
-   * @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
-   *   \Symfony\Component\HttpKernel\Exception\ServiceUnavailableHttpException
-   *
-   * @todo Move to controller after https://drupal.org/node/1987712.
-   */
-  public function deliver($scheme, $target);
-
-  /**
    * Returns the URI of this image when using this style.
    *
    * The path returned by this function may not exist. The default generation
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..c0c6f31
--- /dev/null
+++ b/core/modules/image/lib/Drupal/image/PathProcessor/PathProcessorImageStyles.php
@@ -0,0 +1,57 @@
+<?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 the webserver to serve these 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.
+ * - private image styles: In contrast to public image styles, private
+ *   derivatives are already using 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) {
+      $path_prefix = $directory_path . '/styles/';
+    }
+    elseif (strpos($path, 'system/files/styles/') === 0) {
+      $path_prefix = 'system/files/styles/';
+    }
+    else {
+      return $path;
+    }
+
+    // Strip out path prefix.
+    $rest = preg_replace('|^' . $path_prefix . '|', '', $path);
+
+    // Get the image style, scheme and path.
+    list($image_style, $scheme, $file) = explode('/', $rest, 3);
+
+    // Set the file as query parameter.
+    $request->query->set('file', $file);
+
+    return $path_prefix . $image_style . '/' . $scheme;
+  }
+
+}
diff --git a/core/modules/image/lib/Drupal/image/Plugin/Core/Entity/ImageStyle.php b/core/modules/image/lib/Drupal/image/Plugin/Core/Entity/ImageStyle.php
index 7dd36c0..7fc752b 100644
--- a/core/modules/image/lib/Drupal/image/Plugin/Core/Entity/ImageStyle.php
+++ b/core/modules/image/lib/Drupal/image/Plugin/Core/Entity/ImageStyle.php
@@ -15,10 +15,6 @@
 use Drupal\Component\Utility\Crypt;
 use Drupal\Component\Utility\Url;
 use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
-use Symfony\Component\HttpFoundation\Response;
-use Symfony\Component\HttpFoundation\BinaryFileResponse;
-use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
-use Symfony\Component\HttpKernel\Exception\ServiceUnavailableHttpException;
 
 /**
  * Defines an image style configuration entity.
@@ -164,84 +160,6 @@ protected static function replaceImageStyle(ImageStyleInterface $style) {
   /**
    * {@inheritdoc}
    */
-  public function deliver($scheme, $target) {
-    $original_uri = $scheme . '://' . $target;
-
-    // Check that 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 = file_stream_wrapper_valid_scheme($scheme);
-    if (!\Drupal::config('image.settings')->get('allow_insecure_derivatives')) {
-      $image_derivative_token = \Drupal::request()->query->get(IMAGE_DERIVATIVE_TOKEN);
-      $valid &= isset($image_derivative_token) && $image_derivative_token === $this->getPathToken($original_uri);
-    }
-    if (!$valid) {
-      throw new AccessDeniedHttpException();
-    }
-
-    $derivative_uri = $this->buildUri($original_uri);
-    $headers = array();
-
-    // 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 = \Drupal::moduleHandler()->invokeAll('file_download', array($original_uri));
-        if (in_array(-1, $headers) || empty($headers)) {
-          throw new AccessDeniedHttpException();
-        }
-      }
-    }
-
-    // Don't try to generate file if source is missing.
-    if (!file_exists($original_uri)) {
-      watchdog('image', 'Source image at %source_image_path not found while trying to generate derivative image at %derivative_path.',  array('%source_image_path' => $original_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:' . $this->id() . ':' . Crypt::hashBase64($original_uri);
-    if (!file_exists($derivative_uri)) {
-      $lock_acquired = \Drupal::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) || $this->createDerivative($original_uri, $derivative_uri);
-
-    if (!empty($lock_acquired)) {
-      \Drupal::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);
-    }
-  }
-
-  /**
-   * {@inheritdoc}
-   */
   public function buildUri($uri) {
     $scheme = file_uri_scheme($uri);
     if ($scheme) {
@@ -397,14 +315,13 @@ public function transformDimensions(array &$dimensions) {
    * which can be costly both in CPU time and disk space.
    *
    * @param string $uri
-   *   The URI of the image for this style, for example as returned by
-   *   \Drupal\image\ImageStyleInterface::buildUri().
+   *   The URI of the original image of this style.
    *
    * @return string
    *   An eight-character token which can be used to protect image style
    *   derivatives against denial-of-service attacks.
    */
-  protected function getPathToken($uri) {
+  public function getPathToken($uri) {
     // Return the first eight characters.
     return substr(Crypt::hmacBase64($this->id() . ':' . $uri, drupal_get_private_key() . drupal_get_hash_salt()), 0, 8);
   }
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..8bff1bb
--- /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 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 download(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..bce1f7f
--- /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 = preg_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 ac42ea3..6eb7712 100644
--- a/core/modules/system/system.module
+++ b/core/modules/system/system.module
@@ -609,13 +609,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 301ab6d..d828fc7 100644
--- a/core/modules/system/system.routing.yml
+++ b/core/modules/system/system.routing.yml
@@ -151,6 +151,14 @@ system_admin_index:
   requirements:
     _permission: 'access administration pages'
 
+system_files:
+  pattern: '/system/files/{scheme}'
+  defaults:
+    _controller: 'Drupal\system\FileDownloadController::download'
+    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:
-- 
1.8.3.1

