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.3520';
5 168     168   68350 use strict;
  168         414  
  168         5564  
6 168     168   911 use warnings;
  168         395  
  168         4412  
7 168     168   872 use Carp;
  168         376  
  168         9651  
8 168     168   1189 use Scalar::Util qw(blessed);
  168         453  
  168         9325  
9              
10 168     168   1187 use base 'Dancer::Object';
  168         469  
  168         20858  
11              
12 168     168   1750 use Dancer::Response;
  168         475  
  168         4913  
13 168     168   78370 use Dancer::Renderer;
  168         481  
  168         5719  
14 168     168   1291 use Dancer::Config 'setting';
  168         445  
  168         7241  
15 168     168   1047 use Dancer::Logger;
  168         427  
  168         3111  
16 168     168   875 use Dancer::Factory::Hook;
  168         363  
  168         3176  
17 168     168   921 use Dancer::Session;
  168         376  
  168         4836  
18 168     168   1018 use Dancer::FileUtils qw(open_file);
  168         454  
  168         7504  
19 168     168   1050 use Dancer::Engine;
  168         460  
  168         4003  
20 168     168   1001 use Dancer::Exception qw(:all);
  168         438  
  168         189422  
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 166 my ($self) = @_;
27              
28 55         241 Dancer::Factory::Hook->instance->execute_hooks('before_error_init', $self);
29              
30 55         304 $self->attributes_defaults(
31             title => 'Error ' . $self->code,
32             type => 'runtime error',
33             );
34              
35 55 100       217 $self->has_serializer
36             and return;
37              
38 48         205 my $html_output = "

" . $self->{type} . "

