CGI::Application::Plugin::ValidateQuery - lightweight query validation for CGI::Application


CGI-Application-Plugin-ValidateQuery documentation Contained in the CGI-Application-Plugin-ValidateQuery distribution.

Index


Code Index:

NAME

Top

CGI::Application::Plugin::ValidateQuery - lightweight query validation for CGI::Application

VERSION

Top

Version 1.0.5

SYNOPSIS

Top

 use CGI::Application::ValidateQuery qw(validate_query
                                        validate_app_params
                                        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',
            allow_extra           => 0
     );

 }

 sub my_run_mode {
    my $self = shift;

    # validate select options stored in the app itself
    $self->validate_app_params(
        user_id  => qr/\A[a-zA-Z]\z/,
    );

    # move on...
 }

 sub another_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...

 }

DESCRIPTION

Top

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 Params::Validate to validate either a query object or values stored in a CGI::Application object. 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 log_level, if you do, we will also log each validation failure at the chosen level like this:

 $self->log->$loglevel("Query validation failed: $@");

CGI::Application::Plugin::LogDispatch is one plugin which implements this logging API.

validate_query

    $self->validate_query(
                            pet_id      => qr/\A\d+\z/, # implies regex and type=>SCALAR
                            species     => { type => SCALAR, default => 'lizard' },
                            log_level   => 'critical',  # optional
                            allow_extra => 1  # optional, default is 0
     );

Validates $self->query using Params::Validate. If any required query param is missing or invalid, the run mode defined with validate_query_config will be used. If you don't want to supply one, you can import a plain error run mode--validate_query_error_mode that we provide. It will be returned by default. validate_query_config is usually called in setup(), or a in a project super-class.

If log_level is defined, it will override the the log level provided in validate_query_config and log a validation failure at that log level.

If allow_extra 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 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 Data::FormValidator and you just want to check one or two GET values.

If you set a default for any parameter, the query will be modified with that value should that parameter be missing.

validate_app_params

    $self->validate_app_params(
        user_id  => qr/\A[a-zA-Z]\z/,
    );

Behaves like validate_query with these exceptions:

* allow_extra is set to true
* items specified are looked for and validated using $self-param> instead of $self-query->param>.

IMPLENTATION NOTES

We re-export the constants provided in 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 or a page with a few dispatched args. For larger validation tasks, especially for processing form submissions using Data::FormValidator is recommended, along with CGI::Application::ValidateRM if you're using CGI::Application.

FUTURE

This concept could be extended to all check values set through $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. Params::Validate provides untainting functionality and may be useful.

AUTHOR

Top

Nate Smith nate@summersault.com, Mark Stosberg mark@summersault.com

BUGS & ISSUES

Top

Please report any bugs or feature requests to bug-cgi-application-plugin-validatequery at rt.cpan.org, or through the web interface at http://rt.cpan.org/NoAuth/ReportBug.html?Queue=CGI-Application-Plugin-ValidateQuery. I will be notified, and then you'll automatically be notified of progress on your bug as I make changes.

COPYRIGHT & LICENSE

Top


CGI-Application-Plugin-ValidateQuery documentation Contained in the CGI-Application-Plugin-ValidateQuery distribution.
package CGI::Application::Plugin::ValidateQuery;

use warnings;
use strict;

use base 'Exporter';

use Carp 'croak';
use Params::Validate ':all';

our $VERSION = '1.0.5';

our @EXPORT_OK = qw(
    validate_query_config
    validate_app_params
    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 $opts = {@_};

    $opts = {map {uc $_ => $opts->{$_}} keys %$opts};

    # for now, default checking all params. First config arg is legacy.
    if ( defined $opts->{EXTRA_FIELDS_OPTIONAL} or defined $opts->{ALLOW_EXTRA} ) {
        delete $opts->{EXTRA_FIELDS_OPTIONAL};
        delete $opts->{ALLOW_EXTRA};
        $self->{__CAP_VALQUERY_ALLOW_EXTRA} = 1;
    } else {
        $self->{__CAP_VALQUERY_ALLOW_EXTRA} = 0;
    }

    $self->{__CAP_VALQUERY_ERROR_MODE} = defined $opts->{ERROR_MODE} ?
        delete $opts->{ERROR_MODE} : 'validate_query_error_mode';

    $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_app_params {
    my $self = shift;

    return unless @_;

    my $query_props = {@_};

    $query_props->{allow_extra} = 1;
    $query_props->{app_params}  = 1;

    return _validate($self, $query_props);
}

sub validate_query {
    my $self = shift;

    return unless @_;

    return _validate($self, {@_});
}


sub _validate {
    my $self        = shift;
    my $query_props = shift;

    my $log_level = delete $query_props->{log_level}
                    || $self->{__CAP_VALQUERY_LOG_LEVEL};

    my $allow_extra = delete($query_props->{extra_fields_optional})
                      || delete($query_props->{allow_extra})
                      || $self->{__CAP_ALLOW_EXTRA};

    my $app_params = delete $query_props->{app_params};

    my $param_obj = $app_params ? $self : $self->query;

    # filter query_props to support quick regex syntax
    # turns
        # key => qr/$regex/
    # into
        # key => { regex => qr/$regex/ }
    for my $key (keys %$query_props) {
        my $val = $query_props->{$key};
        if ( ref $val eq 'Regexp' ) {
            $query_props->{$key} = { regex => $val, type => SCALAR };
        }
    }

    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]);
        }
        %validated = validate_with(
            params      => \@vars_array,
            spec        => $query_props,
            allow_extra => $allow_extra
        );
    };
    if ($@) {
        my $log_msg = "Query Validation Failed: $@";
        $self->log->$log_level($log_msg) if $log_level;
        $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__