File Coverage

blib/lib/Mojolicious/Plugin/RenderFile.pm
Criterion Covered Total %
statement 62 66 93.9
branch 18 22 81.8
condition 26 37 70.2
subroutine 8 8 100.0
pod 1 1 100.0
total 115 134 85.8


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