Issues (32)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

src/Whoops/Exception/Inspector.php (1 issue)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
/**
3
 * Whoops - php errors for cool kids
4
 * @author Filipe Dobreira <http://github.com/filp>
5
 */
6
7
namespace Whoops\Exception;
8
9
use Whoops\Util\Misc;
10
11
class Inspector
12
{
13
    /**
14
     * @var \Throwable
15
     */
16
    private $exception;
17
18
    /**
19
     * @var \Whoops\Exception\FrameCollection
20
     */
21
    private $frames;
22
23
    /**
24
     * @var \Whoops\Exception\Inspector
25
     */
26
    private $previousExceptionInspector;
27
28
    /**
29
     * @var \Throwable[]
30
     */
31
    private $previousExceptions;
32
33
    /**
34
     * @param \Throwable $exception The exception to inspect
35
     */
36 5
    public function __construct($exception)
37
    {
38 5
        $this->exception = $exception;
39 5
    }
40
41
    /**
42
     * @return \Throwable
43
     */
44 3
    public function getException()
45
    {
46 3
        return $this->exception;
47
    }
48
49
    /**
50
     * @return string
51
     */
52 2
    public function getExceptionName()
53
    {
54 2
        return get_class($this->exception);
55
    }
56
57
    /**
58
     * @return string
59
     */
60
    public function getExceptionMessage()
61
    {
62
        return $this->extractDocrefUrl($this->exception->getMessage())['message'];
63
    }
64
65
    /**
66
     * @return string[]
67
     */
68 1
    public function getPreviousExceptionMessages()
69
    {
70
        return array_map(function ($prev) {
71
            /** @var \Throwable $prev */
72 1
            return $this->extractDocrefUrl($prev->getMessage())['message'];
73 1
        }, $this->getPreviousExceptions());
74
    }
75
76
    /**
77
     * @return int[]
78
     */
79
    public function getPreviousExceptionCodes()
80
    {
81 1
        return array_map(function ($prev) {
82
            /** @var \Throwable $prev */
83 1
            return $prev->getCode();
84 1
        }, $this->getPreviousExceptions());
85
    }
86
87
    /**
88
     * Returns a url to the php-manual related to the underlying error - when available.
89
     *
90
     * @return string|null
91
     */
92
    public function getExceptionDocrefUrl()
93
    {
94
        return $this->extractDocrefUrl($this->exception->getMessage())['url'];
95
    }
96
97
    private function extractDocrefUrl($message)
98
    {
99
        $docref = [
100
            'message' => $message,
101
            'url' => null,
102
        ];
103
104
        // php embbeds urls to the manual into the Exception message with the following ini-settings defined
105
        // http://php.net/manual/en/errorfunc.configuration.php#ini.docref-root
106
        if (!ini_get('html_errors') || !ini_get('docref_root')) {
107
            return $docref;
108
        }
109
110
        $pattern = "/\[<a href='([^']+)'>(?:[^<]+)<\/a>\]/";
111
        if (preg_match($pattern, $message, $matches)) {
112
            // -> strip those automatically generated links from the exception message
113
            $docref['message'] = preg_replace($pattern, '', $message, 1);
114
            $docref['url'] = $matches[1];
115
        }
116
117
        return $docref;
118
    }
119
120
    /**
121
     * Does the wrapped Exception has a previous Exception?
122
     * @return bool
123
     */
124 2
    public function hasPreviousException()
125
    {
126 2
        return $this->previousExceptionInspector || $this->exception->getPrevious();
127
    }
128
129
    /**
130
     * Returns an Inspector for a previous Exception, if any.
131
     * @todo   Clean this up a bit, cache stuff a bit better.
132
     * @return Inspector
133
     */
134 2
    public function getPreviousExceptionInspector()
135
    {
136 2
        if ($this->previousExceptionInspector === null) {
137 2
            $previousException = $this->exception->getPrevious();
138
139 2
            if ($previousException) {
140 1
                $this->previousExceptionInspector = new Inspector($previousException);
141 1
            }
142 2
        }
143
144 2
        return $this->previousExceptionInspector;
145
    }
146
147
148
    /**
149
     * Returns an array of all previous exceptions for this inspector's exception
150
     * @return \Throwable[]
151
     */
152 2
    public function getPreviousExceptions()
153
    {
154 2
        if ($this->previousExceptions === null) {
155 2
            $this->previousExceptions = [];
156
157 2
            $prev = $this->exception->getPrevious();
158 2
            while ($prev !== null) {
159 1
                $this->previousExceptions[] = $prev;
160 1
                $prev = $prev->getPrevious();
161 1
            }
162 2
        }
163
164 2
        return $this->previousExceptions;
165
    }
166
167
    /**
168
     * Returns an iterator for the inspected exception's
169
     * frames.
170
     * @return \Whoops\Exception\FrameCollection
171
     */
172 3
    public function getFrames()
173
    {
174 3
        if ($this->frames === null) {
175 3
            $frames = $this->getTrace($this->exception);
176
177
            // Fill empty line/file info for call_user_func_array usages (PHP Bug #44428)
178 3
            foreach ($frames as $k => $frame) {
179 3
                if (empty($frame['file'])) {
180
                    // Default values when file and line are missing
181 3
                    $file = '[internal]';
182 3
                    $line = 0;
183
184 3
                    $next_frame = !empty($frames[$k + 1]) ? $frames[$k + 1] : [];
185
186 3
                    if ($this->isValidNextFrame($next_frame)) {
187
                        $file = $next_frame['file'];
188
                        $line = $next_frame['line'];
189
                    }
190
191 3
                    $frames[$k]['file'] = $file;
192 3
                    $frames[$k]['line'] = $line;
193 3
                }
194 3
            }
195
196
            // Find latest non-error handling frame index ($i) used to remove error handling frames
197 3
            $i = 0;
198 3
            foreach ($frames as $k => $frame) {
199 3
                if ($frame['file'] == $this->exception->getFile() && $frame['line'] == $this->exception->getLine()) {
200
                    $i = $k;
201
                }
202 3
            }
203
204
            // Remove error handling frames
205 3
            if ($i > 0) {
206
                array_splice($frames, 0, $i);
207
            }
208
209 3
            $firstFrame = $this->getFrameFromException($this->exception);
210 3
            array_unshift($frames, $firstFrame);
211
212 3
            $this->frames = new FrameCollection($frames);
213
214 3
            if ($previousInspector = $this->getPreviousExceptionInspector()) {
215
                // Keep outer frame on top of the inner one
216 1
                $outerFrames = $this->frames;
217 1
                $newFrames = clone $previousInspector->getFrames();
218
                // I assume it will always be set, but let's be safe
219 1
                if (isset($newFrames[0])) {
220 1
                    $newFrames[0]->addComment(
221 1
                        $previousInspector->getExceptionMessage(),
222
                        'Exception message:'
223 1
                    );
224 1
                }
225 1
                $newFrames->prependFrames($outerFrames->topDiff($newFrames));
0 ignored issues
show
$outerFrames->topDiff($newFrames) is of type array<integer,array>, but the function expects a array<integer,object<Whoops\Exception\Frame>>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
226 1
                $this->frames = $newFrames;
227 1
            }
228 3
        }
229
230 3
        return $this->frames;
231
    }
232
233
    /**
234
     * Gets the backtrace from an exception.
235
     *
236
     * If xdebug is installed
237
     *
238
     * @param \Throwable $e
239
     * @return array
240
     */
241 1
    protected function getTrace($e)
242
    {
243 1
        $traces = $e->getTrace();
244
245
        // Get trace from xdebug if enabled, failure exceptions only trace to the shutdown handler by default
246 1
        if (!$e instanceof \ErrorException) {
247 1
            return $traces;
248
        }
249
250
        if (!Misc::isLevelFatal($e->getSeverity())) {
251
            return $traces;
252
        }
253
254
        if (!extension_loaded('xdebug') || !function_exists('xdebug_is_enabled') || !xdebug_is_enabled()) {
255
            return $traces;
256
        }
257
258
        // Use xdebug to get the full stack trace and remove the shutdown handler stack trace
259
        $stack = array_reverse(xdebug_get_function_stack());
260
        $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
261
        $traces = array_diff_key($stack, $trace);
262
263
        return $traces;
264
    }
265
266
    /**
267
     * Given an exception, generates an array in the format
268
     * generated by Exception::getTrace()
269
     * @param  \Throwable $exception
270
     * @return array
271
     */
272 1
    protected function getFrameFromException($exception)
273
    {
274
        return [
275 1
            'file'  => $exception->getFile(),
276 1
            'line'  => $exception->getLine(),
277 1
            'class' => get_class($exception),
278
            'args'  => [
279 1
                $exception->getMessage(),
280 1
            ],
281 1
        ];
282
    }
283
284
    /**
285
     * Given an error, generates an array in the format
286
     * generated by ErrorException
287
     * @param  ErrorException $exception
288
     * @return array
289
     */
290
    protected function getFrameFromError(ErrorException $exception)
291
    {
292
        return [
293
            'file'  => $exception->getFile(),
294
            'line'  => $exception->getLine(),
295
            'class' => null,
296
            'args'  => [],
297
        ];
298
    }
299
300
    /**
301
     * Determine if the frame can be used to fill in previous frame's missing info
302
     * happens for call_user_func and call_user_func_array usages (PHP Bug #44428)
303
     *
304
     * @param array $frame
305
     * @return bool
306
     */
307 1
    protected function isValidNextFrame(array $frame)
308
    {
309 1
        if (empty($frame['file'])) {
310
            return false;
311
        }
312
313 1
        if (empty($frame['line'])) {
314
            return false;
315
        }
316
317 1
        if (empty($frame['function']) || !stristr($frame['function'], 'call_user_func')) {
318 1
            return false;
319
        }
320
321
        return true;
322
    }
323
}
324