File Coverage

blib/lib/HTTP/Server/Simple/Static.pm
Criterion Covered Total %
statement 24 74 32.4
branch 0 16 0.0
condition 0 9 0.0
subroutine 8 10 80.0
pod 1 2 50.0
total 33 111 29.7


line stmt bran cond sub pod time code
1             package HTTP::Server::Simple::Static;
2 1     1   477 use strict;
  1         1  
  1         23  
3 1     1   3 use warnings;
  1         1  
  1         21  
4              
5 1     1   384 use File::Spec::Functions qw(canonpath);
  1         548  
  1         48  
6 1     1   427 use HTTP::Date ();
  1         2685  
  1         19  
7 1     1   407 use IO::File ();
  1         6272  
  1         18  
8 1     1   420 use URI::Escape ();
  1         928  
  1         21  
9              
10 1     1   4 use base qw(Exporter);
  1         2  
  1         383  
11             our @EXPORT = qw(serve_static);
12              
13             our $VERSION = '0.12';
14              
15             my $line_end = "\015\012";
16              
17             my $magic;
18              
19             my @mime_types = (
20             [ qr/\.htm(l)?$/ => 'text/html' ],
21             [ qr/\.txt$/ => 'text/plain' ],
22             [ qr/\.css$/ => 'text/css' ],
23             [ qr/\.js$/ => 'application/javascript' ],
24             );
25              
26             sub get_mimetype {
27 0     0 0   my ($path) = @_;
28              
29 0           for my $type (@mime_types) {
30 0 0         if ( $path =~ $type->[0] ) {
31 0           return $type->[1];
32             }
33             }
34              
35 0 0         if ( !defined $magic ) {
36 0           require File::LibMagic;
37 0           $magic = File::LibMagic->new();
38             }
39              
40 0           return $magic->checktype_filename($path);
41             }
42              
43             sub serve_static {
44 0     0 1   my ( $self, $cgi, $base ) = @_;
45 0           my $path = $cgi->url( -absolute => 1, -path_info => 1 );
46              
47             # Internet Explorer provides the full URI in the GET section
48             # of the request header, so remove the protocol, domain name,
49             # and port if they exist.
50 0           $path =~ s{^https?://([^/:]+)(:\d+)?/}{/};
51              
52             # Sanitize the path and try it.
53 0           $path = $base . canonpath( URI::Escape::uri_unescape($path) );
54              
55 0           my $fh = IO::File->new();
56 0 0 0       if ( -f $path && $fh->open($path) ) {
57 0           my $mtime = ( stat $path )[9];
58 0           my $now = time;
59              
60             # RFC-2616 Section 14.25 "If-Modified-Since":
61              
62 0           my $if_modified_since = $cgi->http('If-Modified-Since');
63 0 0         if ($if_modified_since) {
64 0           $if_modified_since = HTTP::Date::str2time($if_modified_since);
65              
66 0 0 0       if ( defined $if_modified_since && # parsed ok
      0        
67             $if_modified_since <= $now && # not in future
68             $mtime <= $if_modified_since ) { # not changed
69              
70 0           print 'HTTP/1.1 304 Not Modified' . $line_end;
71 0           print $line_end;
72              
73 0           return 1;
74             }
75             }
76              
77             # Read the file contents and get the length in bytes
78              
79 0           binmode $fh;
80 0           binmode $self->stdout_handle;
81              
82 0           my $content;
83             {
84 0           local $/;
  0            
85 0           $content = <$fh>;
86             }
87 0           $fh->close;
88              
89 0           my $content_length;
90 0 0         if ( defined $content ) {
91 1     1   4 use bytes; # Content-Length in bytes, not characters
  1         1  
  1         5  
92 0           $content_length = length $content;
93             }
94             else {
95 0           $content_length = 0;
96 0           $content = q{};
97             }
98              
99             # Find MIME type
100              
101 0           my $mimetype = get_mimetype($path);
102              
103             # RFC-2616 Section 14.29 "Last-Modified":
104             #
105             # An origin server MUST NOT send a Last-Modified date which is
106             # later than the server's time of message origination. In such
107             # cases, where the resource's last modification would indicate
108             # some time in the future, the server MUST replace that date
109             # with the message origination date.
110              
111 0 0         if ( $mtime > $now ) {
112 0           $mtime = $now;
113             }
114              
115 0           my $last_modified = HTTP::Date::time2str($mtime);
116 0           my $date = HTTP::Date::time2str($now);
117              
118 0           print 'HTTP/1.1 200 OK' . $line_end;
119 0           print 'Date: ' . $date . $line_end;
120 0           print 'Last-Modified: ' . $last_modified . $line_end;
121 0           print 'Content-Type: ' . $mimetype . $line_end;
122 0           print 'Content-Length: ' . $content_length . $line_end;
123 0           print $line_end;
124              
125 0 0         if ( $cgi->request_method() ne 'HEAD' ) {
126 0           print $content;
127             }
128              
129 0           return 1;
130             }
131 0           return 0;
132             }
133              
134             1;
135             __END__