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