sub
init {
my
(
$self
) =
@_
;
$self
->attributes_defaults(
title
=>
'Error '
.
$self
->code,
type
=>
'runtime error'
,
);
$self
->has_serializer
and
return
;
my
$html_output
=
"<h2>"
.
$self
->{type} .
"</h2>"
;
$html_output
.=
$self
->backtrace;
$html_output
.=
$self
->environment;
$self
->{message} =
$html_output
;
}
sub
has_serializer { setting(
'serializer'
) }
sub
code {
$_
[0]->{code} }
sub
title {
$_
[0]->{title} }
sub
message {
$_
[0]->{message} }
sub
backtrace {
my
(
$self
) =
@_
;
$self
->{message} ||=
""
;
my
$message
=
qq|<pre class="error">|
. _html_encode(
$self
->{message}) .
"</pre>"
;
my
(
$file
,
$line
) = (
$message
=~ /at (\S+) line (\d+)/);
(
$file
,
$line
) = (
$message
=~ /at.*\((\S+):(\d+)\)/)
unless
$file
and
$line
;
return
$message
unless
(
$file
and
$line
);
my
$fh
= open_file(
'<'
,
$file
) or
return
$message
;
my
@lines
= <
$fh
>;
close
$fh
;
my
$backtrace
=
$message
;
$backtrace
.=
qq|<div class="title">|
.
"$file around line $line"
.
"</div>"
;
$backtrace
.=
qq|<pre class="content">|
;
$line
--;
my
$start
= ((
$line
- 3) >= 0) ? (
$line
- 3) : 0;
my
$stop
= ((
$line
+ 3) <
scalar
(
@lines
)) ? (
$line
+ 3) :
scalar
(
@lines
);
for
(
my
$l
=
$start
;
$l
<=
$stop
;
$l
++) {
chomp
$lines
[
$l
];
if
(
$l
==
$line
) {
$backtrace
.=
qq|<span class="nu">|
. tabulate(
$l
+ 1,
$stop
+ 1)
.
qq|</span> <span style="color: red;">|
. _html_encode(
$lines
[
$l
])
.
"</span>\n"
;
}
else
{
$backtrace
.=
qq|<span class="nu">|
. tabulate(
$l
+ 1,
$stop
+ 1)
.
"</span> "
. _html_encode(
$lines
[
$l
]) .
"\n"
;
}
}
$backtrace
.=
"</pre>"
;
return
$backtrace
;
}
sub
tabulate {
my
(
$number
,
$max
) =
@_
;
my
$len
=
length
(
$max
);
return
$number
if
length
(
$number
) ==
$len
;
return
" $number"
;
}
sub
dumper {
my
$obj
=
shift
;
return
"Unavailable without Data::Dumper"
unless
Dancer::ModuleLoader->load(
'Data::Dumper'
);
my
%data
=
%$obj
;
my
$censored
= _censor(\
%data
);
my
$dd
= Data::Dumper->new([\
%data
]);
$dd
->Terse(1)->Quotekeys(0)->Indent(1);
my
$content
=
$dd
->Dump();
$content
=~ s{(\s*)(\S+)(\s*)=>}{$1<span class=
"key"
>$2</span>$3 =
>
;}g;
if
(
$censored
) {
$content
.=
"\n\nNote: Values of $censored sensitive-looking keys hidden\n"
;
}
return
$content
;
}
sub
_censor {
my
$hash
=
shift
;
if
(!
$hash
||
ref
$hash
ne
'HASH'
) {
carp
"_censor given incorrect input: $hash"
;
return
;
}
my
$censored
= 0;
for
my
$key
(
keys
%$hash
) {
if
(
ref
$hash
->{
$key
} eq
'HASH'
) {
$censored
+= _censor(
$hash
->{
$key
});
}
elsif
(
$key
=~ /(pass|card?num|pan|secret)/i) {
$hash
->{
$key
} =
"Hidden (looks potentially sensitive)"
;
$censored
++;
}
}
return
$censored
;
}
sub
_html_encode {
my
$value
=
shift
;
$value
=~ s/&/
&
;/g;
$value
=~ s/</
<
;/g;
$value
=~ s/>/
>
;/g;
$value
=~ s/'/&
$value
=~ s/"/
"
;/g;
return
$value
;
}
sub
render {
my
$self
=
shift
;
my
$serializer
= setting(
'serializer'
);
$serializer
?
$self
->_render_serialized() :
$self
->_render_html();
}
sub
_render_serialized {
my
$self
=
shift
;
my
$message
=
!
ref
$self
->message ? {
error
=>
$self
->message} :
$self
->message;
if
(setting(
'show_errors'
)) {
Dancer::Response->new(
status
=>
$self
->code,
content
=> Dancer::Serializer->engine->serialize(
$message
),
headers
=> [
'Content-Type'
=> Dancer::Serializer->engine->content_type]
);
}
else
{
Dancer::Response->new(
status
=>
$self
->code,
content
=>
"An internal error occured"
,
);
}
}
sub
_render_html {
my
$self
=
shift
;
return
Dancer::Response->new(
status
=>
$self
->code,
headers
=> [
'Content-Type'
=>
'text/html'
],
content
=>
Dancer::Renderer->html_page(
$self
->title,
$self
->message,
'error'
)
)
if
setting(
'show_errors'
);
return
Dancer::Renderer->render_error(
$self
->code);
}
sub
environment {
my
(
$self
) =
@_
;
my
$request
= Dancer::SharedData->request;
my
$r_env
= {};
$r_env
=
$request
->env
if
defined
$request
;
my
$env
=
qq|<div class="title">Environment</div><pre class="content">|
. dumper(
$r_env
)
.
"</pre>"
;
my
$settings
=
qq|<div class="title">Settings</div><pre class="content">|
. dumper(Dancer::Config->settings)
.
"</pre>"
;
my
$source
=
qq|<div class="title">Stack</div><pre class="content">|
.
$self
->get_caller
.
"</pre>"
;
my
$session
=
""
;
if
(setting(
'session'
)) {
$session
=
qq[<div class="title">Session</div><pre class="content">]
. dumper(Dancer::Session->get)
.
"</pre>"
;
}
return
"$source $settings $session $env"
;
}
sub
get_caller {
my
(
$self
) =
@_
;
my
@stack
;
my
$deepness
= 0;
while
(
my
(
$package
,
$file
,
$line
) =
caller
(
$deepness
++)) {
push
@stack
,
"$package in $file l. $line"
;
}
return
join
(
"\n"
,
reverse
(
@stack
));
}
1;