/**
  * Log not-otherwise-specified errors, including HTTP 500.
  *
  * @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event
  *   The event to process.
  */
 public function onError(GetResponseForExceptionEvent $event)
 {
     $exception = $event->getException();
     $error = Error::decodeException($exception);
     $this->logger->get('php')->log($error['severity_level'], '%type: @message in %function (line %line of %file).', $error);
     $is_critical = !$exception instanceof HttpExceptionInterface || $exception->getStatusCode() >= 500;
     if ($is_critical) {
         error_log(sprintf('Uncaught PHP Exception %s: "%s" at %s line %s', get_class($exception), $exception->getMessage(), $exception->getFile(), $exception->getLine()));
     }
 }
Beispiel #2
0
 /**
  * Renders an exception error message without further exceptions.
  *
  * @param \Exception|\Throwable $exception
  *   The exception object that was thrown.
  *
  * @return string
  *   An error message.
  */
 public static function renderExceptionSafe($exception)
 {
     $decode = static::decodeException($exception);
     $backtrace = $decode['backtrace'];
     unset($decode['backtrace']);
     // Remove 'main()'.
     array_shift($backtrace);
     // Even though it is possible that this method is called on a public-facing
     // site, it is only called when the exception handler itself threw an
     // exception, which normally means that a code change caused the system to
     // no longer function correctly (as opposed to a user-triggered error), so
     // we assume that it is safe to include a verbose backtrace.
     $decode['@backtrace'] = Error::formatBacktrace($backtrace);
     return SafeMarkup::format('%type: @message in %function (line %line of %file). <pre class="backtrace">@backtrace</pre>', $decode);
 }
 /**
  * Checks for special handling of errors inside Simpletest.
  *
  * @todo The $headers array appears to not actually get used at all in the
  *   original code. It's quite possible that this entire method is now
  *   vestigial and can be removed.
  *
  * @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event
  */
 public function on500(GetResponseForExceptionEvent $event)
 {
     $exception = $event->getException();
     $error = Error::decodeException($exception);
     $headers = array();
     // When running inside the testing framework, we relay the errors
     // to the tested site by the way of HTTP headers.
     if (DRUPAL_TEST_IN_CHILD_SITE && !headers_sent() && (!defined('SIMPLETEST_COLLECT_ERRORS') || SIMPLETEST_COLLECT_ERRORS)) {
         // $number does not use drupal_static as it should not be reset
         // as it uniquely identifies each PHP error.
         static $number = 0;
         $assertion = array($error['@message'], $error['%type'], array('function' => $error['%function'], 'file' => $error['%file'], 'line' => $error['%line']));
         $headers['X-Drupal-Assertion-' . $number] = rawurlencode(serialize($assertion));
         $number++;
     }
 }
 /**
  * {@inheritdoc}
  */
 protected function execute(InputInterface $input, OutputInterface $output)
 {
     $io = new DrupalStyle($input, $output);
     $state = $this->getService('state');
     $io->info($this->trans('commands.site.maintenance.messages.maintenance-on'));
     $io->info($this->trans('commands.update.entities.messages.start'));
     $state->set('system.maintenance_mode', true);
     try {
         $this->getService('entity.definition_update_manager')->applyUpdates();
     } catch (EntityStorageException $e) {
         $variables = Error::decodeException($e);
         $io->info($this->trans('commands.update.entities.messages.error'));
         $io->info($variables);
     }
     $state->set('system.maintenance_mode', false);
     $io->info($this->trans('commands.update.entities.messages.end'));
     $this->getChain()->addCommand('cache:rebuild', ['cache' => 'all']);
     $io->info($this->trans('commands.site.maintenance.messages.maintenance-off'));
 }
