Dancer2
view release on metacpan or search on metacpan
lib/Dancer2/Core/App.pm view on Meta::CPAN
if ( $prefix && $prefix ne '/' ) {
$path =~ s/^\Q$prefix\E//;
}
# static file dir - either system root or public_dir
my $dir = $options{system_path}
? Path::Tiny->rootdir
: $self->_public_dir_path;
my $err_response = sub {
my $status = shift;
$self->response->status($status);
$self->response->header( 'Content-Type', 'text/plain' );
$self->response->content( Dancer2::Core::HTTP->status_message($status) );
$self->with_return->( $self->response );
};
# resolve relative paths (with '../') as much as possible
# On Windows, absolute paths (e.g., C:/foo) must not be joined with
# the rootdir since Path::Tiny would produce an invalid /C:/foo path.
my $pt_path = Path::Tiny::path($path);
$file_path = ( $pt_path->is_absolute
? $pt_path
: Path::Tiny::path( $dir, $path )
)->realpath;
# We need to check whether they are trying to access
# a directory outside their scope
$err_response->(403) if !$dir->realpath->subsumes($file_path);
# other error checks
$err_response->(403) if !$file_path->exists;
$err_response->(404) if !$file_path->is_file;
$err_response->(403) if !-r $file_path;
# Read file content as bytes
$fh = $file_path->openr_raw();
$content_type = $self->mime_type->for_file($file_path) || 'text/plain';
if ( $content_type =~ m!^text/! ) {
$charset = $self->config->{charset};
}
# cleanup for other functions not assuming on Path::Tiny
$file_path = $file_path->stringify;
}
# Now we are sure we can render the file...
$self->execute_hook( 'core.app.before_file_render', $file_path );
# response content type and charset
( exists $options{'content_type'} ) and $content_type = $options{'content_type'};
( exists $options{'charset'} ) and $charset = $options{'charset'};
$content_type .= "; charset=$charset" if $content_type and $charset;
( defined $content_type )
and $self->response->header('Content-Type' => $content_type );
# content disposition
( exists $options{filename} )
and $self->response->header( 'Content-Disposition' =>
($options{content_disposition} || "attachment") . "; filename=\"$options{filename}\"" );
# use a delayed response unless server does not support streaming
my $use_streaming = exists $options{streaming} ? $options{streaming} : 1;
my $response;
my $env = $self->request->env;
if ( $env->{'psgi.streaming'} && $use_streaming ) {
my $cb = sub {
my $responder = $Dancer2::Core::Route::RESPONDER;
my $res = $Dancer2::Core::Route::RESPONSE;
return $responder->(
[ $res->status, $res->headers_to_array, $fh ]
);
};
Scalar::Util::weaken( my $weak_self = $self );
$response = Dancer2::Core::Response::Delayed->new(
error_cb => sub { $weak_self->logger_engine->log( warning => @_ ) },
cb => $cb,
request => $Dancer2::Core::Route::REQUEST,
response => $Dancer2::Core::Route::RESPONSE,
);
}
else {
$response = $self->response;
# direct assignment to hash element, avoids around modifier
# trying to serialise this this content.
# optimized slurp
{
## no critic qw(Variables::RequireInitializationForLocalVars)
local $/;
$response->{'content'} = <$fh>;
}
$response->is_encoded(1); # bytes are already encoded
}
$self->execute_hook( 'core.app.after_file_render', $response );
$self->with_return->( $response );
}
sub BUILD {
my $self = shift;
$self->init_route_handlers();
$self->_init_hooks();
}
sub finish {
my $self = shift;
# normalize some values that require calculations
defined $self->config->{'static_handler'}
or $self->config->{'static_handler'} = -d $self->config->{'public_dir'};
$self->register_route_handlers;
$self->compile_hooks;
@{$self->plugins}
&& $self->plugins->[0]->can('_add_postponed_plugin_hooks')
&& $self->plugins->[0]->_add_postponed_plugin_hooks(
$self->postponed_hooks
);
foreach my $prep_cb ( @{ $self->prep_apps } ) {
$prep_cb->($self);
( run in 0.874 second using v1.01-cache-2.11-cpan-c966e8aa7e8 )