| blib/lib/Dancer/Error.pm | |||
|---|---|---|---|
| Criterion | Covered | Total | % |
| statement | 158 | 165 | 95.7 |
| branch | 48 | 54 | 88.8 |
| condition | 11 | 16 | 68.7 |
| subroutine | 32 | 33 | 96.9 |
| pod | 11 | 12 | 91.6 |
| total | 260 | 280 | 92.8 |
| 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.3514_04'; # TRIAL | ||||||
| 5 | $Dancer::Error::VERSION = '1.351404'; | ||||||
| 6 | 169 | 169 | 53788 | use strict; | |||
| 169 | 294 | ||||||
| 169 | 4452 | ||||||
| 7 | 169 | 169 | 793 | use warnings; | |||
| 169 | 287 | ||||||
| 169 | 3778 | ||||||
| 8 | 169 | 169 | 692 | use Carp; | |||
| 169 | 344 | ||||||
| 169 | 8486 | ||||||
| 9 | 169 | 169 | 985 | use Scalar::Util qw(blessed); | |||
| 169 | 368 | ||||||
| 169 | 7503 | ||||||
| 10 | |||||||
| 11 | 169 | 169 | 986 | use base 'Dancer::Object'; | |||
| 169 | 339 | ||||||
| 169 | 17958 | ||||||
| 12 | |||||||
| 13 | 169 | 169 | 1354 | use Dancer::Response; | |||
| 169 | 329 | ||||||
| 169 | 3833 | ||||||
| 14 | 169 | 169 | 62743 | use Dancer::Renderer; | |||
| 169 | 419 | ||||||
| 169 | 5001 | ||||||
| 15 | 169 | 169 | 1107 | use Dancer::Config 'setting'; | |||
| 169 | 335 | ||||||
| 169 | 6356 | ||||||
| 16 | 169 | 169 | 844 | use Dancer::Logger; | |||
| 169 | 399 | ||||||
| 169 | 2499 | ||||||
| 17 | 169 | 169 | 713 | use Dancer::Factory::Hook; | |||
| 169 | 301 | ||||||
| 169 | 2279 | ||||||
| 18 | 169 | 169 | 663 | use Dancer::Session; | |||
| 169 | 311 | ||||||
| 169 | 3534 | ||||||
| 19 | 169 | 169 | 804 | use Dancer::FileUtils qw(open_file); | |||
| 169 | 300 | ||||||
| 169 | 5697 | ||||||
| 20 | 169 | 169 | 815 | use Dancer::Engine; | |||
| 169 | 311 | ||||||
| 169 | 3381 | ||||||
| 21 | 169 | 169 | 821 | use Dancer::Exception qw(:all); | |||
| 169 | 382 | ||||||
| 169 | 149418 | ||||||
| 22 | |||||||
| 23 | Dancer::Factory::Hook->instance->install_hooks( | ||||||
| 24 | qw/before_error_render after_error_render before_error_init/); | ||||||
| 25 | |||||||
| 26 | sub init { | ||||||
| 27 | 55 | 55 | 1 | 119 | my ($self) = @_; | ||
| 28 | |||||||
| 29 | 55 | 195 | Dancer::Factory::Hook->instance->execute_hooks('before_error_init', $self); | ||||
| 30 | |||||||
| 31 | 55 | 269 | $self->attributes_defaults( | ||||
| 32 | title => 'Error ' . $self->code, | ||||||
| 33 | type => 'runtime error', | ||||||
| 34 | ); | ||||||
| 35 | |||||||
| 36 | 55 | 100 | 174 | $self->has_serializer | |||
| 37 | and return; | ||||||
| 38 | |||||||
| 39 | 48 | 150 | my $html_output = "" . $self->{type} . ""; |
||||
| 40 | 48 | 150 | $html_output .= $self->backtrace; | ||||
| 41 | 48 | 191 | $html_output .= $self->environment; | ||||
| 42 | |||||||
| 43 | 48 | 391 | $self->{message} = $html_output; | ||||
| 44 | } | ||||||
| 45 | |||||||
| 46 | 55 | 55 | 0 | 179 | sub has_serializer { setting('serializer') } | ||
| 47 | 162 | 162 | 1 | 793 | sub code { $_[0]->{code} } | ||
| 48 | 63 | 63 | 1 | 668 | sub title { $_[0]->{title} } | ||
| 49 | 76 | 76 | 1 | 238 | sub message { $_[0]->{message} } | ||
| 50 | 96 | 96 | 1 | 357 | sub exception { $_[0]->{exception} } | ||
| 51 | |||||||
| 52 | sub backtrace { | ||||||
| 53 | 48 | 48 | 1 | 99 | my ($self) = @_; | ||
| 54 | |||||||
| 55 | 48 | 100 | 172 | $self->{message} ||= ""; | |||
| 56 | my $message = | ||||||
| 57 | 48 | 151 | qq|| . _html_encode($self->{message}) . ""; |
||||
| 58 | |||||||
| 59 | # the default perl warning/error pattern | ||||||
| 60 | 48 | 335 | my ($file, $line) = ($message =~ /at (\S+) line (\d+)/); | ||||
| 61 | |||||||
| 62 | # the Devel::SimpleTrace pattern | ||||||
| 63 | 48 | 100 | 66 | 237 | ($file, $line) = ($message =~ /at.*\((\S+):(\d+)\)/) | ||
| 64 | unless $file and $line; | ||||||
| 65 | |||||||
| 66 | # no file/line found, cannot open a file for context | ||||||
| 67 | 48 | 100 | 66 | 230 | return $message unless ($file and $line); | ||
| 68 | |||||||
| 69 | # file and line are located, let's read the source Luke! | ||||||
| 70 | 36 | 50 | 162 | my $fh = open_file('<', $file) or return $message; | |||
| 71 | 36 | 1120 | my @lines = <$fh>; | ||||
| 72 | 36 | 4002 | close $fh; | ||||
| 73 | |||||||
| 74 | 36 | 114 | my $backtrace = $message; | ||||
| 75 | |||||||
| 76 | 36 | 128 | $backtrace | ||||
| 77 | .= qq| | . "$file around line $line" . " "; |
||||||
| 78 | |||||||
| 79 | 36 | 77 | $backtrace .= qq||; |
||||
| 80 | |||||||
| 81 | 36 | 177 | $line--; | ||||
| 82 | 36 | 50 | 168 | my $start = (($line - 3) >= 0) ? ($line - 3) : 0; | |||
| 83 | 36 | 50 | 115 | my $stop = (($line + 3) < scalar(@lines)) ? ($line + 3) : scalar(@lines); | |||
| 84 | |||||||
| 85 | 36 | 123 | for (my $l = $start; $l <= $stop; $l++) { | ||||
| 86 | 252 | 406 | chomp $lines[$l]; | ||||
| 87 | |||||||
| 88 | 252 | 100 | 387 | if ($l == $line) { | |||
| 89 | 36 | 100 | $backtrace | ||||
| 90 | .= qq|| | ||||||
| 91 | . tabulate($l + 1, $stop + 1) | ||||||
| 92 | . qq| | | ||||||
| 93 | . _html_encode($lines[$l]) | ||||||
| 94 | . "\n"; | ||||||
| 95 | } | ||||||
| 96 | else { | ||||||
| 97 | 216 | 381 | $backtrace | ||||
| 98 | .= qq|| | ||||||
| 99 | . tabulate($l + 1, $stop + 1) | ||||||
| 100 | . " " | ||||||
| 101 | . _html_encode($lines[$l]) . "\n"; | ||||||
| 102 | } | ||||||
| 103 | } | ||||||
| 104 | 36 | 77 | $backtrace .= ""; | ||||
| 105 | |||||||
| 106 | |||||||
| 107 | 36 | 422 | return $backtrace; | ||||
| 108 | } | ||||||
| 109 | |||||||
| 110 | sub tabulate { | ||||||
| 111 | 252 | 252 | 1 | 341 | my ($number, $max) = @_; | ||
| 112 | 252 | 306 | my $len = length($max); | ||||
| 113 | 252 | 100 | 685 | return $number if length($number) == $len; | |||
| 114 | 10 | 24 | return " $number"; | ||||
| 115 | } | ||||||
| 116 | |||||||
| 117 | sub dumper { | ||||||
| 118 | 145 | 145 | 1 | 741 | my $obj = shift; | ||
| 119 | 145 | 50 | 581 | return "Unavailable without Data::Dumper" | |||
| 120 | unless Dancer::ModuleLoader->load('Data::Dumper'); | ||||||
| 121 | |||||||
| 122 | |||||||
| 123 | # Take a copy of the data, so we can mask sensitive-looking stuff: | ||||||
| 124 | 145 | 100 | 333 | my $data = Dancer::ModuleLoader->load('Clone') ? | |||
| 125 | Clone::clone($obj) | ||||||
| 126 | : eval Data::Dumper->new([$obj])->Purity(1)->Terse(1)->Deepcopy(1)->Dump; | ||||||
| 127 | |||||||
| 128 | 145 | 100 | 884 | $data = {%$data} if blessed($data); | |||
| 129 | |||||||
| 130 | 145 | 410 | my $censored = _censor($data); | ||||
| 131 | |||||||
| 132 | #use Data::Dumper; | ||||||
| 133 | 145 | 716 | my $dd = Data::Dumper->new([$data]); | ||||
| 134 | 145 | 4000 | $dd->Terse(1)->Quotekeys(0)->Indent(1)->Sortkeys(1); | ||||
| 135 | 145 | 3317 | my $content = $dd->Dump(); | ||||
| 136 | 145 | 103067 | $content =~ s{(\s*)(\S+)(\s*)=>}{$1$2$3 =>}g; | ||||
| 137 | 145 | 100 | 493 | if ($censored) { | |||
| 138 | 48 | 100 | 223 | $content | |||
| 139 | .= "\n\nNote: Values of $censored sensitive-looking key" | ||||||
| 140 | . ($censored == 1 ? '' : 's') | ||||||
| 141 | . " hidden\n"; | ||||||
| 142 | } | ||||||
| 143 | 145 | 2729 | return $content; | ||||
| 144 | } | ||||||
| 145 | |||||||
| 146 | # Given a hashref, censor anything that looks sensitive. Returns number of | ||||||
| 147 | # items which were "censored". | ||||||
| 148 | sub _censor { | ||||||
| 149 | 393 | 393 | 2235 | my ( $hash, $recursecount ) = @_; | |||
| 150 | 393 | 100 | 926 | $recursecount ||= 0; | |||
| 151 | |||||||
| 152 | # we're checking recursion ourselves, no need to warn | ||||||
| 153 | 169 | 169 | 1238 | no warnings 'recursion'; | |||
| 169 | 421 | ||||||
| 169 | 170134 | ||||||
| 154 | |||||||
| 155 | 393 | 100 | 677 | if ( $recursecount++ > 100 ) { | |||
| 156 | 1 | 94 | warn "Data exceeding 100 levels, truncating\n"; | ||||
| 157 | 1 | 7 | return $hash; | ||||
| 158 | } | ||||||
| 159 | |||||||
| 160 | 392 | 50 | 33 | 1187 | if (!$hash || ref $hash ne 'HASH') { | ||
| 161 | 0 | 0 | carp "_censor given incorrect input: $hash"; | ||||
| 162 | 0 | 0 | return; | ||||
| 163 | } | ||||||
| 164 | |||||||
| 165 | 392 | 476 | my $censored = 0; | ||||
| 166 | 392 | 1016 | for my $key (keys %$hash) { | ||||
| 167 | 3414 | 100 | 9986 | if (ref $hash->{$key} eq 'HASH') { | |||
| 100 | |||||||
| 168 | 247 | 571 | $censored += _censor( $hash->{$key}, $recursecount ); | ||||
| 169 | } | ||||||
| 170 | elsif ($key =~ /(pass|card?num|pan|cvv2?|ccv|secret|private_key|cookie_key)/i) { | ||||||
| 171 | 325 | 439 | $hash->{$key} = "Hidden (looks potentially sensitive)"; | ||||
| 172 | 325 | 370 | $censored++; | ||||
| 173 | } | ||||||
| 174 | } | ||||||
| 175 | |||||||
| 176 | 392 | 695 | return $censored; | ||||
| 177 | } | ||||||
| 178 | |||||||
| 179 | # Replaces the entities that are illegal in (X)HTML. | ||||||
| 180 | sub _html_encode { | ||||||
| 181 | 300 | 300 | 394 | my $value = shift; | |||
| 182 | |||||||
| 183 | 300 | 509 | $value =~ s/&/&/g; | ||||
| 184 | 300 | 388 | $value =~ s/</g; | ||||
| 185 | 300 | 501 | $value =~ s/>/>/g; | ||||
| 186 | 300 | 598 | $value =~ s/'/'/g; | ||||
| 187 | 300 | 508 | $value =~ s/"/"/g; | ||||
| 188 | |||||||
| 189 | 300 | 876 | return $value; | ||||
| 190 | } | ||||||
| 191 | |||||||
| 192 | sub render { | ||||||
| 193 | 53 | 53 | 1 | 557 | my $self = shift; | ||
| 194 | |||||||
| 195 | 53 | 148 | my $serializer = setting('serializer'); | ||||
| 196 | 53 | 100 | 166 | my $ops = { title => $self->title, | |||
| 197 | message => $self->message, | ||||||
| 198 | code => $self->code, | ||||||
| 199 | defined $self->exception ? ( exception => $self->exception ) : (), | ||||||
| 200 | }; | ||||||
| 201 | 53 | 344 | Dancer::Factory::Hook->instance->execute_hooks('before_error_render', $self, $ops); | ||||
| 202 | 53 | 114 | my $response; | ||||
| 203 | try { | ||||||
| 204 | 53 | 100 | 53 | 1797 | $response = $serializer ? $self->_render_serialized($ops) : $self->_render_html($ops); | ||
| 205 | } continuation { | ||||||
| 206 | 0 | 0 | 0 | my ($continuation) = @_; | |||
| 207 | # If we have a Route continuation, run the after hook, then | ||||||
| 208 | # propagate the continuation | ||||||
| 209 | 0 | 0 | Dancer::Factory::Hook->instance->execute_hooks('after_error_render', $response); | ||||
| 210 | 0 | 0 | $continuation->rethrow(); | ||||
| 211 | 53 | 497 | }; | ||||
| 212 | 53 | 1711 | Dancer::Factory::Hook->instance->execute_hooks('after_error_render', $response); | ||||
| 213 | 53 | 740 | $response; | ||||
| 214 | } | ||||||
| 215 | |||||||
| 216 | sub _render_serialized { | ||||||
| 217 | 7 | 7 | 12 | my $self = shift; | |||
| 218 | |||||||
| 219 | 7 | 100 | 19 | my $message = | |||
| 220 | !ref $self->message ? {error => $self->message} : $self->message; | ||||||
| 221 | |||||||
| 222 | 7 | 100 | 66 | 34 | if (ref $message eq 'HASH' && defined $self->exception) { | ||
| 223 | 2 | 50 | 5 | if (blessed($self->exception)) { | |||
| 224 | 0 | 0 | $message->{exception} = ref($self->exception); | ||||
| 225 | 0 | 0 | $message->{exception} =~ s/^Dancer::Exception:://; | ||||
| 226 | } else { | ||||||
| 227 | 2 | 3 | $message->{exception} = $self->exception; | ||||
| 228 | } | ||||||
| 229 | } | ||||||
| 230 | |||||||
| 231 | 7 | 100 | 22 | if (setting('show_errors')) { | |||
| 232 | 6 | 18 | Dancer::Response->new( | ||||
| 233 | status => $self->code, | ||||||
| 234 | content => Dancer::Serializer->engine->serialize($message), | ||||||
| 235 | headers => ['Content-Type' => Dancer::Serializer->engine->content_type] | ||||||
| 236 | ); | ||||||
| 237 | } | ||||||
| 238 | |||||||
| 239 | # if show_errors is disabled, we don't expose the real error message to the | ||||||
| 240 | # outside world | ||||||
| 241 | else { | ||||||
| 242 | 1 | 6 | Dancer::Response->new( | ||||
| 243 | status => $self->code, | ||||||
| 244 | content => "An internal error occured", | ||||||
| 245 | ); | ||||||
| 246 | } | ||||||
| 247 | |||||||
| 248 | } | ||||||
| 249 | |||||||
| 250 | sub _render_html { | ||||||
| 251 | 46 | 46 | 82 | my $self = shift; | |||
| 252 | 46 | 67 | my $ops = shift; | ||||
| 253 | |||||||
| 254 | # I think it is irrelevant to look into show_errors. In the | ||||||
| 255 | # template the user can hide them if she desires so. | ||||||
| 256 | 46 | 100 | 128 | if (setting("error_template")) { | |||
| 257 | 18 | 37 | my $template_name = setting("error_template"); | ||||
| 258 | 18 | 116 | my $content = Dancer::Engine->engine("template")->apply_renderer($template_name, $ops); | ||||
| 259 | 18 | 46 | return Dancer::Response->new( | ||||
| 260 | status => $self->code, | ||||||
| 261 | headers => ['Content-Type' => 'text/html'], | ||||||
| 262 | content => $content); | ||||||
| 263 | } else { | ||||||
| 264 | 28 | 100 | 72 | return Dancer::Response->new( | |||
| 265 | status => $self->code, | ||||||
| 266 | headers => ['Content-Type' => 'text/html'], | ||||||
| 267 | content => | ||||||
| 268 | Dancer::Renderer->html_page($self->title, $self->message, 'error') | ||||||
| 269 | ) if setting('show_errors'); | ||||||
| 270 | |||||||
| 271 | 19 | 50 | return Dancer::Renderer->render_error($self->code); | ||||
| 272 | } | ||||||
| 273 | } | ||||||
| 274 | |||||||
| 275 | sub environment { | ||||||
| 276 | 48 | 48 | 1 | 94 | my ($self) = @_; | ||
| 277 | |||||||
| 278 | 48 | 241 | my $request = Dancer::SharedData->request; | ||||
| 279 | 48 | 89 | my $r_env = {}; | ||||
| 280 | 48 | 100 | 206 | $r_env = $request->env if defined $request; | |||
| 281 | |||||||
| 282 | 48 | 138 | my $env = | ||||
| 283 | qq| Environment | |
||||||
| 284 | . dumper($r_env) | ||||||
| 285 | . ""; | ||||||
| 286 | 48 | 463 | my $settings = | ||||
| 287 | qq| Settings | |
||||||
| 288 | . dumper(Dancer::Config->settings) | ||||||
| 289 | . ""; | ||||||
| 290 | 48 | 202 | my $source = | ||||
| 291 | qq| Stack | |
||||||
| 292 | . $self->get_caller | ||||||
| 293 | . ""; | ||||||
| 294 | 48 | 105 | my $session = ""; | ||||
| 295 | |||||||
| 296 | 48 | 100 | 178 | if (setting('session')) { | |||
| 297 | 47 | 347 | $session = | ||||
| 298 | qq[ Session ] |
||||||
| 299 | . dumper(Dancer::Session->get) | ||||||
| 300 | . ""; | ||||||
| 301 | } | ||||||
| 302 | 48 | 1373 | return "$source $settings $session $env"; | ||||
| 303 | } | ||||||
| 304 | |||||||
| 305 | sub get_caller { | ||||||
| 306 | 48 | 48 | 1 | 118 | my ($self) = @_; | ||
| 307 | 48 | 73 | my @stack; | ||||
| 308 | |||||||
| 309 | 48 | 74 | my $deepness = 0; | ||||
| 310 | 48 | 482 | while (my ($package, $file, $line) = caller($deepness++)) { | ||||
| 311 | 633 | 3478 | push @stack, "$package in $file l. $line"; | ||||
| 312 | } | ||||||
| 313 | |||||||
| 314 | 48 | 408 | return join("\n", reverse(@stack)); | ||||
| 315 | } | ||||||
| 316 | |||||||
| 317 | 1; | ||||||
| 318 | |||||||
| 319 | __END__ |