Beispiel #5
0
 /**
  * {@inheritdoc}
  */
 public function write($sid, $value)
 {
     // The exception handler is not active at this point, so we need to do it
     // manually.
     try {
         $request = $this->requestStack->getCurrentRequest();
         $fields = array('uid' => $request->getSession()->get('uid', 0), 'hostname' => $request->getClientIP(), 'session' => $value, 'timestamp' => REQUEST_TIME);
         $this->connection->merge('sessions')->keys(array('sid' => Crypt::hashBase64($sid)))->fields($fields)->execute();
         return TRUE;
     } catch (\Exception $exception) {
         require_once DRUPAL_ROOT . '/core/includes/errors.inc';
         // If we are displaying errors, then do so with no possibility of a
         // further uncaught exception being thrown.
         if (error_displayable()) {
             print '<h1>Uncaught exception thrown in session handler.</h1>';
             print '<p>' . Error::renderExceptionSafe($exception) . '</p><hr />';
         }
         return FALSE;
     }
 }
 /**
  * {@inheritdoc}
  *
  * HTTP middleware that replaces the user agent for simpletest requests.
  */
 public function __invoke()
 {
     // If the database prefix is being used by SimpleTest to run the tests in a copied
     // database then set the user-agent header to the database prefix so that any
     // calls to other Drupal pages will run the SimpleTest prefixed database. The
     // user-agent is used to ensure that multiple testing sessions running at the
     // same time won't interfere with each other as they would if the database
     // prefix were stored statically in a file or database variable.
     return function ($handler) {
         return function (RequestInterface $request, array $options) use($handler) {
             if ($test_prefix = drupal_valid_test_ua()) {
                 $request = $request->withHeader('User-Agent', drupal_generate_test_ua($test_prefix));
             }
             return $handler($request, $options)->then(function (ResponseInterface $response) use($request) {
                 if (!drupal_valid_test_ua()) {
                     return $response;
                 }
                 $headers = $response->getHeaders();
                 foreach ($headers as $header_name => $header_values) {
                     if (preg_match('/^X-Drupal-Assertion-[0-9]+$/', $header_name, $matches)) {
                         foreach ($header_values as $header_value) {
                             // Call \Drupal\simpletest\WebTestBase::error() with the parameters from
                             // the header.
                             $parameters = unserialize(urldecode($header_value));
                             if (count($parameters) === 3) {
                                 throw new \Exception($parameters[1] . ': ' . $parameters[0] . "\n" . Error::formatBacktrace([$parameters[2]]));
                             } else {
                                 throw new \Exception('Error thrown with the wrong amount of parameters.');
                             }
                         }
                     }
                 }
                 return $response;
             });
         };
     };
 }
