-
-
Save anonymous/e5a2cbb9997fc66ef5dd 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/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..4a7c88c 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->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..0e91126 100644 | |
--- a/lib/Mojolicious/Validator.pm | |
+++ b/lib/Mojolicious/Validator.pm | |
@@ -4,7 +4,14 @@ 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') }, | |
+ file_size => sub { _file_size($_[2]->size, @_[3, 4]) }, | |
+ in => \&_in, | |
+ like => sub { $_[2] !~ $_[3] }, | |
+ size => \&_size | |
+ }; | |
}; | |
sub add_check { $_[0]->checks->{$_[1]} = $_[2] and return $_[0] } | |
@@ -19,14 +26,14 @@ sub _equal_to { | |
return $value ne $other; | |
} | |
+sub _file_size { $_[0] < $_[1] || $_[0] > $_[2] } | |
+ | |
sub _in { | |
my ($validation, $name, $value) = (shift, shift, shift); | |
$value eq $_ && return undef for @_; | |
return 1; | |
} | |
-sub _like { $_[2] !~ $_[3] } | |
- | |
sub _size { | |
my ($validation, $name, $value, $min, $max) = @_; | |
my $len = length $value; | |
@@ -39,7 +46,7 @@ sub _size { | |
=head1 NAME | |
-Mojolicious::Validator - Validate parameter | |
+Mojolicious::Validator - Validate values | |
=head1 SYNOPSIS | |
@@ -53,7 +60,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 | |
@@ -65,6 +72,18 @@ These validation checks are available by default. | |
Value needs to be equal to the value of another field. | |
+=head2 file | |
+ | |
+ $validation->file; | |
+ | |
+Value needs to be a L<Mojo::Upload> object, representing a file upload. | |
+ | |
+=head2 file_size | |
+ | |
+ $validation->file_size(1, 1024); | |
+ | |
+Size of file upload needs to be between these two values. | |
+ | |
=head2 in | |
$validation->in(qw(foo bar baz)); | |
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..babc0d1 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,35 @@ 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'; | |
+ | |
+# 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->required('bar')->file_size(1, 6)->is_valid, 'not valid'; | |
+is_deeply $validation->error('bar'), [qw(file_size 1 1 6)], 'right error'; | |
+is_deeply $validation->failed, ['bar'], 'right names'; | |
+ | |
# In | |
$validation = $t->app->validation->input( | |
{foo => [qw(bar whatever)], baz => [qw(yada ohoh)]}); | |
@@ -195,6 +232,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 +328,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