File Coverage

blib/lib/Dancer2/Handler/File.pm
Criterion Covered Total %
statement 33 73 45.2
branch 3 30 10.0
condition 2 16 12.5
subroutine 8 15 53.3
pod 0 6 0.0
total 46 140 32.8


line stmt bran cond sub pod time code
1             # ABSTRACT: class for handling file content rendering
2             $Dancer2::Handler::File::VERSION = '0.400000';
3             use Carp 'croak';
4 142     142   895 use Moo;
  142         278  
  142         5974  
5 142     142   1247 use HTTP::Date;
  142         283  
  142         683  
6 142     142   41629 use Dancer2::FileUtils 'path', 'open_file', 'read_glob_content';
  142         316  
  142         8694  
7 142     142   885 use Dancer2::Core::MIME;
  142         296  
  142         7185  
8 142     142   49917 use Dancer2::Core::Types;
  142         5598  
  142         4540  
9 142     142   922 use File::Spec;
  142         278  
  142         749  
10 142     142   1159817  
  142         335  
  142         123815  
11             with qw<
12             Dancer2::Core::Role::Handler
13             Dancer2::Core::Role::StandardResponses
14             Dancer2::Core::Role::Hookable
15             >;
16              
17             {
18             before_file_render => 'handler.file.before_render',
19             after_file_render => 'handler.file.after_render',
20 0     0 0 0 }
21             }
22              
23              
24             has mime => (
25 0     0 0 0 is => 'ro',
  0         0  
26             isa => InstanceOf ['Dancer2::Core::MIME'],
27             default => sub { Dancer2::Core::MIME->new },
28             );
29              
30             has encoding => (
31             is => 'ro',
32             default => sub {'utf-8'},
33             );
34              
35             has public_dir => (
36             is => 'ro',
37             lazy => 1,
38             builder => '_build_public_dir',
39             );
40              
41             has regexp => (
42             is => 'ro',
43             default => sub {'/**'},
44             );
45              
46             my $self = shift;
47             return $self->app->config->{public_dir}
48             || $ENV{DANCER_PUBLIC}
49             || path( $self->app->location, 'public' );
50 0     0   0 }
51              
52             my ( $self, $app ) = @_;
53 0   0     0  
54             # don't register the handler if no valid public dir
55             return if !-d $self->public_dir;
56              
57 0     0 0 0 $app->add_route(
58             method => $_,
59             regexp => $self->regexp,
60 0 0       0 code => $self->code( $app->prefix ),
61             ) for $self->methods;
62             }
63              
64              
65             my ( $self, $prefix ) = @_;
66 0         0  
67             sub {
68             my $app = shift;
69 0     0 0 0 my $prefix = shift;
70             my $path = $app->request->path_info;
71              
72 0     0 0 0 if ( $path =~ /\0/ ) {
73             return $self->standard_response( $app, 400 );
74             }
75 0     0   0  
76 0         0 if ( $prefix && $prefix ne '/' ) {
77 0         0 $path =~ s/^\Q$prefix\E//;
78             }
79 0 0       0  
80 0         0 my $file_path = $self->merge_paths( $path, $self->public_dir );
81             return $self->standard_response( $app, 403 ) if !defined $file_path;
82              
83 0 0 0     0 if ( !-f $file_path ) {
84 0         0 $app->response->has_passed(1);
85             return;
86             }
87 0         0  
88 0 0       0 if ( !-r $file_path ) {
89             return $self->standard_response( $app, 403 );
90 0 0       0 }
91 0         0  
92 0         0 # Now we are sure we can render the file...
93             $self->execute_hook( 'handler.file.before_render', $file_path );
94              
95 0 0       0 # Read file content as bytes
96 0         0 my $fh = open_file( "<", $file_path );
97             binmode $fh;
98             my $content = read_glob_content($fh);
99              
100 0         0 # Assume m/^text/ mime types are correctly encoded
101             my $content_type = $self->mime->for_file($file_path) || 'text/plain';
102             if ( $content_type =~ m!^text/! ) {
103 0         0 $content_type .= "; charset=" . ( $self->encoding || "utf-8" );
104 0         0 }
105 0         0  
106             my @stat = stat $file_path;
107              
108 0   0     0 $app->response->header('Content-Type')
109 0 0       0 or $app->response->header( 'Content-Type', $content_type );
110 0   0     0  
111             $app->response->header('Content-Length')
112             or $app->response->header( 'Content-Length', $stat[7] );
113 0         0  
114             $app->response->header('Last-Modified')
115 0 0       0 or $app->response->header(
116             'Last-Modified',
117             HTTP::Date::time2str( $stat[9] )
118 0 0       0 );
119              
120             $app->response->content($content);
121 0 0       0 $app->response->is_encoded(1); # bytes are already encoded
122             $self->execute_hook( 'handler.file.after_render', $app->response );
123             return ( $app->request->method eq 'GET' ) ? $content : '';
124             };
125             }
126              
127 0         0 my ( undef, $path, $public_dir ) = @_;
128 0         0  
129 0         0 my ( $volume, $dirs, $file ) = File::Spec->splitpath( $path );
130 0 0       0 my @tokens = File::Spec->splitdir( "$dirs$file" );
131 0         0 my $updir = File::Spec->updir;
132             return if grep $_ eq $updir, @tokens;
133              
134             my ( $pub_vol, $pub_dirs, $pub_file ) = File::Spec->splitpath( $public_dir );
135 9     9 0 25 my @pub_tokens = File::Spec->splitdir( "$pub_dirs$pub_file" );
136             return if length $volume and length $pub_vol and $volume ne $pub_vol;
137 9         130  
138 9         71 my @final_vol = ( length $pub_vol ? $pub_vol : length $volume ? $volume : () );
139 9         36 my @file_path = ( @final_vol, @pub_tokens, @tokens );
140 9 50       35 my $file_path = path( @file_path );
141             return $file_path;
142 9         63 }
143 9         39  
144 9 0 33     33 1;
      33        
145              
146 9 50       31  
    50          
147 9         23 =pod
148 9         39  
149 9         29 =encoding UTF-8
150              
151             =head1 NAME
152              
153             Dancer2::Handler::File - class for handling file content rendering
154              
155             =head1 VERSION
156              
157             version 0.400000
158              
159             =head1 AUTHOR
160              
161             Dancer Core Developers
162              
163             =head1 COPYRIGHT AND LICENSE
164              
165             This software is copyright (c) 2022 by Alexis Sukrieh.
166              
167             This is free software; you can redistribute it and/or modify it under
168             the same terms as the Perl 5 programming language system itself.
169              
170             =cut