File Coverage

blib/lib/Limper/SendFile.pm
Criterion Covered Total %
statement 15 50 30.0
branch 0 20 0.0
condition 0 9 0.0
subroutine 5 9 55.5
pod 0 4 0.0
total 20 92 21.7


line stmt bran cond sub pod time code
1             package Limper::SendFile;
2             $Limper::SendFile::VERSION = '0.003';
3 2     2   23953 use base 'Limper';
  2         6  
  2         1226  
4 2     2   55649 use 5.10.0;
  2         6  
  2         67  
5 2     2   18 use strict;
  2         3  
  2         94  
6 2     2   11 use warnings;
  2         3  
  2         94  
7              
8             package Limper;
9             $Limper::VERSION = '0.003';
10 2     2   1313 use Time::Local 'timegm';
  2         3978  
  2         2070  
11              
12             push @Limper::EXPORT, qw/public send_file/;
13             push @Limper::EXPORT_OK, qw/mime_types parse_date/;
14              
15             my %mime_types = map { chomp; split /\t/; } ();
16              
17 0     0 0   sub mime_types { \%mime_types }
18              
19             my $public = './public/';
20              
21             sub public {
22 0 0   0 0   if (defined wantarray) { $public } else { ($public) = @_ }
  0            
  0            
23             }
24              
25             # parse whatever crappy date a client might give
26             my @months = qw/Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec/;
27             sub parse_date {
28 0     0 0   my ($d, $m, $y, $h, $n, $s) = $_[0] =~ qr/^(?:\w+), (\d\d)[ -](\w+)[ -](\d\d(?:\d\d)?) (\d\d):(\d\d):(\d\d) GMT$/;
29 0 0         ($m, $d, $h, $n, $s, $y) = $_[0] =~ qr/^(?:\w+) (\w+) ([ \d]\d) (\d\d):(\d\d):(\d\d) (\d{4})$/ unless defined $d;
30 0 0         return 0 unless defined $d;
31 0 0         timegm( $s, $n, $h, $d, (grep { $months[$_] eq $m } 0..$#months)[0], $y + (length $y == 2 ? 1900 : 0) );
  0            
32             }
33              
34             # support If-Modified-Since and If-Unmodified-Since
35             hook after => sub {
36             my ($request, $response) = @_;
37             if ($request->{method} // '' eq 'GET' and substr($response->{status} // 200, 0, 1) == 2 and
38             my ($lm) = grep { lc $_ eq 'last-modified' } @{$response->{headers}}) {
39             for my $since (grep { /if-(?:un)?modified-since/ } keys %{$request->{hheaders}}) {
40             next if $since eq 'if-modified-since' and ($response->{status} // 200) != 200;
41             if (parse_date($request->{hheaders}{$since}) >= parse_date({@{$response->{headers}}}->{$lm})) {
42             $response->{body} = '';
43             $response->{status} = $since eq 'if-modified-since' ? 304 : 412;
44             }
45             }
46             }
47             };
48              
49             sub send_file {
50 0   0 0 0   my ($file) = @_ || request->{uri};
51              
52 0           $file =~ s{^/}{$public/};
53 0 0         if ($file =~ qr{/\.\./}) {
54 0           status 403;
55 0           return 'Forbidden';
56             }
57 0 0 0       if (-e $file and -r $file) {
58 0 0         if (-f $file) {
    0          
59 0 0 0       if (!grep { $_ eq 'Content-Type' } keys %{{&headers}} and my ($ext) = $file =~ /\.(\w+)$/) {
  0            
  0            
60 0 0         headers 'Content-Type' => $mime_types{$ext}, headers if exists $mime_types{$ext};
61             }
62 0           open my $fh, '<', $file;
63 0           headers 'Last-Modified' => rfc1123date((stat($fh))[9]), headers;
64 0           join '', map { $_ } (<$fh>);
  0            
65             } elsif (-d $file) {
66 0           opendir(my $dh, $file);
67 0           my @files = sort grep { !/^\./ } readdir $dh;
  0            
68 0           @files = map { "$_
" } @files;
  0            
69 0           headers 'Content-Type' => 'text/html';
70 0           join "\n", 'Directory listing of ' . request->{uri} . '', @files, '';
71             } else {
72 0           status 500;
73 0           $Limper::reasons->{500};
74             }
75             } else {
76 0           status 404;
77 0           'This is the void';
78             }
79             }
80              
81             1;
82              
83             =for Pod::Coverage
84              
85             =head1 NAME
86              
87             Limper::SendFile - add static content support to Limper
88              
89             =head1 VERSION
90              
91             version 0.003
92              
93             =head1 SYNOPSIS
94              
95             # order is important:
96             use Limper::SendFile;
97             use Limper;
98              
99             # some other routes
100              
101             get qr{^/} => sub {
102             send_file; # sends request->{uri} by default
103             };
104              
105             limp;
106              
107             =head1 DESCRIPTION
108              
109             B extends L to also return actual files. Because sometimes that's needed.
110              
111             =head1 EXPORTS
112              
113             The following are all additionally exported by default:
114              
115             public send_file
116              
117             Also exportable:
118              
119             mime_types parse_date
120              
121             =head1 FUNCTIONS
122              
123             =head2 send_file
124              
125             Sends either the file name given, or the value of C<< request->{uri} >> if no file name given.
126              
127             The following as the last defined route will have B look for the file as a last resort:
128              
129             get qr{^/} => sub { send_file }
130              
131             B will be set by file extension if known and header has not already been defined.
132             Default is B.
133              
134             =head2 public
135              
136             Get or set the public root directory. Default is B<./public/>.
137              
138             my $public = public;
139              
140             public '/var/www/langlang.us/public_html';
141              
142             =head1 ADDITIONAL FUNCTIONS
143              
144             =head2 parse_date
145              
146             Liberally parses whatever date a client might give, returning a Unix timestamp.
147              
148             # these all return 784111777
149             my $date = parse_date("Sun, 06 Nov 1994 08:49:37 GMT");
150             my $date = parse_date("Sunday, 06-Nov-94 08:49:37 GMT");
151             my $date = parse_date("Sun Nov 6 08:49:37 1994");
152              
153             =head2 mime_types
154              
155             Returns a B of file extension / content-type pairs.
156              
157             =head1 HOOKS
158              
159             =head2 after
160              
161             An B hook is created to support B and B, comparing to B.
162             This runs for all defined routes, not just those using B.
163              
164             =head1 COPYRIGHT AND LICENSE
165              
166             Copyright (C) 2014 by Ashley Willis Eashley+perl@gitable.orgE
167              
168             This library is free software; you can redistribute it and/or modify
169             it under the same terms as Perl itself, either Perl version 5.12.4 or,
170             at your option, any later version of Perl 5 you may have available.
171              
172             =head1 SEE ALSO
173              
174             L
175              
176             L
177              
178             L
179              
180             =cut
181              
182             __DATA__