| 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/</g; | ||||
| 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__ |