1<?php
2
3
4/**
5 * Base class for SimpleSAMLphp Exceptions
6 *
7 * This class tries to make sure that every exception is serializable.
8 *
9 * @author Thomas Graff <thomas.graff@uninett.no>
10 * @package SimpleSAMLphp
11 */
12class SimpleSAML_Error_Exception extends Exception
13{
14
15    /**
16     * The backtrace for this exception.
17     *
18     * We need to save the backtrace, since we cannot rely on
19     * serializing the Exception::trace-variable.
20     *
21     * @var array
22     */
23    private $backtrace;
24
25
26    /**
27     * The cause of this exception.
28     *
29     * @var SimpleSAML_Error_Exception
30     */
31    private $cause;
32
33
34    /**
35     * Constructor for this error.
36     *
37     * Note that the cause will be converted to a SimpleSAML_Error_UnserializableException unless it is a subclass of
38     * SimpleSAML_Error_Exception.
39     *
40     * @param string         $message Exception message
41     * @param int            $code Error code
42     * @param Exception|null $cause The cause of this exception.
43     */
44    public function __construct($message, $code = 0, Exception $cause = null)
45    {
46        assert(is_string($message));
47        assert(is_int($code));
48
49        parent::__construct($message, $code);
50
51        $this->initBacktrace($this);
52
53        if ($cause !== null) {
54            $this->cause = SimpleSAML_Error_Exception::fromException($cause);
55        }
56    }
57
58
59    /**
60     * Convert any exception into a SimpleSAML_Error_Exception.
61     *
62     * @param Exception $e The exception.
63     *
64     * @return SimpleSAML_Error_Exception The new exception.
65     */
66    public static function fromException(Exception $e)
67    {
68
69        if ($e instanceof SimpleSAML_Error_Exception) {
70            return $e;
71        }
72        return new SimpleSAML_Error_UnserializableException($e);
73    }
74
75
76    /**
77     * Load the backtrace from the given exception.
78     *
79     * @param Exception $exception The exception we should fetch the backtrace from.
80     */
81    protected function initBacktrace(Exception $exception)
82    {
83
84        $this->backtrace = array();
85
86        // position in the top function on the stack
87        $pos = $exception->getFile().':'.$exception->getLine();
88
89        foreach ($exception->getTrace() as $t) {
90            $function = $t['function'];
91            if (array_key_exists('class', $t)) {
92                $function = $t['class'].'::'.$function;
93            }
94
95            $this->backtrace[] = $pos.' ('.$function.')';
96
97            if (array_key_exists('file', $t)) {
98                $pos = $t['file'].':'.$t['line'];
99            } else {
100                $pos = '[builtin]';
101            }
102        }
103
104        $this->backtrace[] = $pos.' (N/A)';
105    }
106
107
108    /**
109     * Retrieve the backtrace.
110     *
111     * @return array An array where each function call is a single item.
112     */
113    public function getBacktrace()
114    {
115        return $this->backtrace;
116    }
117
118
119    /**
120     * Retrieve the cause of this exception.
121     *
122     * @return SimpleSAML_Error_Exception|null The cause of this exception.
123     */
124    public function getCause()
125    {
126        return $this->cause;
127    }
128
129
130    /**
131     * Retrieve the class of this exception.
132     *
133     * @return string The name of the class.
134     */
135    public function getClass()
136    {
137        return get_class($this);
138    }
139
140
141    /**
142     * Format this exception for logging.
143     *
144     * Create an array of lines for logging.
145     *
146     * @param boolean $anonymize Whether the resulting messages should be anonymized or not.
147     *
148     * @return array Log lines that should be written out.
149     */
150    public function format($anonymize = false)
151    {
152        $ret = array(
153            $this->getClass().': '.$this->getMessage(),
154        );
155        return array_merge($ret, $this->formatBacktrace($anonymize));
156    }
157
158
159    /**
160     * Format the backtrace for logging.
161     *
162     * Create an array of lines for logging from the backtrace.
163     *
164     * @param boolean $anonymize Whether the resulting messages should be anonymized or not.
165     *
166     * @return array All lines of the backtrace, properly formatted.
167     */
168    public function formatBacktrace($anonymize = false)
169    {
170        $ret = array();
171        $basedir = SimpleSAML_Configuration::getInstance()->getBaseDir();
172
173        $e = $this;
174        do {
175            if ($e !== $this) {
176                $ret[] = 'Caused by: '.$e->getClass().': '.$e->getMessage();
177            }
178            $ret[] = 'Backtrace:';
179
180            $depth = count($e->backtrace);
181            foreach ($e->backtrace as $i => $trace) {
182                if ($anonymize) {
183                    $trace = str_replace($basedir, '', $trace);
184                }
185
186                $ret[] = ($depth - $i - 1).' '.$trace;
187            }
188            $e = $e->cause;
189        } while ($e !== null);
190
191        return $ret;
192    }
193
194
195    /**
196     * Print the backtrace to the log if the 'debug' option is enabled in the configuration.
197     */
198    protected function logBacktrace($level = \SimpleSAML\Logger::DEBUG)
199    {
200        // see if debugging is enabled for backtraces
201        $debug = SimpleSAML_Configuration::getInstance()->getArrayize('debug', array('backtraces' => false));
202
203        if (!(in_array('backtraces', $debug, true) // implicitly enabled
204              || (array_key_exists('backtraces', $debug) && $debug['backtraces'] === true) // explicitly set
205              // TODO: deprecate the old style and remove it in 2.0
206              || (array_key_exists(0, $debug) && $debug[0] === true) // old style 'debug' configuration option
207        )) {
208            return;
209        }
210
211        $backtrace = $this->formatBacktrace();
212
213        $callback = array('\SimpleSAML\Logger');
214        $functions = array(
215            \SimpleSAML\Logger::ERR     => 'error',
216            \SimpleSAML\Logger::WARNING => 'warning',
217            \SimpleSAML\Logger::INFO    => 'info',
218            \SimpleSAML\Logger::DEBUG   => 'debug',
219        );
220        $callback[] = $functions[$level];
221
222        foreach ($backtrace as $line) {
223            call_user_func($callback, $line);
224        }
225    }
226
227
228    /**
229     * Print the exception to the log, by default with log level error.
230     *
231     * Override to allow errors extending this class to specify the log level themselves.
232     *
233     * @param int $default_level The log level to use if this method was not overridden.
234     */
235    public function log($default_level)
236    {
237        $fn = array(
238            SimpleSAML\Logger::ERR     => 'logError',
239            SimpleSAML\Logger::WARNING => 'logWarning',
240            SimpleSAML\Logger::INFO    => 'logInfo',
241            SimpleSAML\Logger::DEBUG   => 'logDebug',
242        );
243        call_user_func(array($this, $fn[$default_level]), $default_level);
244    }
245
246
247    /**
248     * Print the exception to the log with log level error.
249     *
250     * This function will write this exception to the log, including a full backtrace.
251     */
252    public function logError()
253    {
254        SimpleSAML\Logger::error($this->getClass().': '.$this->getMessage());
255        $this->logBacktrace(\SimpleSAML\Logger::ERR);
256    }
257
258
259    /**
260     * Print the exception to the log with log level warning.
261     *
262     * This function will write this exception to the log, including a full backtrace.
263     */
264    public function logWarning()
265    {
266        SimpleSAML\Logger::warning($this->getClass().': '.$this->getMessage());
267        $this->logBacktrace(\SimpleSAML\Logger::WARNING);
268    }
269
270
271    /**
272     * Print the exception to the log with log level info.
273     *
274     * This function will write this exception to the log, including a full backtrace.
275     */
276    public function logInfo()
277    {
278        SimpleSAML\Logger::info($this->getClass().': '.$this->getMessage());
279        $this->logBacktrace(\SimpleSAML\Logger::INFO);
280    }
281
282
283    /**
284     * Print the exception to the log with log level debug.
285     *
286     * This function will write this exception to the log, including a full backtrace.
287     */
288    public function logDebug()
289    {
290        SimpleSAML\Logger::debug($this->getClass().': '.$this->getMessage());
291        $this->logBacktrace(\SimpleSAML\Logger::DEBUG);
292    }
293
294
295    /**
296     * Function for serialization.
297     *
298     * This function builds a list of all variables which should be serialized. It will serialize all variables except
299     * the Exception::trace variable.
300     *
301     * @return array Array with the variables that should be serialized.
302     */
303    public function __sleep()
304    {
305
306        $ret = array_keys((array) $this);
307
308        foreach ($ret as $i => $e) {
309            if ($e === "\0Exception\0trace") {
310                unset($ret[$i]);
311            }
312        }
313
314        return $ret;
315    }
316}
317