1<?php
2
3/*
4 * This file is part of the TYPO3 CMS project.
5 *
6 * It is free software; you can redistribute it and/or modify it under
7 * the terms of the GNU General Public License, either version 2
8 * of the License, or any later version.
9 *
10 * For the full copyright and license information, please read the
11 * LICENSE.txt file that was distributed with this source code.
12 *
13 * The TYPO3 project - inspiring people to share!
14 */
15
16namespace TYPO3\CMS\Core\Error;
17
18use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
19use TYPO3\CMS\Core\Controller\ErrorPageController;
20use TYPO3\CMS\Core\Error\Http\AbstractClientErrorException;
21use TYPO3\CMS\Core\Messaging\AbstractMessage;
22use TYPO3\CMS\Core\Utility\GeneralUtility;
23
24/**
25 * An exception handler which catches any exception and
26 * renders an error page without backtrace (Web) or a slim
27 * message on CLI.
28 */
29class ProductionExceptionHandler extends AbstractExceptionHandler
30{
31    /**
32     * Default title for error messages
33     *
34     * @var string
35     */
36    protected $defaultTitle = 'Oops, an error occurred!';
37
38    /**
39     * Default message for error messages
40     *
41     * @var string
42     */
43    protected $defaultMessage = '';
44
45    /**
46     * Constructs this exception handler - registers itself as the default exception handler.
47     */
48    public function __construct()
49    {
50        $callable = [$this, 'handleException'];
51        if (is_callable($callable)) {
52            set_exception_handler($callable);
53        }
54    }
55
56    /**
57     * Echoes an exception for the web.
58     *
59     * @param \Throwable $exception The throwable object.
60     */
61    public function echoExceptionWeb(\Throwable $exception)
62    {
63        $this->sendStatusHeaders($exception);
64        $this->writeLogEntries($exception, self::CONTEXT_WEB);
65        echo GeneralUtility::makeInstance(ErrorPageController::class)->errorAction(
66            $this->getTitle($exception),
67            $this->getMessage($exception),
68            AbstractMessage::ERROR,
69            $this->discloseExceptionInformation($exception) ? $exception->getCode() : 0,
70            503
71        );
72    }
73
74    /**
75     * Echoes an exception for the command line.
76     *
77     * @param \Throwable $exception The throwable object.
78     */
79    public function echoExceptionCLI(\Throwable $exception)
80    {
81        $filePathAndName = $exception->getFile();
82        $exceptionCodeNumber = $exception->getCode() > 0 ? '#' . $exception->getCode() . ': ' : '';
83        $this->writeLogEntries($exception, self::CONTEXT_CLI);
84        echo LF . 'Uncaught TYPO3 Exception ' . $exceptionCodeNumber . $exception->getMessage() . LF;
85        echo 'thrown in file ' . $filePathAndName . LF;
86        echo 'in line ' . $exception->getLine() . LF . LF;
87        die(1);
88    }
89
90    /**
91     * Determines, whether Exception details should be outputted
92     *
93     * @param \Throwable $exception The throwable object.
94     * @return bool
95     */
96    protected function discloseExceptionInformation(\Throwable $exception)
97    {
98        // Allow message to be shown in production mode if the exception is about
99        // trusted host configuration.  By doing so we do not disclose
100        // any valuable information to an attacker but avoid confusions among TYPO3 admins
101        // in production context.
102        if ($exception->getCode() === 1396795884) {
103            return true;
104        }
105        // Show client error messages 40x in every case
106        if ($exception instanceof AbstractClientErrorException) {
107            return true;
108        }
109        // Only show errors if a BE user is authenticated
110        $backendUser = $this->getBackendUser();
111        if ($backendUser instanceof BackendUserAuthentication) {
112            return ($backendUser->user['uid'] ?? 0) > 0;
113        }
114        return false;
115    }
116
117    /**
118     * Returns the title for the error message
119     *
120     * @param \Throwable $exception The throwable object.
121     * @return string
122     */
123    protected function getTitle(\Throwable $exception)
124    {
125        if ($this->discloseExceptionInformation($exception) && method_exists($exception, 'getTitle') && $exception->getTitle() !== '') {
126            return $exception->getTitle();
127        }
128        return $this->defaultTitle;
129    }
130
131    /**
132     * Returns the message for the error message
133     *
134     * @param \Throwable $exception The throwable object.
135     * @return string
136     */
137    protected function getMessage(\Throwable $exception)
138    {
139        if ($this->discloseExceptionInformation($exception)) {
140            return $exception->getMessage();
141        }
142        return $this->defaultMessage;
143    }
144}
145