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   55482 use strict;
  3         7  
  3         142  
3 3     3   15 use warnings;
  3         3  
  3         100  
4 3     3   1116 use Mojo::Base 'Mojolicious::Plugin';
  3         16546  
  3         22  
5             our $VERSION = '0.01';
6 3     3   3484 use Data::Dumper;
  3         15903  
  3         242  
7 3     3   1087 use Mojo::JSON qw(decode_json encode_json);
  3         116051  
  3         259  
8 3         379 use Mojo::Util qw{encode decode xml_escape hmac_sha1_sum secure_compare
9 3     3   32 b64_decode b64_encode};
  3         5  
10 3     3   2064 use HTML::ValidationRules::Legacy qw{validate extract};
  3         8  
  3         3678  
11              
12             our $TERM_ACTION = 0;
13             our $TERM_SCHEMA = 1;
14              
15             ### ---
16             ### register
17             ### ---
18             sub register {
19 1     1 1 62 my ($self, $app, $opt) = @_;
20            
21 1         2 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   1295144 my $c = shift;
28 53         429 my $req = $c->req;
29            
30 53 100 100     4795 if ($req->method eq 'POST' && grep {$_ eq $req->url->path} @$actions) {
  102         8086  
31            
32 50   100     4315 my $wrapper = deserialize(unsign(
33             $req->param($schema_key),
34             ($c->session($sess_key) || ''). $app->secrets->[0]
35             ));
36            
37 50         18578 $req->params->remove($schema_key);
38            
39 50 100       1625 if (!$wrapper) {
40 5         37 return $opt->{blackhole}->($c,
41             'Form schema is missing, possible hacking attempt');
42             }
43 45 100       4868 if ($req->url->path ne $wrapper->{$TERM_ACTION}) {
44 1         95 return $opt->{blackhole}->($c,
45             'Action attribute has been tampered');
46             }
47            
48 44 100       3808 if (my $err = validate($wrapper->{$TERM_SCHEMA}, $req->params)) {
49 23         150 return $opt->{blackhole}->($c, $err);
50             }
51             }
52 1         10 });
53            
54             $app->hook(after_dispatch => sub {
55 53     53   137992 my $c = shift;
56            
57 53 100 100     232 if ($c->res->headers->content_type =~ qr{^text/html} &&
58             $c->res->body =~ qr{
59            
60 1         341 my $sessid = $c->session($sess_key);
61            
62 1 50       436 if (! $sessid) {
63 1         19 $sessid = hmac_sha1_sum(time(). {}. rand(), $$);
64 1         39 $c->session($sess_key => $sessid);
65             }
66            
67 1         221 $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         44 });
76             }
77              
78             sub inject {
79 1     1 1 371 my ($html, $actions, $token_key, $secret, $charset) = @_;
80            
81 1 50       6 if (! ref $html) {
82 1 50       8 $html = Mojo::DOM->new($charset ? decode($charset, $html) : $html);
83             }
84              
85             $html->find(qq{form[action][method="post"]})->each(sub {
86 18     18   15170 my $form = shift;
87 18         69 my $action = $form->attr('action');
88            
89 18 100       1032 return if (! grep {$_ eq $action} @$actions);
  36         124  
90            
91 17         68 my $wrapper = sign(serialize({
92             $TERM_ACTION => $action,
93             $TERM_SCHEMA => extract($form, $charset),
94             }), $secret);
95            
96 17         436 $form->append_content(sprintf(<<"EOF", $token_key, xml_escape $wrapper));
97            
98            
99            
100             EOF
101 1         18490 });
102            
103 1         536 return encode($charset, $html);
104             }
105              
106             sub serialize {
107 37   100 37 0 25361 return b64_encode(encode_json(shift // return), '');
108             }
109              
110             sub deserialize {
111 80   100 80 0 4781 return decode_json(b64_decode(shift // return));
112             }
113              
114             sub sign {
115 21     21 0 5779 my ($value, $secret) = @_;
116 21         96 return $value. '--' . hmac_sha1_sum($value, $secret);
117             }
118              
119             sub unsign {
120 72     72 0 248035 my ($value, $secret) = @_;
121 72 100 66     1457 if ($value && $secret && $value =~ s/--([^\-]+)$//) {
      100        
122 68         203 my $sig = $1;
123 68 100       373 return $value if (secure_compare($sig, hmac_sha1_sum($value, $secret)));
124             }
125 8         315 return;
126             }
127              
128             1;
129              
130             __END__