-
-
Save anonymous/d5bad1bda9feea9bfa45 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
diff --git a/Changes b/Changes | |
index 67cab97..5451247 100644 | |
--- a/Changes | |
+++ b/Changes | |
@@ -1,5 +1,7 @@ | |
-6.13 2015-06-19 | |
+6.13 2015-06-25 | |
+ - Added support for validating file uploads. | |
+ - Added file check to Mojolicious::Validator. | |
6.12 2015-06-18 | |
- Welcome to the Mojolicious core team Dan Book. | |
diff --git a/lib/Mojolicious.pm b/lib/Mojolicious.pm | |
index 22094da..e4f62c7 100644 | |
--- a/lib/Mojolicious.pm | |
+++ b/lib/Mojolicious.pm | |
@@ -516,7 +516,7 @@ L<Mojolicious::Types> object. | |
my $validator = $app->validator; | |
$app = $app->validator(Mojolicious::Validator->new); | |
-Validate parameters, defaults to a L<Mojolicious::Validator> object. | |
+Validate values, defaults to a L<Mojolicious::Validator> object. | |
# Add validation check | |
$app->validator->add_check(foo => sub { | |
diff --git a/lib/Mojolicious/Controller.pm b/lib/Mojolicious/Controller.pm | |
index 44c8bc2..d6df2b2 100644 | |
--- a/lib/Mojolicious/Controller.pm | |
+++ b/lib/Mojolicious/Controller.pm | |
@@ -341,6 +341,7 @@ sub validation { | |
my $header = $req->headers->header('X-CSRF-Token'); | |
my $hash = $req->params->to_hash; | |
$hash->{csrf_token} //= $header if $token && $header; | |
+ $hash->{$_} = $req->every_upload($_) for map { $_->name } @{$req->uploads}; | |
my $validation = $self->app->validator->validation->input($hash); | |
return $stash->{'mojo.validation'} = $validation->csrf_token($token); | |
} | |
@@ -916,16 +917,22 @@ to inherit query parameters from the current request. | |
my $validation = $c->validation; | |
Get L<Mojolicious::Validator::Validation> object for current request to | |
-validate C<GET> and C<POST> parameters extracted from the query string and | |
-C<application/x-www-form-urlencoded> or C<multipart/form-data> message body. | |
-Parts of the request body need to be loaded into memory to parse C<POST> | |
-parameters, so you have to make sure it is not excessively large, there's a | |
-16MB limit by default. | |
+validate file uploads as well as C<GET> and C<POST> parameters extracted from | |
+the query string and C<application/x-www-form-urlencoded> or | |
+C<multipart/form-data> message body. Parts of the request body need to be loaded | |
+into memory to parse C<POST> parameters, so you have to make sure it is not | |
+excessively large, there's a 16MB limit by default. | |
+ # Validate GET/POST parameter | |
my $validation = $c->validation; | |
$validation->required('title')->size(3, 50); | |
my $title = $validation->param('title'); | |
+ # Validate file upload | |
+ my $validation = $c->validation; | |
+ $validation->required('tarball')->file->size(1, 1024 * 1024); | |
+ my $tarball = $validation->param('tarball'); | |
+ | |
=head2 write | |
$c = $c->write; | |
diff --git a/lib/Mojolicious/Plugin/TagHelpers.pm b/lib/Mojolicious/Plugin/TagHelpers.pm | |
index 0e4b435..c2f449d 100644 | |
--- a/lib/Mojolicious/Plugin/TagHelpers.pm | |
+++ b/lib/Mojolicious/Plugin/TagHelpers.pm | |
@@ -16,17 +16,16 @@ sub register { | |
my @helpers = ( | |
qw(csrf_field form_for hidden_field javascript label_for link_to), | |
- qw(password_field select_field stylesheet submit_button tag_with_error), | |
- qw(text_area) | |
+ qw(select_field stylesheet submit_button tag_with_error text_area) | |
); | |
$app->helper($_ => __PACKAGE__->can("_$_")) for @helpers; | |
$app->helper(check_box => | |
sub { _input(shift, shift, value => shift, @_, type => 'checkbox') }); | |
- $app->helper(file_field => | |
- sub { shift; _tag('input', name => shift, @_, type => 'file') }); | |
+ $app->helper(file_field => sub { _empty_field('file', @_) }); | |
$app->helper(image => sub { _tag('img', src => shift->url_for(shift), @_) }); | |
$app->helper(input_tag => sub { _input(@_) }); | |
+ $app->helper(password_field => sub { _empty_field('password', @_) }); | |
$app->helper(radio_button => | |
sub { _input(shift, shift, value => shift, @_, type => 'radio') }); | |
@@ -39,6 +38,11 @@ sub _csrf_field { | |
return _hidden_field($c, csrf_token => $c->helpers->csrf_token, @_); | |
} | |
+sub _empty_field { | |
+ my ($type, $c, $name) = (shift, shift, shift); | |
+ return _validation($c, $name, 'input', name => $name, @_, type => $type); | |
+} | |
+ | |
sub _form_for { | |
my ($c, @url) = (shift, shift); | |
push @url, shift if ref $_[0] eq 'HASH'; | |
@@ -119,12 +123,6 @@ sub _option { | |
return _tag('option', %attrs, @$pair[2 .. $#$pair], $pair->[0]); | |
} | |
-sub _password_field { | |
- my ($c, $name) = (shift, shift); | |
- return _validation($c, $name, 'input', name => $name, @_, | |
- type => 'password'); | |
-} | |
- | |
sub _select_field { | |
my ($c, $name, $options, %attrs) = (shift, shift, shift, @_); | |
diff --git a/lib/Mojolicious/Validator.pm b/lib/Mojolicious/Validator.pm | |
index 124eae7..1f732fc 100644 | |
--- a/lib/Mojolicious/Validator.pm | |
+++ b/lib/Mojolicious/Validator.pm | |
@@ -4,7 +4,13 @@ use Mojo::Base -base; | |
use Mojolicious::Validator::Validation; | |
has checks => sub { | |
- {equal_to => \&_equal_to, in => \&_in, like => \&_like, size => \&_size}; | |
+ { | |
+ equal_to => \&_equal_to, | |
+ file => sub { !ref $_[2] || !$_[2]->isa('Mojo::Upload') }, | |
+ in => \&_in, | |
+ like => sub { $_[2] !~ $_[3] }, | |
+ size => \&_size | |
+ }; | |
}; | |
sub add_check { $_[0]->checks->{$_[1]} = $_[2] and return $_[0] } | |
@@ -25,11 +31,9 @@ sub _in { | |
return 1; | |
} | |
-sub _like { $_[2] !~ $_[3] } | |
- | |
sub _size { | |
my ($validation, $name, $value, $min, $max) = @_; | |
- my $len = length $value; | |
+ my $len = ref $value ? $value->size : length $value; | |
return $len < $min || $len > $max; | |
} | |
@@ -39,7 +43,7 @@ sub _size { | |
=head1 NAME | |
-Mojolicious::Validator - Validate parameter | |
+Mojolicious::Validator - Validate values | |
=head1 SYNOPSIS | |
@@ -53,7 +57,7 @@ Mojolicious::Validator - Validate parameter | |
=head1 DESCRIPTION | |
-L<Mojolicious::Validator> validates parameters for L<Mojolicious>. | |
+L<Mojolicious::Validator> validates values for L<Mojolicious>. | |
=head1 CHECKS | |
@@ -61,25 +65,34 @@ These validation checks are available by default. | |
=head2 equal_to | |
- $validation->equal_to('foo'); | |
+ $validation = $validation->equal_to('foo'); | |
+ | |
+Value needs to be equal to the value of another field. Note that this check does | |
+not work with file uploads for security reasons. | |
+ | |
+=head2 file | |
+ | |
+ $validation = $validation->file; | |
-Value needs to be equal to the value of another field. | |
+Value needs to be a L<Mojo::Upload> object, representing a file upload. | |
=head2 in | |
- $validation->in(qw(foo bar baz)); | |
+ $validation = $validation->in(qw(foo bar baz)); | |
-Value needs to match one of the values in the list. | |
+Value needs to match one of the values in the list. Note that this check does | |
+not work with file uploads for security reasons. | |
=head2 like | |
- $validation->like(qr/^[A-Z]/); | |
+ $validation = $validation->like(qr/^[A-Z]/); | |
-Value needs to match the regular expression. | |
+Value needs to match the regular expression. Note that this check does not work | |
+with file uploads for security reasons. | |
=head2 size | |
- $validation->size(2, 5); | |
+ $validation = $validation->size(2, 5); | |
Value length in characters needs to be between these two values. | |
@@ -92,8 +105,8 @@ L<Mojolicious::Validator> implements the following attributes. | |
my $checks = $validator->checks; | |
$validator = $validator->checks({size => sub {...}}); | |
-Registered validation checks, by default only L</"equal_to">, L</"in">, | |
-L</"like"> and L</"size"> are already defined. | |
+Registered validation checks, by default only L</"equal_to">, L</"file">, | |
+L</"in">, L</"like"> and L</"size"> are already defined. | |
=head1 METHODS | |
diff --git a/lib/Mojolicious/Validator/Validation.pm b/lib/Mojolicious/Validator/Validation.pm | |
index 0ce067b..ac1c410 100644 | |
--- a/lib/Mojolicious/Validator/Validation.pm | |
+++ b/lib/Mojolicious/Validator/Validation.pm | |
@@ -191,9 +191,9 @@ array reference. | |
my $names = $validation->failed; | |
-Return a list of all names for parameters that failed validation. | |
+Return a list of all names for values that failed validation. | |
- # Names of all parameters that failed | |
+ # Names of all values that failed | |
say for @{$validation->failed}; | |
=head2 has_data | |
@@ -227,17 +227,16 @@ Change validation L</"topic">. | |
my $value = $validation->param('foo'); | |
-Access validated parameters. If there are multiple values sharing the same | |
-name, and you want to access more than just the last one, you can use | |
-L</"every_param">. | |
+Access validated values. If there are multiple values sharing the same name, and | |
+you want to access more than just the last one, you can use L</"every_param">. | |
=head2 passed | |
my $names = $validation->passed; | |
-Return a list of all names for parameters that passed validation. | |
+Return a list of all names for values that passed validation. | |
- # Names of all parameters that passed | |
+ # Names of all values that passed | |
say for @{$validation->passed}; | |
=head2 required | |
diff --git a/t/mojolicious/validation_lite_app.t b/t/mojolicious/validation_lite_app.t | |
index aa3ddd2..10cfa22 100644 | |
--- a/t/mojolicious/validation_lite_app.t | |
+++ b/t/mojolicious/validation_lite_app.t | |
@@ -3,6 +3,7 @@ use Mojo::Base -strict; | |
BEGIN { $ENV{MOJO_REACTOR} = 'Mojo::Reactor::Poll' } | |
use Test::More; | |
+use Mojo::Upload; | |
use Mojolicious::Lite; | |
use Test::Mojo; | |
@@ -21,6 +22,13 @@ any '/' => sub { | |
$validation->optional('yada')->two; | |
} => 'index'; | |
+any '/upload' => sub { | |
+ my $c = shift; | |
+ my $validation = $c->validation; | |
+ return $c->render unless $validation->has_data; | |
+ $validation->required('foo')->file; | |
+}; | |
+ | |
any '/forgery' => sub { | |
my $c = shift; | |
my $validation = $c->validation; | |
@@ -73,6 +81,23 @@ ok $validation->has_error, 'has error'; | |
is_deeply $validation->error('yada'), [qw(equal_to 1 foo)], 'right error'; | |
is_deeply $validation->failed, [qw(baz yada)], 'right names'; | |
+# File | |
+$validation = $t->app->validation->input( | |
+ { | |
+ foo => Mojo::Upload->new, | |
+ bar => [Mojo::Upload->new, Mojo::Upload->new], | |
+ baz => [Mojo::Upload->new, 'test'] | |
+ } | |
+); | |
+ok $validation->required('foo')->file->is_valid, 'valid'; | |
+ok $validation->required('bar')->file->is_valid, 'valid'; | |
+ok $validation->required('baz')->is_valid, 'valid'; | |
+ok !$validation->has_error, 'no error'; | |
+ok !$validation->file->is_valid, 'not valid'; | |
+ok $validation->has_error, 'has error'; | |
+is_deeply $validation->error('baz'), [qw(file 1)], 'right error'; | |
+is_deeply $validation->failed, ['baz'], 'right names'; | |
+ | |
# In | |
$validation = $t->app->validation->input( | |
{foo => [qw(bar whatever)], baz => [qw(yada ohoh)]}); | |
@@ -115,6 +140,20 @@ is_deeply $validation->output, {foo => 'bar'}, 'right result'; | |
ok $validation->has_error, 'has error'; | |
is_deeply $validation->error('yada'), [qw(size 1 5 10)], 'right error'; | |
+# File size | |
+$validation = $t->app->validation->input( | |
+ { | |
+ foo => [Mojo::Upload->new->tap(sub { $_->asset->add_chunk('valid') })], | |
+ bar => [Mojo::Upload->new->tap(sub { $_->asset->add_chunk('not valid') })] | |
+ } | |
+); | |
+ok $validation->required('foo')->file->size(1, 6)->is_valid, 'valid'; | |
+ok !$validation->has_error, 'no error'; | |
+ok !$validation->required('bar')->file->size(1, 6)->is_valid, 'not valid'; | |
+ok $validation->has_error, 'has error'; | |
+is_deeply $validation->error('bar'), [qw(size 1 1 6)], 'right error'; | |
+is_deeply $validation->failed, ['bar'], 'right names'; | |
+ | |
# Multiple empty values | |
$validation = $t->app->validation; | |
ok !$validation->has_data, 'no data'; | |
@@ -195,6 +234,29 @@ $t->post_ok('/' => form => {foo => 'no'})->status_is(200) | |
->element_count_is('.field-with-error', 2) | |
->element_count_is('.field-with-error', 2, 'with description'); | |
+# Successful file upload | |
+$t->post_ok( | |
+ '/upload' => form => {foo => {content => 'bar', filename => 'test.txt'}}) | |
+ ->element_exists_not('.field-with-error'); | |
+ | |
+# Successful file upload (multiple files) | |
+$t->post_ok( | |
+ '/upload' => form => { | |
+ foo => [ | |
+ {content => 'One', filename => 'one.txt'}, | |
+ {content => 'Two', filename => 'two.txt'} | |
+ ] | |
+ } | |
+)->element_exists_not('.field-with-error'); | |
+ | |
+# Failed file upload | |
+$t->post_ok('/upload' => form => {foo => 'bar'}) | |
+ ->element_exists('.field-with-error'); | |
+ | |
+# Failed file upload (multiple files) | |
+$t->post_ok('/upload' => form => {foo => ['one', 'two']}) | |
+ ->element_exists('.field-with-error'); | |
+ | |
# Missing CSRF token | |
$t->get_ok('/forgery' => form => {foo => 'bar'})->status_is(200) | |
->content_like(qr/Wrong or missing CSRF token!/) | |
@@ -268,6 +330,12 @@ __DATA__ | |
%= password_field 'yada' | |
% end | |
+@@ upload.html.ep | |
+%= form_for upload => begin | |
+ %= file_field 'foo' | |
+ %= submit_button | |
+% end | |
+ | |
@@ forgery.html.ep | |
%= form_for forgery => begin | |
%= 'Wrong or missing CSRF token!' if validation->has_error('csrf_token') |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment