File Coverage

blib/lib/Plack/App/File.pm
Criterion Covered Total %
statement 79 85 92.9
branch 23 28 82.1
condition 10 14 71.4
subroutine 18 18 100.0
pod 1 9 11.1
total 131 154 85.0


line stmt bran cond sub pod time code
1             package Plack::App::File;
2 8     8   81347 use strict;
  8         16  
  8         200  
3 8     8   33 use warnings;
  8         13  
  8         191  
4 8     8   31 use parent qw/Plack::Component/;
  8         17  
  8         49  
5 8     8   348 use File::Spec::Unix;
  8         14  
  8         294  
6 8     8   38 use Cwd ();
  8         22  
  8         101  
7 8     8   38 use Plack::Util;
  8         13  
  8         147  
8 8     8   3931 use Plack::MIME;
  8         17  
  8         243  
9 8     8   3224 use HTTP::Date;
  8         24622  
  8         515  
10              
11 8     8   1096 use Plack::Util::Accessor qw( root file content_type encoding );
  8         17  
  8         103  
12              
13             sub should_handle {
14 117     117 0 220 my($self, $file) = @_;
15 117         2279 return -f $file;
16             }
17              
18             sub call {
19 131     131 1 190 my $self = shift;
20 131         158 my $env = shift;
21              
22 131         187 my $method = $env->{REQUEST_METHOD};
23 131 100 66     308 return $self->return_405 unless $method eq 'GET' || $method eq 'HEAD';
24              
25 130   100     384 my($file, $path_info) = $self->file || $self->locate_file($env);
26 130 100       872 return $file if ref $file eq 'ARRAY';
27              
28 19 50       45 if ($path_info) {
29 0         0 $env->{'plack.file.SCRIPT_NAME'} = $env->{SCRIPT_NAME} . $env->{PATH_INFO};
30 0         0 $env->{'plack.file.SCRIPT_NAME'} =~ s/\Q$path_info\E$//;
31 0         0 $env->{'plack.file.PATH_INFO'} = $path_info;
32             } else {
33 19         85 $env->{'plack.file.SCRIPT_NAME'} = $env->{SCRIPT_NAME} . $env->{PATH_INFO};
34 19         55 $env->{'plack.file.PATH_INFO'} = '';
35             }
36              
37 19         66 return $self->serve_path($env, $file);
38             }
39              
40             sub locate_file {
41 126     126 0 234 my($self, $env) = @_;
42              
43 126   50     287 my $path = $env->{PATH_INFO} || '';
44              
45 126 100       307 if ($path =~ /\0/) {
46 1         7 return $self->return_400;
47             }
48              
49 125   50     288 my $docroot = $self->root || ".";
50 125         1757 my @path = split /[\\\/]/, $path, -1; # -1 *MUST* be here to avoid security issues!
51 125 50       258 if (@path) {
52 125 100       273 shift @path if $path[0] eq '';
53             } else {
54 0         0 @path = ('.');
55             }
56              
57 125 100       656 if (grep /^\.{2,}$/, @path) {
58 5         20 return $self->return_403;
59             }
60              
61 120         158 my($file, @path_info);
62 120         228 while (@path) {
63 120         1115 my $try = File::Spec::Unix->catfile($docroot, @path);
64 120 100       290 if ($self->should_handle($try)) {
    50          
65 15         37 $file = $try;
66 15         39 last;
67             } elsif (!$self->allow_path_info) {
68 105         180 last;
69             }
70 0         0 unshift @path_info, pop @path;
71             }
72              
73 120 100       238 if (!$file) {
74 105         178 return $self->return_404;
75             }
76              
77 15 50       179 if (!-r $file) {
78 0         0 return $self->return_403;
79             }
80              
81 15         99 return $file, join("/", "", @path_info);
82             }
83              
84 105     105 0 355 sub allow_path_info { 0 }
85              
86             sub serve_path {
87 16     16 0 36 my($self, $env, $file) = @_;
88              
89 16   100     49 my $content_type = $self->content_type || Plack::MIME->mime_type($file)
90             || 'text/plain';
91              
92 16 100       58 if ("CODE" eq ref $content_type) {
93 1         3 $content_type = $content_type->($file);
94             }
95              
96 16 100       90 if ($content_type =~ m!^text/!) {
97 14   50     53 $content_type .= "; charset=" . ($self->encoding || "utf-8");
98             }
99              
100 16 50       708 open my $fh, "<:raw", $file
101             or return $self->return_403;
102              
103 16         189 my @stat = stat $file;
104              
105 16         519 Plack::Util::set_io_path($fh, Cwd::realpath($file));
106              
107             return [
108 16         84 200,
109             [
110             'Content-Type' => $content_type,
111             'Content-Length' => $stat[7],
112             'Last-Modified' => HTTP::Date::time2str( $stat[9] )
113             ],
114             $fh,
115             ];
116             }
117              
118             sub return_405 {
119 1     1 0 4 my $self = shift;
120 1         6 return [405, ['Content-Type' => 'text/plain', 'Content-Length' => 18], ['Method Not Allowed']];
121             }
122              
123             sub return_403 {
124 5     5 0 10 my $self = shift;
125 5         28 return [403, ['Content-Type' => 'text/plain', 'Content-Length' => 9], ['forbidden']];
126             }
127              
128             sub return_400 {
129 1     1 0 2 my $self = shift;
130 1         5 return [400, ['Content-Type' => 'text/plain', 'Content-Length' => 11], ['Bad Request']];
131             }
132              
133             # Hint: subclasses can override this to return undef to pass through 404
134             sub return_404 {
135 105     105 0 153 my $self = shift;
136 105         976 return [404, ['Content-Type' => 'text/plain', 'Content-Length' => 9], ['not found']];
137             }
138              
139             1;
140             __END__