File Coverage

blib/lib/CGI/Easy/SendFile.pm
Criterion Covered Total %
statement 81 81 100.0
branch 26 30 86.6
condition 31 33 93.9
subroutine 14 14 100.0
pod 1 1 100.0
total 153 159 96.2


line stmt bran cond sub pod time code
1             package CGI::Easy::SendFile;
2              
3 3     3   82641 use warnings;
  3         7  
  3         108  
4 3     3   19 use strict;
  3         4  
  3         102  
5 3     3   21 use Carp;
  3         12  
  3         242  
6              
7 3     3   1691 use version; our $VERSION = qv('1.0.1'); # REMINDER: update Changes
  3         4596  
  3         18  
8              
9             # REMINDER: update dependencies in Makefile.PL
10 3     3   2077 use Perl6::Export::Attrs;
  3         24628  
  3         28  
11 3     3   154 use List::Util qw( min );
  3         7  
  3         400  
12 3     3   3066 use CGI::Easy::Util qw( date_http );
  3         14727  
  3         24  
13              
14 3     3   5084 use constant STAT_MTIME => 9;
  3         6  
  3         292  
15 3     3   18 use constant BUF_SIZE => 64*1024;
  3         5  
  3         1223  
16              
17              
18             sub send_file :Export {
19 25     25 1 37880 my ($r, $h, $file, $opt) = @_;
20 25 100       183 my %p = (
21             type => 'application/x-download',
22             range => !ref $file,
23             cache => 0,
24             inline => 0,
25 25         52 %{$opt || {}},
26             );
27              
28 25 100       71 if (!$p{cache}) {
29 20         42 $h->{'Expires'} = 'Sat, 01 Jan 2000 00:00:00 GMT';
30             }
31             else {
32 5         11 delete $h->{'Expires'};
33 5 100       12 if (!ref $file) {
34 4         78 my $lastmod = date_http((stat $file)[STAT_MTIME]);
35 4         100 my $ifmod = $r->{ENV}{HTTP_IF_MODIFIED_SINCE};
36 4 100 100     24 if ($ifmod && $ifmod eq $lastmod) {
37 1         3 $h->{'Status'} = '304 Not Modified';
38 1         6 return \q{};
39             }
40             else {
41 3         7 $h->{'Last-Modified'} = $lastmod;
42             }
43             }
44             }
45              
46 24 100       114 my $len = ref $file ? length ${$file} : -s $file;
  19         31  
47 24         68 my ($start, $end) = _get_range($p{range}, $r, $len);
48 24         60 my $size = $end-$start+1;
49              
50 24         57 $h->{'Accept-Ranges'} = 'bytes';
51 24         34 $h->{'Content-Length'} = $size;
52 24         43 $h->{'Content-Type'} = $p{type};
53 24 100       49 if (!$p{inline}) {
54 19         54 $h->{'Content-Disposition'} = 'attachment';
55             }
56 24 100 100     102 if (!($start == 0 && $end == $len-1)) {
57 8         14 $h->{Status} = '206 Partial Content';
58 8         26 $h->{'Content-Range'} = "bytes $start-$end/$len";
59             }
60              
61 24         87 return _read_block($file, $start, $size);
62 3     3   26 }
  3         6  
  3         25  
