Update to Drupal 8.0.0 beta 14. For more information, see https://drupal.org/node/2544542

This commit is contained in:
Pantheon Automation 2015-08-27 12:03:05 -07:00 committed by Greg Anderson
parent 3b2511d96d
commit 81ccda77eb
2155 changed files with 54307 additions and 46870 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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