File Coverage

blib/lib/Catalyst/Plugin/Static/File.pm
Criterion Covered Total %
statement 44 44 100.0
branch 3 4 75.0
condition 2 3 66.6
subroutine 12 12 100.0
pod 1 1 100.0
total 62 64 96.8


line stmt bran cond sub pod time code
1             package Catalyst::Plugin::Static::File;
2              
3 5     5   11500494 use v5.14;
  5         66  
4              
5             # ABSTRACT: Serve a specific static file
6              
7 5     5   32 use Moose::Role;
  5         10  
  5         62  
8              
9 5     5   30579 use File::Spec;
  5         16  
  5         157  
10 5     5   31 use File::stat;
  5         13  
  5         48  
11 5     5   346 use IO::File;
  5         15  
  5         783  
12 5     5   1994 use Plack::MIME;
  5         3366  
  5         199  
13 5     5   38 use Plack::Util;
  5         18  
  5         154  
14 5     5   34 use Try::Tiny;
  5         11  
  5         295  
15              
16 5     5   33 use namespace::autoclean;
  5         10  
  5         32  
17              
18             our $VERSION = 'v0.2.1';
19              
20              
21             sub serve_static_file {
22 8     8 1 370471 my ( $c, $path, $type ) = @_;
23              
24 8         62 my $res = $c->res;
25              
26 8         698 my $abs = File::Spec->rel2abs("$path");
27              
28             try {
29              
30             # Ideally we could let the file open fail when a file does not exist, but this seems to cause the process to
31             # exit in a way that try/catch cannot handle on some systems. We can risk a potential race condition where the
32             # file disappears between the existence check and opening: the worst case is that it would have the same effect
33             # as not checking for file existence.
34              
35 8 100   8   880 die "No such file or directory" unless -e $abs;
36              
37 7 50       100 my $fh = IO::File->new( $abs, "r" ) or die $!;
38              
39 7         1125 binmode($fh);
40 7         47 Plack::Util::set_io_path( $fh, $abs );
41 7         623 $res->body($fh);
42              
43 7   66     165 $type //= Plack::MIME->mime_type($abs);
44              
45 7         181 my $headers = $res->headers;
46 7         1430 $headers->content_type("$type");
47              
48 7         133 my $stat = stat($fh);
49 7         1590 $headers->content_length( $stat->size );
50 7         428 $headers->last_modified( $stat->mtime );
51              
52             }
53             catch {
54              
55 1     1   18 my $error = $_;
56 1         23 Catalyst::Exception->throw("Unable to open ${abs} for reading: ${error}");
57              
58 8         150 };
59              
60 7         5953 return 1;
61             }
62              
63              
64             1;
65              
66             __END__
67              
68             =pod
69              
70             =encoding UTF-8
71              
72             =head1 NAME
73              
74             Catalyst::Plugin::Static::File - Serve a specific static file
75              
76             =head1 VERSION
77              
78             version v0.2.1
79              
80             =head1 SYNOPSIS
81              
82             In your Catalyst class:
83              
84             use Catalyst qw/
85             Static::File
86             /;
87              
88             In a controller method:
89              
90             $c->serve_static_file( $absolute_path, $type );
91              
92             =head1 DESCRIPTION
93              
94             This plugin provides a simple method for your L<Catalyst> app to send a specific static file.
95              
96             Unlike L<Catalyst::Plugin::Static::Simple>,
97              
98             =over
99              
100             =item *
101              
102             It only supports serving a single file, not a directory of static files. Use L<Plack::Middleware::Static> if you want to
103             serve multiple files.
104              
105             =item *
106              
107             It assumes that you know what you're doing. If the file does not exist, it will throw an fatal error.
108              
109             =item *
110              
111             It uses L<Plack::MIME> to identify the content type, but you can override that.
112              
113             =item *
114              
115             It adds a file path to the file handle, plays nicely with L<Plack::Middleware::XSendfile> and L<Plack::Middleware::ETag>.
116              
117             =item *
118              
119             It does not log anything.
120              
121             =back
122              
123             =head1 METHODS
124              
125             =head2 serve_static_file
126              
127             $c->serve_static_file( $absolute_path, $type );
128              
129             This serves the file in C<$absolute_path>, with the C<$type> content type.
130              
131             If the C<$type> is omitted, it will guess the type using the filename.
132              
133             It will also set the C<Last-Modified> and C<Content-Length> headers.
134              
135             It returns a true value on success.
136              
137             If you want to use conditional requests, use L<Plack::Middleware::ConditionalGET>.
138              
139             =head1 SECURITY
140              
141             The L<serve_static_file> method does not validate the file that is passed to it.
142              
143             You should ensure that arbitrary filenames are not passed to it. You should strictly validate any external data that is
144             used for generating the filename.
145              
146             =head1 SUPPORT FOR OLDER PERL VERSIONS
147              
148             This module requires Perl v5.14 or later.
149              
150             Future releases may only support Perl versions released in the last ten years.
151              
152             =head1 SEE ALSO
153              
154             L<Catalyst>
155              
156             L<Catalyst::Plugin::Static::Simple>
157              
158             =head1 SOURCE
159              
160             The development version is on github at L<https://github.com/robrwo/Catalyst-Plugin-Static-File>
161             and may be cloned from L<git://github.com/robrwo/Catalyst-Plugin-Static-File.git>
162              
163             =head1 BUGS
164              
165             Please report any bugs or feature requests on the bugtracker website
166             L<https://github.com/robrwo/Catalyst-Plugin-Static-File/issues>
167              
168             When submitting a bug or request, please include a test-file or a
169             patch to an existing test-file that illustrates the bug or desired
170             feature.
171              
172             =head1 AUTHOR
173              
174             Robert Rothenberg <rrwo@cpan.org>
175              
176             =head1 COPYRIGHT AND LICENSE
177              
178             This software is Copyright (c) 2023 by Robert Rothenberg.
179              
180             This is free software, licensed under:
181              
182             The Artistic License 2.0 (GPL Compatible)
183              
184             =cut