63              
64             sub _get_range {
65 24     24   35 my ($allow_range, $r, $len) = @_;
66 24         36 my ($start, $end) = (0, $len-1);
67 24 100 100     129 if ($allow_range && defined $r->{ENV}{HTTP_RANGE}) {
68 15 50       89 if ($r->{ENV}{HTTP_RANGE} =~ /\Abytes=(\d*)-(\d*)\z/ixms) {
69 15         40 my ($from, $to) = ($1, $2);
70 15 100 100     197 if ($from ne q{} && $to ne q{} && $from <= $to && $to < $len) { # 0-0, 0-499, 500-999
    100 66        
    100 100        
      100        
      100        
      66        
      100        
      100        
71 5         9 $start = $from;
72 5         8 $end = $to;
73             }
74             elsif ($from ne q{} && $to eq q{} && $from < $len) { # 0-, 500-, 999-
75 3         7 $start = $from;
76             }
77             elsif ($from eq q{} && $to ne q{} && 0 < $to && $to <= $len) { # -1, -500, -1000
78 3         6 $start = $len - $to;
79             }
80             }
81             }
82 24         60 return ($start, $end);
83             }
84              
85             sub _read_block {
86 24     24   40 my ($file, $start, $size) = @_;
87 24         26 my $data = q{};
88 24 50   1   424 open my $fh, '<', $file or croak "open: $!";
  1         11  
  1         1  
  1         7  
89 24         1584 seek $fh, $start, 0;
90 24         27 my ($n, $buf);
91 24         252 while ($n = read $fh, $buf, min($size, BUF_SIZE)) {
92 24         28 $size -= length $buf;
93 24         160 $data .= $buf;
94             }
95 24 50       46 croak "read: $!" if !defined $n;
96 24 50       99 close $fh or croak "close: $!";
97 24         134 return \$data;
98             }
99              
100              
101             1; # Magic true value required at end of module
102             __END__
103              
104             =encoding utf8
105              
106             =head1 NAME
107              
108             CGI::Easy::SendFile - send files from CGI to browser
109              
110              
111             =head1 SYNOPSIS
112              
113             use CGI::Easy::SendFile qw( send_file );
114              
115             my $r = CGI::Easy::Request->new();
116             my $h = CGI::Easy::Headers->new();
117              
118             my $data = send_file($r, $h, '/path/file.zip');
119             print $h->compose();
120             print ${$data};
121              
122             # -- send "file" generated in memory instead of real file
123             my $dynamic_file = '…some binary data…';
124             my $data = send_file($r, $h, \$dynamic_file);
125              
126             # -- simulate static image served by web server
127             # (without "download file" dialog popup in browser)
128             my $data = send_file($r, $h, 'avatar.png', {
129             type => 'image/png',
130             cache => 1,
131             inline => 1,
132             });
133              
134              
135             =head1 DESCRIPTION
136              
137             This module provide single function, which helps you prepare CGI reply for
138             sending file to browser.
139              
140              
141             =head1 EXPORTS
142              
143             Nothing by default, but all documented functions can be explicitly imported.
144              
145              
146             =head1 INTERFACE
147              
148             =over
149              
150             =item send_file( $r, $h, $file, \%opt )
151              
152             Prepare HTTP headers and content for CGI reply to send file.
153              
154             $r CGI::Easy::Request object
155             $h CGI::Easy::Headers object
156             $file STRING (file name) or SCALARREF (file contents)
157             %opt
158             {type} STRING (default "application/x-download")
159             {range} BOOL (default TRUE if $file is STRING,
160             FALSE if $file is SCALARREF)
161             {cache} BOOL (default FALSE)
162             {inline} BOOL (default FALSE)
163              
164             =over
165              
166             =item {type}
167              
168             Custom value for 'Content-Type' header. These are equivalents:
169              
170             $data = send_file($r, $h, $file, {type=>'image/png'});
171              
172             $data = send_file($r, $h, $file);
173             $h->{'Content-Type'} = 'image/png';
174              
175             =item {range}
176              
177             Enable/disable support for sending partial file contents, if requested
178             (this is usually used by file downloader applications to fetch files
179             faster using several simultaneous connections to download different file
180             parts). You shouldn't enable this option for dynamic files generated by
181             your CGI if contents of these files may differ for different CGI requests
182             sent by same user to same url.
183              
184             If your web server configured to gzip CGI replies, it will disable this
185             feature. To make this feature working disable gzip in web server (usually
186             by adding C< SetEnv no-gzip > in C< .htaccess > file).
187              
188             When enabled and user requested partial contents will change 'Status' to
189             '206 Partial Content'.
190              
191             =item {cache}
192              
193             Enable/disable caching file contents.
194              
195             HTTP header 'Expires' will be removed if {cache} is TRUE, or set to
196             'Sat, 01 Jan 2000 00:00:00 GMT' if {cache} is FALSE.
197              
198             If {cache} is TRUE and $file is STRING will set 'Last-Modified' header;
199             when browser use 'If-Modified-Since' and file doesn't changed will set
200             'Status' to '304 Not Modified' and return REF to empty string to avoid
201             sending any needless data to browser.
202              
203             You may want to add custom 'ETag' caching manually:
204              
205             $h->{ETag} = calc_my_ETag($file);
206             if ($r->{ENV}{IF_NONE_MATCH} eq $h->{ETag}) {
207             $h->{Status} = '304 Not Modified';
208             $data = \q{};
209             } else {
210             $data = send_file($r, $h, $file, {cache=>1});
211             }
212             print $h->compose(), ${$data};
213              
214             =item {inline}
215              
216             Try to control how browser should handle sent file (this have sense only
217             for file types which browser can just show instead of asking user where to
218             save downloaded file on disk - like images).
219              
220             If FALSE will set 'Content-Disposition' to 'attachment', this should force
221             browser to save downloaded file instead of just showing it.
222              
223             =back
224              
225             Return SCALARREF with (full/partial/empty) file contents which should be
226             send as body of CGI reply.
227              
228              
229             =back
230              
231              
232             =head1 BUGS AND LIMITATIONS
233              
234             No bugs have been reported.
235              
236             Sending large files will use a lot of memory - this module doesn't use
237             temporary files and keep everything in memory.
238              
239              
240             =head1 SUPPORT
241              
242             Please report any bugs or feature requests through the web interface at
243             L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=CGI-Easy-SendFile>.
244             I will be notified, and then you'll automatically be notified of progress
245             on your bug as I make changes.
246              
247             You can also look for information at:
248              
249             =over
250              
251             =item * RT: CPAN's request tracker
252              
253             L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=CGI-Easy-SendFile>
254              
255             =item * AnnoCPAN: Annotated CPAN documentation
256              
257             L<http://annocpan.org/dist/CGI-Easy-SendFile>
258              
259             =item * CPAN Ratings
260              
261             L<http://cpanratings.perl.org/d/CGI-Easy-SendFile>
262              
263             =item * Search CPAN
264              
265             L<http://search.cpan.org/dist/CGI-Easy-SendFile/>
266              
267             =back
268              
269              
270             =head1 AUTHOR
271              
272             Alex Efros C<< <powerman-asdf@ya.ru> >>
273              
274              
275             =head1 LICENSE AND COPYRIGHT
276              
277             Copyright 2009-2010 Alex Efros <powerman-asdf@ya.ru>.
278              
279             This program is distributed under the MIT (X11) License:
280             L<http://www.opensource.org/licenses/mit-license.php>
281              
282             Permission is hereby granted, free of charge, to any person
283             obtaining a copy of this software and associated documentation
284             files (the "Software"), to deal in the Software without
285             restriction, including without limitation the rights to use,
286             copy, modify, merge, publish, distribute, sublicense, and/or sell
287             copies of the Software, and to permit persons to whom the
288             Software is furnished to do so, subject to the following
289             conditions:
290              
291             The above copyright notice and this permission notice shall be
292             included in all copies or substantial portions of the Software.
293              
294             THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
295             EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
296             OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
297             NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
298             HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
299             WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
300             FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
301             OTHER DEALINGS IN THE SOFTWARE.
302