File Coverage

blib/lib/Dancer/Error.pm
Criterion Covered Total %
statement 158 165 95.7
branch 46 54 85.1
condition 11 16 68.7
subroutine 32 33 96.9
pod 11 12 91.6
total 258 280 92.1


line stmt bran cond sub pod time code
1             package Dancer::Error;
2             our $AUTHORITY = 'cpan:SUKRIA';
3             #ABSTRACT: class for representing fatal errors
4             $Dancer::Error::VERSION = '1.3521';
5 168     168   66086 use strict;
  168         358  
  168         5213  
6 168     168   909 use warnings;
  168         386  
  168         4234  
7 168     168   867 use Carp;
  168         357  
  168         9389  
8 168     168   1295 use Scalar::Util qw(blessed);
  168         398  
  168         9016  
9              
10 168     168   1121 use base 'Dancer::Object';
  168         439  
  168         19975  
11              
12 168     168   1742 use Dancer::Response;
  168         462  
  168         4735  
13 168     168   77113 use Dancer::Renderer;
  168         560  
  168         5722  
14 168     168   1228 use Dancer::Config 'setting';
  168         441  
  168         7272  
15 168     168   1163 use Dancer::Logger;
  168         392  
  168         3049  
16 168     168   838 use Dancer::Factory::Hook;
  168         384  
  168         3064  
17 168     168   980 use Dancer::Session;
  168         378  
  168         4620  
18 168     168   945 use Dancer::FileUtils qw(open_file);
  168         473  
  168         7192  
19 168     168   1040 use Dancer::Engine;
  168         494  
  168         3964  
20 168     168   971 use Dancer::Exception qw(:all);
  168         516  
  168         182420  