Beispiel #7
0
 /**
  * Takes an Exception object and both saves and displays it.
  *
  * Pulls in additional information on the location triggering the exception.
  *
  * @param \Exception $exception
  *   Object representing the exception.
  * @param bool $save
  *   (optional) Whether to save the message in the migration's mapping table.
  *   Set to FALSE in contexts where this doesn't make sense.
  */
 protected function handleException(\Exception $exception, $save = TRUE)
 {
     $result = Error::decodeException($exception);
     $message = $result['@message'] . ' (' . $result['%file'] . ':' . $result['%line'] . ')';
     if ($save) {
         $this->saveMessage($message);
     }
     $this->message->display($message, 'error');
 }
 /**
  * This method is a temporary port of _drupal_decode_exception().
  *
  * @todo This should get refactored. FlattenException could use some
  *   improvement as well.
  *
  * @param \Symfony\Component\Debug\Exception\FlattenException $exception
  *  The flattened exception.
  *
  * @return array
  *   An array of string-substitution tokens for formatting a message about the
  *   exception.
  */
 protected function decodeException(FlattenException $exception)
 {
     $message = $exception->getMessage();
     $backtrace = $exception->getTrace();
     // This value is missing from the stack for some reason in the
     // FlattenException version of the backtrace.
     $backtrace[0]['line'] = $exception->getLine();
     // For database errors, we try to return the initial caller,
     // skipping internal functions of the database layer.
     if (strpos($exception->getClass(), 'DatabaseExceptionWrapper') !== FALSE) {
         // A DatabaseExceptionWrapper exception is actually just a courier for
         // the original PDOException.  It's the stack trace from that exception
         // that we care about.
         $backtrace = $exception->getPrevious()->getTrace();
         $backtrace[0]['line'] = $exception->getLine();
         // The first element in the stack is the call, the second element gives us the caller.
         // We skip calls that occurred in one of the classes of the database layer
         // or in one of its global functions.
         $db_functions = array('db_query', 'db_query_range');
         while (!empty($backtrace[1]) && ($caller = $backtrace[1]) && (strpos($caller['namespace'], 'Drupal\\Core\\Database') !== FALSE || strpos($caller['class'], 'PDO') !== FALSE) || in_array($caller['function'], $db_functions)) {
             // We remove that call.
             array_shift($backtrace);
         }
     }
     $caller = Error::getLastCaller($backtrace);
     return array('%type' => $exception->getClass(), '!message' => String::checkPlain($message), '%function' => $caller['function'], '%file' => $caller['file'], '%line' => $caller['line'], 'severity_level' => WATCHDOG_ERROR);
 }
 /**
  * Handle exceptions.
  *
  * @see set_exception_handler
  */
 protected function exceptionHandler($exception)
 {
     $backtrace = $exception->getTrace();
     $verbose_backtrace = $backtrace;
     // Push on top of the backtrace the call that generated the exception.
     array_unshift($backtrace, array('line' => $exception->getLine(), 'file' => $exception->getFile()));
     $decoded_exception = Error::decodeException($exception);
     unset($decoded_exception['backtrace']);
     $message = SafeMarkup::format('%type: @message in %function (line %line of %file). <pre class="backtrace">@backtrace</pre>', $decoded_exception + array('@backtrace' => Error::formatBacktrace($verbose_backtrace)));
     $this->error($message, 'Uncaught exception', Error::getLastCaller($backtrace));
 }
 /**
  * {@inheritdoc}
  */
 public function write($sid, $value)
 {
     global $user;
     // The exception handler is not active at this point, so we need to do it
     // manually.
     try {
         if (!$this->sessionManager->isEnabled()) {
             // We don't have anything to do if we are not allowed to save the
             // session.
             return TRUE;
         }
         // Either ssid or sid or both will be added from $key below.
         $fields = array('uid' => $user->id(), 'hostname' => $this->requestStack->getCurrentRequest()->getClientIP(), 'session' => $value, 'timestamp' => REQUEST_TIME);
         // Use the session ID as 'sid' and an empty string as 'ssid' by default.
         // read() does not allow empty strings so that's a safe default.
         $key = array('sid' => Crypt::hashBase64($sid), 'ssid' => '');
         // On HTTPS connections, use the session ID as both 'sid' and 'ssid'.
         if ($this->requestStack->getCurrentRequest()->isSecure()) {
             $key['ssid'] = $key['sid'];
             // The "secure pages" setting allows a site to simultaneously use both
             // secure and insecure session cookies. If enabled and both cookies
             // are presented then use both keys. The session ID from the cookie is
             // hashed before being stored in the database as a security measure.
             if ($this->sessionManager->isMixedMode()) {
                 $insecure_session_name = $this->sessionManager->getInsecureName();
                 $cookies = $this->requestStack->getCurrentRequest()->cookies;
                 if ($cookies->has($insecure_session_name)) {
                     $key['sid'] = Crypt::hashBase64($cookies->get($insecure_session_name));
                 }
             }
         } elseif ($this->sessionManager->isMixedMode()) {
             unset($key['ssid']);
         }
         $this->connection->merge('sessions')->keys($key)->fields($fields)->execute();
         // Remove obsolete sessions.
         $this->cleanupObsoleteSessions();
         // Likewise, do not update access time more than once per 180 seconds.
         if ($user->isAuthenticated() && REQUEST_TIME - $user->getLastAccessedTime() > Settings::get('session_write_interval', 180)) {
             /** @var \Drupal\user\UserStorageInterface $storage */
             $storage = \Drupal::entityManager()->getStorage('user');
             $storage->updateLastAccessTimestamp($user, REQUEST_TIME);
         }
         return TRUE;
     } catch (\Exception $exception) {
         require_once DRUPAL_ROOT . '/core/includes/errors.inc';
         // If we are displaying errors, then do so with no possibility of a
         // further uncaught exception being thrown.
         if (error_displayable()) {
             print '<h1>Uncaught exception thrown in session handler.</h1>';
             print '<p>' . Error::renderExceptionSafe($exception) . '</p><hr />';
         }
         return FALSE;
     }
 }
 /**
  * Makes a subrequest to retrieve the default error page.
  *
  * @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event
  *   The event to process
  * @param string $url
  *   The path/url to which to make a subrequest for this error message.
  * @param int $status_code
  *   The status code for the error being handled.
  */
 protected function makeSubrequest(GetResponseForExceptionEvent $event, $url, $status_code)
 {
     $request = $event->getRequest();
     $exception = $event->getException();
     if (!($url && $url[0] == '/')) {
         $url = $request->getBasePath() . '/' . $url;
     }
     $current_url = $request->getBasePath() . $request->getPathInfo();
     if ($url != $request->getBasePath() . '/' && $url != $current_url) {
         if ($request->getMethod() === 'POST') {
             $sub_request = Request::create($url, 'POST', $this->redirectDestination->getAsArray() + ['_exception_statuscode' => $status_code] + $request->request->all(), $request->cookies->all(), [], $request->server->all());
         } else {
             $sub_request = Request::create($url, 'GET', $request->query->all() + $this->redirectDestination->getAsArray() + ['_exception_statuscode' => $status_code], $request->cookies->all(), [], $request->server->all());
         }
         try {
             // Persist the 'exception' attribute to the subrequest.
             $sub_request->attributes->set('exception', $request->attributes->get('exception'));
             // Persist the access result attribute to the subrequest, so that the
             // error page inherits the access result of the master request.
             $sub_request->attributes->set(AccessAwareRouterInterface::ACCESS_RESULT, $request->attributes->get(AccessAwareRouterInterface::ACCESS_RESULT));
             // Carry over the session to the subrequest.
             if ($session = $request->getSession()) {
                 $sub_request->setSession($session);
             }
             $response = $this->httpKernel->handle($sub_request, HttpKernelInterface::SUB_REQUEST);
             // Only 2xx responses should have their status code overridden; any
             // other status code should be passed on: redirects (3xx), error (5xx)…
             // @see https://www.drupal.org/node/2603788#comment-10504916
             if ($response->isSuccessful()) {
                 $response->setStatusCode($status_code);
             }
             // Persist any special HTTP headers that were set on the exception.
             if ($exception instanceof HttpExceptionInterface) {
                 $response->headers->add($exception->getHeaders());
             }
             $event->setResponse($response);
         } catch (\Exception $e) {
             // If an error happened in the subrequest we can't do much else. Instead,
             // just log it. The DefaultExceptionSubscriber will catch the original
             // exception and handle it normally.
             $error = Error::decodeException($e);
             $this->logger->log($error['severity_level'], '%type: @message in %function (line %line of %file).', $error);
         }
     }
 }
 /**
  * Makes a subrequest to retrieve the default error page.
  *
  * @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event
  *   The event to process.
  * @param string $url
  *   The path/url to which to make a subrequest for this error message.
  * @param int $status_code
  *   The status code for the error being handled.
  */
 protected function makeSubrequest(GetResponseForExceptionEvent $event, $url, $status_code)
 {
     $request = $event->getRequest();
     $exception = $event->getException();
     try {
         // Reuse the exact same request (so keep the same URL, keep the access
         // result, the exception, et cetera) but override the routing information.
         // This means that aside from routing, this is identical to the master
         // request. This allows us to generate a response that is executed on
         // behalf of the master request, i.e. for the original URL. This is what
         // allows us to e.g. generate a 404 response for the original URL; if we
         // would execute a subrequest with the 404 route's URL, then it'd be
         // generated for *that* URL, not the *original* URL.
         $sub_request = clone $request;
         $sub_request->attributes->add($this->accessUnawareRouter->match($url));
         // Add to query (GET) or request (POST) parameters:
         // - 'destination' (to ensure e.g. the login form in a 403 response
         //   redirects to the original URL)
         // - '_exception_statuscode'
         $parameters = $sub_request->isMethod('GET') ? $sub_request->query : $sub_request->request;
         $parameters->add($this->redirectDestination->getAsArray() + ['_exception_statuscode' => $status_code]);
         $response = $this->httpKernel->handle($sub_request, HttpKernelInterface::SUB_REQUEST);
         // Only 2xx responses should have their status code overridden; any
         // other status code should be passed on: redirects (3xx), error (5xx)…
         // @see https://www.drupal.org/node/2603788#comment-10504916
         if ($response->isSuccessful()) {
             $response->setStatusCode($status_code);
         }
         // Persist any special HTTP headers that were set on the exception.
         if ($exception instanceof HttpExceptionInterface) {
             $response->headers->add($exception->getHeaders());
         }
         $event->setResponse($response);
     } catch (\Exception $e) {
         // If an error happened in the subrequest we can't do much else. Instead,
         // just log it. The DefaultExceptionSubscriber will catch the original
         // exception and handle it normally.
         $error = Error::decodeException($e);
         $this->logger->log($error['severity_level'], '%type: @message in %function (line %line of %file).', $error);
     }
 }
 /**
  * {@inheritdoc}
  */
 public function write($sid, $value)
 {
     $user = \Drupal::currentUser();
     // The exception handler is not active at this point, so we need to do it
     // manually.
     try {
         $fields = array('uid' => $user->id(), 'hostname' => $this->requestStack->getCurrentRequest()->getClientIP(), 'session' => $value, 'timestamp' => REQUEST_TIME);
         $this->connection->merge('sessions')->keys(array('sid' => Crypt::hashBase64($sid)))->fields($fields)->execute();
         // Likewise, do not update access time more than once per 180 seconds.
         if ($user->isAuthenticated() && REQUEST_TIME - $user->getLastAccessedTime() > Settings::get('session_write_interval', 180)) {
             /** @var \Drupal\user\UserStorageInterface $storage */
             $storage = \Drupal::entityManager()->getStorage('user');
             $storage->updateLastAccessTimestamp($user, REQUEST_TIME);
         }
         return TRUE;
     } catch (\Exception $exception) {
         require_once DRUPAL_ROOT . '/core/includes/errors.inc';
         // If we are displaying errors, then do so with no possibility of a
         // further uncaught exception being thrown.
         if (error_displayable()) {
             print '<h1>Uncaught exception thrown in session handler.</h1>';
             print '<p>' . Error::renderExceptionSafe($exception) . '</p><hr />';
         }
         return FALSE;
     }
 }
