annotate core/lib/Drupal/Core/EventSubscriber/DefaultExceptionHtmlSubscriber.php @ 13:5fb285c0d0e3

Update Drupal core to 8.4.7 via Composer. Security update; I *think* we've been lucky to get away with this so far, as we don't support self-registration which seems to be used by the so-called "drupalgeddon 2" attack that 8.4.5 was vulnerable to.
author Chris Cannam
date Mon, 23 Apr 2018 09:33:26 +0100
parents 4c8ae668cc8c
children 1fec387a4317
rev   line source
Chris@0 1 <?php
Chris@0 2
Chris@0 3 namespace Drupal\Core\EventSubscriber;
Chris@0 4
Chris@0 5 use Drupal\Core\Routing\RedirectDestinationInterface;
Chris@0 6 use Drupal\Core\Utility\Error;
Chris@0 7 use Psr\Log\LoggerInterface;
Chris@0 8 use Symfony\Component\HttpFoundation\Response;
Chris@0 9 use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
Chris@0 10 use Symfony\Component\HttpKernel\HttpKernelInterface;
Chris@0 11 use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
Chris@0 12 use Symfony\Component\Routing\Matcher\UrlMatcherInterface;
Chris@0 13
Chris@0 14 /**
Chris@0 15 * Exception subscriber for handling core default HTML error pages.
Chris@0 16 */
Chris@0 17 class DefaultExceptionHtmlSubscriber extends HttpExceptionSubscriberBase {
Chris@0 18
Chris@0 19 /**
Chris@0 20 * The HTTP kernel.
Chris@0 21 *
Chris@0 22 * @var \Symfony\Component\HttpKernel\HttpKernelInterface
Chris@0 23 */
Chris@0 24 protected $httpKernel;
Chris@0 25
Chris@0 26 /**
Chris@0 27 * The logger instance.
Chris@0 28 *
Chris@0 29 * @var \Psr\Log\LoggerInterface
Chris@0 30 */
Chris@0 31 protected $logger;
Chris@0 32
Chris@0 33 /**
Chris@0 34 * The redirect destination service.
Chris@0 35 *
Chris@0 36 * @var \Drupal\Core\Routing\RedirectDestinationInterface
Chris@0 37 */
Chris@0 38 protected $redirectDestination;
Chris@0 39
Chris@0 40 /**
Chris@0 41 * A router implementation which does not check access.
Chris@0 42 *
Chris@0 43 * @var \Symfony\Component\Routing\Matcher\UrlMatcherInterface
Chris@0 44 */
Chris@0 45 protected $accessUnawareRouter;
Chris@0 46
Chris@0 47 /**
Chris@0 48 * Constructs a new DefaultExceptionHtmlSubscriber.
Chris@0 49 *
Chris@0 50 * @param \Symfony\Component\HttpKernel\HttpKernelInterface $http_kernel
Chris@0 51 * The HTTP kernel.
Chris@0 52 * @param \Psr\Log\LoggerInterface $logger
Chris@0 53 * The logger service.
Chris@0 54 * @param \Drupal\Core\Routing\RedirectDestinationInterface $redirect_destination
Chris@0 55 * The redirect destination service.
Chris@0 56 * @param \Symfony\Component\Routing\Matcher\UrlMatcherInterface $access_unaware_router
Chris@0 57 * A router implementation which does not check access.
Chris@0 58 */
Chris@0 59 public function __construct(HttpKernelInterface $http_kernel, LoggerInterface $logger, RedirectDestinationInterface $redirect_destination, UrlMatcherInterface $access_unaware_router) {
Chris@0 60 $this->httpKernel = $http_kernel;
Chris@0 61 $this->logger = $logger;
Chris@0 62 $this->redirectDestination = $redirect_destination;
Chris@0 63 $this->accessUnawareRouter = $access_unaware_router;
Chris@0 64 }
Chris@0 65
Chris@0 66 /**
Chris@0 67 * {@inheritdoc}
Chris@0 68 */
Chris@0 69 protected static function getPriority() {
Chris@0 70 // A very low priority so that custom handlers are almost certain to fire
Chris@0 71 // before it, even if someone forgets to set a priority.
Chris@0 72 return -128;
Chris@0 73 }
Chris@0 74
Chris@0 75 /**
Chris@0 76 * {@inheritdoc}
Chris@0 77 */
Chris@0 78 protected function getHandledFormats() {
Chris@0 79 return ['html'];
Chris@0 80 }
Chris@0 81
Chris@0 82 /**
Chris@0 83 * Handles a 4xx error for HTML.
Chris@0 84 *
Chris@0 85 * @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event
Chris@0 86 * The event to process.
Chris@0 87 */
Chris@0 88 public function on4xx(GetResponseForExceptionEvent $event) {
Chris@0 89 if (($exception = $event->getException()) && $exception instanceof HttpExceptionInterface) {
Chris@0 90 $this->makeSubrequest($event, '/system/4xx', $exception->getStatusCode());
Chris@0 91 }
Chris@0 92 }
Chris@0 93
Chris@0 94 /**
Chris@0 95 * Handles a 401 error for HTML.
Chris@0 96 *
Chris@0 97 * @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event
Chris@0 98 * The event to process.
Chris@0 99 */
Chris@0 100 public function on401(GetResponseForExceptionEvent $event) {
Chris@0 101 $this->makeSubrequest($event, '/system/401', Response::HTTP_UNAUTHORIZED);
Chris@0 102 }
Chris@0 103
Chris@0 104 /**
Chris@0 105 * Handles a 403 error for HTML.
Chris@0 106 *
Chris@0 107 * @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event
Chris@0 108 * The event to process.
Chris@0 109 */
Chris@0 110 public function on403(GetResponseForExceptionEvent $event) {
Chris@0 111 $this->makeSubrequest($event, '/system/403', Response::HTTP_FORBIDDEN);
Chris@0 112 }
Chris@0 113
Chris@0 114 /**
Chris@0 115 * Handles a 404 error for HTML.
Chris@0 116 *
Chris@0 117 * @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event
Chris@0 118 * The event to process.
Chris@0 119 */
Chris@0 120 public function on404(GetResponseForExceptionEvent $event) {
Chris@0 121 $this->makeSubrequest($event, '/system/404', Response::HTTP_NOT_FOUND);
Chris@0 122 }
Chris@0 123
Chris@0 124 /**
Chris@0 125 * Makes a subrequest to retrieve the default error page.
Chris@0 126 *
Chris@0 127 * @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event
Chris@0 128 * The event to process.
Chris@0 129 * @param string $url
Chris@0 130 * The path/url to which to make a subrequest for this error message.
Chris@0 131 * @param int $status_code
Chris@0 132 * The status code for the error being handled.
Chris@0 133 */
Chris@0 134 protected function makeSubrequest(GetResponseForExceptionEvent $event, $url, $status_code) {
Chris@0 135 $request = $event->getRequest();
Chris@0 136 $exception = $event->getException();
Chris@0 137
Chris@0 138 try {
Chris@0 139 // Reuse the exact same request (so keep the same URL, keep the access
Chris@0 140 // result, the exception, et cetera) but override the routing information.
Chris@0 141 // This means that aside from routing, this is identical to the master
Chris@0 142 // request. This allows us to generate a response that is executed on
Chris@0 143 // behalf of the master request, i.e. for the original URL. This is what
Chris@0 144 // allows us to e.g. generate a 404 response for the original URL; if we
Chris@0 145 // would execute a subrequest with the 404 route's URL, then it'd be
Chris@0 146 // generated for *that* URL, not the *original* URL.
Chris@0 147 $sub_request = clone $request;
Chris@0 148
Chris@0 149 // The routing to the 404 page should be done as GET request because it is
Chris@0 150 // restricted to GET and POST requests only. Otherwise a DELETE request
Chris@0 151 // would for example trigger a method not allowed exception.
Chris@0 152 $request_context = clone ($this->accessUnawareRouter->getContext());
Chris@0 153 $request_context->setMethod('GET');
Chris@0 154 $this->accessUnawareRouter->setContext($request_context);
Chris@0 155
Chris@0 156 $sub_request->attributes->add($this->accessUnawareRouter->match($url));
Chris@0 157
Chris@0 158 // Add to query (GET) or request (POST) parameters:
Chris@0 159 // - 'destination' (to ensure e.g. the login form in a 403 response
Chris@0 160 // redirects to the original URL)
Chris@0 161 // - '_exception_statuscode'
Chris@0 162 $parameters = $sub_request->isMethod('GET') ? $sub_request->query : $sub_request->request;
Chris@0 163 $parameters->add($this->redirectDestination->getAsArray() + ['_exception_statuscode' => $status_code]);
Chris@0 164
Chris@0 165 $response = $this->httpKernel->handle($sub_request, HttpKernelInterface::SUB_REQUEST);
Chris@0 166 // Only 2xx responses should have their status code overridden; any
Chris@0 167 // other status code should be passed on: redirects (3xx), error (5xx)…
Chris@0 168 // @see https://www.drupal.org/node/2603788#comment-10504916
Chris@0 169 if ($response->isSuccessful()) {
Chris@0 170 $response->setStatusCode($status_code);
Chris@0 171 }
Chris@0 172
Chris@0 173 // Persist any special HTTP headers that were set on the exception.
Chris@0 174 if ($exception instanceof HttpExceptionInterface) {
Chris@0 175 $response->headers->add($exception->getHeaders());
Chris@0 176 }
Chris@0 177
Chris@0 178 $event->setResponse($response);
Chris@0 179 }
Chris@0 180 catch (\Exception $e) {
Chris@0 181 // If an error happened in the subrequest we can't do much else. Instead,
Chris@0 182 // just log it. The DefaultExceptionSubscriber will catch the original
Chris@0 183 // exception and handle it normally.
Chris@0 184 $error = Error::decodeException($e);
Chris@0 185 $this->logger->log($error['severity_level'], '%type: @message in %function (line %line of %file).', $error);
Chris@0 186 }
Chris@0 187 }
Chris@0 188
Chris@0 189 }