diff --git a/src/Symfony/Component/Debug/ExceptionHandler.php b/src/Symfony/Component/Debug/ExceptionHandler.php index 2be65ab6fe46d..cda0bd22ca7bc 100644 --- a/src/Symfony/Component/Debug/ExceptionHandler.php +++ b/src/Symfony/Component/Debug/ExceptionHandler.php @@ -15,7 +15,7 @@ use Symfony\Component\Debug\Exception\OutOfMemoryException; use Symfony\Component\HttpKernel\Debug\FileLinkFormatter; -@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.4, use "%s" instead.', ExceptionHandler::class, \Symfony\Component\ErrorHandler\ExceptionHandler::class), E_USER_DEPRECATED); +@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.4, use "%s" instead.', ExceptionHandler::class, \Symfony\Component\ErrorHandler\ErrorHandler::class), E_USER_DEPRECATED); /** * ExceptionHandler converts an exception to a Response object. @@ -31,7 +31,7 @@ * * @final since Symfony 4.3 * - * @deprecated since Symfony 4.4, use Symfony\Component\ErrorHandler\ExceptionHandler instead. + * @deprecated since Symfony 4.4, use Symfony\Component\ErrorHandler\ErrorHandler instead. */ class ExceptionHandler { diff --git a/src/Symfony/Component/ErrorHandler/Debug.php b/src/Symfony/Component/ErrorHandler/Debug.php index 392abdb475288..e3bb55c0a0741 100644 --- a/src/Symfony/Component/ErrorHandler/Debug.php +++ b/src/Symfony/Component/ErrorHandler/Debug.php @@ -44,7 +44,6 @@ public static function enable($errorReportingLevel = E_ALL, $displayErrors = tru if (!\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true)) { ini_set('display_errors', 0); - ExceptionHandler::register(); } elseif ($displayErrors && (!filter_var(ini_get('log_errors'), FILTER_VALIDATE_BOOLEAN) || ini_get('error_log'))) { // CLI - display errors only if they're not already logged to STDERR ini_set('display_errors', 1); diff --git a/src/Symfony/Component/ErrorHandler/ErrorHandler.php b/src/Symfony/Component/ErrorHandler/ErrorHandler.php index 2a2a8ca384bab..fa54ac90ac588 100644 --- a/src/Symfony/Component/ErrorHandler/ErrorHandler.php +++ b/src/Symfony/Component/ErrorHandler/ErrorHandler.php @@ -21,7 +21,6 @@ use Symfony\Component\ErrorHandler\FatalErrorHandler\FatalErrorHandlerInterface; use Symfony\Component\ErrorHandler\FatalErrorHandler\UndefinedFunctionFatalErrorHandler; use Symfony\Component\ErrorHandler\FatalErrorHandler\UndefinedMethodFatalErrorHandler; -use Symfony\Component\ErrorRenderer\Exception\FlattenException; /** * A generic ErrorHandler for the PHP engine. @@ -414,7 +413,7 @@ public function handleError($type, $message, $file, $line) } if (false !== strpos($message, "class@anonymous\0")) { - $logMessage = $this->levels[$type].': '.(new FlattenException())->setMessage($message)->getMessage(); + $logMessage = $this->parseAnonymousClass($message); } else { $logMessage = $this->levels[$type].': '.$message; } @@ -539,7 +538,7 @@ public function handleException($exception, array $error = null) if (($this->loggedErrors & $type) || $exception instanceof FatalThrowableError) { if (false !== strpos($message = $exception->getMessage(), "class@anonymous\0")) { - $message = (new FlattenException())->setMessage($message)->getMessage(); + $message = $this->parseAnonymousClass($message); } if ($exception instanceof FatalErrorException) { if ($exception instanceof FatalThrowableError) { @@ -712,4 +711,15 @@ private function cleanTrace($backtrace, $type, $file, $line, $throw) return $lightTrace; } + + /** + * Parse the error message by removing the anonymous class notation + * and using the parent class instead if possible. + */ + private function parseAnonymousClass(string $message): string + { + return preg_replace_callback('/class@anonymous\x00.*?\.php0x?[0-9a-fA-F]++/', static function ($m) { + return class_exists($m[0], false) ? get_parent_class($m[0]).'@anonymous' : $m[0]; + }, $message); + } } diff --git a/src/Symfony/Component/ErrorHandler/ExceptionHandler.php b/src/Symfony/Component/ErrorHandler/ExceptionHandler.php deleted file mode 100644 index 577234a8d9b59..0000000000000 --- a/src/Symfony/Component/ErrorHandler/ExceptionHandler.php +++ /dev/null @@ -1,187 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\ErrorHandler; - -use Symfony\Component\ErrorHandler\Exception\OutOfMemoryException; -use Symfony\Component\ErrorRenderer\ErrorRenderer\HtmlErrorRenderer; -use Symfony\Component\ErrorRenderer\Exception\FlattenException; -use Symfony\Component\HttpKernel\Debug\FileLinkFormatter; - -/** - * ExceptionHandler converts an exception to a Response object. - * - * It is mostly useful in debug mode to replace the default PHP/XDebug - * output with something prettier and more useful. - * - * As this class is mainly used during Kernel boot, where nothing is yet - * available, the Response content is always HTML. - * - * @author Fabien Potencier - * @author Nicolas Grekas - * - * @final since Symfony 4.3 - */ -class ExceptionHandler -{ - private $debug; - private $charset; - private $handler; - private $caughtBuffer; - private $caughtLength; - private $fileLinkFormat; - private $htmlErrorRenderer; - - public function __construct(bool $debug = true, string $charset = null, $fileLinkFormat = null) - { - $this->debug = $debug; - $this->charset = $charset ?: ini_get('default_charset') ?: 'UTF-8'; - $this->fileLinkFormat = $fileLinkFormat; - } - - /** - * Registers the exception handler. - * - * @param bool $debug Enable/disable debug mode, where the stack trace is displayed - * @param string|null $charset The charset used by exception messages - * @param string|null $fileLinkFormat The IDE link template - * - * @return static - */ - public static function register($debug = true, $charset = null, $fileLinkFormat = null) - { - $handler = new static($debug, $charset, $fileLinkFormat); - - $prev = set_exception_handler([$handler, 'handle']); - if (\is_array($prev) && $prev[0] instanceof ErrorHandler) { - restore_exception_handler(); - $prev[0]->setExceptionHandler([$handler, 'handle']); - } - - return $handler; - } - - /** - * Sets a user exception handler. - * - * @param callable $handler An handler that will be called on Exception - * - * @return callable|null The previous exception handler if any - */ - public function setHandler(callable $handler = null) - { - $old = $this->handler; - $this->handler = $handler; - - return $old; - } - - /** - * Sets the format for links to source files. - * - * @param string|FileLinkFormatter $fileLinkFormat The format for links to source files - * - * @return string The previous file link format - */ - public function setFileLinkFormat($fileLinkFormat) - { - $old = $this->fileLinkFormat; - $this->fileLinkFormat = $fileLinkFormat; - - return $old; - } - - /** - * Sends a response for the given Exception. - * - * To be as fail-safe as possible, the exception is first handled - * by our simple exception handler, then by the user exception handler. - * The latter takes precedence and any output from the former is cancelled, - * if and only if nothing bad happens in this handling path. - */ - public function handle(\Exception $exception) - { - if (null === $this->handler || $exception instanceof OutOfMemoryException) { - $this->sendPhpResponse($exception); - - return; - } - - $caughtLength = $this->caughtLength = 0; - - ob_start(function ($buffer) { - $this->caughtBuffer = $buffer; - - return ''; - }); - - $this->sendPhpResponse($exception); - while (null === $this->caughtBuffer && ob_end_flush()) { - // Empty loop, everything is in the condition - } - if (isset($this->caughtBuffer[0])) { - ob_start(function ($buffer) { - if ($this->caughtLength) { - // use substr_replace() instead of substr() for mbstring overloading resistance - $cleanBuffer = substr_replace($buffer, '', 0, $this->caughtLength); - if (isset($cleanBuffer[0])) { - $buffer = $cleanBuffer; - } - } - - return $buffer; - }); - - echo $this->caughtBuffer; - $caughtLength = ob_get_length(); - } - $this->caughtBuffer = null; - - try { - ($this->handler)($exception); - $this->caughtLength = $caughtLength; - } catch (\Exception $e) { - if (!$caughtLength) { - // All handlers failed. Let PHP handle that now. - throw $exception; - } - } - } - - /** - * Sends the error associated with the given Exception as a plain PHP response. - * - * This method uses plain PHP functions like header() and echo to output - * the response. - * - * @param \Throwable|FlattenException $exception A \Throwable or FlattenException instance - */ - public function sendPhpResponse($exception) - { - if ($exception instanceof \Throwable) { - $exception = FlattenException::createFromThrowable($exception); - } - - if (!headers_sent()) { - header(sprintf('HTTP/1.0 %s', $exception->getStatusCode())); - foreach ($exception->getHeaders() as $name => $value) { - header($name.': '.$value, false); - } - header('Content-Type: text/html; charset='.$this->charset); - } - - if (null === $this->htmlErrorRenderer) { - $this->htmlErrorRenderer = new HtmlErrorRenderer($this->debug, $this->charset, $this->fileLinkFormat); - } - - echo $this->htmlErrorRenderer->render($exception); - } -} diff --git a/src/Symfony/Component/ErrorHandler/Tests/ExceptionHandlerTest.php b/src/Symfony/Component/ErrorHandler/Tests/ExceptionHandlerTest.php deleted file mode 100644 index db01374b9170c..0000000000000 --- a/src/Symfony/Component/ErrorHandler/Tests/ExceptionHandlerTest.php +++ /dev/null @@ -1,144 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\ErrorHandler\Tests; - -use PHPUnit\Framework\TestCase; -use Symfony\Component\ErrorHandler\Exception\OutOfMemoryException; -use Symfony\Component\ErrorHandler\ExceptionHandler; -use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException; -use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; - -require_once __DIR__.'/HeaderMock.php'; - -class ExceptionHandlerTest extends TestCase -{ - protected function setUp() - { - testHeader(); - } - - protected function tearDown() - { - testHeader(); - } - - /** - * @group legacy - */ - public function testDebug() - { - $handler = new ExceptionHandler(false); - - ob_start(); - $handler->sendPhpResponse(new \RuntimeException('Foo')); - $response = ob_get_clean(); - - $this->assertContains('The server returned a "500 Internal Server Error".', $response); - $this->assertNotContains('
', $response); - - $handler = new ExceptionHandler(true); - - ob_start(); - $handler->sendPhpResponse(new \RuntimeException('Foo')); - $response = ob_get_clean(); - - $this->assertContains('

Foo

', $response); - $this->assertContains('
', $response); - - // taken from https://www.owasp.org/index.php/Cross-site_Scripting_(XSS) - $htmlWithXss = ' click me! '; - ob_start(); - $handler->sendPhpResponse(new \RuntimeException($htmlWithXss)); - $response = ob_get_clean(); - - $this->assertContains(sprintf('

%s

', htmlspecialchars($htmlWithXss, ENT_COMPAT | ENT_SUBSTITUTE, 'UTF-8')), $response); - } - - public function testStatusCode() - { - $handler = new ExceptionHandler(false, 'iso8859-1'); - - ob_start(); - $handler->sendPhpResponse(new NotFoundHttpException('Foo')); - $response = ob_get_clean(); - - $this->assertContains('The server returned a "404 Not Found".', $response); - - $expectedHeaders = [ - ['HTTP/1.0 404', true, null], - ['Content-Type: text/html; charset=iso8859-1', true, null], - ]; - - $this->assertSame($expectedHeaders, testHeader()); - } - - public function testHeaders() - { - $handler = new ExceptionHandler(false, 'iso8859-1'); - - ob_start(); - $handler->sendPhpResponse(new MethodNotAllowedHttpException(['POST'])); - $response = ob_get_clean(); - - $expectedHeaders = [ - ['HTTP/1.0 405', true, null], - ['Allow: POST', false, null], - ['Content-Type: text/html; charset=iso8859-1', true, null], - ]; - - $this->assertSame($expectedHeaders, testHeader()); - } - - public function testNestedExceptions() - { - $handler = new ExceptionHandler(true); - ob_start(); - $handler->sendPhpResponse(new \RuntimeException('Foo', 0, new \RuntimeException('Bar'))); - $response = ob_get_clean(); - - $this->assertStringMatchesFormat('%A

Foo

%A

Bar

%A', $response); - } - - public function testHandle() - { - $exception = new \Exception('foo'); - - $handler = $this->getMockBuilder('Symfony\Component\ErrorHandler\ExceptionHandler')->setMethods(['sendPhpResponse'])->getMock(); - $handler - ->expects($this->exactly(2)) - ->method('sendPhpResponse'); - - $handler->handle($exception); - - $handler->setHandler(function ($e) use ($exception) { - $this->assertSame($exception, $e); - }); - - $handler->handle($exception); - } - - public function testHandleOutOfMemoryException() - { - $exception = new OutOfMemoryException('foo', 0, E_ERROR, __FILE__, __LINE__); - - $handler = $this->getMockBuilder('Symfony\Component\ErrorHandler\ExceptionHandler')->setMethods(['sendPhpResponse'])->getMock(); - $handler - ->expects($this->once()) - ->method('sendPhpResponse'); - - $handler->setHandler(function ($e) { - $this->fail('OutOfMemoryException should bypass the handler'); - }); - - $handler->handle($exception); - } -} diff --git a/src/Symfony/Component/ErrorHandler/Tests/MockExceptionHandler.php b/src/Symfony/Component/ErrorHandler/Tests/MockExceptionHandler.php deleted file mode 100644 index 700990de58ec6..0000000000000 --- a/src/Symfony/Component/ErrorHandler/Tests/MockExceptionHandler.php +++ /dev/null @@ -1,24 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\ErrorHandler\Tests; - -use Symfony\Component\ErrorHandler\ExceptionHandler; - -class MockExceptionHandler extends ExceptionHandler -{ - public $e; - - public function handle(\Exception $e) - { - $this->e = $e; - } -} diff --git a/src/Symfony/Component/ErrorHandler/composer.json b/src/Symfony/Component/ErrorHandler/composer.json index 3794930f35b9b..ece07645a5489 100644 --- a/src/Symfony/Component/ErrorHandler/composer.json +++ b/src/Symfony/Component/ErrorHandler/composer.json @@ -17,8 +17,7 @@ ], "require": { "php": "^7.1.3", - "psr/log": "~1.0", - "symfony/error-renderer": "^4.4|^5.0" + "psr/log": "~1.0" }, "conflict": { "symfony/http-kernel": "<3.4" diff --git a/src/Symfony/Component/HttpKernel/EventListener/DebugHandlersListener.php b/src/Symfony/Component/HttpKernel/EventListener/DebugHandlersListener.php index 4f7a54291e525..d9227d888baa3 100644 --- a/src/Symfony/Component/HttpKernel/EventListener/DebugHandlersListener.php +++ b/src/Symfony/Component/HttpKernel/EventListener/DebugHandlersListener.php @@ -16,7 +16,6 @@ use Symfony\Component\Console\Event\ConsoleEvent; use Symfony\Component\Console\Output\ConsoleOutputInterface; use Symfony\Component\ErrorHandler\ErrorHandler; -use Symfony\Component\ErrorHandler\ExceptionHandler; use Symfony\Component\ErrorRenderer\ErrorRenderer; use Symfony\Component\ErrorRenderer\ErrorRenderer\HtmlErrorRenderer; use Symfony\Component\ErrorRenderer\Exception\ErrorRendererNotFoundException; @@ -138,19 +137,7 @@ public function configure(Event $event = null) } if ($this->exceptionHandler) { if ($handler instanceof ErrorHandler) { - $h = $handler->setExceptionHandler('var_dump'); - if (\is_array($h) && $h[0] instanceof ExceptionHandler) { - $handler->setExceptionHandler($h); - $handler = $h[0]; - } else { - $handler->setExceptionHandler($this->exceptionHandler); - } - } - if ($handler instanceof ExceptionHandler) { - $handler->setHandler($this->exceptionHandler); - if (null !== $this->fileLinkFormat) { - $handler->setFileLinkFormat($this->fileLinkFormat); - } + $handler->setExceptionHandler($this->exceptionHandler); } $this->exceptionHandler = null; } diff --git a/src/Symfony/Component/HttpKernel/Tests/EventListener/DebugHandlersListenerTest.php b/src/Symfony/Component/HttpKernel/Tests/EventListener/DebugHandlersListenerTest.php index 746756cf01c69..f9c0f6b6b7300 100644 --- a/src/Symfony/Component/HttpKernel/Tests/EventListener/DebugHandlersListenerTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/EventListener/DebugHandlersListenerTest.php @@ -20,7 +20,6 @@ use Symfony\Component\Console\Input\ArgvInput; use Symfony\Component\Console\Output\ConsoleOutput; use Symfony\Component\ErrorHandler\ErrorHandler; -use Symfony\Component\ErrorHandler\ExceptionHandler; use Symfony\Component\EventDispatcher\EventDispatcher; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Event\KernelEvent; @@ -38,9 +37,7 @@ public function testConfigure() $logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock(); $userHandler = function () {}; $listener = new DebugHandlersListener($userHandler, $logger); - $xHandler = new ExceptionHandler(); $eHandler = new ErrorHandler(); - $eHandler->setExceptionHandler([$xHandler, 'handle']); $exception = null; set_error_handler([$eHandler, 'handleError']); @@ -56,7 +53,7 @@ public function testConfigure() throw $exception; } - $this->assertSame($userHandler, $xHandler->setHandler('var_dump')); + $this->assertSame($userHandler, $eHandler->setExceptionHandler('var_dump')); $loggers = $eHandler->setLoggers([]);