File Coverage

blib/lib/Mojolicious/Plugin/RenderFile.pm
Criterion Covered Total %
statement 60 64 93.7
branch 17 20 85.0
condition 23 31 74.1
subroutine 8 8 100.0
pod 1 1 100.0
total 109 124 87.9


line stmt bran cond sub pod time code
1             package Mojolicious::Plugin::RenderFile;
2 2     2   3019 use Mojo::Base 'Mojolicious::Plugin';
  2         5  
  2         16  
3              
4 2     2   419 use strict;
  2         4  
  2         39  
5 2     2   18 use warnings;
  2         3  
  2         54  
6 2     2   11 use File::Basename;
  2         3  
  2         159  
7 2     2   9 use Encode qw( encode decode_utf8 );
  2         4  
  2         169  
8 2     2   11 use Mojo::Util 'quote';
  2         2  
  2         1698  
9              
10             our $VERSION = '0.10';
11              
12             sub register {
13 2     2 1 77 my ( $self, $app ) = @_;
14              
15             $app->helper( 'render_file' => sub {
16 13     13   124437 my $c = shift;
17 13         58 my %args = @_;
18              
19 13 100 100     113 utf8::decode($args{filename}) if $args{filename} && !utf8::is_utf8($args{filename});
20 13 100 100     122 utf8::decode($args{filepath}) if $args{filepath} && !utf8::is_utf8($args{filepath});
21              
22 13         35 my $filename = $args{filename};
23 13   100     63 my $status = $args{status} || 200;
24 13   100     53 my $content_disposition = $args{content_disposition} || 'attachment';
25 13   50     62 my $cleanup = $args{cleanup} // 0;
26              
27             # Content type based on format
28 13         19 my $content_type;
29 13 100       49 $content_type = $c->app->types->type( $args{format} ) if $args{format};
30 13   100     137 $content_type ||= 'application/x-download';
31              
32             # Create asset
33 13         16 my $asset;
34 13 100       52 if ( my $filepath = $args{filepath} ) {
    50          
35 9 50 33     429 unless ( -f $filepath && -r $filepath ) {
36 0         0 $c->app->log->error("Cannot read file [$filepath]. error [$!]");
37 0         0 return;
38             }
39              
40 9   66     171 $filename ||= fileparse($filepath);
41 9         74 $asset = Mojo::Asset::File->new( path => $filepath );
42 9         96 $asset->cleanup($cleanup);
43             } elsif ( $args{data} ) {
44 4   50     16 $filename ||= $c->req->url->path->parts->[-1] || 'download';
      66        
45 4         83 $asset = Mojo::Asset::Memory->new();
46 4         35 $asset->add_chunk( $args{data} );
47             } else {
48 0         0 $c->app->log->error('You must provide "data" or "filepath" option');
49 0         0 return;
50             }
51              
52             # Set response headers
53 13         137 my $headers = $c->res->content->headers();
54              
55 13         242 $filename = quote($filename); # quote the filename, per RFC 5987
56 13         150 $filename = encode("UTF-8", $filename);
57              
58 13         635 $headers->add( 'Content-Type', $content_type . ';name=' . $filename );
59 13         159 $headers->add( 'Content-Disposition', $content_disposition . ';filename=' . $filename );
60              
61             # Range
62             # Partially based on Mojolicious::Static
63 13 100       140 if ( my $range = $c->req->headers->range ) {
64 5         147 my $start = 0;
65 5         22 my $size = $asset->size;
66 5 50       418 my $end = $size - 1 >= 0 ? $size - 1 : 0;
67              
68             # Check range
69 5 100 66     47 if ( $range =~ m/^bytes=(\d+)-(\d+)?/ && $1 <= $end ) {
70 4         9 $start = $1;
71 4 100 66     20 $end = $2 if defined $2 && $2 <= $end;
72              
73 4         7 $status = 206;
74 4         16 $headers->add( 'Content-Length' => $end - $start + 1 );
75 4         52 $headers->add( 'Content-Range' => "bytes $start-$end/$size" );
76             } else {
77             # Not satisfiable
78 1         5 return $c->rendered(416);
79             }
80              
81             # Set range for asset
82 4         44 $asset->start_range($start)->end_range($end);
83             } else {
84 8         422 $headers->add( 'Content-Length' => $asset->size );
85             }
86              
87             # Stream content directly from file
88 12         1028 $c->res->content->asset($asset);
89 12         215 return $c->rendered($status);
90 2         25 } );
91             }
92              
93             1;
94              
95             =head1 NAME
96              
97             Mojolicious::Plugin::RenderFile - "render_file" helper for Mojolicious
98              
99             =head1 SYNOPSIS
100              
101             # Mojolicious
102             $self->plugin('RenderFile');
103              
104             # Mojolicious::Lite
105             plugin 'RenderFile';
106              
107             # In controller
108             $self->render_file('filepath' => '/tmp/files/file.pdf'); # file name will be "file.pdf"
109              
110             # Provide any file name
111             $self->render_file('filepath' => '/tmp/files/file.pdf', 'filename' => 'report.pdf');
112              
113             # Render data from memory as file
114             $self->render_file('data' => 'some data here', 'filename' => 'report.pdf');
115              
116             # Open file in browser(do not show save dialog)
117             $self->render_file(
118             'filepath' => '/tmp/files/file.pdf',
119             'format' => 'pdf', # will change Content-Type "application/x-download" to "application/pdf"
120             'content_disposition' => 'inline', # will change Content-Disposition from "attachment" to "inline"
121             'cleanup' => 1, # delete file after completed
122             );
123              
124             =head1 DESCRIPTION
125              
126             L is a L plugin that adds "render_file" helper. It does not read file in memory and just streaming it to a client.
127              
128             =head1 HELPERS
129              
130             =head2 C
131              
132             $self->render_file(filepath => '/tmp/files/file.pdf', 'filename' => 'report.pdf' );
133              
134             With this helper you can easily provide files for download. By default "Content-Type" header is "application/x-download" and "content_disposition" option value is "attachment".
135             Therefore, a browser will ask where to save file. You can provide "format" option to change "Content-Type" header.
136              
137              
138             =head3 Supported Options:
139              
140             =over
141              
142             =item C
143              
144             Path on the filesystem to the file. You must always pass "filepath" or "data" option
145              
146             =item C
147              
148             Binary content which will be transfered to browser. You must always pass "filepath" or "data" option
149              
150             =item C (optional)
151              
152             Browser will use this name for saving the file
153              
154             =item C (optional)
155              
156             The "Content-Type" header is based on the MIME type mapping of the "format" option value. These mappings can be easily extended or changed with L.
157              
158             By default "Content-Type" header is "application/x-download"
159              
160             =item C (optional)
161              
162             Tells browser how to present the file.
163              
164             "attachment" (default) - is for dowloading
165              
166             "inline" - is for showing file inline
167              
168             =item C (optional)
169              
170             Indicates if the file should be deleted when rendering is complete
171              
172             =back
173              
174             This plugin respects HTTP Range headers.
175              
176             =head1 AUTHOR
177              
178             Viktor Turskyi
179              
180             =head1 CONTRIBUTORS
181              
182             Nils Diewald (Akron)
183             Danil Greben (SDSWanderer)
184              
185             =head1 BUGS
186              
187             Please report any bugs or feature requests to Github L
188              
189             =head1 SEE ALSO
190              
191             L, L, L.
192              
193             Copyright 2011 Viktor Turskyi
194              
195             This program is free software; you can redistribute it and/or modify it
196             under the terms of either: the GNU General Public License as published
197             by the Free Software Foundation; or the Artistic License.
198              
199             See http://dev.perl.org/licenses/ for more information.
200              
201             =cut