File Coverage

blib/lib/Plack/Util.pm
Criterion Covered Total %
statement 196 207 94.6
branch 75 88 85.2
condition 22 31 70.9
subroutine 40 42 95.2
pod 17 19 89.4
total 350 387 90.4


line stmt bran cond sub pod time code
1             package Plack::Util;
2 150     150   950676 use strict;
  150         232  
  150         3848  
3 150     150   577 use Carp ();
  150         220  
  150         1909  
4 150     150   510 use Scalar::Util;
  150         218  
  150         5230  
5 150     150   42101 use IO::Handle;
  150         480917  
  150         6046  
6 150     150   33919 use overload ();
  150         114580  
  150         2849  
7 150     150   2364 use File::Spec ();
  150         271  
  150         14227  
8              
9             sub TRUE() { 1==1 }
10             sub FALSE() { !TRUE }
11              
12             # there does not seem to be a relevant RT or perldelta entry for this
13 150     150   3540 use constant _SPLICE_SAME_ARRAY_SEGFAULT => $] < '5.008007';
  150         1770  
  150         30931  
14              
15             sub load_class {
16 109     109 1 854 my($class, $prefix) = @_;
17              
18 109 100       2052 if ($prefix) {
19 108 100 66     7658 unless ($class =~ s/^\+// || $class =~ /^$prefix/) {
20 61         546 $class = "$prefix\::$class";
21             }
22             }
23              
24 109         499 my $file = $class;
25 109         1872 $file =~ s!::!/!g;
26 109         84780 require "$file.pm"; ## no critic
27              
28 108         503 return $class;
29             }
30              
31             sub is_real_fh ($) {
32 13     13 1 182868 my $fh = shift;
33              
34             {
35 150     150   809 no warnings 'uninitialized';
  150         1915  
  150         306702  
  13         18  
36 13 100 66     261 return FALSE if -p $fh or -c _ or -b _;
      66        
37             }
38              
39 11 100       57 my $reftype = Scalar::Util::reftype($fh) or return;
40 9 100 100     33 if ( $reftype eq 'IO'
      66        
41 7         64 or $reftype eq 'GLOB' && *{$fh}{IO}
42             ) {
43             # if it's a blessed glob make sure to not break encapsulation with
44             # fileno($fh) (e.g. if you are filtering output then file descriptor
45             # based operations might no longer be valid).
46             # then ensure that the fileno *opcode* agrees too, that there is a
47             # valid IO object inside $fh either directly or indirectly and that it
48             # corresponds to a real file descriptor.
49 7         42 my $m_fileno = $fh->fileno;
50 7 50       43 return FALSE unless defined $m_fileno;
51 7 100       46 return FALSE unless $m_fileno >= 0;
52              
53 4         9 my $f_fileno = fileno($fh);
54 4 50       8 return FALSE unless defined $f_fileno;
55 4 50       9 return FALSE unless $f_fileno >= 0;
56 4         34 return TRUE;
57             } else {
58             # anything else, including GLOBS without IO (even if they are blessed)
59             # and non GLOB objects that look like filehandle objects cannot have a
60             # valid file descriptor in fileno($fh) context so may break.
61 2         11 return FALSE;
62             }
63             }
64              
65             sub set_io_path {
66 19     19 1 186555 my($fh, $path) = @_;
67 19         156 bless $fh, 'Plack::Util::IOWithPath';
68 19         70 $fh->path($path);
69             }
70              
71             sub content_length {
72 598     598 1 1093 my $body = shift;
73              
74 598 50       1244 return unless defined $body;
75              
76 598 100       2260 if (ref $body eq 'ARRAY') {
    100          
77 595         1156 my $cl = 0;
78 595         2975 for my $chunk (@$body) {
79 664         1498 $cl += length $chunk;
80             }
81 595         2906 return $cl;
82             } elsif ( is_real_fh($body) ) {
83 2         21 return (-s $body) - tell($body);
84             }
85              
86 1         9 return;
87             }
88              
89             sub foreach {
90 713     713 1 261996 my($body, $cb) = @_;
91              
92 713 100       2337 if (ref $body eq 'ARRAY') {
93 701         4086 for my $line (@$body) {
94 758 50       3226 $cb->($line) if length $line;
95             }
96             } else {
97 12 50       296 local $/ = \65536 unless ref $/;
98 12         972 while (defined(my $line = $body->getline)) {
99 10 50       666 $cb->($line) if length $line;
100             }
101 6         683 $body->close;
102             }
103             }
104              
105             sub class_to_file {
106 1     1 0 3 my $class = shift;
107 1         2 $class =~ s!::!/!g;
108 1         2 $class . ".pm";
109             }
110              
111             sub _load_sandbox {
112 9     9   12 my $_file = shift;
113              
114 9         13 my $_package = $_file;
115 9         46 $_package =~ s/([^A-Za-z0-9_])/sprintf("_%2x", unpack("C", $1))/eg;
  106         221  
116              
117 9         100 local $0 = $_file; # so FindBin etc. works
118 9         18 local @ARGV = (); # Some frameworks might try to parse @ARGV
119              
120 9         1005 return eval sprintf <<'END_EVAL', $_package;
121             package Plack::Sandbox::%s;
122             {
123             my $app = do $_file;
124             if ( !$app && ( my $error = $@ || $! )) { die $error; }
125             $app;
126             }
127             END_EVAL
128             }
129              
130             sub load_psgi {
131 9     9 1 292041 my $stuff = shift;
132              
133 9   50     114 local $ENV{PLACK_ENV} = $ENV{PLACK_ENV} || 'development';
134              
135 9 100       302 my $file = $stuff =~ /^[a-zA-Z0-9\_\:]+$/ ? class_to_file($stuff) : File::Spec->rel2abs($stuff);
136 9         27 my $app = _load_sandbox($file);
137 9 100       42 die "Error while loading $file: $@" if $@;
138              
139 7         46 return $app;
140             }
141              
142             sub run_app($$) {
143 740     740 1 1723 my($app, $env) = @_;
144              
145 740   66     1224 return eval { $app->($env) } || do {
146             my $body = "Internal Server Error";
147             $env->{'psgi.errors'}->print($@);
148             [ 500, [ 'Content-Type' => 'text/plain', 'Content-Length' => length($body) ], [ $body ] ];
149             };
150             }
151              
152             sub headers {
153 691     691 1 190447 my $headers = shift;
154             inline_object(
155 0     0   0 iter => sub { header_iter($headers, @_) },
156 21     21   71 get => sub { header_get($headers, @_) },
157 9     9   23 set => sub { header_set($headers, @_) },
158 587     587   1695 push => sub { header_push($headers, @_) },
159 1268     1268   3301 exists => sub { header_exists($headers, @_) },
160 22     22   40 remove => sub { header_remove($headers, @_) },
161 2     2   14 headers => sub { $headers },
162 691         24280 );
163             }
164              
165             sub header_iter {
166 666     666 0 1645 my($headers, $code) = @_;
167              
168 666         1798 my @headers = @$headers; # copy
169 666         5074 while (my($key, $val) = splice @headers, 0, 2) {
170 1487         3204 $code->($key, $val);
171             }
172             }
173              
174             sub header_get {
175 26     26 1 190485 my($headers, $key) = (shift, lc shift);
176              
177 26 100       59 return () if not @$headers;
178              
179 24         28 my $i = 0;
180              
181 24 100       47 if (wantarray) {
182             return map {
183 2 100 33     12 $key eq lc $headers->[$i++] ? $headers->[$i++] : ++$i && ();
  4         27  
184             } 1 .. @$headers/2;
185             }
186              
187 22         45 while ($i < @$headers) {
188 27 100       200 return $headers->[$i+1] if $key eq lc $headers->[$i];
189 5         7 $i += 2;
190             }
191              
192 0         0 ();
193             }
194              
195             sub header_set {
196 30     30 1 183412 my($headers, $key, $val) = @_;
197              
198 30 100       76 @$headers = ($key, $val), return if not @$headers;
199              
200 28         60 my ($i, $_key) = (0, lc $key);
201              
202             # locate and change existing header
203 28         70 while ($i < @$headers) {
204 35 100       105 $headers->[$i+1] = $val, last if $_key eq lc $headers->[$i];
205 27         55 $i += 2;
206             }
207              
208 28 100       67 if ($i > $#$headers) { # didn't find it?
209 20         48 push @$headers, $key, $val;
210 20         57 return;
211             }
212              
213 8         12 $i += 2; # found and changed it; so, first, skip that pair
214              
215 8 100       42 return if $i > $#$headers; # anything left?
216              
217             # yes... so do the same thing as header_remove
218             # but for the tail of the array only, starting at $i
219              
220 4         10 my $keep;
221             my @keep = grep {
222 4 100       26 $_ & 1 ? $keep : ($keep = $_key ne lc $headers->[$_]);
  10         27  
223             } $i .. $#$headers;
224              
225 4         8 my $remainder = @$headers - $i;
226 4 100       41 return if @keep == $remainder; # if we're not changing anything...
227              
228             splice @$headers, $i, $remainder, ( _SPLICE_SAME_ARRAY_SEGFAULT
229 1         4 ? @{[ @$headers[@keep] ]} # force different source array
230             : @$headers[@keep]
231             );
232 1         2 ();
233             }
234              
235             sub header_push {
236 590     590 1 177821 my($headers, $key, $val) = @_;
237 590         8719 push @$headers, $key, $val;
238             }
239              
240             sub header_exists {
241 1275     1275 1 199938 my($headers, $key) = (shift, lc shift);
242              
243 1275         1392 my $check;
244 1275         4249 for (@$headers) {
245 3363 100 100     13002 return 1 if ($check = not $check) and $key eq lc;
246             }
247              
248 1186         12166 return !1;
249             }
250              
251             sub header_remove {
252 40     40 1 184787 my($headers, $key) = (shift, lc shift);
253              
254 40 100       92 return if not @$headers;
255              
256 39         71 my $keep;
257             my @keep = grep {
258 39 100       107 $_ & 1 ? $keep : ($keep = $key ne lc $headers->[$_]);
  124         287  
259             } 0 .. $#$headers;
260              
261 39 100       108 @$headers = @$headers[@keep] if @keep < @$headers;
262 39         114 ();
263             }
264              
265             sub status_with_no_entity_body {
266 672     672 1 1465 my $status = shift;
267 672   66     26700 return $status < 200 || $status == 204 || $status == 304;
268             }
269              
270             sub encode_html {
271 42     42 1 40 my $str = shift;
272 42         38 $str =~ s/&/&/g;
273 42         30 $str =~ s/>/>/g;
274 42         51 $str =~ s/
275 42         34 $str =~ s/"/"/g;
276 42         38 $str =~ s/'/'/g;
277 42         79 return $str;
278             }
279              
280             sub inline_object {
281 733     733 1 210979 my %args = @_;
282 733         19331 bless \%args, 'Plack::Util::Prototype';
283             }
284              
285             sub response_cb {
286 762     762 1 3162 my($res, $cb) = @_;
287              
288             my $body_filter = sub {
289 762     762   1428 my($cb, $res) = @_;
290 762         2775 my $filter_cb = $cb->($res);
291             # If response_cb returns a callback, treat it as a $body filter
292 760 100 100     4197 if (defined $filter_cb && ref $filter_cb eq 'CODE') {
293 9         33 Plack::Util::header_remove($res->[1], 'Content-Length');
294 9 100       16 if (defined $res->[2]) {
295 2 50       5 if (ref $res->[2] eq 'ARRAY') {
296 2         3 for my $line (@{$res->[2]}) {
  2         5  
297 3         7 $line = $filter_cb->($line);
298             }
299             # Send EOF.
300 2         10 my $eof = $filter_cb->( undef );
301 2 50       11 push @{ $res->[2] }, $eof if defined $eof;
  0         0  
302             } else {
303 0         0 my $body = $res->[2];
304 0         0 my $getline = sub { $body->getline };
  0         0  
305             $res->[2] = Plack::Util::inline_object
306 0         0 getline => sub { $filter_cb->($getline->()) },
307 0         0 close => sub { $body->close };
  0         0  
308             }
309             } else {
310 7         13 return $filter_cb;
311             }
312             }
313 762         16840 };
314              
315 762 100       3728 if (ref $res eq 'ARRAY') {
    50          
316 741         2804 $body_filter->($cb, $res);
317 741         22071 return $res;
318             } elsif (ref $res eq 'CODE') {
319             return sub {
320 21     21   23 my $respond = shift;
321 21         50 my $cb = $cb; # To avoid the nested closure leak for 5.8.x
322             $res->(sub {
323 21         1500985 my $res = shift;
324 21         50 my $filter_cb = $body_filter->($cb, $res);
325 19 100       39 if ($filter_cb) {
326 7         20 my $writer = $respond->($res);
327 7 50       33 if ($writer) {
328             return Plack::Util::inline_object
329 10         24 write => sub { $writer->write($filter_cb->(@_)) },
330             close => sub {
331 7         22 my $chunk = $filter_cb->(undef);
332 7 50       17 $writer->write($chunk) if defined $chunk;
333 7         23 $writer->close;
334 7         49 };
335             }
336             } else {
337 12         44 return $respond->($res);
338             }
339 21         127 });
340 21         185 };
341             }
342              
343 0         0 return $res;
344             }
345              
346             package Plack::Util::Prototype;
347              
348             our $AUTOLOAD;
349             sub can {
350 3 50   3   1091 return $_[0]->{$_[1]} if Scalar::Util::blessed($_[0]);
351 0         0 goto &UNIVERSAL::can;
352             }
353              
354             sub AUTOLOAD {
355 1997     1997   6008 my $self = shift;
356 1997         3888 my $attr = $AUTOLOAD;
357 1997         15867 $attr =~ s/.*://;
358 1997 100       6086 if (ref($self->{$attr}) eq 'CODE') {
359 1996         4395 $self->{$attr}->(@_);
360             } else {
361 1         120 Carp::croak(qq/Can't locate object method "$attr" via package "Plack::Util::Prototype"/);
362             }
363             }
364              
365       0     sub DESTROY { }
366              
367             package Plack::Util::IOWithPath;
368 150     150   8223 use parent qw(IO::Handle);
  150         5135  
  150         2785  
369              
370             sub path {
371 21     21   39 my $self = shift;
372 21 100       67 if (@_) {
373 19         74 ${*$self}{+__PACKAGE__} = shift;
  19         212  
374             }
375 21         73 ${*$self}{+__PACKAGE__};
  21         119  
376             }
377              
378             package Plack::Util;
379              
380             1;
381              
382             __END__