Beispiel #14
0
 /**
  * Handle exceptions.
  *
  * @see set_exception_handler
  */
 protected function exceptionHandler($exception)
 {
     require_once DRUPAL_ROOT . '/core/includes/errors.inc';
     $backtrace = $exception->getTrace();
     $verbose_backtrace = $backtrace;
     // Push on top of the backtrace the call that generated the exception.
     array_unshift($backtrace, array('line' => $exception->getLine(), 'file' => $exception->getFile()));
     // \Drupal\Core\Utility\Error::decodeException() runs the exception
     // message through \Drupal\Component\Utility\String::checkPlain().
     $decoded_exception = Error::decodeException($exception);
     unset($decoded_exception['backtrace']);
     $message = String::format('%type: !message in %function (line %line of %file). <pre class="backtrace">!backtrace</pre>', $decoded_exception + array('!backtrace' => Error::formatBacktrace($verbose_backtrace)));
     $this->error($message, 'Uncaught exception', Error::getLastCaller($backtrace));
 }
 /**
  * Makes a subrequest to retrieve the default error page.
  *
  * @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event
  *   The event to process
  * @param string $path
  *   The path to which to make a subrequest for this error message.
  * @param int $status_code
  *   The status code for the error being handled.
  */
 protected function makeSubrequest(GetResponseForExceptionEvent $event, $path, $status_code)
 {
     $request = $event->getRequest();
     // @todo Remove dependency on the internal _system_path attribute:
     //   https://www.drupal.org/node/2293523.
     $system_path = $request->attributes->get('_system_path');
     if ($path && $path != $system_path) {
         if ($request->getMethod() === 'POST') {
             $sub_request = Request::create($request->getBaseUrl() . '/' . $path, 'POST', ['destination' => $system_path, '_exception_statuscode' => $status_code] + $request->request->all(), $request->cookies->all(), [], $request->server->all());
         } else {
             $sub_request = Request::create($request->getBaseUrl() . '/' . $path, 'GET', $request->query->all() + ['destination' => $system_path, '_exception_statuscode' => $status_code], $request->cookies->all(), [], $request->server->all());
         }
         try {
             // Persist the 'exception' attribute to the subrequest.
             $sub_request->attributes->set('exception', $request->attributes->get('exception'));
             $response = $this->httpKernel->handle($sub_request, HttpKernelInterface::SUB_REQUEST);
             $response->setStatusCode($status_code);
             $event->setResponse($response);
         } catch (\Exception $e) {
             // If an error happened in the subrequest we can't do much else. Instead,
             // just log it. The DefaultExceptionSubscriber will catch the original
             // exception and handle it normally.
             $error = Error::decodeException($e);
             $this->logger->log($error['severity_level'], '%type: !message in %function (line %line of %file).', $error);
         }
     }
 }
 /**
  * Handles any exception as a generic error page for JSON.
  *
  * @todo This should probably check the error reporting level.
  *
  * @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event
  *   The event to process.
  */
 protected function onJson(GetResponseForExceptionEvent $event)
 {
     $exception = $event->getException();
     $error = Error::decodeException($exception);
     // Display the message if the current error reporting level allows this type
     // of message to be displayed,
     $data = NULL;
     if (error_displayable($error) && ($message = $exception->getMessage())) {
         $data = ['message' => sprintf('A fatal error occurred: %s', $message)];
     }
     $response = new JsonResponse($data, Response::HTTP_INTERNAL_SERVER_ERROR);
     if ($exception instanceof HttpExceptionInterface) {
         $response->setStatusCode($exception->getStatusCode());
         $response->headers->add($exception->getHeaders());
     }
     $event->setResponse($response);
 }
