This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | /** |
||
3 | * This program is free software; you can redistribute it and/or modify |
||
4 | * it under the terms of the GNU General Public License as published by |
||
5 | * the Free Software Foundation; either version 2 of the License, or |
||
6 | * (at your option) any later version. |
||
7 | * |
||
8 | * This program is distributed in the hope that it will be useful, |
||
9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
||
10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||
11 | * GNU General Public License for more details. |
||
12 | * |
||
13 | * You should have received a copy of the GNU General Public License along |
||
14 | * with this program; if not, write to the Free Software Foundation, Inc., |
||
15 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||
16 | * http://www.gnu.org/copyleft/gpl.html |
||
17 | * |
||
18 | * @file |
||
19 | * @author Aaron Schulz |
||
20 | */ |
||
21 | |||
22 | /** |
||
23 | * Class to expose exceptions to the client (API bots, users, admins using CLI scripts) |
||
24 | * @since 1.28 |
||
25 | */ |
||
26 | class MWExceptionRenderer { |
||
27 | const AS_RAW = 1; // show as text |
||
28 | const AS_PRETTY = 2; // show as HTML |
||
29 | |||
30 | /** |
||
31 | * @param Exception|Throwable $e Original exception |
||
32 | * @param integer $mode MWExceptionExposer::AS_* constant |
||
33 | * @param Exception|Throwable|null $eNew New exception from attempting to show the first |
||
34 | */ |
||
35 | public static function output( $e, $mode, $eNew = null ) { |
||
36 | global $wgMimeType; |
||
37 | |||
38 | if ( defined( 'MW_API' ) ) { |
||
39 | // Unhandled API exception, we can't be sure that format printer is alive |
||
40 | self::header( 'MediaWiki-API-Error: internal_api_error_' . get_class( $e ) ); |
||
41 | wfHttpError( 500, 'Internal Server Error', self::getText( $e ) ); |
||
42 | } elseif ( self::isCommandLine() ) { |
||
43 | self::printError( self::getText( $e ) ); |
||
44 | } elseif ( $mode === self::AS_PRETTY ) { |
||
45 | if ( $e instanceof DBConnectionError ) { |
||
46 | self::reportOutageHTML( $e ); |
||
47 | } else { |
||
48 | self::statusHeader( 500 ); |
||
49 | self::header( "Content-Type: $wgMimeType; charset=utf-8" ); |
||
50 | self::reportHTML( $e ); |
||
51 | } |
||
52 | } else { |
||
53 | if ( $eNew ) { |
||
54 | $message = "MediaWiki internal error.\n\n"; |
||
55 | if ( self::showBackTrace( $e ) ) { |
||
56 | $message .= 'Original exception: ' . |
||
57 | MWExceptionHandler::getLogMessage( $e ) . |
||
58 | "\nBacktrace:\n" . MWExceptionHandler::getRedactedTraceAsString( $e ) . |
||
59 | "\n\nException caught inside exception handler: " . |
||
60 | MWExceptionHandler::getLogMessage( $eNew ) . |
||
61 | "\nBacktrace:\n" . MWExceptionHandler::getRedactedTraceAsString( $eNew ); |
||
62 | } else { |
||
63 | $message .= "Exception caught inside exception handler.\n\n" . |
||
64 | "Set \$wgShowExceptionDetails = true; at the bottom of LocalSettings.php " . |
||
65 | "to show detailed debugging information."; |
||
66 | } |
||
67 | $message .= "\n"; |
||
68 | } else { |
||
69 | if ( self::showBackTrace( $e ) ) { |
||
70 | $message = MWExceptionHandler::getLogMessage( $e ) . |
||
71 | "\nBacktrace:\n" . |
||
72 | MWExceptionHandler::getRedactedTraceAsString( $e ) . "\n"; |
||
73 | } else { |
||
74 | $message = MWExceptionHandler::getPublicLogMessage( $e ); |
||
75 | } |
||
76 | } |
||
77 | if ( self::isCommandLine() ) { |
||
78 | self::printError( $message ); |
||
79 | } else { |
||
80 | echo nl2br( htmlspecialchars( $message ) ) . "\n"; |
||
81 | } |
||
82 | } |
||
83 | } |
||
84 | |||
85 | /** |
||
86 | * Run hook to allow extensions to modify the text of the exception |
||
87 | * |
||
88 | * Called by MWException for b/c |
||
89 | * |
||
90 | * @param Exception|Throwable $e |
||
91 | * @param string $name Class name of the exception |
||
92 | * @param array $args Arguments to pass to the callback functions |
||
93 | * @return string|null String to output or null if any hook has been called |
||
94 | */ |
||
95 | public static function runHooks( $e, $name, $args = [] ) { |
||
96 | global $wgExceptionHooks; |
||
97 | |||
98 | if ( !isset( $wgExceptionHooks ) || !is_array( $wgExceptionHooks ) ) { |
||
99 | return null; // Just silently ignore |
||
100 | } |
||
101 | |||
102 | if ( !array_key_exists( $name, $wgExceptionHooks ) || |
||
103 | !is_array( $wgExceptionHooks[$name] ) |
||
104 | ) { |
||
105 | return null; |
||
106 | } |
||
107 | |||
108 | $hooks = $wgExceptionHooks[$name]; |
||
109 | $callargs = array_merge( [ $e ], $args ); |
||
110 | |||
111 | foreach ( $hooks as $hook ) { |
||
112 | if ( |
||
113 | is_string( $hook ) || |
||
114 | ( is_array( $hook ) && count( $hook ) >= 2 && is_string( $hook[0] ) ) |
||
115 | ) { |
||
116 | // 'function' or [ 'class', 'hook' ] |
||
117 | $result = call_user_func_array( $hook, $callargs ); |
||
118 | } else { |
||
119 | $result = null; |
||
120 | } |
||
121 | |||
122 | if ( is_string( $result ) ) { |
||
123 | return $result; |
||
124 | } |
||
125 | } |
||
126 | |||
127 | return null; |
||
128 | } |
||
129 | |||
130 | /** |
||
131 | * @param Exception|Throwable $e |
||
132 | * @return bool Should the exception use $wgOut to output the error? |
||
133 | */ |
||
134 | private static function useOutputPage( $e ) { |
||
0 ignored issues
–
show
|
|||
135 | // Can the extension use the Message class/wfMessage to get i18n-ed messages? |
||
136 | View Code Duplication | foreach ( $e->getTrace() as $frame ) { |
|
137 | if ( isset( $frame['class'] ) && $frame['class'] === 'LocalisationCache' ) { |
||
138 | return false; |
||
139 | } |
||
140 | } |
||
141 | |||
142 | return ( |
||
143 | !empty( $GLOBALS['wgFullyInitialised'] ) && |
||
144 | !empty( $GLOBALS['wgOut'] ) && |
||
145 | !defined( 'MEDIAWIKI_INSTALL' ) |
||
146 | ); |
||
147 | } |
||
148 | |||
149 | /** |
||
150 | * Output the exception report using HTML |
||
151 | * |
||
152 | * @param Exception|Throwable $e |
||
153 | */ |
||
154 | private static function reportHTML( $e ) { |
||
155 | global $wgOut, $wgSitename; |
||
156 | |||
157 | if ( self::useOutputPage( $e ) ) { |
||
158 | if ( $e instanceof MWException ) { |
||
159 | $wgOut->prepareErrorPage( $e->getPageTitle() ); |
||
160 | } elseif ( $e instanceof DBReadOnlyError ) { |
||
161 | $wgOut->prepareErrorPage( self::msg( 'readonly', 'Database is locked' ) ); |
||
162 | } elseif ( $e instanceof DBExpectedError ) { |
||
163 | $wgOut->prepareErrorPage( self::msg( 'databaseerror', 'Database error' ) ); |
||
164 | } else { |
||
165 | $wgOut->prepareErrorPage( self::msg( 'internalerror', 'Internal error' ) ); |
||
166 | } |
||
167 | |||
168 | $hookResult = self::runHooks( $e, get_class( $e ) ); |
||
169 | if ( $hookResult ) { |
||
0 ignored issues
–
show
The expression
$hookResult of type null|string is loosely compared to true ; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.
In PHP, under loose comparison (like For '' == false // true
'' == null // true
'ab' == false // false
'ab' == null // false
// It is often better to use strict comparison
'' === false // false
'' === null // false
![]() |
|||
170 | $wgOut->addHTML( $hookResult ); |
||
171 | } else { |
||
172 | // Show any custom GUI message before the details |
||
173 | if ( $e instanceof MessageSpecifier ) { |
||
174 | $wgOut->addHTML( Message::newFromSpecifier( $e )->escaped() ); |
||
175 | } |
||
176 | $wgOut->addHTML( self::getHTML( $e ) ); |
||
177 | } |
||
178 | |||
179 | $wgOut->output(); |
||
180 | } else { |
||
181 | self::header( 'Content-Type: text/html; charset=utf-8' ); |
||
182 | $pageTitle = self::msg( 'internalerror', 'Internal error' ); |
||
183 | echo "<!DOCTYPE html>\n" . |
||
184 | '<html><head>' . |
||
185 | // Mimick OutputPage::setPageTitle behaviour |
||
186 | '<title>' . |
||
187 | htmlspecialchars( self::msg( 'pagetitle', "$1 - $wgSitename", $pageTitle ) ) . |
||
188 | '</title>' . |
||
189 | '<style>body { font-family: sans-serif; margin: 0; padding: 0.5em 2em; }</style>' . |
||
190 | "</head><body>\n"; |
||
191 | |||
192 | $hookResult = self::runHooks( $e, get_class( $e ) . 'Raw' ); |
||
193 | if ( $hookResult ) { |
||
0 ignored issues
–
show
The expression
$hookResult of type null|string is loosely compared to true ; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.
In PHP, under loose comparison (like For '' == false // true
'' == null // true
'ab' == false // false
'ab' == null // false
// It is often better to use strict comparison
'' === false // false
'' === null // false
![]() |
|||
194 | echo $hookResult; |
||
195 | } else { |
||
196 | echo self::getHTML( $e ); |
||
197 | } |
||
198 | |||
199 | echo "</body></html>\n"; |
||
200 | } |
||
201 | } |
||
202 | |||
203 | /** |
||
204 | * If $wgShowExceptionDetails is true, return a HTML message with a |
||
205 | * backtrace to the error, otherwise show a message to ask to set it to true |
||
206 | * to show that information. |
||
207 | * |
||
208 | * @param Exception|Throwable $e |
||
209 | * @return string Html to output |
||
210 | */ |
||
211 | public static function getHTML( $e ) { |
||
212 | if ( self::showBackTrace( $e ) ) { |
||
213 | $html = "<div class=\"errorbox\"><p>" . |
||
214 | nl2br( htmlspecialchars( MWExceptionHandler::getLogMessage( $e ) ) ) . |
||
215 | '</p><p>Backtrace:</p><p>' . |
||
216 | nl2br( htmlspecialchars( MWExceptionHandler::getRedactedTraceAsString( $e ) ) ) . |
||
217 | "</p></div>\n"; |
||
218 | } else { |
||
219 | $logId = WebRequest::getRequestId(); |
||
220 | $html = "<div class=\"errorbox\">" . |
||
221 | '[' . $logId . '] ' . |
||
222 | gmdate( 'Y-m-d H:i:s' ) . ": " . |
||
223 | self::msg( "internalerror-fatal-exception", |
||
224 | "Fatal exception of type $1", |
||
225 | get_class( $e ), |
||
226 | $logId, |
||
227 | MWExceptionHandler::getURL() |
||
228 | ) . "</div>\n" . |
||
229 | "<!-- Set \$wgShowExceptionDetails = true; " . |
||
230 | "at the bottom of LocalSettings.php to show detailed " . |
||
231 | "debugging information. -->"; |
||
232 | } |
||
233 | |||
234 | return $html; |
||
235 | } |
||
236 | |||
237 | /** |
||
238 | * Get a message from i18n |
||
239 | * |
||
240 | * @param string $key Message name |
||
241 | * @param string $fallback Default message if the message cache can't be |
||
242 | * called by the exception |
||
243 | * The function also has other parameters that are arguments for the message |
||
244 | * @return string Message with arguments replaced |
||
245 | */ |
||
246 | View Code Duplication | private static function msg( $key, $fallback /*[, params...] */ ) { |
|
247 | $args = array_slice( func_get_args(), 2 ); |
||
248 | try { |
||
249 | return wfMessage( $key, $args )->text(); |
||
250 | } catch ( Exception $e ) { |
||
251 | return wfMsgReplaceArgs( $fallback, $args ); |
||
252 | } |
||
253 | } |
||
254 | |||
255 | /** |
||
256 | * @param Exception|Throwable $e |
||
257 | * @return string |
||
258 | */ |
||
259 | View Code Duplication | private static function getText( $e ) { |
|
260 | if ( self::showBackTrace( $e ) ) { |
||
261 | return MWExceptionHandler::getLogMessage( $e ) . |
||
262 | "\nBacktrace:\n" . |
||
263 | MWExceptionHandler::getRedactedTraceAsString( $e ) . "\n"; |
||
264 | } else { |
||
265 | return "Set \$wgShowExceptionDetails = true; " . |
||
266 | "in LocalSettings.php to show detailed debugging information.\n"; |
||
267 | } |
||
268 | } |
||
269 | |||
270 | /** |
||
271 | * @param Exception|Throwable $e |
||
272 | * @return bool |
||
273 | */ |
||
274 | private static function showBackTrace( $e ) { |
||
275 | global $wgShowExceptionDetails, $wgShowDBErrorBacktrace; |
||
276 | |||
277 | return ( |
||
278 | $wgShowExceptionDetails && |
||
279 | ( !( $e instanceof DBError ) || $wgShowDBErrorBacktrace ) |
||
280 | ); |
||
281 | } |
||
282 | |||
283 | /** |
||
284 | * @return bool |
||
285 | */ |
||
286 | private static function isCommandLine() { |
||
0 ignored issues
–
show
isCommandLine uses the super-global variable $GLOBALS which is generally not recommended.
Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable: // Bad
class Router
{
public function generate($path)
{
return $_SERVER['HOST'].$path;
}
}
// Better
class Router
{
private $host;
public function __construct($host)
{
$this->host = $host;
}
public function generate($path)
{
return $this->host.$path;
}
}
class Controller
{
public function myAction(Request $request)
{
// Instead of
$page = isset($_GET['page']) ? intval($_GET['page']) : 1;
// Better (assuming you use the Symfony2 request)
$page = $request->query->get('page', 1);
}
}
![]() |
|||
287 | return !empty( $GLOBALS['wgCommandLineMode'] ); |
||
288 | } |
||
289 | |||
290 | /** |
||
291 | * @param string $header |
||
292 | */ |
||
293 | private static function header( $header ) { |
||
294 | if ( !headers_sent() ) { |
||
295 | header( $header ); |
||
296 | } |
||
297 | } |
||
298 | |||
299 | /** |
||
300 | * @param integer $code |
||
301 | */ |
||
302 | private static function statusHeader( $code ) { |
||
303 | if ( !headers_sent() ) { |
||
304 | HttpStatus::header( $code ); |
||
305 | } |
||
306 | } |
||
307 | |||
308 | /** |
||
309 | * Print a message, if possible to STDERR. |
||
310 | * Use this in command line mode only (see isCommandLine) |
||
311 | * |
||
312 | * @param string $message Failure text |
||
313 | */ |
||
314 | private static function printError( $message ) { |
||
315 | // NOTE: STDERR may not be available, especially if php-cgi is used from the |
||
316 | // command line (bug #15602). Try to produce meaningful output anyway. Using |
||
317 | // echo may corrupt output to STDOUT though. |
||
318 | if ( defined( 'STDERR' ) ) { |
||
319 | fwrite( STDERR, $message ); |
||
320 | } else { |
||
321 | echo $message; |
||
322 | } |
||
323 | } |
||
324 | |||
325 | /** |
||
326 | * @param Exception|Throwable $e |
||
327 | */ |
||
328 | private static function reportOutageHTML( $e ) { |
||
329 | global $wgShowDBErrorBacktrace, $wgShowHostnames, $wgShowSQLErrors; |
||
330 | |||
331 | $sorry = htmlspecialchars( self::msg( |
||
332 | 'dberr-problems', |
||
333 | 'Sorry! This site is experiencing technical difficulties.' |
||
334 | ) ); |
||
335 | $again = htmlspecialchars( self::msg( |
||
336 | 'dberr-again', |
||
337 | 'Try waiting a few minutes and reloading.' |
||
338 | ) ); |
||
339 | |||
340 | if ( $wgShowHostnames || $wgShowSQLErrors ) { |
||
341 | $info = str_replace( |
||
342 | '$1', |
||
343 | Html::element( 'span', [ 'dir' => 'ltr' ], htmlspecialchars( $e->getMessage() ) ), |
||
344 | htmlspecialchars( self::msg( 'dberr-info', '($1)' ) ) |
||
345 | ); |
||
346 | } else { |
||
347 | $info = htmlspecialchars( self::msg( |
||
348 | 'dberr-info-hidden', |
||
349 | '(Cannot access the database)' |
||
350 | ) ); |
||
351 | } |
||
352 | |||
353 | MessageCache::singleton()->disable(); // no DB access |
||
354 | |||
355 | $html = "<h1>$sorry</h1><p>$again</p><p><small>$info</small></p>"; |
||
356 | |||
357 | if ( $wgShowDBErrorBacktrace ) { |
||
358 | $html .= '<p>Backtrace:</p><pre>' . |
||
359 | htmlspecialchars( $e->getTraceAsString() ) . '</pre>'; |
||
360 | } |
||
361 | |||
362 | $html .= '<hr />'; |
||
363 | $html .= self::googleSearchForm(); |
||
364 | |||
365 | echo $html; |
||
366 | } |
||
367 | |||
368 | /** |
||
369 | * @return string |
||
370 | */ |
||
371 | private static function googleSearchForm() { |
||
372 | global $wgSitename, $wgCanonicalServer, $wgRequest; |
||
373 | |||
374 | $usegoogle = htmlspecialchars( self::msg( |
||
375 | 'dberr-usegoogle', |
||
376 | 'You can try searching via Google in the meantime.' |
||
377 | ) ); |
||
378 | $outofdate = htmlspecialchars( self::msg( |
||
379 | 'dberr-outofdate', |
||
380 | 'Note that their indexes of our content may be out of date.' |
||
381 | ) ); |
||
382 | $googlesearch = htmlspecialchars( self::msg( 'searchbutton', 'Search' ) ); |
||
383 | $search = htmlspecialchars( $wgRequest->getVal( 'search' ) ); |
||
384 | $server = htmlspecialchars( $wgCanonicalServer ); |
||
385 | $sitename = htmlspecialchars( $wgSitename ); |
||
386 | $trygoogle = <<<EOT |
||
387 | <div style="margin: 1.5em">$usegoogle<br /> |
||
388 | <small>$outofdate</small> |
||
389 | </div> |
||
390 | <form method="get" action="//www.google.com/search" id="googlesearch"> |
||
391 | <input type="hidden" name="domains" value="$server" /> |
||
392 | <input type="hidden" name="num" value="50" /> |
||
393 | <input type="hidden" name="ie" value="UTF-8" /> |
||
394 | <input type="hidden" name="oe" value="UTF-8" /> |
||
395 | <input type="text" name="q" size="31" maxlength="255" value="$search" /> |
||
396 | <input type="submit" name="btnG" value="$googlesearch" /> |
||
397 | <p> |
||
398 | <label><input type="radio" name="sitesearch" value="$server" checked="checked" />$sitename</label> |
||
399 | <label><input type="radio" name="sitesearch" value="" />WWW</label> |
||
400 | </p> |
||
401 | </form> |
||
402 | EOT; |
||
403 | return $trygoogle; |
||
404 | } |
||
405 | } |
||
406 |
Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable: