File Coverage

blib/lib/CGI/Easy/SendFile.pm
Criterion Covered Total %
statement 83 83 100.0
branch 26 30 86.6
condition 31 33 93.9
subroutine 15 15 100.0
pod 1 1 100.0
total 156 162 96.3


line stmt bran cond sub pod time code
1             package CGI::Easy::SendFile;
2 2     2   116372 use 5.010001;
  2         9  
3 2     2   8 use warnings;
  2         2  
  2         37  
4 2     2   7 use strict;
  2         2  
  2         36  
5 2     2   477 use utf8;
  2         13  
  2         6  
6 2     2   39 use Carp;
  2         2  
  2         111  
7              
8             our $VERSION = 'v2.0.1';
9              
10 2     2   449 use Export::Attrs;
  2         6432  
  2         8  
11 2     2   126 use List::Util qw( min );
  2         3  
  2         151  
12 2     2   391 use CGI::Easy::Util qw( date_http );
  2         4409  
  2         7  
13              
14 2     2   1094 use constant STAT_MTIME => 9;
  2         2  
  2         178  
15 2     2   14 use constant BUF_SIZE => 64*1024;
  2         3  
  2         561  
16              
17              
18             sub send_file :Export {
19 25     25 1 24825 my ($r, $h, $file, $opt) = @_;
20             my %p = (
21             type => 'application/x-download',
22             range => !ref $file,
23             cache => 0,
24             inline => 0,
25 25 100       41 %{$opt || {}},
  25         120  
26             );
27              
28 25 100       55 if (!$p{cache}) {
29 20         26 $h->{'Expires'} = 'Sat, 01 Jan 2000 00:00:00 GMT';
30             }
31             else {
32 5         7 delete $h->{'Expires'};
33 5 100       9 if (!ref $file) {
34 4         56 my $lastmod = date_http((stat $file)[STAT_MTIME]);
35 4         90 my $ifmod = $r->{ENV}{HTTP_IF_MODIFIED_SINCE};
36 4 100 100     17 if ($ifmod && $ifmod eq $lastmod) {
37 1         2 $h->{'Status'} = '304 Not Modified';
38 1         4 return \q{};
39             }
40             else {
41 3         6 $h->{'Last-Modified'} = $lastmod;
42             }
43             }
44             }
45              
46 24 100       88 my $len = ref $file ? length ${$file} : -s $file;
  19         25  
47 24         49 my ($start, $end) = _get_range($p{range}, $r, $len);
48 24         38 my $size = $end-$start+1;
49              
50 24         32 $h->{'Accept-Ranges'} = 'bytes';
51 24         25 $h->{'Content-Length'} = $size;
52 24         31 $h->{'Content-Type'} = $p{type};
53 24 100       37 if (!$p{inline}) {
54 19         38 $h->{'Content-Disposition'} = 'attachment';
55             }
56 24 100 100     59 if (!($start == 0 && $end == $len-1)) {
57 8         9 $h->{Status} = '206 Partial Content';
58 8         16 $h->{'Content-Range'} = "bytes $start-$end/$len";
59             }
60              
61 24         37 return _read_block($file, $start, $size);
62 2     2   12 }
  2         3  
  2         7  
63              
64             sub _get_range {
65 24     24   34 my ($allow_range, $r, $len) = @_;
66 24         38 my ($start, $end) = (0, $len-1);
67 24 100 100     76 if ($allow_range && defined $r->{ENV}{HTTP_RANGE}) {
68 15 50       78 if ($r->{ENV}{HTTP_RANGE} =~ /\Abytes=(\d*)-(\d*)\z/ixms) {
69 15         40 my ($from, $to) = ($1, $2);
70 15 100 100     98 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         7 $start = $from;
72 5         7 $end = $to;
73             }
74             elsif ($from ne q{} && $to eq q{} && $from < $len) { # 0-, 500-, 999-
75 3         5 $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         45 return ($start, $end);
83             }
84              
85             sub _read_block {
86 24     24   32 my ($file, $start, $size) = @_;
87 24         25 my $data = q{};
88 24 50   1   297 open my $fh, '<', $file or croak "open: $!";
  1         5  
  1         1  
  1         4  
89 24         634 seek $fh, $start, 0;
90 24         34 my ($n, $buf);
91 24         157 while ($n = read $fh, $buf, min($size, BUF_SIZE)) {
92 24         30 $size -= length $buf;
93 24         104 $data .= $buf;
94             }
95 24 50       41 croak "read: $!" if !defined $n;
96 24 50       76 close $fh or croak "close: $!";
97 24         108 return \$data;
98             }
99              
100              
101             1; # Magic true value required at end of module
102             __END__