——use
warnings;
use
strict;
=head1 NAME
CGI::Application::Plugin::ValidateQuery - lightweight query validation for CGI::Application
=head1 VERSION
Version 1.0.4
=cut
our
$VERSION
=
'1.0.4'
;
our
@EXPORT_OK
=
qw(
validate_query_config
validate_query
validate_query_error_mode
)
;
push
@EXPORT_OK
,
@Params::Validate::EXPORT_OK
;
our
%EXPORT_TAGS
= (
all
=> \
@EXPORT_OK
,
types
=>
$Params::Validate::EXPORT_TAGS
{types}
);
local
$Params::Validate::NO_VALIDATION
= 0;
sub
validate_query_config {
my
$self
=
shift
;
my
@args
=
@_
;
my
$opts
=
ref
$args
[0] eq
'HASH'
?
$self
->_cap_hash(
$args
[0]) :
$self
->_cap_hash({
@args
});
# assume query
$self
->{__CAP_APP_PARAMS} =
defined
$opts
->{APP_PARAMS} ?
delete
$opts
->{APP_PARAMS} : 0;
# for now, assume checking all params
$self
->{__CAP_VALQUERY_EXTRA_OPTIONAL} =
defined
$opts
->{EXTRA_FIELDS_OPTIONAL} ?
delete
$opts
->{EXTRA_FIELDS_OPTIONAL} : 0;
$self
->{__CAP_VALQUERY_ERROR_MODE} =
defined
$opts
->{ERROR_MODE} ?
delete
$opts
->{ERROR_MODE} :
'validate_query_error_mode'
;
# Potential problem with this code: given log_level isn't checked. Question:
# Does this module /need/ to check user input that will end up being
# checked (and croaked on) by a logging api, anyway?
$self
->{__CAP_VALQUERY_LOG_LEVEL} =
defined
$opts
->{LOG_LEVEL} ?
delete
$opts
->{LOG_LEVEL} :
undef
;
croak
'log_level given but no logging interface exists.'
if
$self
->{__CAP_VALQUERY_LOG_LEVEL} && !
$self
->can(
'log'
);
croak
'Invalid option(s) ('
.
join
(
', '
,
keys
%{
$opts
}).
') passed to'
.
'validate_query_config'
if
%{
$opts
};
}
sub
validate_query {
my
$self
=
shift
;
return
unless
@_
;
my
@args
=
@_
;
my
$query_props
=
ref
$args
[0] eq
'HASH'
?
$args
[0] : {
@args
};
# Potential problem with this code: given log level isn't checked. Question:
# Does this module /need/ to check user input that will end up being
# checked (and probably croaked on) by a logging api, anyway?
my
$log_level
=
delete
$query_props
->{log_level}
||
$self
->{__CAP_VALQUERY_LOG_LEVEL};
# what's left of $query_props should be something we can pass to validate().
# problem: users may only want to validate a small handful of GET
# variables instead of the full query object (which potentially contains a
# large numer of POST ariables already being validated by DFV or something
# similar).
# Given, say, a post of {one=>'one',two=>'two'} and a get of
# {three=>'three'} and a $query_props of just {three=>'three'} validation
# will fail given the unknown keys in POST.
# This makes sense; if someone is tampering with the query object you want
# to catch any extra keys.
# option 1: for any key found in query not present in query_props, add to
# query props and mark as optional.
# option 2: just pass query_props and let it fail for any keys found in
# query no in query_props.
# Solution: pass extra_fields_optional to toggle behavior. If you know you are in
# a situation where you need only test one or two things in query (because
# the rest is delegated) you can toggle for situation one. Otherwise
# toggle for situation two and validate the whole query object.
my
$extra_fields_optional
=
delete
$query_props
->{extra_fields_optional}
||
$self
->{__CAP_EXTRA_OPTIONAL};
my
$app_params
=
delete
$query_props
->{app_params} ||
$self
->{__CAP_APP_PARAMS};
my
$param_obj
=
$app_params
?
$self
:
$self
->query;
my
%validated
;
eval
{
my
@vars_array
;
for
my
$p
(
$param_obj
->param) {
my
@values
=
$param_obj
->param(
$p
);
push
@vars_array
, (
$p
,
scalar
@values
> 1 ? \
@values
:
$values
[0]);
$query_props
->{
$p
} = 0
if
(
$extra_fields_optional
&& !
exists
$query_props
->{
$p
});
}
%validated
= validate(
@vars_array
,
$query_props
);
};
if
($@) {
my
$log_msg
=
"Query Validation Failed: $@"
;
if
(
$log_level
) {
$self
->
log
->
$log_level
(
$log_msg
);
}
$self
->error_mode(
$self
->{__CAP_VALQUERY_ERROR_MODE});
croak
$log_msg
;
}
# Account for default values, and use the expanded -name / -value
# syntax for CGI to ensure proper handling of multivalued fields.
my
$sub
=
$app_params
?
sub
{
my
$p
=
shift
;
$param_obj
->param(
$p
,
$validated
{
$p
}) }
:
sub
{
my
$p
=
shift
;
$param_obj
->param(
-name
=>
$p
,
-value
=>
$validated
{
$p
}) };
map
{
$sub
->(
$_
) }
keys
%validated
;
return
%validated
;
}
sub
validate_query_error_mode {
my
$self
=
shift
;
return
"<html><head><title>Request not understood</title></head><body>The
request submitted could not be understood.</body></html>";
}
1;
__END__
=head1 SYNOPSIS
use CGI::Application::ValidateQuery qw(validate_query
validate_query_config
:types);
sub setup {
my $self = shift;
$self->validate_query_config(
# define a page to show for invalid queries, or default to
# serving a plain, internal page
error_mode => 'my_invalid_query_run_mode',
log_level => 'notice',
extra_fields_optional => 0
);
}
sub my_run_mode {
my $self = shift;
# validate the query and return a standard error page on failure.
$self->validate_query(
pet_id => SCALAR,
direction => { type => SCALAR, default => 'up' },
);
# go on with life...
}
=head1 DESCRIPTION
This plugin is for small query validation tasks. For example, perhaps
you link to a page where a "pet_id" is required, and you need to reality
check that this exists or return essentially a generic error message to
the user.
Even if your application generates the link, it may become altered
through tampering, malware, or other unanticipated events.
This plugin uses L<Params::Validate> to validate the query string. You
can define your own error page to return on failure, or import a plain default
one that we supply.
You may also define a C<log_level>, if you do, we will also log each
validation failure at the chosen level like this:
$self->log->$loglevel("Query validation failed: $@");
L<CGI::Application::Plugin::LogDispatch> is one plugin which implements
this logging API.
=head2 validate_query
$self->validate_query(
pet_id => SCALAR,
type => { type => SCALAR, default => 'food' },
log_level => 'critical', # optional
app_params => 0,
extra_fields_optional => 1 # optional, default is 0
);
Validates C<< $self->query >> using L<Params::Validate>. If any required
query param is missing or invalid, the run mode defined with C<<
validate_query_config >> will be used. If you don't want to supply one, you
can import a plain error run mode--C<< validate_query_error_mode >>
that we provide. It will be returned by default. C<< validate_query_config >>
is usually called in C<< setup() >>, or a in a project super-class.
If C<log_level> is defined, it will override the the log level provided in
C<< validate_query_config >> and log a validation failure at that log
level.
If extra_fields_optional is defined and true, any parameter found in $self->query not
listed in the call to validate_query will be ignored by the check (in other
words, it will be included in the profile passed to L<Params::Validate> but
marked only as optional). If this is all the validation you're performing,
don't use this; this option is here for cases where, for example, a bunch of
POST values are already being checked by something heavier like
L<Data::FormValidator> and you just want to check one or two GET values.
If app_params is set to 1, C<< $self >> itself is validated (via
C<< $self->param >>). Note that you will almost certainly want to set
extra_fields_optional when using this flag. Its default is 0.
If you set a default for any parameter, the query will be modified with that
value should that parameter be missing.
=head2 IMPLENTATION NOTES
We re-export the constants provided in L<Params::Validate>. They can be loaded
using either the :all tag or by including the :types tag along with the
validate_query methods. Using :all will import everything from both this
module and from Params::Validate.
We set "local $Params::Validate::NO_VALIDATION = 0;" to be sure that
Params::Validate works for us, even if is globally disabled.
To alter the application flow when validation fails, we set
'error_mode()' at the last minute, and then die, so the error mode is
triggered. Other uses of error_mode() should continue to work as normal.
This module is intended to be use for simple query validation tasks,
such as a link with query string with a small number of arguments. For
larger validation tasks, especially for processing for submissions using
L<Data::FormValidator> is recommended, along with L<CGI::Application::ValidateRM>
if you're using L<CGI::Application>.
=head2 FUTURE
This concept could be extended to all check values set through
C<< $ENV{PATH_INFO} >>.
This plugin does not handle file upload validations, and won't in the
future.
Providing untainting is not a goal of this module, but if it's easy and
if someone else provides a patch, perhaps support will be added. L<Params::Validate>
provides untainting functionality and may be useful.
=head1 AUTHOR
Nate Smith C<< nate@summersault.com >>, Mark Stosberg C<< mark@summersault.com >>
=head1 BUGS & ISSUES
Please report any bugs or feature requests to
C<bug-cgi-application-plugin-validatequery at rt.cpan.org>, or through the web interface at
I will be notified, and then you'll automatically be notified of progress on
your bug as I make changes.
=head1 COPYRIGHT & LICENSE
Copyright 2009 Summersault, LLC., all rights reserved.
This program is free software; you can redistribute it and/or modify it
under the same terms as Perl itself.
=cut