Mojolicious-Plugin-FormValidatorLazy

 view release on metacpan or  search on metacpan

lib/Mojolicious/Plugin/FormValidatorLazy.pm  view on Meta::CPAN

package Mojolicious::Plugin::FormValidatorLazy;
use strict;
use warnings;
use Mojo::Base 'Mojolicious::Plugin';
our $VERSION = '0.03';
use Data::Dumper;
use Mojo::JSON qw(decode_json encode_json);
use Mojo::Util qw{encode decode xml_escape hmac_sha1_sum secure_compare
                                                        b64_decode b64_encode};
use HTML::ValidationRules::Legacy qw{validate extract};

our $TERM_ACTION = 0;
our $TERM_SCHEMA = 1;

### ---
### register
### ---
sub register {
    my ($self, $app, $opt) = @_;
    
    my $schema_key = $opt->{namespace}. "-schema";
    my $sess_key = $opt->{namespace}. '-sessid';
    
    my $actions = ref $opt->{action} ? $opt->{action} : [$opt->{action}];
    
    $app->hook(before_dispatch => sub {
        my $c = shift;
        my $req = $c->req;
        
        if ($req->method eq 'POST' && grep {$_ eq $req->url->path} @$actions) {
            
            my $wrapper = deserialize(unsign(
                $req->param($schema_key),
                ($c->session($sess_key) || ''). $app->secrets->[0]
            ));
            
            $req->params->remove($schema_key);
            
            if (!$wrapper) {
                return $opt->{blackhole}->($c,
                            'Form schema is missing, possible hacking attempt');
            }
            if ($req->url->path ne $wrapper->{$TERM_ACTION}) {
                return $opt->{blackhole}->($c,
                                        'Action attribute has been tampered');
            }
            
            if (my $err = validate($wrapper->{$TERM_SCHEMA}, $req->params)) {
                return $opt->{blackhole}->($c, $err);
            }
        }
    });
    
    $app->hook(after_dispatch => sub {
        my $c = shift;
        
        if ($c->res->headers->content_type =~ qr{^text/html} &&
                                                $c->res->body =~ qr{<form\b}i) {
            
            my $sessid = $c->session($sess_key);
            
            if (! $sessid) {
                $sessid = hmac_sha1_sum(time(). {}. rand(), $$);
                $c->session($sess_key => $sessid);
            }
            
            $c->res->body(inject(
                $c->res->body,
                $actions,
                $schema_key,
                $sessid. $app->secrets->[0],
                $c->res->content->charset)
            );
        }
    });
}

sub inject {
    my ($html, $actions, $token_key, $secret, $charset) = @_;
    
    if (! ref $html) {
        $html = Mojo::DOM->new($charset ? decode($charset, $html) : $html);
    }

    $html->find(qq{form[action][method="post"]})->each(sub {
        my $form    = shift;
        my $action  = $form->attr('action');
        
        return if (! grep {$_ eq $action} @$actions);
        
        my $wrapper = sign(serialize({
            $TERM_ACTION    => $action,
            $TERM_SCHEMA    => extract($form, $charset),
        }), $secret);
        
        $form->append_content(sprintf(<<"EOF", $token_key, xml_escape $wrapper));
<div style="display:none">
    <input type="hidden" name="%s" value="%s">
</div>
EOF
    });
    
    return encode($charset, $html);
}

sub serialize {
    return b64_encode(encode_json(shift // return), '');
}



( run in 0.421 second using v1.01-cache-2.11-cpan-39bf76dae61 )