File Coverage

blib/lib/TUWF/Misc.pm
Criterion Covered Total %
statement 73 126 57.9
branch 55 96 57.2
condition 43 53 81.1
subroutine 11 22 50.0
pod 2 2 100.0
total 184 299 61.5


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