Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
bring back validation for file uploads
  • Loading branch information
kraih committed Jul 8, 2015
1 parent 07bca76 commit 918c13f
Show file tree
Hide file tree
Showing 4 changed files with 101 additions and 14 deletions.
2 changes: 2 additions & 0 deletions Changes
@@ -1,5 +1,7 @@

6.13 2015-07-08
- Added support for validating file uploads.
- Added upload check to Mojolicious::Validator.
- Improved error messages for broken applications in Mojo::Server. (mst)
- Improved subscribers method in Mojo::EventEmitter to allow subscribers to be
modified more easily.
Expand Down
16 changes: 11 additions & 5 deletions lib/Mojolicious/Controller.pm
Expand Up @@ -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);
}
Expand Down Expand Up @@ -916,17 +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')->upload->size(1, 1048576);
my $tarball = $validation->param('tarball');
=head2 write
$c = $c->write;
Expand Down
29 changes: 20 additions & 9 deletions lib/Mojolicious/Validator.pm
Expand Up @@ -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,
in => \&_in,
like => sub { $_[2] !~ $_[3] },
size => \&_size,
upload => sub { !ref $_[2] || !$_[2]->isa('Mojo::Upload') }
};
};

sub add_check { $_[0]->checks->{$_[1]} = $_[2] and return $_[0] }
Expand All @@ -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;
}

Expand Down Expand Up @@ -63,25 +67,32 @@ These validation checks are available by default.
$validation = $validation->equal_to('foo');
Value needs to be equal to the value of another field.
String value needs to be equal to the value of another field.
=head2 in
$validation = $validation->in(qw(foo bar baz));
Value needs to match one of the values in the list.
String value needs to match one of the values in the list.
=head2 like
$validation = $validation->like(qr/^[A-Z]/);
Value needs to match the regular expression.
String value needs to match the regular expression.
=head2 size
$validation = $validation->size(2, 5);
Value length needs to be between these two values.
String value length or size of L<Mojo::Upload> object needs to be between these
two values.
=head2 upload
$validation = $validation->upload;
Value needs to be a L<Mojo::Upload> object, representing a file upload.
=head1 ATTRIBUTES
Expand All @@ -93,7 +104,7 @@ L<Mojolicious::Validator> implements the following attributes.
$validator = $validator->checks({size => sub {...}});
Registered validation checks, by default only L</"equal_to">, L</"in">,
L</"like"> and L</"size"> are already defined.
L</"like">, L</"size"> and L</"upload"> are already defined.
=head1 METHODS
Expand Down
68 changes: 68 additions & 0 deletions t/mojolicious/validation_lite_app.t
Expand Up @@ -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;

Expand All @@ -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')->upload;
};

any '/forgery' => sub {
my $c = shift;
my $validation = $c->validation;
Expand Down Expand Up @@ -115,6 +123,37 @@ 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';

# Upload
$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')->upload->is_valid, 'valid';
ok $validation->required('bar')->upload->is_valid, 'valid';
ok $validation->required('baz')->is_valid, 'valid';
ok !$validation->has_error, 'no error';
ok !$validation->upload->is_valid, 'not valid';
ok $validation->has_error, 'has error';
is_deeply $validation->error('baz'), [qw(upload 1)], 'right error';
is_deeply $validation->failed, ['baz'], 'right names';

# Upload 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')->upload->size(1, 6)->is_valid, 'valid';
ok !$validation->has_error, 'no error';
ok !$validation->required('bar')->upload->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';
Expand Down Expand Up @@ -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!/)
Expand Down Expand Up @@ -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')
Expand Down

0 comments on commit 918c13f

Please sign in to comment.