Security update for Core, with self-updated composer
[yaffs-website] / web / core / lib / Drupal / Core / EventSubscriber / FinalExceptionSubscriber.php
1 <?php
2
3 namespace Drupal\Core\EventSubscriber;
4
5 use Drupal\Component\Utility\SafeMarkup;
6 use Drupal\Core\Config\ConfigFactoryInterface;
7 use Drupal\Core\StringTranslation\StringTranslationTrait;
8 use Drupal\Core\Utility\Error;
9 use Symfony\Component\EventDispatcher\EventSubscriberInterface;
10 use Symfony\Component\HttpFoundation\Response;
11 use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
12 use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
13 use Symfony\Component\HttpKernel\KernelEvents;
14
15 /**
16  * Last-chance handler for exceptions: the final exception subscriber.
17  *
18  * This handler will catch any exceptions not caught elsewhere and report
19  * them as an error page.
20  *
21  * Each format has its own way of handling exceptions:
22  * - html: exception.default_html, exception.custom_page_html and
23  *   exception.fast_404_html
24  * - json: exception.default_json
25  *
26  * And when the serialization module is installed, all serialization formats are
27  * handled by a single exception subscriber:: serialization.exception.default.
28  *
29  * This exception subscriber runs after all the above (it has a lower priority),
30  * which makes it the last-chance exception handler. It always sends a plain
31  * text response. If it's a displayable error and the error level is configured
32  * to be verbose, then a helpful backtrace is also printed.
33  */
34 class FinalExceptionSubscriber implements EventSubscriberInterface {
35   use StringTranslationTrait;
36
37   /**
38    * @var string
39    *
40    * One of the error level constants defined in bootstrap.inc.
41    */
42   protected $errorLevel;
43
44   /**
45    * The config factory.
46    *
47    * @var \Drupal\Core\Config\ConfigFactoryInterface
48    */
49   protected $configFactory;
50
51   /**
52    * Constructs a new FinalExceptionSubscriber.
53    *
54    * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
55    *   The configuration factory.
56    */
57   public function __construct(ConfigFactoryInterface $config_factory) {
58     $this->configFactory = $config_factory;
59   }
60
61   /**
62    * Gets the configured error level.
63    *
64    * @return string
65    */
66   protected function getErrorLevel() {
67     if (!isset($this->errorLevel)) {
68       $this->errorLevel = $this->configFactory->get('system.logging')->get('error_level');
69     }
70     return $this->errorLevel;
71   }
72
73   /**
74    * Handles exceptions for this subscriber.
75    *
76    * @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event
77    *   The event to process.
78    */
79   public function onException(GetResponseForExceptionEvent $event) {
80     $exception = $event->getException();
81     $error = Error::decodeException($exception);
82
83     // Display the message if the current error reporting level allows this type
84     // of message to be displayed, and unconditionally in update.php.
85     $message = '';
86     if ($this->isErrorDisplayable($error)) {
87       // If error type is 'User notice' then treat it as debug information
88       // instead of an error message.
89       // @see debug()
90       if ($error['%type'] == 'User notice') {
91         $error['%type'] = 'Debug';
92       }
93
94       $error = $this->simplifyFileInError($error);
95
96       unset($error['backtrace']);
97
98       if (!$this->isErrorLevelVerbose()) {
99         // Without verbose logging, use a simple message.
100
101         // We call SafeMarkup::format directly here, rather than use t() since
102         // we are in the middle of error handling, and we don't want t() to
103         // cause further errors.
104         $message = SafeMarkup::format('%type: @message in %function (line %line of %file).', $error);
105       }
106       else {
107         // With verbose logging, we will also include a backtrace.
108
109         $backtrace_exception = $exception;
110         while ($backtrace_exception->getPrevious()) {
111           $backtrace_exception = $backtrace_exception->getPrevious();
112         }
113         $backtrace = $backtrace_exception->getTrace();
114         // First trace is the error itself, already contained in the message.
115         // While the second trace is the error source and also contained in the
116         // message, the message doesn't contain argument values, so we output it
117         // once more in the backtrace.
118         array_shift($backtrace);
119
120         // Generate a backtrace containing only scalar argument values.
121         $error['@backtrace'] = Error::formatBacktrace($backtrace);
122         $message = SafeMarkup::format('%type: @message in %function (line %line of %file). <pre class="backtrace">@backtrace</pre>', $error);
123       }
124     }
125
126     $content = $this->t('The website encountered an unexpected error. Please try again later.');
127     $content .= $message ? '</br></br>' . $message : '';
128     $response = new Response($content, 500, ['Content-Type' => 'text/plain']);
129
130     if ($exception instanceof HttpExceptionInterface) {
131       $response->setStatusCode($exception->getStatusCode());
132       $response->headers->add($exception->getHeaders());
133     }
134     else {
135       $response->setStatusCode(Response::HTTP_INTERNAL_SERVER_ERROR, '500 Service unavailable (with message)');
136     }
137
138     $event->setResponse($response);
139   }
140
141   /**
142    * {@inheritdoc}
143    */
144   public static function getSubscribedEvents() {
145     // Run as the final (very late) KernelEvents::EXCEPTION subscriber.
146     $events[KernelEvents::EXCEPTION][] = ['onException', -256];
147     return $events;
148   }
149
150   /**
151    * Checks whether the error level is verbose or not.
152    *
153    * @return bool
154    */
155   protected function isErrorLevelVerbose() {
156     return $this->getErrorLevel() === ERROR_REPORTING_DISPLAY_VERBOSE;
157   }
158
159   /**
160    * Wrapper for error_displayable().
161    *
162    * @param $error
163    *   Optional error to examine for ERROR_REPORTING_DISPLAY_SOME.
164    *
165    * @return bool
166    *
167    * @see \error_displayable
168    */
169   protected function isErrorDisplayable($error) {
170     return error_displayable($error);
171   }
172
173   /**
174    * Attempts to reduce error verbosity in the error message's file path.
175    *
176    * Attempts to reduce verbosity by removing DRUPAL_ROOT from the file path in
177    * the message. This does not happen for (false) security.
178    *
179    * @param $error
180    *   Optional error to examine for ERROR_REPORTING_DISPLAY_SOME.
181    *
182    * @return
183    *   The updated $error.
184    */
185   protected function simplifyFileInError($error) {
186     // Attempt to reduce verbosity by removing DRUPAL_ROOT from the file path
187     // in the message. This does not happen for (false) security.
188     $root_length = strlen(DRUPAL_ROOT);
189     if (substr($error['%file'], 0, $root_length) == DRUPAL_ROOT) {
190       $error['%file'] = substr($error['%file'], $root_length + 1);
191     }
192     return $error;
193   }
194
195 }