Issue #1987712 by dawehner, claudiu.cristea, damiankloip, andypost: Convert file_download() to a new style controller.

8.0.x
Alex Pott 2013-07-20 13:25:28 +01:00
parent ad137531c9
commit 36d647e61b
16 changed files with 476 additions and 209 deletions

View File

@ -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.
@ -1331,42 +1328,6 @@ function file_unmanaged_save_data($data, $destination = NULL, $replace = FILE_EX
return file_unmanaged_move($temp_name, $destination, $replace);
}
/**
* 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.
*

View File

@ -10,9 +10,10 @@ namespace Drupal\config\Controller;
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.
@ -22,22 +23,33 @@ class ConfigController implements ControllerInterface {
/**
* The target storage.
*
* @var \Drupal\Core\Config\StorageInterface;
* @var \Drupal\Core\Config\StorageInterface
*/
protected $targetStorage;
/**
* The source storage.
*
* @var \Drupal\Core\Config\StorageInterface;
* @var \Drupal\Core\Config\StorageInterface
*/
protected $sourceStorage;
/**
* The file download controller.
*
* @var \Drupal\Core\Controller\ControllerInterface
*/
protected $fileDownloadController;
/**
* {@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'),
FileDownloadController::create($container)
);
}
/**
@ -47,10 +59,13 @@ class ConfigController implements ControllerInterface {
* The target storage.
* @param \Drupal\Core\Config\StorageInterface $source_storage
* The source storage
* @param \Drupal\Core\Controller\ControllerInterface $file_download_controller
* The file download controller.
*/
public function __construct(StorageInterface $target_storage, StorageInterface $source_storage) {
public function __construct(StorageInterface $target_storage, StorageInterface $source_storage, ControllerInterface $file_download_controller) {
$this->targetStorage = $target_storage;
$this->sourceStorage = $source_storage;
$this->fileDownloadController = $file_download_controller;
}
/**
@ -64,7 +79,9 @@ class ConfigController implements ControllerInterface {
$config_files[] = $config_dir . '/' . $config_name . '.yml';
}
$archiver->createModify($config_files, '', config_get_config_directory());
return file_download('temporary', 'config.tar.gz');
$request = new Request(array('file' => 'config.tar.gz'));
return $this->fileDownloadController->download($request, 'temporary');
}
/**

View File

@ -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.

View File

@ -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.',
@ -391,30 +369,6 @@ function image_style_options($include_empty = TRUE) {
return $options;
}
/**
* 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.
*

View File

@ -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'

View File

@ -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 }

View File

@ -0,0 +1,176 @@
<?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\Core\StringTranslation\Translator\TranslatorInterface;
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;
/**
* The translator service.
*
* @var \Drupal\Core\StringTranslation\Translator\TranslatorInterface
*/
protected $translator;
/**
* 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.
* @param \Drupal\Core\StringTranslation\Translator\TranslatorInterface $translator
* The translator service.
*/
public function __construct(ModuleHandlerInterface $module_handler, ConfigFactory $config_factory, LockBackendInterface $lock, TranslatorInterface $translator) {
parent::__construct($module_handler);
$this->configFactory = $config_factory;
$this->lock = $lock;
$this->translator = $translator;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('module_handler'),
$container->get('config.factory'),
$container->get('lock'),
$container->get('string_translation')
);
}
/**
* 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($this->translator->translate('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, $this->translator->translate('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($this->translator->translate('Error generating image.'), 500);
}
}
}

View File

@ -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);
}
}

View File

@ -14,30 +14,6 @@ use Drupal\Core\Config\Entity\ConfigEntityInterface;
*/
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.
*

View File

@ -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;
}
}

View File

@ -15,10 +15,6 @@ use Drupal\image\ImageStyleInterface;
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.
@ -161,84 +157,6 @@ class ImageStyle extends ConfigEntityBase implements ImageStyleInterface {
}
}
/**
* {@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}
*/
@ -397,14 +315,13 @@ class ImageStyle extends ConfigEntityBase implements ImageStyleInterface {
* 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);
}

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -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',

View File

@ -165,6 +165,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:

View File

@ -18,6 +18,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: