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