diff --git a/lib/Dancer2.pm b/lib/Dancer2.pm index 782c6f120..630ed6eba 100644 --- a/lib/Dancer2.pm +++ b/lib/Dancer2.pm @@ -10,7 +10,6 @@ use Import::Into; use Dancer2::Core; use Dancer2::Core::App; use Dancer2::Core::Runner; -use Dancer2::FileUtils; our $AUTHORITY = 'SUKRIA'; diff --git a/lib/Dancer2/CLI/Command/gen.pm b/lib/Dancer2/CLI/Command/gen.pm index 1511e48dc..e2d84a77d 100644 --- a/lib/Dancer2/CLI/Command/gen.pm +++ b/lib/Dancer2/CLI/Command/gen.pm @@ -7,13 +7,11 @@ use warnings; use App::Cmd::Setup -command; use HTTP::Tiny; +use Path::Tiny (); use File::Find; -use File::Path 'mkpath'; -use File::Spec::Functions; use File::Share 'dist_dir'; -use File::Basename qw/dirname basename/; -use Dancer2::Template::Simple; use Module::Runtime 'require_module'; +use Dancer2::Template::Simple; my $SKEL_APP_FILE = 'lib/AppFile.pm'; @@ -45,12 +43,12 @@ sub validate_args { ); } - my $path = $opt->{path}; - -d $path or $self->usage_error("directory '$path' does not exist"); + my $path = Path::Tiny::path( $opt->{path} ); + $path->is_dir or $self->usage_error("directory '$path' does not exist"); -w $path or $self->usage_error("directory '$path' is not writeable"); if ( my $skel_path = $opt->{skel} ) { - -d $skel_path + Path::Tiny::path($skel_path)->is_dir or $self->usage_error("skeleton directory '$skel_path' not found"); } } @@ -59,22 +57,27 @@ sub execute { my ($self, $opt, $args) = @_; $self->_version_check() unless $opt->{'no_check'}; - my $dist_dir = dist_dir('Dancer2'); - my $skel_dir = $opt->{skel} || catdir($dist_dir, 'skel'); - -d $skel_dir or die "$skel_dir doesn't exist"; + my @dirs = + $opt->{'skel'} + ? ($opt->{'skel'}) + : (dist_dir('Dancer2'), 'skel'); + + my $skel_dir = Path::Tiny::path(@dirs); + $skel_dir->is_dir + or die "Skeleton directory '$skel_dir' doesn't exist"; my $app_name = $opt->{application}; my $app_file = _get_app_file($app_name); my $app_path = _get_app_path($opt->{path}, $app_name); if( my $dir = $opt->{directory} ) { - $app_path = catdir( $opt->{path}, $dir ); + $app_path = Path::Tiny::path( $opt->{path}, $dir ); } my $files_to_copy = _build_file_list($skel_dir, $app_path); foreach my $pair (@$files_to_copy) { if ($pair->[0] =~ m/$SKEL_APP_FILE$/) { - $pair->[1] = catfile($app_path, $app_file); + $pair->[1] = Path::Tiny::path($app_path, $app_file); last; } } @@ -82,7 +85,7 @@ sub execute { my $vars = { appname => $app_name, appfile => $app_file, - appdir => File::Spec->rel2abs($app_path), + appdir => Path::Tiny::path($app_path)->absolute, perl_interpreter => _get_perl_interpreter(), cleanfiles => _get_dashed_name($app_name), dancer_version => $self->version(), @@ -148,7 +151,7 @@ sub _build_file_list { my $is_git = $file =~ m{^\.git(/|$)} and return; - push @result, [ $_, catfile($to, $file) ]; + push @result, [ $_, Path::Tiny::path($to, $file) ]; }; find({ wanted => $wanted, no_chdir => 1 }, $from); @@ -167,15 +170,15 @@ sub _copy_templates { next unless ($res eq 'y') or ($res eq 'a'); } - my $to_dir = dirname($to); - if (! -d $to_dir) { + my $to_dir = Path::Tiny::path($to)->parent; + if (! $to_dir->is_dir ) { print "+ $to_dir\n"; - mkpath $to_dir or die "could not mkpath $to_dir: $!"; + $to_dir->mkpath or die "could not mkpath $to_dir: $!"; } - my $to_file = basename($to); + my $to_file = Path::Tiny::path($to)->parent->stringify; my $ex = ($to_file =~ s/^\+//); - $to = catfile($to_dir, $to_file) if $ex; + $to = Path::Tiny::path($to_dir, $to_file)->stringify if $ex; print "+ $to\n"; my $content; @@ -210,7 +213,7 @@ sub _create_manifest { foreach my $file (@{$files}) { my $filename = substr $file->[1], length($dir) + 1; - my $basename = basename $filename; + my $basename = path($filename)->parent->stringify; my $clean_basename = $basename; $clean_basename =~ s/^\+//; $filename =~ s/\Q$basename\E/$clean_basename/; @@ -239,13 +242,14 @@ sub _process_template { sub _get_app_path { my ($path, $appname) = @_; - return catdir($path, _get_dashed_name($appname)); + return Path::Tiny::path($path, _get_dashed_name($appname) ); } sub _get_app_file { my $appname = shift; + $appname =~ s{::}{/}g; - return catfile('lib', "$appname.pm"); + return Path::Tiny::path( 'lib', "$appname.pm" ); } sub _get_perl_interpreter { diff --git a/lib/Dancer2/Cookbook.pod b/lib/Dancer2/Cookbook.pod index 0fc3aee0e..809551f2f 100644 --- a/lib/Dancer2/Cookbook.pod +++ b/lib/Dancer2/Cookbook.pod @@ -903,13 +903,13 @@ API and then displays it dynamically in a web page. Other than L for defining routes, we will use L to make the weather API request, L to decode it from JSON format, -and finally L to provide a fully-qualified path to our +and finally L to provide a fully-qualified path to our template engine. use JSON; use Dancer2; use HTTP::Tiny; - use File::Spec; + use Path::Tiny; =head4 Configuration @@ -919,16 +919,16 @@ to I directory in our current directory. Since we want to put our template in our current directory, we will configure that. However, I does not want us to provide a relative path without configuring it to allow it. This is a security issue. So, we're using -L to create a full path to where we are. +L to create a full path to where we are. We also unset the default layout, so Dancer won't try to wrap our template with another one. This is a feature in Dancer to allow you to wrap your templates with a layout when your templating system doesn't support it. Since we're not using a layout here, we don't need it. - set template => 'template_toolkit'; # set template engine - set layout => undef; # disable layout - set views => File::Spec->rel2abs('.'); # full path to views + set template => 'template_toolkit'; # set template engine + set layout => undef; # disable layout + set views => Path::Tiny->cwd->absolute; # full path to views Now, we define our URL: diff --git a/lib/Dancer2/Core/App.pm b/lib/Dancer2/Core/App.pm index 41e6518a5..4eb5a453e 100644 --- a/lib/Dancer2/Core/App.pm +++ b/lib/Dancer2/Core/App.pm @@ -7,7 +7,7 @@ use Scalar::Util 'blessed'; use Module::Runtime 'is_module_name'; use Safe::Isa; use Sub::Quote; -use File::Spec; +use Path::Tiny (); use Module::Runtime 'use_module'; use List::Util (); use Ref::Util qw< is_ref is_globref is_scalarref >; @@ -18,9 +18,9 @@ use Plack::Middleware::Head; use Plack::Middleware::Conditional; use Plack::Middleware::ConditionalGET; -use Dancer2::FileUtils 'path'; use Dancer2::Core; use Dancer2::Core::Cookie; +use Dancer2::Core::HTTP; use Dancer2::Core::Error; use Dancer2::Core::Types; use Dancer2::Core::Route; @@ -28,8 +28,6 @@ use Dancer2::Core::Hook; use Dancer2::Core::Request; use Dancer2::Core::Factory; -use Dancer2::Handler::File; - our $EVAL_SHIM; $EVAL_SHIM ||= sub { my $code = shift; $code->(@_); @@ -245,7 +243,7 @@ sub _build_session_engine { # Note that engine options will replace the default session_dir (if provided). return $self->_factory->create( session => $value, - session_dir => path( $self->config->{appdir}, 'sessions' ), + session_dir => Path::Tiny::path( $self->config->{appdir}, 'sessions' )->stringify, %{$engine_options}, postponed_hooks => $self->postponed_hooks, @@ -681,13 +679,13 @@ around execute_hook => sub { sub _build_default_config { my $self = shift; - my $public = $ENV{DANCER_PUBLIC} || path( $self->location, 'public' ); + my $public = $ENV{DANCER_PUBLIC} || Path::Tiny::path( $self->location, 'public' )->stringify; return { content_type => ( $ENV{DANCER_CONTENT_TYPE} || 'text/html' ), charset => ( $ENV{DANCER_CHARSET} || '' ), logger => ( $ENV{DANCER_LOGGER} || 'console' ), views => ( $ENV{DANCER_VIEWS} - || path( $self->location, 'views' ) ), + || Path::Tiny::path( $self->location, 'views' )->stringify ), environment => $self->environment, appdir => $self->location, public_dir => $public, @@ -1030,12 +1028,11 @@ sub send_file { } # static file dir - either system root or public_dir my $dir = $options{system_path} - ? File::Spec->rootdir + ? Path::Tiny->rootdir : $ENV{DANCER_PUBLIC} || $self->config->{public_dir} - || path( $self->location, 'public' ); + || Path::Tiny::path( $self->location, 'public' )->stringify; - $file_path = Dancer2::Handler::File->merge_paths( $path, $dir ); my $err_response = sub { my $status = shift; $self->response->status($status); @@ -1043,17 +1040,29 @@ sub send_file { $self->response->content( Dancer2::Core::HTTP->status_message($status) ); $self->with_return->( $self->response ); }; - $err_response->(403) if !defined $file_path; - $err_response->(404) if !-f $file_path; + + # resolve relative paths (with '../') as much as possible + $file_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 !Path::Tiny::path($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 = Dancer2::FileUtils::open_file( "<", $file_path ); - binmode $fh; + $fh = $file_path->openr_raw(); + $content_type = Dancer2::runner()->mime_type->for_file($file_path) || 'text/plain'; if ( $content_type =~ m!^text/! ) { $charset = $self->config->{charset} || "utf-8"; } + + # cleanup for other functions not assuming on Path::Tiny + $file_path = $file_path->stringify; } # Now we are sure we can render the file... @@ -1097,7 +1106,14 @@ sub send_file { $response = $self->response; # direct assignment to hash element, avoids around modifier # trying to serialise this this content. - $response->{content} = Dancer2::FileUtils::read_glob_content($fh); + + # optimized slurp + { + ## no critic qw(Variables::RequireInitializationForLocalVars) + local $/; + $response->{'content'} = <$fh>; + } + $response->is_encoded(1); # bytes are already encoded } @@ -1402,7 +1418,15 @@ sub to_app { # when the file exists. Otherwise the request passes into our app. $psgi = Plack::Middleware::Conditional->wrap( $psgi, - condition => sub { -f path( $self->config->{public_dir}, shift->{PATH_INFO} ) }, + condition => sub { + my $env = shift; + Path::Tiny::path( + $self->config->{'public_dir'}, + defined $env->{'PATH_INFO'} && length $env->{'PATH_INFO'} + ? ($env->{'PATH_INFO'}) + : (), + )->is_file; + }, builder => sub { Plack::Middleware::ConditionalGET->wrap( $static_app ) }, ); } diff --git a/lib/Dancer2/Core/DSL.pm b/lib/Dancer2/Core/DSL.pm index fd29ac746..ca756ae1d 100644 --- a/lib/Dancer2/Core/DSL.pm +++ b/lib/Dancer2/Core/DSL.pm @@ -4,10 +4,10 @@ package Dancer2::Core::DSL; use Moo; use Carp; +use Path::Tiny (); use Module::Runtime 'require_module'; use Ref::Util qw< is_arrayref >; use Dancer2::Core::Hook; -use Dancer2::FileUtils; use Dancer2::Core::Response::Delayed; with 'Dancer2::Core::Role::DSL'; @@ -143,8 +143,8 @@ sub error { shift->app->log( error => @_ ) } sub true {1} sub false {0} -sub dirname { shift and Dancer2::FileUtils::dirname(@_) } -sub path { shift and Dancer2::FileUtils::path(@_) } +sub dirname { shift and Path::Tiny::path(@_)->parent->stringify } +sub path { shift and Path::Tiny::path(@_)->stringify } sub config { shift->app->settings } diff --git a/lib/Dancer2/Core/Error.pm b/lib/Dancer2/Core/Error.pm index d56922a52..3767ef1f0 100644 --- a/lib/Dancer2/Core/Error.pm +++ b/lib/Dancer2/Core/Error.pm @@ -6,7 +6,7 @@ use Carp; use Dancer2::Core::Types; use Dancer2::Core::HTTP; use Data::Dumper; -use Dancer2::FileUtils qw/path open_file/; +use Path::Tiny (); use Sub::Quote; use Module::Runtime 'require_module'; use Ref::Util qw< is_hashref >; @@ -343,9 +343,8 @@ sub backtrace { return $html unless $file and $line; # file and line are located, let's read the source Luke! - my $fh = eval { open_file('<', $file) } or return $html; - my @lines = <$fh>; - close $fh; + my @lines; + eval { @lines = Path::Tiny::path($file)->lines_utf8; 1; } or return $html; $html .= qq|
$file around line $line
|; diff --git a/lib/Dancer2/Core/Request/Upload.pm b/lib/Dancer2/Core/Request/Upload.pm index d46ddb0c6..6ece4a545 100644 --- a/lib/Dancer2/Core/Request/Upload.pm +++ b/lib/Dancer2/Core/Request/Upload.pm @@ -4,11 +4,10 @@ package Dancer2::Core::Request::Upload; use Moo; use Carp; -use File::Spec; -use Module::Runtime 'require_module'; +use Path::Tiny (); +use File::Copy (); use Dancer2::Core::Types; -use Dancer2::FileUtils qw(open_file); has filename => ( is => 'ro', @@ -33,13 +32,11 @@ has size => ( sub file_handle { my ($self) = @_; return $self->{_fh} if defined $self->{_fh}; - my $fh = open_file( '<', $self->tempname ); - $self->{_fh} = $fh; + $self->{_fh} = Path::Tiny::path( $self->tempname )->openr_raw; } sub copy_to { my ( $self, $target ) = @_; - require_module('File::Copy'); File::Copy::copy( $self->tempname, $target ); } @@ -69,8 +66,7 @@ sub content { sub basename { my ($self) = @_; - require_module('File::Basename'); - File::Basename::basename( $self->filename ); + return Path::Tiny::path( $self->filename )->basename; } sub type { diff --git a/lib/Dancer2/Core/Role/ConfigReader.pm b/lib/Dancer2/Core/Role/ConfigReader.pm index 0d0bc1abe..6da20611e 100644 --- a/lib/Dancer2/Core/Role/ConfigReader.pm +++ b/lib/Dancer2/Core/Role/ConfigReader.pm @@ -3,16 +3,15 @@ package Dancer2::Core::Role::ConfigReader; use Moo::Role; -use File::Spec; +use Carp 'croak'; +use Path::Tiny (); use Config::Any; use Hash::Merge::Simple; -use Carp 'croak'; use Module::Runtime 'require_module'; use Dancer2::Core::Factory; use Dancer2::Core; use Dancer2::Core::Types; -use Dancer2::FileUtils 'path'; with 'Dancer2::Core::Role::HasLocation'; @@ -39,9 +38,19 @@ has environments_location => ( isa => Str, lazy => 1, default => sub { - $ENV{DANCER_ENVDIR} - || File::Spec->catdir( $_[0]->config_location, 'environments' ) - || File::Spec->catdir( $_[0]->location, 'environments' ); + # short circuit + defined $ENV{'DANCER_ENVDIR'} + and return $ENV{'DANCER_ENVDIR'}; + + my $self = shift; + + foreach my $maybe_path ( $self->config_location, $self->location ) { + my $path = Path::Tiny::path($maybe_path, 'environments'); + $path->exists && $path->is_dir + and return $path->stringify; + } + + return ''; }, ); @@ -129,15 +138,22 @@ sub _build_config_files { } } - foreach my $file ( [ $location, "config" ], + foreach my $file ( [ $location, 'config' ], [ $self->environments_location, $running_env ] ) { foreach my $ext (@exts) { - my $path = path( $file->[0], $file->[1] . ".$ext" ); + # This is a potential security problem, + # escaping outside the current directory + # It's now disabled + # FIXME: Commit separately + $file->[0] eq '' + and next; + + my $path = Path::Tiny::path( $file->[0], $file->[1] . ".$ext" )->stringify; next if !-r $path; # Look for *_local.ext files - my $local = path( $file->[0], $file->[1] . "_local.$ext" ); + my $local = Path::Tiny::path( $file->[0], $file->[1] . "_local.$ext" ); push @files, $path, ( -r $local ? $local : () ); } } diff --git a/lib/Dancer2/Core/Role/HasLocation.pm b/lib/Dancer2/Core/Role/HasLocation.pm index e37d2e531..4c509e726 100644 --- a/lib/Dancer2/Core/Role/HasLocation.pm +++ b/lib/Dancer2/Core/Role/HasLocation.pm @@ -1,11 +1,11 @@ package Dancer2::Core::Role::HasLocation; # ABSTRACT: Role for application location "guessing" +use Carp (); use Moo::Role; -use Dancer2::Core::Types; -use Dancer2::FileUtils; -use File::Spec; use Sub::Quote 'quote_sub'; +use Path::Tiny (); +use Dancer2::Core::Types; # the path to the caller script/app # Note: to remove any ambiguity between the accessor for the @@ -16,9 +16,9 @@ has caller => ( is => 'ro', isa => Str, default => quote_sub( q{ + require Path::Tiny; my ( $caller, $script ) = CORE::caller; - $script = File::Spec->abs2rel( $script ) if File::Spec->file_name_is_absolute( $script ); - $script; + Path::Tiny::path($script)->relative->stringify; } ), ); @@ -33,7 +33,10 @@ sub _build_location { my $script = $self->caller; # default to the dir that contains the script... - my $location = Dancer2::FileUtils::dirname($script); + my $location = Path::Tiny::path($script)->parent; + + $location->is_dir + or Carp::croak("Caller $script is not an existing file"); #we try to find bin and lib my $subdir = $location; @@ -43,35 +46,31 @@ sub _build_location { for ( 1 .. 10 ) { #try to find libdir and bindir to determine the root of dancer app - my $libdir = Dancer2::FileUtils::path( $subdir, 'lib' ); - my $bindir = Dancer2::FileUtils::path( $subdir, 'bin' ); + my $libdir = $subdir->child('lib'); + my $bindir = $subdir->child('bin'); #try to find .dancer_app file to determine the root of dancer app - my $dancerdir = Dancer2::FileUtils::path( $subdir, '.dancer' ); + my $dancerdir = $subdir->child('.dancer'); # if one of them is found, keep that; but skip ./blib since both lib and bin exist # under it, but views and public do not. if ( - ( $subdir !~ m![\\/]blib[\\/]?$! && -d $libdir && -d $bindir ) || - ( -f $dancerdir ) + ( $subdir !~ m![\\/]blib[\\/]?$! && $libdir->is_dir && $bindir->is_dir ) || + ( $dancerdir->is_file ) ) { $subdir_found = 1; last; } - $subdir = Dancer2::FileUtils::path( $subdir, '..' ) || '.'; - last if File::Spec->rel2abs($subdir) eq File::Spec->rootdir; + $subdir = $subdir->parent; + last if $subdir->realpath->stringify eq Path::Tiny->rootdir->stringify; } my $path = $subdir_found ? $subdir : $location; - # return if absolute - File::Spec->file_name_is_absolute($path) - and return $path; - # convert relative to absolute - return File::Spec->rel2abs($path); + return $path->realpath->stringify; } 1; diff --git a/lib/Dancer2/Core/Role/SessionFactory/File.pm b/lib/Dancer2/Core/Role/SessionFactory/File.pm index 04f4d594b..1e16fc9a3 100644 --- a/lib/Dancer2/Core/Role/SessionFactory/File.pm +++ b/lib/Dancer2/Core/Role/SessionFactory/File.pm @@ -6,9 +6,10 @@ with 'Dancer2::Core::Role::SessionFactory'; use Carp 'croak'; use Dancer2::Core::Types; -use Dancer2::FileUtils qw(path set_file_mode escape_filename); +use Dancer2::FileUtils qw(escape_filename); use Fcntl ':flock'; use File::Copy (); +use Path::Tiny (); #--------------------------------------------------------------------------# # Required by classes consuming this role @@ -26,7 +27,7 @@ requires '_freeze_to_handle'; # given handle and data, serialize it has session_dir => ( is => 'ro', isa => Str, - default => sub { path( '.', 'sessions' ) }, + default => sub { Path::Tiny::path( '.', 'sessions' )->stringify }, ); sub BUILD { @@ -62,7 +63,10 @@ sub _sessions { sub _retrieve { my ( $self, $id ) = @_; - my $session_file = path( $self->session_dir, escape_filename($id) . $self->_suffix ); + my $session_file = Path::Tiny::path( + $self->session_dir, + escape_filename($id) . $self->_suffix, + )->stringify; croak "Invalid session ID: $id" unless -f $session_file; @@ -77,20 +81,27 @@ sub _retrieve { sub _change_id { my ($self, $old_id, $new_id) = @_; - my $old_path = - path($self->session_dir, escape_filename($old_id) . $self->_suffix); + my $old_path = Path::Tiny::path( + $self->session_dir, + escape_filename($old_id) . $self->_suffix + )->stringify; return if !-f $old_path; - my $new_path = - path($self->session_dir, escape_filename($new_id) . $self->_suffix); + my $new_path = Path::Tiny::path( + $self->session_dir, + escape_filename($new_id) . $self->_suffix + )->stringify; File::Copy::move($old_path, $new_path); } sub _destroy { my ( $self, $id ) = @_; - my $session_file = path( $self->session_dir, escape_filename($id) . $self->_suffix ); + my $session_file = Path::Tiny::path( + $self->session_dir, + escape_filename($id) . $self->_suffix + )->stringify; return if !-f $session_file; unlink $session_file; @@ -98,13 +109,16 @@ sub _destroy { sub _flush { my ( $self, $id, $data ) = @_; - my $session_file = path( $self->session_dir, escape_filename($id) . $self->_suffix ); + my $session_file = Path::Tiny::path( + $self->session_dir, + escape_filename($id) . $self->_suffix + )->stringify; open my $fh, '>', $session_file or die "Can't open '$session_file': $!\n"; flock $fh, LOCK_EX or die "Can't lock file '$session_file': $!\n"; seek $fh, 0, 0 or die "Can't seek in file '$session_file': $!\n"; truncate $fh, 0 or die "Can't truncate file '$session_file': $!\n"; - set_file_mode($fh); + binmode $fh, ':encoding(UTF-8)'; $self->_freeze_to_handle( $fh, $data ); close $fh or die "Can't close '$session_file': $!\n"; diff --git a/lib/Dancer2/Core/Role/Template.pm b/lib/Dancer2/Core/Role/Template.pm index 9dad85b7f..2afa0d6d3 100644 --- a/lib/Dancer2/Core/Role/Template.pm +++ b/lib/Dancer2/Core/Role/Template.pm @@ -3,7 +3,7 @@ package Dancer2::Core::Role::Template; use Dancer2::Core::Types; -use Dancer2::FileUtils 'path'; +use Path::Tiny (); use Carp 'croak'; use Ref::Util qw< is_ref >; @@ -99,22 +99,22 @@ sub view_pathname { my ( $self, $view ) = @_; $view = $self->_template_name($view); - return path( $self->views, $view ); + return Path::Tiny::path( $self->views, $view )->stringify; } sub layout_pathname { my ( $self, $layout ) = @_; - return path( + return Path::Tiny::path( $self->views, $self->layout_dir, $self->_template_name($layout), - ); + )->stringify; } sub pathname_exists { my ( $self, $pathname ) = @_; - return -f $pathname; + return Path::Tiny::path($pathname)->is_file; } sub render_layout { diff --git a/lib/Dancer2/FileUtils.pm b/lib/Dancer2/FileUtils.pm index b9ff681c8..d80d8f6e2 100644 --- a/lib/Dancer2/FileUtils.pm +++ b/lib/Dancer2/FileUtils.pm @@ -4,8 +4,8 @@ package Dancer2::FileUtils; use strict; use warnings; -use File::Basename (); use File::Spec; +use Path::Tiny (); use Carp; use Exporter 'import'; @@ -30,11 +30,11 @@ sub path_or_empty { return -e $path ? $path : ''; } -sub dirname { File::Basename::dirname(@_) } +sub dirname { return Path::Tiny::path(@_)->parent->stringify; } sub set_file_mode { my $fh = shift; - my $charset = 'utf-8'; + my $charset = 'UTF-8'; binmode $fh, ":encoding($charset)"; return $fh; } @@ -42,10 +42,9 @@ sub set_file_mode { sub open_file { my ( $mode, $filename ) = @_; - open my $fh, $mode, $filename - or croak "Can't open '$filename' using mode '$mode': $!"; - - return set_file_mode($fh); + return Path::Tiny::path($filename)->filehandle( + $mode, ':encoding(UTF-8)', + ); } sub read_file_content { diff --git a/lib/Dancer2/Handler/File.pm b/lib/Dancer2/Handler/File.pm index 2f398b74b..2949fc966 100644 --- a/lib/Dancer2/Handler/File.pm +++ b/lib/Dancer2/Handler/File.pm @@ -4,9 +4,10 @@ package Dancer2::Handler::File; use Carp 'croak'; use Moo; use HTTP::Date; -use Dancer2::FileUtils 'path', 'open_file', 'read_glob_content'; +use Dancer2::FileUtils 'path'; use Dancer2::Core::MIME; use Dancer2::Core::Types; +use Path::Tiny (); use File::Spec; with qw< @@ -50,7 +51,7 @@ sub _build_public_dir { my $self = shift; return $self->app->config->{public_dir} || $ENV{DANCER_PUBLIC} - || path( $self->app->location, 'public' ); + || Path::Tiny::path( $self->app->location, 'public' )->stringify; } sub register { @@ -84,7 +85,7 @@ sub code { $path =~ s/^\Q$prefix\E//; } - my $file_path = $self->merge_paths( $path, $self->public_dir ); + my $file_path = Path::Tiny::path( $self->public_dir, $path )->stringify; return $self->standard_response( $app, 403 ) if !defined $file_path; if ( !-f $file_path ) { @@ -100,9 +101,7 @@ sub code { $self->execute_hook( 'handler.file.before_render', $file_path ); # Read file content as bytes - my $fh = open_file( "<", $file_path ); - binmode $fh; - my $content = read_glob_content($fh); + my $content = Path::Tiny::path($file_path)->slurp_raw; # Assume m/^text/ mime types are correctly encoded my $content_type = $self->mime->for_file($file_path) || 'text/plain'; @@ -131,22 +130,4 @@ sub code { }; } -sub merge_paths { - my ( undef, $path, $public_dir ) = @_; - - my ( $volume, $dirs, $file ) = File::Spec->splitpath( $path ); - my @tokens = File::Spec->splitdir( "$dirs$file" ); - my $updir = File::Spec->updir; - return if grep $_ eq $updir, @tokens; - - my ( $pub_vol, $pub_dirs, $pub_file ) = File::Spec->splitpath( $public_dir ); - my @pub_tokens = File::Spec->splitdir( "$pub_dirs$pub_file" ); - return if length $volume and length $pub_vol and $volume ne $pub_vol; - - my @final_vol = ( length $pub_vol ? $pub_vol : length $volume ? $volume : () ); - my @file_path = ( @final_vol, @pub_tokens, @tokens ); - my $file_path = path( @file_path ); - return $file_path; -} - 1; diff --git a/lib/Dancer2/Logger/File.pm b/lib/Dancer2/Logger/File.pm index 2bc8f456b..619ddd239 100644 --- a/lib/Dancer2/Logger/File.pm +++ b/lib/Dancer2/Logger/File.pm @@ -7,9 +7,8 @@ use Dancer2::Core::Types; with 'Dancer2::Core::Role::Logger'; -use File::Spec; use Fcntl qw(:flock SEEK_END); -use Dancer2::FileUtils qw(open_file); +use Path::Tiny (); use IO::File; has environment => ( @@ -58,24 +57,27 @@ has fh => ( builder => '_build_fh', ); -sub _build_log_dir { File::Spec->catdir( $_[0]->location, 'logs' ) } +sub _build_log_dir { Path::Tiny::path( $_[0]->location, 'logs' )->stringify } sub _build_file_name {$_[0]->environment . ".log"} sub _build_log_file { my $self = shift; - return File::Spec->catfile( $self->log_dir, $self->file_name ); + return Path::Tiny::path( $self->log_dir, $self->file_name )->stringify; } sub _build_fh { my $self = shift; my $logfile = $self->log_file; - my $fh; - unless ( $fh = open_file( '>>', $logfile ) ) { - carp "unable to create or append to $logfile"; + my $fh = eval { + Path::Tiny::path($logfile)->filehandle( + '>>', ':encoding(UTF-8)', + ); + } or do { + Carp::carp("unable to create or append to $logfile"); return; - } + }; $fh->autoflush; diff --git a/lib/Dancer2/Manual.pod b/lib/Dancer2/Manual.pod index aca2b893b..d10869b9f 100644 --- a/lib/Dancer2/Manual.pod +++ b/lib/Dancer2/Manual.pod @@ -2401,6 +2401,12 @@ Returns the dirname of the path given: my $dir = dirname($some_path); +This can be replaced by using L directly, which is what this DSL +will effectively do: + + use Path::Tiny qw< path >; + my $dir = path($some_path)->parent->stringify; + =head2 encode_json ($structure) Serializes a structure to a UTF-8 binary JSON string. @@ -2761,6 +2767,12 @@ operating system: It also normalizes (cleans) the path aesthetically. It does not verify whether the path exists, though. +This can also be done cleanly using L, which is what this will +effectively do: + + use Path::Tiny qw< path >; + path($0)->sibling( qw< lib File.pm > ); + =head2 post Defines a route for HTTP B requests to the given URL: diff --git a/lib/Dancer2/Template/Simple.pm b/lib/Dancer2/Template/Simple.pm index 2b330a85b..c54b14c9d 100644 --- a/lib/Dancer2/Template/Simple.pm +++ b/lib/Dancer2/Template/Simple.pm @@ -4,6 +4,7 @@ package Dancer2::Template::Simple; use Moo; use Dancer2::FileUtils 'read_file_content'; use Ref::Util qw; +use Path::Tiny (); with 'Dancer2::Core::Role::Template'; @@ -27,10 +28,19 @@ sub BUILD { sub render { my ( $self, $template, $tokens ) = @_; + my $content; + if ( ref $template eq 'SCALAR' ) { + open my $fh, '<:encoding(UTF-8)', $template + or die "Cannot open in-memory content: $!\n"; + ## no critic qw(Variables::RequireInitializationForLocalVars) + local $/; + $content = <$fh>; + } else { + $content = Path::Tiny::path($template)->slurp_utf8; + } - $content = read_file_content($template); - $content = $self->parse_branches( $content, $tokens ); + $content = $self->parse_branches($content, $tokens); return $content; } diff --git a/lib/Dancer2/Template/TemplateToolkit.pm b/lib/Dancer2/Template/TemplateToolkit.pm index 589bc5f59..d2f0d9bf5 100644 --- a/lib/Dancer2/Template/TemplateToolkit.pm +++ b/lib/Dancer2/Template/TemplateToolkit.pm @@ -5,7 +5,7 @@ package Dancer2::Template::TemplateToolkit; use Moo; use Carp qw; use Dancer2::Core::Types; -use Dancer2::FileUtils qw; +use Path::Tiny (); use Scalar::Util (); use Template; @@ -70,10 +70,10 @@ sub view_pathname { sub layout_pathname { my ( $self, $layout ) = @_; - return path( + return Path::Tiny::path( $self->layout_dir, $self->_template_name($layout), - ); + )->stringify; } sub pathname_exists { diff --git a/lib/Dancer2/Template/Tiny.pm b/lib/Dancer2/Template/Tiny.pm index e85d764ce..3cd6619bf 100644 --- a/lib/Dancer2/Template/Tiny.pm +++ b/lib/Dancer2/Template/Tiny.pm @@ -3,9 +3,9 @@ package Dancer2::Template::Tiny; use Moo; use Carp qw/croak/; +use Path::Tiny (); use Dancer2::Core::Types; use Dancer2::Template::Implementation::ForkedTiny; -use Dancer2::FileUtils 'read_file_content'; with 'Dancer2::Core::Role::Template'; @@ -26,7 +26,7 @@ sub render { my $template_data = ref $template ? ${$template} - : read_file_content($template); + : Path::Tiny::path($template)->slurp_utf8; my $content; diff --git a/lib/Dancer2/Tutorial.pod b/lib/Dancer2/Tutorial.pod index 3f6dd121d..07e2af342 100644 --- a/lib/Dancer2/Tutorial.pod +++ b/lib/Dancer2/Tutorial.pod @@ -192,10 +192,10 @@ installed on your machine. =head3 Required Perl modules Obviously you need L installed. You'll also need the -L