File Coverage

blib/lib/Dancer2/Handler/File.pm
Criterion Covered Total %
statement 18 59 30.5
branch 0 22 0.0
condition 0 10 0.0
subroutine 6 14 42.8
pod 0 5 0.0
total 24 110 21.8


line stmt bran cond sub pod time code
1             package Dancer2::Handler::File;
2             # ABSTRACT: class for handling file content rendering
3             $Dancer2::Handler::File::VERSION = '2.1.0';
4 1     1   315586 use Carp 'croak';
  1         3  
  1         93  
5 1     1   670 use Moo;
  1         11684  
  1         8  
6 1     1   2664 use HTTP::Date;
  1         7378  
  1         80  
7 1     1   535 use Dancer2::Core::MIME;
  1         5  
  1         45  
8 1     1   10 use Dancer2::Core::Types;
  1         2  
  1         11  
9 1     1   16279 use Path::Tiny ();
  1         19123  
  1         1046  
10              
11             with qw<
12             Dancer2::Core::Role::Handler
13             Dancer2::Core::Role::StandardResponses
14             Dancer2::Core::Role::Hookable
15             >;
16              
17             sub hook_aliases {
18             {
19 0     0 0   before_file_render => 'handler.file.before_render',
20             after_file_render => 'handler.file.after_render',
21             }
22             }
23              
24 0     0 0   sub supported_hooks { values %{ shift->hook_aliases } }
  0            
25              
26             has mime => (
27             is => 'ro',
28             isa => InstanceOf ['Dancer2::Core::MIME'],
29             default => sub { Dancer2::Core::MIME->new },
30             );
31              
32             has encoding => (
33             is => 'ro',
34             default => sub {'utf-8'},
35             );
36              
37             has public_dir => (
38             is => 'ro',
39             lazy => 1,
40             builder => '_build_public_dir',
41             );
42              
43             has _public_dir_path => (
44             is => 'ro',
45             lazy => 1,
46             builder => '_build_public_dir_path',
47             init_arg => undef,
48             );
49              
50             has regexp => (
51             is => 'ro',
52             default => sub {'/**'},
53             );
54              
55             sub _build_public_dir {
56 0     0     my $self = shift;
57             return $self->app->config->{public_dir}
58             || $ENV{DANCER_PUBLIC}
59 0   0       || Path::Tiny::path( $self->app->location, 'public' )->stringify;
60             }
61              
62             sub _build_public_dir_path {
63 0     0     my $self = shift;
64 0           return Path::Tiny::path( $self->public_dir );
65             }
66              
67             sub register {
68 0     0 0   my ( $self, $app ) = @_;
69              
70             # don't register the handler if no valid public dir
71 0 0         return if !$self->_public_dir_path->is_dir;
72              
73             $app->add_route(
74             method => $_,
75             regexp => $self->regexp,
76             code => $self->code( $app->prefix ),
77 0           ) for $self->methods;
78             }
79              
80 0     0 0   sub methods { ( 'head', 'get' ) }
81              
82             sub code {
83 0     0 0   my ( $self, $prefix ) = @_;
84              
85             sub {
86 0     0     my $app = shift;
87 0           my $prefix = shift;
88 0           my $path = $app->request->path_info;
89              
90 0 0         if ( $path =~ /\0/ ) {
91 0           return $self->standard_response( $app, 400 );
92             }
93              
94 0 0 0       if ( $prefix && $prefix ne '/' ) {
95 0           $path =~ s/^\Q$prefix\E//;
96             }
97              
98 0           my $file_path = Path::Tiny::path( $self->_public_dir_path, $path );
99 0           my $file_path_str = $file_path->stringify;
100 0 0         return $self->standard_response( $app, 403 ) if !defined $file_path_str;
101              
102 0 0         if ( !-f $file_path_str ) {
103 0           $app->response->has_passed(1);
104 0           return;
105             }
106              
107 0 0         if ( !-r $file_path_str ) {
108 0           return $self->standard_response( $app, 403 );
109             }
110              
111             # Now we are sure we can render the file...
112 0           $self->execute_hook( 'handler.file.before_render', $file_path_str );
113              
114             # Read file content as bytes
115 0           my $content = $file_path->slurp_raw;
116              
117             # Assume m/^text/ mime types are correctly encoded
118 0   0       my $content_type = $self->mime->for_file($file_path_str) || 'text/plain';
119 0 0         if ( $content_type =~ m!^text/! ) {
120 0   0       $content_type .= "; charset=" . ( $self->encoding || "utf-8" );
121             }
122              
123 0           my @stat = stat $file_path_str;
124              
125 0 0         $app->response->header('Content-Type')
126             or $app->response->header( 'Content-Type', $content_type );
127              
128 0 0         $app->response->header('Content-Length')
129             or $app->response->header( 'Content-Length', $stat[7] );
130              
131 0 0         $app->response->header('Last-Modified')
132             or $app->response->header(
133             'Last-Modified',
134             HTTP::Date::time2str( $stat[9] )
135             );
136              
137 0           $app->response->content($content);
138 0           $app->response->is_encoded(1); # bytes are already encoded
139 0           $self->execute_hook( 'handler.file.after_render', $app->response );
140 0 0         return ( $app->request->method eq 'GET' ) ? $content : '';
141 0           };
142             }
143              
144             1;
145              
146             __END__