21              
22             Dancer::Factory::Hook->instance->install_hooks(
23             qw/before_error_render after_error_render before_error_init/);
24              
25             sub init {
26 55     55 1 183 my ($self) = @_;
27              
28 55         254 Dancer::Factory::Hook->instance->execute_hooks('before_error_init', $self);
29              
30 55         289 $self->attributes_defaults(
31             title => 'Error ' . $self->code,
32             type => 'runtime error',
33             );
34              
35 55 100       204 $self->has_serializer
36             and return;
37              
38 48         189 my $html_output = "

" . $self->{type} . "

";
39 48         204 $html_output .= $self->backtrace;
40 48         193 $html_output .= $self->environment;
41              
42 48         264 $self->{message} = $html_output;
43             }
44              
45 55     55 0 222 sub has_serializer { setting('serializer') }
46 162     162 1 811 sub code { $_[0]->{code} }
47 63     63 1 655 sub title { $_[0]->{title} }
48 76     76 1 253 sub message { $_[0]->{message} }
49 96     96 1 362 sub exception { $_[0]->{exception} }
50              
51             sub backtrace {
52 48     48 1 125 my ($self) = @_;
53              
54 48   100     176 $self->{message} ||= "";
55             my $message =
56 48         168 qq|
| . _html_encode($self->{message}) . "
";
57              
58             # the default perl warning/error pattern
59 48         368 my ($file, $line) = ($message =~ /at (\S+) line (\d+)/);
60              
61             # the Devel::SimpleTrace pattern
62 48 100 66     303 ($file, $line) = ($message =~ /at.*\((\S+):(\d+)\)/)
63             unless $file and $line;
64              
65             # no file/line found, cannot open a file for context
66 48 100 66     273 return $message unless ($file and $line);
67              
68             # file and line are located, let's read the source Luke!
69 36 50       162 my $fh = open_file('<', $file) or return $message;
70 36         1161 my @lines = <$fh>;
71 36         4713 close $fh;
72              
73 36         131 my $backtrace = $message;
74              
75 36         159 $backtrace
76             .= qq|
| . "$file around line $line" . "
";
77              
78 36         79 $backtrace .= qq|
|; 
79              
80 36         99 $line--;
81 36 50       123 my $start = (($line - 3) >= 0) ? ($line - 3) : 0;
82 36 50       126 my $stop = (($line + 3) < scalar(@lines)) ? ($line + 3) : scalar(@lines);
83              
84 36         150 for (my $l = $start; $l <= $stop; $l++) {
85 252         453 chomp $lines[$l];
86              
87 252 100       434 if ($l == $line) {
88 36         111 $backtrace
89             .= qq||
90             . tabulate($l + 1, $stop + 1)
91             . qq| |
92             . _html_encode($lines[$l])
93             . "\n";
94             }
95             else {
96 216         434 $backtrace
97             .= qq||
98             . tabulate($l + 1, $stop + 1)
99             . " "
100             . _html_encode($lines[$l]) . "\n";
101             }
102             }
103 36         104 $backtrace .= "";
104              
105              
106 36         430 return $backtrace;
107             }
108              
109             sub tabulate {
110 252     252 1 428 my ($number, $max) = @_;
111 252         351 my $len = length($max);
112 252 100       793 return $number if length($number) == $len;
113 10         37 return " $number";
114             }
115              
116             sub dumper {
117 144     144 1 761 my $obj = shift;
118 144 50       646 return "Unavailable without Data::Dumper"
119             unless Dancer::ModuleLoader->load('Data::Dumper');
120              
121              
122             # Take a copy of the data, so we can mask sensitive-looking stuff:
123 144 50       385 my $data = Dancer::ModuleLoader->load('Clone') ?
124             Clone::clone($obj)
125             : eval Data::Dumper->new([$obj])->Purity(1)->Terse(1)->Deepcopy(1)->Dump;
126              
127 144 100       1047 $data = {%$data} if blessed($data);
128              
129 144         372 my $censored = _censor($data);
130              
131             #use Data::Dumper;
132 144         802 my $dd = Data::Dumper->new([$data]);
133 144         4545 $dd->Terse(1)->Quotekeys(0)->Indent(1)->Sortkeys(1);
134 144         3756 my $content = $dd->Dump();
135 144         119918 $content =~ s{(\s*)(\S+)(\s*)=>}{$1$2$3 =>}g;
136 144 100       579 if ($censored) {
137 47 50       290 $content
138             .= "\n\nNote: Values of $censored sensitive-looking key"
139             . ($censored == 1 ? '' : 's')
140             . " hidden\n";
141             }
142 144         3246 return $content;
143             }
144              
145             # Given a hashref, censor anything that looks sensitive. Returns number of
146             # items which were "censored".
147             sub _censor {
148 391     391   2487 my ( $hash, $recursecount ) = @_;
149 391   100     1062 $recursecount ||= 0;
150              
151             # we're checking recursion ourselves, no need to warn
152 168     168   1578 no warnings 'recursion';
  168         457  
  168         204120  
153              
154 391 100       772 if ( $recursecount++ > 100 ) {
155 1         49 warn "Data exceeding 100 levels, truncating\n";
156 1         7 return $hash;
157             }
158              
159 390 50 33     1343 if (!$hash || ref $hash ne 'HASH') {
160 0         0 carp "_censor given incorrect input: $hash";
161 0         0 return;
162             }
163              
164 390         550 my $censored = 0;
165 390         1233 for my $key (keys %$hash) {
166 3412 100       12021 if (ref $hash->{$key} eq 'HASH') {
    100          
167 246         714 $censored += _censor( $hash->{$key}, $recursecount );
168             }
169             elsif ($key =~ /(pass|card?num|pan|cvv2?|ccv|secret|private_key|cookie_key)/i) {
170 324         520 $hash->{$key} = "Hidden (looks potentially sensitive)";
171 324         458 $censored++;
172             }
173             }
174              
175 390         853 return $censored;
176             }
177              
178             # Replaces the entities that are illegal in (X)HTML.
179             sub _html_encode {
180 300     300   519 my $value = shift;
181              
182 300         614 $value =~ s/&/&/g;
183 300         470 $value =~ s/
184 300         656 $value =~ s/>/>/g;
185 300         660 $value =~ s/'/'/g;
186 300         526 $value =~ s/"/"/g;
187              
188 300         1153 return $value;
189             }
190              
191             sub render {
192 53     53 1 543 my $self = shift;
193              
194 53         178 my $serializer = setting('serializer');
195 53 100       196 my $ops = { title => $self->title,
196             message => $self->message,
197             code => $self->code,
198             defined $self->exception ? ( exception => $self->exception ) : (),
199             };
200 53         363 Dancer::Factory::Hook->instance->execute_hooks('before_error_render', $self, $ops);
201 53         143 my $response;
202             try {
203 53 100   53   1935 $response = $serializer ? $self->_render_serialized($ops) : $self->_render_html($ops);
204             } continuation {
205 0     0   0 my ($continuation) = @_;
206             # If we have a Route continuation, run the after hook, then
207             # propagate the continuation
208 0         0 Dancer::Factory::Hook->instance->execute_hooks('after_error_render', $response);
209 0         0 $continuation->rethrow();
210 53         587 };
211 53         1290 Dancer::Factory::Hook->instance->execute_hooks('after_error_render', $response);
212 53         711 $response;
213             }
214              
215             sub _render_serialized {
216 7     7   29 my $self = shift;
217              
218 7 100       23 my $message =
219             !ref $self->message ? {error => $self->message} : $self->message;
220              
221 7 100 66     44 if (ref $message eq 'HASH' && defined $self->exception) {
222 2 50       4 if (blessed($self->exception)) {
223 0         0 $message->{exception} = ref($self->exception);
224 0         0 $message->{exception} =~ s/^Dancer::Exception:://;
225             } else {
226 2         21 $message->{exception} = $self->exception;
227             }
228             }
229              
230 7 100       27 if (setting('show_errors')) {
231 6         27 Dancer::Response->new(
232             status => $self->code,
233             content => Dancer::Serializer->engine->serialize($message),
234             headers => ['Content-Type' => Dancer::Serializer->engine->content_type]
235             );
236             }
237              
238             # if show_errors is disabled, we don't expose the real error message to the
239             # outside world
240             else {
241 1         5 Dancer::Response->new(
242             status => $self->code,
243             content => "An internal error occured",
244             );
245             }
246              
247             }
248              
249             sub _render_html {
250 46     46   112 my $self = shift;
251 46         75 my $ops = shift;
252            
253             # I think it is irrelevant to look into show_errors. In the
254             # template the user can hide them if she desires so.
255 46 100       132 if (setting("error_template")) {
256 18         45 my $template_name = setting("error_template");
257 18         105 my $content = Dancer::Engine->engine("template")->apply_renderer($template_name, $ops);
258 18         56 return Dancer::Response->new(
259             status => $self->code,
260             headers => ['Content-Type' => 'text/html'],
261             content => $content);
262             } else {
263 28 100       86 return Dancer::Response->new(
264             status => $self->code,
265             headers => ['Content-Type' => 'text/html'],
266             content =>
267             Dancer::Renderer->html_page($self->title, $self->message, 'error')
268             ) if setting('show_errors');
269              
270 19         63 return Dancer::Renderer->render_error($self->code);
271             }
272             }
273              
274             sub environment {
275 48     48 1 112 my ($self) = @_;
276              
277 48         275 my $request = Dancer::SharedData->request;
278 48         106 my $r_env = {};
279 48 100       294 $r_env = $request->env if defined $request;
280              
281 48         168 my $env =
282             qq|
Environment
| 
283             . dumper($r_env)
284             . "";
285 48         501 my $settings =
286             qq|
Settings
| 
287             . dumper(Dancer::Config->settings)
288             . "";
289 48         211 my $source =
290             qq|
Stack
| 
291             . $self->get_caller
292             . "";
293 48         116 my $session = "";
294              
295 48 100       176 if (setting('session')) {
296 47         336 $session =
297             qq[
Session
] 
298             . dumper(Dancer::Session->get)
299             . "";
300             }
301 48         1542 return "$source $settings $session $env";
302             }
303              
304             sub get_caller {
305 48     48 1 130 my ($self) = @_;
306 48         86 my @stack;
307              
308 48         74 my $deepness = 0;
309 48         478 while (my ($package, $file, $line) = caller($deepness++)) {
310 633         3931 push @stack, "$package in $file l. $line";
311             }
312              
313 48         484 return join("\n", reverse(@stack));
314             }
315              
316             1;
317              
318             __END__