File Coverage

blib/lib/Plack/App/File.pm
Criterion Covered Total %
statement 77 83 92.7
branch 22 28 78.5
condition 9 14 64.2
subroutine 17 17 100.0
pod 1 8 12.5
total 126 150 84.0


line stmt bran cond sub pod time code
1             package Plack::App::File;
2 8     8   112808 use strict;
  8         14  
  8         237  
3 8     8   30 use warnings;
  8         8  
  8         338  
4 8     8   35 use parent qw/Plack::Component/;
  8         10  
  8         37  
5 8     8   368 use File::Spec::Unix;
  8         11  
  8         223  
6 8     8   31 use Cwd ();
  8         9  
  8         116  
7 8     8   32 use Plack::Util;
  8         12  
  8         154  
8 8     8   3411 use Plack::MIME;
  8         19  
  8         433  
9 8     8   3156 use HTTP::Date;
  8         28115  
  8         537  
10              
11 8     8   985 use Plack::Util::Accessor qw( root file content_type encoding );
  8         13  
  8         45  
12              
13             sub should_handle {
14 118     118 0 211 my($self, $file) = @_;
15 118         2758 return -f $file;
16             }
17              
18             sub call {
19 131     131 1 172 my $self = shift;
20 131         153 my $env = shift;
21              
22 131   100     312 my($file, $path_info) = $self->file || $self->locate_file($env);
23 131 100       713 return $file if ref $file eq 'ARRAY';
24              
25 19         41 my $method = $env->{REQUEST_METHOD};
26 19 50 33     63 return $self->return_405 unless $method eq 'GET' || $method eq 'HEAD';
27              
28 19 50       43 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         69 $env->{'plack.file.SCRIPT_NAME'} = $env->{SCRIPT_NAME} . $env->{PATH_INFO};
34 19         84 $env->{'plack.file.PATH_INFO'} = '';
35             }
36              
37 19         96 return $self->serve_path($env, $file);
38             }
39              
40             sub locate_file {
41 127     127 0 204 my($self, $env) = @_;
42              
43 127   50     257 my $path = $env->{PATH_INFO} || '';
44              
45 127 100       311 if ($path =~ /\0/) {
46 1         7 return $self->return_400;
47             }
48              
49 126   50     234 my $docroot = $self->root || ".";
50 126         1851 my @path = split /[\\\/]/, $path, -1; # -1 *MUST* be here to avoid security issues!
51 126 50       221 if (@path) {
52 126 100       281 shift @path if $path[0] eq '';
53             } else {
54 0         0 @path = ('.');
55             }
56              
57 126 100       657 if (grep /^\.{2,}$/, @path) {
58 5         22 return $self->return_403;
59             }
60              
61 121         168 my($file, @path_info);
62 121         248 while (@path) {
63 121         1271 my $try = File::Spec::Unix->catfile($docroot, @path);
64 121 100       305 if ($self->should_handle($try)) {
    50          
65 15         62 $file = $try;
66 15         37 last;
67             } elsif (!$self->allow_path_info) {
68 106         164 last;
69             }
70 0         0 unshift @path_info, pop @path;
71             }
72              
73 121 100       227 if (!$file) {
74 106         217 return $self->return_404;
75             }
76              
77 15 50       164 if (!-r $file) {
78 0         0 return $self->return_403;
79             }
80              
81 15         95 return $file, join("/", "", @path_info);
82             }
83              
84 106     106 0 239 sub allow_path_info { 0 }
85              
86             sub serve_path {
87 16     16 0 39 my($self, $env, $file) = @_;
88              
89 16   100     58 my $content_type = $self->content_type || Plack::MIME->mime_type($file)
90             || 'text/plain';
91              
92 16 100       52 if ("CODE" eq ref $content_type) {
93 1         8 $content_type = $content_type->($file);
94             }
95              
96 16 100       93 if ($content_type =~ m!^text/!) {
97 14   50     58 $content_type .= "; charset=" . ($self->encoding || "utf-8");
98             }
99              
100 16 50       760 open my $fh, "<:raw", $file
101             or return $self->return_403;
102              
103 16         161 my @stat = stat $fh;
104              
105 16         537 Plack::Util::set_io_path($fh, Cwd::realpath($file));
106              
107             return [
108 16         80 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_403 {
119 5     5 0 9 my $self = shift;
120 5         44 return [403, ['Content-Type' => 'text/plain', 'Content-Length' => 9], ['forbidden']];
121             }
122              
123             sub return_400 {
124 1     1 0 1 my $self = shift;
125 1         6 return [400, ['Content-Type' => 'text/plain', 'Content-Length' => 11], ['Bad Request']];
126             }
127              
128             # Hint: subclasses can override this to return undef to pass through 404
129             sub return_404 {
130 106     106 0 128 my $self = shift;
131 106         953 return [404, ['Content-Type' => 'text/plain', 'Content-Length' => 9], ['not found']];
132             }
133              
134             1;
135             __END__