File Coverage

blib/lib/TUWF/Misc.pm
Criterion Covered Total %
statement 76 116 65.5
branch 55 84 65.4
condition 43 53 81.1
subroutine 12 22 54.5
pod 2 2 100.0
total 188 277 67.8


line stmt bran cond sub pod time code
1              
2             package TUWF::Misc;
3             # Yeah, just put all miscellaneous functions in one module!
4             # Geez, talk about being sloppy...
5              
6 1     1   58722 use strict;
  1         9  
  1         24  
7 1     1   5 use warnings;
  1         1  
  1         23  
8 1     1   4 use Carp 'croak';
  1         1  
  1         38  
9 1     1   4 use Exporter 'import';
  1         10  
  1         32  
10 1     1   523 use Encode 'encode_utf8';
  1         8437  
  1         55  
11 1     1   7 use Scalar::Util 'looks_like_number';
  1         2  
  1         36  
12 1     1   339 use TUWF::Validate;
  1         1  
  1         1646  
13              
14              
15             our $VERSION = '1.4';
16             our @EXPORT_OK = ('uri_escape', 'kv_validate');
17              
18              
19             sub uri_escape {
20 0     0 1 0 local $_ = encode_utf8 shift;
21 0         0 s/([^A-Za-z0-9._~-])/sprintf '%%%02X', ord $1/eg;
  0         0  
22 0         0 return $_;
23             }
24              
25              
26              
27              
28             sub _template_validate_num {
29 13     13   28 $_[0] *= 1; # Normalize to perl number
30 13 100 100     62 return 0 if defined($_[1]{min}) && $_[0] < $_[1]{min};
31 9 100 100     38 return 0 if defined($_[1]{max}) && $_[0] > $_[1]{max};
32 6         14 return 1;
33             }
34              
35              
36             my %default_templates = (
37             # JSON number format, regex from http://stackoverflow.com/questions/13340717/json-numbers-regular-expression
38             num => { func => \&_template_validate_num, regex => qr/^-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?$/, inherit => ['min','max'] },
39             int => { func => \&_template_validate_num, regex => qr/^-?(?:0|[1-9]\d*)$/, inherit => ['min','max'] },
40             uint => { func => \&_template_validate_num, regex => qr/^(?:0|[1-9]\d*)$/, inherit => ['min','max'] },
41             ascii => { regex => qr/^[\x20-\x7E]*$/ },
42             email => { regex => $TUWF::Validate::re_email, maxlength => 254 },
43             weburl => { regex => $TUWF::Validate::re_weburl, maxlength => 65536 }, # the maxlength is a bit arbitrary, but better than unlimited
44             );
45              
46              
47             sub kv_validate {
48 92     92 1 55520 my($sources, $templates, $params) = @_;
49 92         537 $templates = { %default_templates, %$templates };
50              
51 92         168 my @err;
52             my %ret;
53              
54 92         148 for my $f (@$params) {
55             # Inherit some options from templates.
56             !exists($f->{$_}) && _val_from_tpl($f, $_, $templates, $f)
57 92   100     279 for(qw|required default rmwhitespace multi mincount maxcount|);
58              
59 92         321 my $src = (grep $f->{$_}, keys %$sources)[0];
60 92         210 my @values = $sources->{$src}->($f->{$src});
61 92 100       859 @values = ($values[0]) if !$f->{multi};
62              
63             # check each value and add it to %ret
64 92         124 for (@values) {
65 97   100     137 my $errfield = _validate_early($_, $f) || _validate($_, $templates, $f);
66 97 100 100     278 next if !$errfield || $errfield eq 'default';
67 45         106 push @err, [ $f->{$src}, $errfield, $f->{$errfield} ];
68 45         64 last;
69             }
70 92 100       206 $ret{$f->{$src}} = $f->{multi} ? \@values : $values[0];
71              
72             # check mincount/maxcount
73 92 100 100     175 push @err, [ $f->{$src}, 'mincount', $f->{mincount} ] if $f->{mincount} && @values < $f->{mincount};
74 92 100 100     200 push @err, [ $f->{$src}, 'maxcount', $f->{maxcount} ] if $f->{maxcount} && @values > $f->{maxcount};
75             }
76              
77 92 100       160 $ret{_err} = \@err if @err;
78 92         336 return \%ret;
79             }
80              
81              
82             sub _val_from_tpl {
83 896     896   1024 my($top_rules, $field, $tpls, $rules) = @_;
84 896 100       1539 return if !$rules->{template};
85 379         414 my $tpl = $tpls->{$rules->{template}};
86 379 100       510 if(exists $tpl->{$field}) {
87 17         37 $top_rules->{$field} = $tpl->{$field};
88             } else {
89 362         438 _val_from_tpl($top_rules, $field, $tpls, $tpl);
90             }
91             }
92              
93              
94             # Initial validation of a value. Same as _validate() below, but this one
95             # validates options that need to be checked only once. (The checks in
96             # _validate() may run several times when templates are used).
97             sub _validate_early { # value, \%rules
98 97     97   145 my($v, $r) = @_;
99              
100 97 100       188 $r->{required}++ if not exists $r->{required};
101 97 100       153 $r->{rmwhitespace}++ if not exists $r->{rmwhitespace};
102              
103             # remove whitespace
104 97 100 100     276 if($v && $r->{rmwhitespace}) {
105 75         152 $_[0] =~ s/\r//g;
106 75         207 $_[0] =~ s/^[\s\n]+//;
107 75         126 $_[0] =~ s/[\s\n]+$//;
108 75         93 $v = $_[0]
109             }
110              
111             # empty
112 97 100 100     283 if(!defined($v) || length($v) < 1) {
113 9 100       25 return 'required' if $r->{required};
114 3 100       7 $_[0] = $r->{default} if exists $r->{default};
115 3         7 return 'default';
116             }
117 88         191 return undef;
118             }
119              
120              
121             # Internal function used by kv_validate, checks one value on the validation
122             # rules, the name of the failed rule on error, undef otherwise
123             sub _validate { # value, \%templates, \%rules
124 155     155   211 my($v, $t, $r) = @_;
125              
126 155 50 66     341 croak "Template $r->{template} not defined." if $r->{template} && !$t->{$r->{template}};
127              
128             # length
129 155 100 100     249 return 'minlength' if $r->{minlength} && length $v < $r->{minlength};
130 153 100 100     272 return 'maxlength' if $r->{maxlength} && length $v > $r->{maxlength};
131             # enum
132 150 100 100     223 return 'enum' if $r->{enum} && !grep $_ eq $v, @{$r->{enum}};
  3         23  
133             # regex
134 149 100 100     661 return 'regex' if $r->{regex} && (ref($r->{regex}) eq 'ARRAY' ? ($v !~ m/$r->{regex}[0]/) : ($v !~ m/$r->{regex}/));
    100          
135             # template
136 126 100       186 if($r->{template}) {
137 67         99 my $in = $t->{$r->{template}}{inherit};
138 67 100       124 my %r = (($in ? (map exists($r->{$_}) ? ($_,$r->{$_}) : (), @$in) : ()), %{$t->{$r->{template}}});
  67 100       213  
139 67 100       145 return 'template' if _validate($_[0], $t, \%r);
140             }
141             # function
142 96 100 100     187 return 'func' if $r->{func} && (ref($r->{func}) eq 'ARRAY' ? !$r->{func}[0]->($_[0], $r) : !$r->{func}->($_[0], $r));
    100          
143             # passed validation
144 86         228 return undef;
145             }
146              
147              
148              
149              
150             sub TUWF::Object::formValidate {
151 0     0     my($self, @fields) = @_;
152             return kv_validate(
153 0     0     { post => sub { $self->reqPosts(shift) },
154 0     0     get => sub { $self->reqGets(shift) },
155 0     0     param => sub { $self->reqParams(shift) },
156 0     0     cookie => sub { $self->reqCookie(shift) },
157             }, $self->{_TUWF}{validate_templates} || {},
158             \@fields
159 0   0       );
160             }
161              
162              
163              
164             # A simple mail function, body and headers as arguments. Usage:
165             # $self->mail('body', header1 => 'value of header 1', ..);
166             sub TUWF::Object::mail {
167 0     0     my $self = shift;
168 0           my $body = shift;
169 0           my %hs = @_;
170              
171 0 0         croak "No To: specified!\n" if !$hs{To};
172 0 0         croak "No Subject: specified!\n" if !$hs{Subject};
173 0   0       $hs{'Content-Type'} ||= 'text/plain; charset=\'UTF-8\'';
174 0   0       $hs{From} ||= $self->{_TUWF}{mail_from};
175 0           $body =~ s/\r?\n/\n/g;
176              
177 0           my $mail = '';
178 0           foreach (keys %hs) {
179 0           $hs{$_} =~ s/[\r\n]//g;
180 0           $mail .= sprintf "%s: %s\n", $_, $hs{$_};
181             }
182 0           $mail .= sprintf "\n%s", $body;
183              
184 0 0         if($self->{_TUWF}{mail_sendmail} eq 'log') {
    0          
185 0           $self->log("tuwf->mail(): The following mail would have been sent:\n$mail");
186             } elsif(open(my $mailer, '|-:utf8', "$self->{_TUWF}{mail_sendmail} -t -f '$hs{From}'")) {
187 0           print $mailer $mail;
188 0 0         croak "Error running sendmail ($!)"
189             if !close($mailer);
190             } else {
191 0           croak "Error opening sendail ($!)";
192             }
193             }
194              
195              
196             sub TUWF::Object::compile {
197 0     0     TUWF::Validate::compile($_[0]{_TUWF}{custom_validations}, $_[1]);
198             }
199              
200              
201             sub _compile {
202 0 0   0     ref $_[0] eq 'TUWF::Validate' ? $_[0] : $TUWF::OBJ->compile($_[0]);
203             }
204              
205              
206             sub TUWF::Object::validate {
207 0     0     my $self = shift;
208 0           my $what = shift;
209              
210 0 0         return _compile($_[0])->validate($self->reqJSON) if $what eq 'json';
211              
212             # 'param' is special, and not really encouraged. Create a new hash based on
213             # reqParam() and cache the result.
214             $self->{_TUWF}{Req}{PARAM} ||= {
215 0 0 0       map { my @v = $self->reqParams($_); +($_, @v > 1 ? \@v : $v[0]) } $self->reqParams()
  0 0          
  0            
216             } if $what eq 'param';
217              
218             my $source =
219             $what eq 'get' ? $self->{_TUWF}{Req}{GET} :
220             $what eq 'post' ? $self->{_TUWF}{Req}{POST} :
221             $what eq 'param' ? $self->{_TUWF}{Req}{PARAM}
222 0 0         : croak "Invalid source type '$what'";
    0          
    0          
223              
224             # Multi-value, schema hash or object
225 0 0         return _compile($_[0])->validate($source) if @_ == 1;
226              
227             # Single value
228 0 0         return _compile($_[1])->validate($source->{$_[0]}) if @_ == 2;
229              
230             # Multi-value, separate params
231 0           _compile({ type => 'hash', keys => { @_ } })->validate($source);
232             }
233              
234             1;