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