Update to Drupal 8.0.0 beta 14. For more information, see https://drupal.org/node/2544542
This commit is contained in:
parent
3b2511d96d
commit
81ccda77eb
2155 changed files with 54307 additions and 46870 deletions
|
@ -9,7 +9,6 @@ namespace Drupal\Core\EventSubscriber;
|
|||
|
||||
use Drupal\Component\Utility\SafeMarkup;
|
||||
use Drupal\Core\Config\ConfigFactoryInterface;
|
||||
use Drupal\Core\Render\BareHtmlPageRendererInterface;
|
||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
|
||||
use Drupal\Core\Utility\Error;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
|
@ -43,24 +42,14 @@ class DefaultExceptionSubscriber implements EventSubscriberInterface {
|
|||
*/
|
||||
protected $configFactory;
|
||||
|
||||
/**
|
||||
* The bare HTML page renderer.
|
||||
*
|
||||
* @var \Drupal\Core\Render\BareHtmlPageRendererInterface
|
||||
*/
|
||||
protected $bareHtmlPageRenderer;
|
||||
|
||||
/**
|
||||
* Constructs a new DefaultExceptionSubscriber.
|
||||
*
|
||||
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
|
||||
* The configuration factory.
|
||||
* @param \Drupal\Core\Render\BareHtmlPageRendererInterface $bare_html_page_renderer
|
||||
* The bare HTML page renderer.
|
||||
*/
|
||||
public function __construct(ConfigFactoryInterface $config_factory, BareHtmlPageRendererInterface $bare_html_page_renderer) {
|
||||
public function __construct(ConfigFactoryInterface $config_factory) {
|
||||
$this->configFactory = $config_factory;
|
||||
$this->bareHtmlPageRenderer = $bare_html_page_renderer;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -87,15 +76,13 @@ class DefaultExceptionSubscriber implements EventSubscriberInterface {
|
|||
|
||||
// Display the message if the current error reporting level allows this type
|
||||
// of message to be displayed, and unconditionally in update.php.
|
||||
$message = '';
|
||||
if (error_displayable($error)) {
|
||||
$class = 'error';
|
||||
|
||||
// If error type is 'User notice' then treat it as debug information
|
||||
// instead of an error message.
|
||||
// @see debug()
|
||||
if ($error['%type'] == 'User notice') {
|
||||
$error['%type'] = 'Debug';
|
||||
$class = 'status';
|
||||
}
|
||||
|
||||
// Attempt to reduce verbosity by removing DRUPAL_ROOT from the file path
|
||||
|
@ -125,11 +112,11 @@ class DefaultExceptionSubscriber implements EventSubscriberInterface {
|
|||
// sure the backtrace is escaped as it can contain user submitted data.
|
||||
$message .= '<pre class="backtrace">' . SafeMarkup::escape(Error::formatBacktrace($backtrace)) . '</pre>';
|
||||
}
|
||||
drupal_set_message(SafeMarkup::set($message), $class, TRUE);
|
||||
}
|
||||
|
||||
$content = $this->t('The website encountered an unexpected error. Please try again later.');
|
||||
$response = $this->bareHtmlPageRenderer->renderBarePage(['#markup' => $content], $this->t('Error'), 'maintenance_page');
|
||||
$content .= $message ? '</br></br>' . $message : '';
|
||||
$response = new Response($content, 500);
|
||||
|
||||
if ($exception instanceof HttpExceptionInterface) {
|
||||
$response->setStatusCode($exception->getStatusCode());
|
||||
|
|
|
@ -0,0 +1,181 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\EventSubscriber;
|
||||
|
||||
use Drupal\Core\Ajax\AjaxResponse;
|
||||
use Drupal\Core\Cache\CacheableDependencyInterface;
|
||||
use Drupal\Core\Cache\CacheableResponseInterface;
|
||||
use Drupal\Core\Controller\ControllerResolverInterface;
|
||||
use Drupal\Core\Render\AttachmentsInterface;
|
||||
use Drupal\Core\Render\BubbleableMetadata;
|
||||
use Drupal\Core\Render\RenderContext;
|
||||
use Drupal\Core\Render\RendererInterface;
|
||||
use Drupal\Core\Routing\RouteMatchInterface;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
|
||||
use Symfony\Component\HttpKernel\KernelEvents;
|
||||
|
||||
/**
|
||||
* Subscriber that wraps controllers, to handle early rendering.
|
||||
*
|
||||
* When controllers call drupal_render() (RendererInterface::render()) outside
|
||||
* of a render context, we call that "early rendering". Controllers should
|
||||
* return only render arrays, but we cannot prevent controllers from doing early
|
||||
* rendering. The problem with early rendering is that the bubbleable metadata
|
||||
* (cacheability & attachments) are lost.
|
||||
*
|
||||
* This can lead to broken pages (missing assets), stale pages (missing cache
|
||||
* tags causing a page not to be invalidated) or even security problems (missing
|
||||
* cache contexts causing a cached page not to be varied sufficiently).
|
||||
*
|
||||
* This event subscriber wraps all controller executions in a closure that sets
|
||||
* up a render context. Consequently, any early rendering will have their
|
||||
* bubbleable metadata (assets & cacheability) stored on that render context.
|
||||
*
|
||||
* If the render context is empty, then the controller either did not do any
|
||||
* rendering at all, or used the RendererInterface::renderRoot() or
|
||||
* ::renderPlain() methods. In that case, no bubbleable metadata is lost.
|
||||
*
|
||||
* If the render context is not empty, then the controller did use
|
||||
* drupal_render(), and bubbleable metadata was collected. This bubbleable
|
||||
* metadata is then merged onto the render array.
|
||||
*
|
||||
* In other words: this just exists to ease the transition to Drupal 8: it
|
||||
* allows controllers that return render arrays (the majority) and
|
||||
* \Drupal\Core\Ajax\AjaxResponse\AjaxResponse objects (a sizable minority that
|
||||
* often involve a fair amount of rendering) to still do early rendering. But
|
||||
* controllers that return any other kind of response are already expected to
|
||||
* do the right thing, so if early rendering is detected in such a case, an
|
||||
* exception is thrown.
|
||||
*
|
||||
* @see \Drupal\Core\Render\RendererInterface
|
||||
* @see \Drupal\Core\Render\Renderer
|
||||
*
|
||||
* @todo Remove in Drupal 9.0.0, by disallowing early rendering.
|
||||
*/
|
||||
class EarlyRenderingControllerWrapperSubscriber implements EventSubscriberInterface {
|
||||
|
||||
/**
|
||||
* The controller resolver.
|
||||
*
|
||||
* @var \Drupal\Core\Controller\ControllerResolverInterface
|
||||
*/
|
||||
protected $controllerResolver;
|
||||
|
||||
/**
|
||||
* The renderer.
|
||||
*
|
||||
* @var \Drupal\Core\Render\RendererInterface
|
||||
*/
|
||||
protected $renderer;
|
||||
|
||||
/**
|
||||
* Constructs a new EarlyRenderingControllerWrapperSubscriber instance.
|
||||
*
|
||||
* @param \Drupal\Core\Controller\ControllerResolverInterface $controller_resolver
|
||||
* The controller resolver.
|
||||
* @param \Drupal\Core\Render\RendererInterface $renderer
|
||||
* The renderer.
|
||||
*/
|
||||
public function __construct(ControllerResolverInterface $controller_resolver, RendererInterface $renderer) {
|
||||
$this->controllerResolver = $controller_resolver;
|
||||
$this->renderer = $renderer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures bubbleable metadata from early rendering is not lost.
|
||||
*
|
||||
* @param \Symfony\Component\HttpKernel\Event\FilterControllerEvent $event
|
||||
* The controller event.
|
||||
*/
|
||||
public function onController(FilterControllerEvent $event) {
|
||||
$controller = $event->getController();
|
||||
|
||||
// See \Symfony\Component\HttpKernel\HttpKernel::handleRaw().
|
||||
$arguments = $this->controllerResolver->getArguments($event->getRequest(), $controller);
|
||||
|
||||
$event->setController(function() use ($controller, $arguments) {
|
||||
return $this->wrapControllerExecutionInRenderContext($controller, $arguments);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps a controller execution in a render context.
|
||||
*
|
||||
* @param callable $controller
|
||||
* The controller to execute.
|
||||
* @param array $arguments
|
||||
* The arguments to pass to the controller.
|
||||
*
|
||||
* @return mixed
|
||||
* The return value of the controller.
|
||||
*
|
||||
* @throws \LogicException
|
||||
* When early rendering has occurred in a controller that returned a
|
||||
* Response or domain object that cares about attachments or cacheability.
|
||||
*
|
||||
* @see \Symfony\Component\HttpKernel\HttpKernel::handleRaw()
|
||||
*/
|
||||
protected function wrapControllerExecutionInRenderContext($controller, array $arguments) {
|
||||
$context = new RenderContext();
|
||||
|
||||
$response = $this->renderer->executeInRenderContext($context, function() use ($controller, $arguments) {
|
||||
// Now call the actual controller, just like HttpKernel does.
|
||||
return call_user_func_array($controller, $arguments);
|
||||
});
|
||||
|
||||
// If early rendering happened, i.e. if code in the controller called
|
||||
// drupal_render() outside of a render context, then the bubbleable metadata
|
||||
// for that is stored in the current render context.
|
||||
if (!$context->isEmpty()) {
|
||||
/** @var \Drupal\Core\Render\BubbleableMetadata $early_rendering_bubbleable_metadata */
|
||||
$early_rendering_bubbleable_metadata = $context->pop();
|
||||
|
||||
// If a render array or AjaxResponse is returned by the controller, merge
|
||||
// the "lost" bubbleable metadata.
|
||||
if (is_array($response)) {
|
||||
BubbleableMetadata::createFromRenderArray($response)
|
||||
->merge($early_rendering_bubbleable_metadata)
|
||||
->applyTo($response);
|
||||
}
|
||||
elseif ($response instanceof AjaxResponse) {
|
||||
$response->addAttachments($early_rendering_bubbleable_metadata->getAttachments());
|
||||
// @todo Make AjaxResponse cacheable in
|
||||
// https://www.drupal.org/node/956186. Meanwhile, allow contrib
|
||||
// subclasses to be.
|
||||
if ($response instanceof CacheableResponseInterface) {
|
||||
$response->addCacheableDependency($early_rendering_bubbleable_metadata);
|
||||
}
|
||||
}
|
||||
// If a non-Ajax Response or domain object is returned and it cares about
|
||||
// attachments or cacheability, then throw an exception: early rendering
|
||||
// is not permitted in that case. It is the developer's responsibility
|
||||
// to not use early rendering.
|
||||
elseif ($response instanceof AttachmentsInterface || $response instanceof CacheableResponseInterface || $response instanceof CacheableDependencyInterface) {
|
||||
throw new \LogicException(sprintf('The controller result claims to be providing relevant cache metadata, but leaked metadata was detected. Please ensure you are not rendering content too early. Returned object class: %s.', get_class($response)));
|
||||
}
|
||||
else {
|
||||
// A Response or domain object is returned that does not care about
|
||||
// attachments nor cacheability. E.g. a RedirectResponse. It is safe to
|
||||
// discard any early rendering metadata.
|
||||
}
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function getSubscribedEvents() {
|
||||
$events[KernelEvents::CONTROLLER][] = ['onController'];
|
||||
|
||||
return $events;
|
||||
}
|
||||
|
||||
}
|
|
@ -113,6 +113,7 @@ class FinishResponseSubscriber implements EventSubscriberInterface {
|
|||
// XSS and other vulnerabilities.
|
||||
// https://www.owasp.org/index.php/List_of_useful_HTTP_headers
|
||||
$response->headers->set('X-Content-Type-Options', 'nosniff', FALSE);
|
||||
$response->headers->set('X-Frame-Options', 'SAMEORIGIN', FALSE);
|
||||
|
||||
// Expose the cache contexts and cache tags associated with this page in a
|
||||
// X-Drupal-Cache-Contexts and X-Drupal-Cache-Tags header respectively.
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\EventSubscriber\PsrResponseSubscriber.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\EventSubscriber;
|
||||
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
use Symfony\Bridge\PsrHttpMessage\HttpFoundationFactoryInterface;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent;
|
||||
use Symfony\Component\HttpKernel\KernelEvents;
|
||||
|
||||
/**
|
||||
* Response subscriber for handling PSR-7 responses.
|
||||
*/
|
||||
class PsrResponseSubscriber implements EventSubscriberInterface {
|
||||
|
||||
/**
|
||||
* The httpFoundation factory.
|
||||
*
|
||||
* @var \Symfony\Bridge\PsrHttpMessage\HttpFoundationFactoryInterface
|
||||
*/
|
||||
protected $httpFoundationFactory;
|
||||
|
||||
/**
|
||||
* Constructs a new PathRootsSubscriber instance.
|
||||
*
|
||||
* @param \Symfony\Bridge\PsrHttpMessage\HttpFoundationFactoryInterface $http_foundation_factory
|
||||
* The httpFoundation factory.
|
||||
*/
|
||||
public function __construct(HttpFoundationFactoryInterface $http_foundation_factory) {
|
||||
$this->httpFoundationFactory = $http_foundation_factory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a PSR-7 response to a Symfony response.
|
||||
*
|
||||
* @param \Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent $event
|
||||
* The Event to process.
|
||||
*/
|
||||
public function onKernelView(GetResponseForControllerResultEvent $event) {
|
||||
$controller_result = $event->getControllerResult();
|
||||
|
||||
if ($controller_result instanceof ResponseInterface) {
|
||||
$event->setResponse($this->httpFoundationFactory->createResponse($controller_result));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function getSubscribedEvents() {
|
||||
$events[KernelEvents::VIEW][] = ['onKernelView'];
|
||||
return $events;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\EventSubscriber\RedirectLeadingSlashesSubscriber.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\EventSubscriber;
|
||||
|
||||
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
|
||||
use Symfony\Component\HttpKernel\KernelEvents;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
|
||||
/**
|
||||
* Redirects paths starting with multiple slashes to a single slash.
|
||||
*/
|
||||
class RedirectLeadingSlashesSubscriber implements EventSubscriberInterface {
|
||||
|
||||
/**
|
||||
* Redirects paths starting with multiple slashes to a single slash.
|
||||
*
|
||||
* @param \Symfony\Component\HttpKernel\Event\GetResponseEvent $event
|
||||
* The GetResponseEvent to process.
|
||||
*/
|
||||
public function redirect(GetResponseEvent $event) {
|
||||
$request = $event->getRequest();
|
||||
// Get the requested path minus the base path.
|
||||
$path = $request->getPathInfo();
|
||||
|
||||
// It is impossible to create a link or a route to a path starting with
|
||||
// multiple leading slashes. However if a form is added to the 404 page that
|
||||
// submits back to the same URI this presents an open redirect
|
||||
// vulnerability. Also, Drupal 7 renders the same page for
|
||||
// http://www.example.org/foo and http://www.example.org////foo.
|
||||
if (strpos($path, '//') === 0) {
|
||||
$path = '/' . ltrim($path, '/');
|
||||
$qs = $request->getQueryString();
|
||||
if ($qs) {
|
||||
$qs = '?' . $qs;
|
||||
}
|
||||
$event->setResponse(new RedirectResponse($request->getUriForPath($path) . $qs));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
static function getSubscribedEvents() {
|
||||
$events[KernelEvents::REQUEST][] = ['redirect', 1000];
|
||||
return $events;
|
||||
}
|
||||
|
||||
}
|
|
@ -7,13 +7,15 @@
|
|||
|
||||
namespace Drupal\Core\EventSubscriber;
|
||||
|
||||
use Drupal\Component\HttpFoundation\SecuredRedirectResponse;
|
||||
use Drupal\Component\Utility\UrlHelper;
|
||||
use Drupal\Core\Routing\LocalRedirectResponse;
|
||||
use Drupal\Core\Routing\RequestContext;
|
||||
use Drupal\Core\Routing\UrlGeneratorInterface;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
|
||||
use Symfony\Component\HttpKernel\KernelEvents;
|
||||
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
|
||||
use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent;
|
||||
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
|
||||
|
@ -51,48 +53,88 @@ class RedirectResponseSubscriber implements EventSubscriberInterface {
|
|||
public function checkRedirectUrl(FilterResponseEvent $event) {
|
||||
$response = $event->getResponse();
|
||||
if ($response instanceOf RedirectResponse) {
|
||||
$options = array();
|
||||
|
||||
$request = $event->getRequest();
|
||||
|
||||
// Let the 'destination' query parameter override the redirect target.
|
||||
// If $response is already a SecuredRedirectResponse, it might reject the
|
||||
// new target as invalid, in which case proceed with the old target.
|
||||
$destination = $request->query->get('destination');
|
||||
// A destination from \Drupal::request()->query always overrides the
|
||||
// current RedirectResponse. We do not allow absolute URLs to be passed
|
||||
// via \Drupal::request()->query, as this can be an attack vector, with
|
||||
// the following exception:
|
||||
// - Absolute URLs that point to this site (i.e. same base URL and
|
||||
// base path) are allowed.
|
||||
if ($destination) {
|
||||
if (!UrlHelper::isExternal($destination)) {
|
||||
// The destination query parameter can be a relative URL in the sense
|
||||
// of not including the scheme and host, but its path is expected to
|
||||
// be absolute (start with a '/'). For such a case, prepend the
|
||||
// scheme and host, because the 'Location' header must be absolute.
|
||||
if (strpos($destination, '/') === 0) {
|
||||
$destination = $request->getSchemeAndHttpHost() . $destination;
|
||||
}
|
||||
else {
|
||||
// Legacy destination query parameters can be relative paths that
|
||||
// have not yet been converted to URLs (outbound path processors
|
||||
// and other URL handling still needs to be performed).
|
||||
// @todo As generateFromPath() is deprecated, remove this in
|
||||
// https://www.drupal.org/node/2418219.
|
||||
$destination = UrlHelper::parse($destination);
|
||||
$path = $destination['path'];
|
||||
$options['query'] = $destination['query'];
|
||||
$options['fragment'] = $destination['fragment'];
|
||||
// The 'Location' HTTP header must always be absolute.
|
||||
$options['absolute'] = TRUE;
|
||||
$destination = $this->urlGenerator->generateFromPath($path, $options);
|
||||
}
|
||||
// The 'Location' HTTP header must always be absolute.
|
||||
$destination = $this->getDestinationAsAbsoluteUrl($destination, $request->getSchemeAndHttpHost());
|
||||
try {
|
||||
$response->setTargetUrl($destination);
|
||||
}
|
||||
elseif (UrlHelper::externalIsLocal($destination, $this->requestContext->getCompleteBaseUrl())) {
|
||||
$response->setTargetUrl($destination);
|
||||
catch (\InvalidArgumentException $e) {
|
||||
}
|
||||
}
|
||||
|
||||
// Regardless of whether the target is the original one or the overridden
|
||||
// destination, ensure that all redirects are safe.
|
||||
if (!($response instanceOf SecuredRedirectResponse)) {
|
||||
try {
|
||||
// SecuredRedirectResponse is an abstract class that requires a
|
||||
// concrete implementation. Default to LocalRedirectResponse, which
|
||||
// considers only redirects to within the same site as safe.
|
||||
$safe_response = LocalRedirectResponse::createFromRedirectResponse($response);
|
||||
$safe_response->setRequestContext($this->requestContext);
|
||||
}
|
||||
catch (\InvalidArgumentException $e) {
|
||||
// If the above failed, it's because the redirect target wasn't
|
||||
// local. Do not follow that redirect. Display an error message
|
||||
// instead. We're already catching one exception, so trigger_error()
|
||||
// rather than throw another one.
|
||||
// We don't throw an exception, because this is a client error rather than a
|
||||
// server error.
|
||||
$message = 'Redirects to external URLs are not allowed by default, use \Drupal\Core\Routing\TrustedRedirectResponse for it.';
|
||||
trigger_error($message, E_USER_ERROR);
|
||||
$safe_response = new Response($message, 400);
|
||||
}
|
||||
$event->setResponse($safe_response);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the passed in destination into an absolute URL.
|
||||
*
|
||||
* @param string $destination
|
||||
* The path for the destination. In case it starts with a slash it should
|
||||
* have the base path included already.
|
||||
* @param string $scheme_and_host
|
||||
* The scheme and host string of the current request.
|
||||
*
|
||||
* @return string
|
||||
* The destination as absolute URL.
|
||||
*/
|
||||
protected function getDestinationAsAbsoluteUrl($destination, $scheme_and_host) {
|
||||
if (!UrlHelper::isExternal($destination)) {
|
||||
// The destination query parameter can be a relative URL in the sense of
|
||||
// not including the scheme and host, but its path is expected to be
|
||||
// absolute (start with a '/'). For such a case, prepend the scheme and
|
||||
// host, because the 'Location' header must be absolute.
|
||||
if (strpos($destination, '/') === 0) {
|
||||
$destination = $scheme_and_host . $destination;
|
||||
}
|
||||
else {
|
||||
// Legacy destination query parameters can be relative paths that have
|
||||
// not yet been converted to URLs (outbound path processors and other
|
||||
// URL handling still needs to be performed).
|
||||
// @todo As generateFromPath() is deprecated, remove this in
|
||||
// https://www.drupal.org/node/2418219.
|
||||
$destination = UrlHelper::parse($destination);
|
||||
$path = $destination['path'];
|
||||
$options = [
|
||||
'query' => $destination['query'],
|
||||
'fragment' => $destination['fragment'],
|
||||
'absolute' => TRUE,
|
||||
];
|
||||
$destination = $this->urlGenerator->generateFromPath($path, $options);
|
||||
}
|
||||
}
|
||||
return $destination;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize the destination parameter to prevent open redirect attacks.
|
||||
*
|
||||
|
|
Reference in a new issue