";
39 48         193 $html_output .= $self->backtrace;
40 48         204 $html_output .= $self->environment;
41              
42 48         273 $self->{message} = $html_output;
43             }
44              
45 55     55 0 190 sub has_serializer { setting('serializer') }
46 162     162 1 812 sub code { $_[0]->{code} }
47 63     63 1 880 sub title { $_[0]->{title} }
48 76     76 1 264 sub message { $_[0]->{message} }
49 96     96 1 374 sub exception { $_[0]->{exception} }
50              
51             sub backtrace {
52 48     48 1 113 my ($self) = @_;
53              
54 48   100     188 $self->{message} ||= "";
55             my $message =
56 48         162 qq|
| . _html_encode($self->{message}) . "
";
57              
58             # the default perl warning/error pattern
59 48         371 my ($file, $line) = ($message =~ /at (\S+) line (\d+)/);
60              
61             # the Devel::SimpleTrace pattern
62 48 100 66     268 ($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     265 return $message unless ($file and $line);
67              
68             # file and line are located, let's read the source Luke!
69 36 50       138 my $fh = open_file('<', $file) or return $message;
70 36         1159 my @lines = <$fh>;
71 36         4753 close $fh;
72              
73 36         131 my $backtrace = $message;
74              
75 36         146 $backtrace
76             .= qq|
| . "$file around line $line" . "
";
77              
78 36         73 $backtrace .= qq|
|; 
79              
80 36         97 $line--;
81 36 50       123 my $start = (($line - 3) >= 0) ? ($line - 3) : 0;
82 36 50       111 my $stop = (($line + 3) < scalar(@lines)) ? ($line + 3) : scalar(@lines);
83              
84 36         127 for (my $l = $start; $l <= $stop; $l++) {
85 252         436 chomp $lines[$l];
86              
87 252 100       419 if ($l == $line) {
88 36         117 $backtrace
89             .= qq||
90             . tabulate($l + 1, $stop + 1)
91             . qq| |
92             . _html_encode($lines[$l])
93             . "\n";
94             }
95             else {
96 216         420 $backtrace
97             .= qq||
98             . tabulate($l + 1, $stop + 1)
99             . " "
100             . _html_encode($lines[$l]) . "\n";
101             }
102             }
103 36         78 $backtrace .= "";
104              
105              
106 36         444 return $backtrace;
107             }
108              
109             sub tabulate {
110 252     252 1 430 my ($number, $max) = @_;
111 252         335 my $len = length($max);
112 252 100       774 return $number if length($number) == $len;
113 10         34 return " $number";
114             }
115              
116             sub dumper {
117 144     144 1 750 my $obj = shift;
118 144 50       555 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       401 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       1045 $data = {%$data} if blessed($data);
128              
129 144         372 my $censored = _censor($data);
130              
131             #use Data::Dumper;
132 144         816 my $dd = Data::Dumper->new([$data]);
133 144         4374 $dd->Terse(1)->Quotekeys(0)->Indent(1)->Sortkeys(1);
134 144         3733 my $content = $dd->Dump();
135 144         119033 $content =~ s{(\s*)(\S+)(\s*)=>}{$1$2$3 =>}g;
136 144 100       542 if ($censored) {
137 47 50       258 $content
138             .= "\n\nNote: Values of $censored sensitive-looking key"
139             . ($censored == 1 ? '' : 's')
140             . " hidden\n";
141             }
142 144         3176 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   2546 my ( $hash, $recursecount ) = @_;
149 391   100     1128 $recursecount ||= 0;
150              
151             # we're checking recursion ourselves, no need to warn
152 168     168   1600 no warnings 'recursion';
  168         505  
  168         213570  
153              
154 391 100       752 if ( $recursecount++ > 100 ) {
155 1         57 warn "Data exceeding 100 levels, truncating\n";
156 1         8 return $hash;
157             }
158              
159 390 50 33     1322 if (!$hash || ref $hash ne 'HASH') {
160 0         0 carp "_censor given incorrect input: $hash";
161 0         0 return;
162             }
163              
164 390         548 my $censored = 0;
165 390         1193 for my $key (keys %$hash) {
166 3412 100       12074 if (ref $hash->{$key} eq 'HASH') {
    100          
167 246         748 $censored += _censor( $hash->{$key}, $recursecount );
168             }
169             elsif ($key =~ /(pass|card?num|pan|cvv2?|ccv|secret|private_key|cookie_key)/i) {
170 324         526 $hash->{$key} = "Hidden (looks potentially sensitive)";
171 324         462 $censored++;
172             }
173             }
174              
175 390         906 return $censored;
176             }
177              
178             # Replaces the entities that are illegal in (X)HTML.
179             sub _html_encode {
180 300     300   493 my $value = shift;
181              
182 300         646 $value =~ s/&/&/g;
183 300         452 $value =~ s/
184 300         599 $value =~ s/>/>/g;
185 300         627 $value =~ s/'/'/g;
186 300         541 $value =~ s/"/"/g;
187              
188 300         1080 return $value;
189             }
190              
191             sub render {
192 53     53 1 688 my $self = shift;
193              
194 53         204 my $serializer = setting('serializer');
195 53 100       193 my $ops = { title => $self->title,
196             message => $self->message,
197             code => $self->code,
198             defined $self->exception ? ( exception => $self->exception ) : (),
199             };
200 53         324 Dancer::Factory::Hook->instance->execute_hooks('before_error_render', $self, $ops);
201 53         116 my $response;
202             try {
203 53 100   53   1911 $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         558 };
211 53         1202 Dancer::Factory::Hook->instance->execute_hooks('after_error_render', $response);
212 53         670 $response;
213             }
214              
215             sub _render_serialized {
216 7     7   25 my $self = shift;
217              
218 7 100       23 my $message =
219             !ref $self->message ? {error => $self->message} : $self->message;
220              
221 7 100 66     36 if (ref $message eq 'HASH' && defined $self->exception) {
222 2 50       6 if (blessed($self->exception)) {
223 0         0 $message->{exception} = ref($self->exception);
224 0         0 $message->{exception} =~ s/^Dancer::Exception:://;
225             } else {
226 2         4 $message->{exception} = $self->exception;
227             }
228             }
229              
230 7 100       27 if (setting('show_errors')) {
231 6         20 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         4 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   94 my $self = shift;
251 46         72 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       130 if (setting("error_template")) {
256 18         39 my $template_name = setting("error_template");
257 18         92 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       97 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         66 return Dancer::Renderer->render_error($self->code);
271             }
272             }
273              
274             sub environment {
275 48     48 1 108 my ($self) = @_;
276              
277 48         264 my $request = Dancer::SharedData->request;
278 48         114 my $r_env = {};
279 48 100       245 $r_env = $request->env if defined $request;
280              
281 48         188 my $env =
282             qq|
Environment
| 
283             . dumper($r_env)
284             . "";
285 48         451 my $settings =
286             qq|
Settings
| 
287             . dumper(Dancer::Config->settings)
288             . "";
289 48         222 my $source =
290             qq|
Stack
| 
291             . $self->get_caller
292             . "";
293 48         115 my $session = "";
294              
295 48 100       178 if (setting('session')) {
296 47         333 $session =
297             qq[
Session
] 
298             . dumper(Dancer::Session->get)
299             . "";
300             }
301 48         1635 return "$source $settings $session $env";
302             }
303              
304             sub get_caller {
305 48     48 1 128 my ($self) = @_;
306 48         94 my @stack;
307              
308 48         84 my $deepness = 0;
309 48         468 while (my ($package, $file, $line) = caller($deepness++)) {
310 633         3754 push @stack, "$package in $file l. $line";
311             }
312              
313 48         520 return join("\n", reverse(@stack));
314             }
315              
316             1;
317              
318             __END__