Beispiel #17
0
 /**
  * Tests the formatBacktrace() method.
  *
  * @param array $backtrace
  *   The test backtrace array.
  * @param array $expected
  *   The expected return array.
  *
  * @dataProvider providerTestFormatBacktrace
  */
 public function testFormatBacktrace($backtrace, $expected)
 {
     $this->assertSame($expected, Error::formatBacktrace($backtrace));
 }
 /**
  * Makes a subrequest to retrieve the default error page.
  *
  * @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event
  *   The event to process
  * @param string $url
  *   The path/url to which to make a subrequest for this error message.
  * @param int $status_code
  *   The status code for the error being handled.
  */
 protected function makeSubrequest(GetResponseForExceptionEvent $event, $url, $status_code)
 {
     $request = $event->getRequest();
     if (!($url && $url[0] == '/')) {
         $url = $request->getBasePath() . '/' . $url;
     }
     $current_url = $request->getBasePath() . $request->getPathInfo();
     if ($url != $request->getBasePath() . '/' && $url != $current_url) {
         if ($request->getMethod() === 'POST') {
             $sub_request = Request::create($url, 'POST', $this->drupalGetDestination() + ['_exception_statuscode' => $status_code] + $request->request->all(), $request->cookies->all(), [], $request->server->all());
         } else {
             $sub_request = Request::create($url, 'GET', $request->query->all() + $this->drupalGetDestination() + ['_exception_statuscode' => $status_code], $request->cookies->all(), [], $request->server->all());
         }
         try {
             // Persist the 'exception' attribute to the subrequest.
             $sub_request->attributes->set('exception', $request->attributes->get('exception'));
             // Carry over the session to the subrequest.
             if ($session = $request->getSession()) {
                 $sub_request->setSession($session);
             }
             $response = $this->httpKernel->handle($sub_request, HttpKernelInterface::SUB_REQUEST);
             $response->setStatusCode($status_code);
             $event->setResponse($response);
         } catch (\Exception $e) {
             // If an error happened in the subrequest we can't do much else. Instead,
             // just log it. The DefaultExceptionSubscriber will catch the original
             // exception and handle it normally.
             $error = Error::decodeException($e);
             $this->logger->log($error['severity_level'], '%type: !message in %function (line %line of %file).', $error);
         }
     }
 }