File Coverage

lib/Mojolicious/Plugin/FormValidatorLazy.pm
Criterion Covered Total %
statement 66 66 100.0
branch 20 24 83.3
condition 17 18 94.4
subroutine 16 16 100.0
pod 2 6 33.3
total 121 130 93.0


line stmt bran cond sub pod time code
1             package Mojolicious::Plugin::FormValidatorLazy;
2 3     3   27613 use strict;
  3         8  
  3         133  
3 3     3   15 use warnings;
  3         5  
  3         108  
4 3     3   683 use Mojo::Base 'Mojolicious::Plugin';
  3         10258  
  3         23  
5             our $VERSION = '0.02';
6 3     3   3186 use Data::Dumper;
  3         10679  
  3         323  
7 3     3   937 use Mojo::JSON qw(decode_json encode_json);
  3         50169  
  3         217  
8 3         264 use Mojo::Util qw{encode decode xml_escape hmac_sha1_sum secure_compare
9 3     3   21 b64_decode b64_encode};
  3         5  
10 3     3   1540 use HTML::ValidationRules::Legacy qw{validate extract};
  3         5  
  3         2786  
11              
12             our $TERM_ACTION = 0;
13             our $TERM_SCHEMA = 1;
14              
15             ### ---
16             ### register
17             ### ---
18             sub register {
19 1     1 1 68 my ($self, $app, $opt) = @_;
20            
21 1         3 my $schema_key = $opt->{namespace}. "-schema";
22 1         2 my $sess_key = $opt->{namespace}. '-sessid';
23            
24 1 50       4 my $actions = ref $opt->{action} ? $opt->{action} : [$opt->{action}];
25            
26             $app->hook(before_dispatch => sub {
27 53     53   863735 my $c = shift;
28 53         314 my $req = $c->req;
29            
30 53 100 100     2879 if ($req->method eq 'POST' && grep {$_ eq $req->url->path} @$actions) {
  102         5423  
31            
32 50   100     2556 my $wrapper = deserialize(unsign(
33             $req->param($schema_key),
34             ($c->session($sess_key) || ''). $app->secrets->[0]
35             ));
36            
37 50         12102 $req->params->remove($schema_key);
38            
39 50 100       1402 if (!$wrapper) {
40 5         34 return $opt->{blackhole}->($c,
41             'Form schema is missing, possible hacking attempt');
42             }
43 45 100       1114 if ($req->url->path ne $wrapper->{$TERM_ACTION}) {
44 1         54 return $opt->{blackhole}->($c,
45             'Action attribute has been tampered');
46             }
47            
48 44 100       2588 if (my $err = validate($wrapper->{$TERM_SCHEMA}, $req->params)) {
49 23         139 return $opt->{blackhole}->($c, $err);
50             }
51             }
52 1         11 });
53            
54             $app->hook(after_dispatch => sub {
55 53     53   92969 my $c = shift;
56            
57 53 100 100     166 if ($c->res->headers->content_type =~ qr{^text/html} &&
58             $c->res->body =~ qr{
59            
60 1         175 my $sessid = $c->session($sess_key);
61            
62 1 50       279 if (! $sessid) {
63 1         25 $sessid = hmac_sha1_sum(time(). {}. rand(), $$);
64 1         36 $c->session($sess_key => $sessid);
65             }
66            
67 1         131 $c->res->body(inject(
68             $c->res->body,
69             $actions,
70             $schema_key,
71             $sessid. $app->secrets->[0],
72             $c->res->content->charset)
73             );
74             }
75 1         43 });
76             }
77              
78             sub inject {
79 1     1 1 203 my ($html, $actions, $token_key, $secret, $charset) = @_;
80            
81 1 50       4 if (! ref $html) {
82 1 50       5 $html = Mojo::DOM->new($charset ? decode($charset, $html) : $html);
83             }
84              
85             $html->find(qq{form[action][method="post"]})->each(sub {
86 18     18   10170 my $form = shift;
87 18         47 my $action = $form->attr('action');
88            
89 18 100       634 return if (! grep {$_ eq $action} @$actions);
  36         87  
90            
91 17         56 my $wrapper = sign(serialize({
92             $TERM_ACTION => $action,
93             $TERM_SCHEMA => extract($form, $charset),
94             }), $secret);
95            
96 17         301 $form->append_content(sprintf(<<"EOF", $token_key, xml_escape $wrapper));
97            
98            
99            
100             EOF
101 1         12233 });
102            
103 1         395 return encode($charset, $html);
104             }
105              
106             sub serialize {
107 37   100 37 0 13712 return b64_encode(encode_json(shift // return), '');
108             }
109              
110             sub deserialize {
111 80   100 80 0 3862 return decode_json(b64_decode(shift // return));
112             }
113              
114             sub sign {
115 21     21 0 3381 my ($value, $secret) = @_;
116 21         66 return $value. '--' . hmac_sha1_sum($value, $secret);
117             }
118              
119             sub unsign {
120 72     72 0 218018 my ($value, $secret) = @_;
121 72 100 66     1129 if ($value && $secret && $value =~ s/--([^\-]+)$//) {
      100        
122 68         220 my $sig = $1;
123 68 100       259 return $value if (secure_compare($sig, hmac_sha1_sum($value, $secret)));
124             }
125 8         212 return;
126             }
127              
128             1;
129              
130             __END__