Passed
Push — db-cleanup ( 50d5f5...4b272b )
by Simon
08:27 queued 05:49
created

ExceptionHandler::logExceptionToDisk()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 31
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 4.0312

Importance

Changes 0
Metric Value
eloc 16
c 0
b 0
f 0
dl 0
loc 31
ccs 14
cts 16
cp 0.875
rs 9.7333
cc 4
nc 6
nop 3
crap 4.0312
1
<?php
2
/******************************************************************************
3
 * Wikipedia Account Creation Assistance tool                                 *
4
 *                                                                            *
5
 * All code in this file is released into the public domain by the ACC        *
6
 * Development Team. Please see team.json for a list of contributors.         *
7
 ******************************************************************************/
8
9
namespace Waca;
10
11
use ErrorException;
12
use Throwable;
13
14
class ExceptionHandler
15
{
16
    /**
17
     * Global exception handler
18
     *
19
     * Smarty would be nice to use, but it COULD BE smarty that throws the errors.
20
     * Let's build something ourselves, and hope it works.
21
     *
22
     * @param $exception
23
     *
24
     * @category Security-Critical - has the potential to leak data when exception is thrown.
25
     */
26 1
    public static function exceptionHandler(Throwable $exception)
27
    {
28
        /** @global $siteConfiguration SiteConfiguration */
29
        global $siteConfiguration;
30
31 1
        $errorDocument = <<<HTML
32 1
<!DOCTYPE html>
33
<html lang="en"><head>
34
<meta charset="utf-8">
35
<title>Oops! Something went wrong!</title>
36
<meta name="viewport" content="width=device-width, initial-scale=1.0">
37 1
<link href="{$siteConfiguration->getBaseUrl()}/resources/generated/bootstrap-main.css" rel="stylesheet">
38
<style>
39
  body {
40
    padding-top: 60px;
41
  }
42
</style>
43
</head><body><div class="container">
44
<h1>Oops! Something went wrong!</h1>
45
<p>We'll work on fixing this for you, so why not come back later?</p>
46
<p class="muted">If our trained monkeys ask, tell them this error ID: <code>$1$</code></p>
47
$2$
48
</div></body></html>
49 1
HTML;
50
51 1
        list($errorData, $errorId) = self::logExceptionToDisk($exception, $siteConfiguration, true);
52
53
        // clear and discard any content that's been saved to the output buffer
54 1
        if (ob_get_level() > 0) {
55 1
            ob_end_clean();
56
        }
57
58
        // push error ID into the document.
59 1
        $message = str_replace('$1$', $errorId, $errorDocument);
60
61 1
        if ($siteConfiguration->getDebuggingTraceEnabled()) {
62
            ob_start();
63
            var_dump($errorData);
64
            $textErrorData = ob_get_contents();
65
            ob_end_clean();
66
67
            $message = str_replace('$2$', $textErrorData, $message);
68
        }
69
        else {
70 1
            $message = str_replace('$2$', "", $message);
71
        }
72
73
        // While we *shouldn't* have sent headers by now due to the output buffering, PHPUnit does weird things.
74
        // This is "only" needed for the tests, but it's a good idea to wrap this anyway.
75 1
        if (!headers_sent()) {
76
            header('HTTP/1.1 500 Internal Server Error');
77
        }
78
79
        // output the document
80 1
        print $message;
81
    }
82
83
    /**
84
     * @param int    $errorSeverity The severity level of the exception.
85
     * @param string $errorMessage  The Exception message to throw.
86
     * @param string $errorFile     The filename where the exception is thrown.
87
     * @param int    $errorLine     The line number where the exception is thrown.
88
     *
89
     * @throws ErrorException
90
     */
91 1
    public static function errorHandler($errorSeverity, $errorMessage, $errorFile, $errorLine)
92
    {
93
        // call into the main exception handler above
94 1
        throw new ErrorException($errorMessage, 0, $errorSeverity, $errorFile, $errorLine);
95
    }
96
97
    /**
98
     * @param Throwable $exception
99
     *
100
     * @return null|array
101
     */
102 1
    public static function getExceptionData($exception)
103
    {
104 1
        if ($exception == null) {
105 1
            return null;
106
        }
107
108 1
        $array = array(
109 1
            'exception' => get_class($exception),
110 1
            'message'   => $exception->getMessage(),
111 1
            'stack'     => $exception->getTraceAsString(),
112 1
        );
113
114 1
        $array['previous'] = self::getExceptionData($exception->getPrevious());
115
116 1
        return $array;
117
    }
118
119 1
    public static function logExceptionToDisk(
120
        Throwable $exception,
121
        SiteConfiguration $siteConfiguration,
122
        bool $fromGlobalHandler = false
123
    ): array {
124 1
        $errorData = self::getExceptionData($exception);
125 1
        $errorData['server'] = $_SERVER;
126 1
        $errorData['get'] = $_GET;
127 1
        $errorData['post'] = $_POST;
128
129 1
        $redactions = ['password', 'newpassword', 'newpasswordconfirm', 'otp'];
130 1
        foreach ($redactions as $redaction) {
131 1
            if (isset($errorData['post'][$redaction])) {
132
                $errorData['post'][$redaction] = '<redacted>';
133
            }
134
        }
135
136 1
        if ($fromGlobalHandler) {
137 1
            $errorData['globalHandler'] = true;
138
        }
139
        else {
140
            $errorData['globalHandler'] = false;
141
        }
142
143 1
        $state = serialize($errorData);
144 1
        $errorId = sha1($state);
145
146
        // Save the error for later analysis
147 1
        file_put_contents($siteConfiguration->getErrorLog() . '/' . $errorId . '.log', $state);
148
149 1
        return array($errorData, $errorId);
150
    }
151
}
152