File Coverage

blib/lib/TUWF/Misc.pm
Criterion Covered Total %
statement 73 100 73.0
branch 55 64 85.9
condition 43 51 84.3
subroutine 11 18 61.1
pod 4 4 100.0
total 186 237 78.4


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   59097 use strict;
  1         1  
  1         26  
7 1     1   4 use warnings;
  1         1  
  1         23  
8 1     1   4 use Carp 'croak';
  1         1  
  1         36  
9 1     1   4 use Exporter 'import';
  1         2  
  1         30  
10 1     1   494 use Encode 'encode_utf8';
  1         8457  
  1         57  
11 1     1   7 use Scalar::Util 'looks_like_number';
  1         1  
  1         1580  
12              
13              
14             our $VERSION = '1.2';
15             our @EXPORT = ('formValidate', 'mail');
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   27 $_[0] *= 1; # Normalize to perl number
30 13 100 100     49 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         13 return 1;
33             }
34              
35             my $re_fqdn = qr/(?:[a-zA-Z0-9][\w-]*\.)+[a-zA-Z][a-zA-Z0-9-]{1,25}\.?/;
36             my $re_ip4_digit = qr/(?:0|[1-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])/;
37             my $re_ip4 = qr/($re_ip4_digit\.){3}$re_ip4_digit/;
38             # This monstrosity is based on http://stackoverflow.com/questions/53497/regular-expression-that-matches-valid-ipv6-addresses
39             # Doesn't allow IPv4-mapped-IPv6 addresses or other fancy stuff.
40             my $re_ip6 = qr/(?:[0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|(?:[0-9a-fA-F]{1,4}:){1,7}:|(?:[0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|(?:[0-9a-fA-F]{1,4}:){1,5}(?::[0-9a-fA-F]{1,4}){1,2}|(?:[0-9a-fA-F]{1,4}:){1,4}(?::[0-9a-fA-F]{1,4}){1,3}|(?:[0-9a-fA-F]{1,4}:){1,3}(?::[0-9a-fA-F]{1,4}){1,4}|(?:[0-9a-fA-F]{1,4}:){1,2}(?::[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:(?:(?::[0-9a-fA-F]{1,4}){1,6})|:(?:(?::[0-9a-fA-F]{1,4}){1,7}|:)/;
41             my $re_domain = qr/(?:$re_fqdn|$re_ip4|\[$re_ip6\])/;
42              
43             my %default_templates = (
44             # JSON number format, regex from http://stackoverflow.com/questions/13340717/json-numbers-regular-expression
45             num => { func => \&_template_validate_num, regex => qr/^-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?$/, inherit => ['min','max'] },
46             int => { func => \&_template_validate_num, regex => qr/^-?(?:0|[1-9]\d*)$/, inherit => ['min','max'] },
47             uint => { func => \&_template_validate_num, regex => qr/^(?:0|[1-9]\d*)$/, inherit => ['min','max'] },
48             ascii => { regex => qr/^[\x20-\x7E]*$/ },
49             email => { regex => qr/^[-\+\.#\$=\w]+\@$re_domain$/, maxlength => 254 },
50             weburl => { regex => qr/^https?:\/\/$re_domain(?::[1-9][0-9]{0,5})?\/[^\s<>"]*$/, maxlength => 65536 }, # the maxlength is a bit arbitrary, but better than unlimited
51             );
52              
53              
54             sub kv_validate {
55 92     92 1 54336 my($sources, $templates, $params) = @_;
56 92         540 $templates = { %default_templates, %$templates };
57              
58 92         168 my @err;
59             my %ret;
60              
61 92         154 for my $f (@$params) {
62             # Inherit some options from templates.
63             !exists($f->{$_}) && _val_from_tpl($f, $_, $templates, $f)
64 92   100     278 for(qw|required default rmwhitespace multi mincount maxcount|);
65              
66 92         283 my $src = (grep $f->{$_}, keys %$sources)[0];
67 92         190 my @values = $sources->{$src}->($f->{$src});
68 92 100       869 @values = ($values[0]) if !$f->{multi};
69              
70             # check each value and add it to %ret
71 92         127 for (@values) {
72 97   100     128 my $errfield = _validate_early($_, $f) || _validate($_, $templates, $f);
73 97 100 100     278 next if !$errfield || $errfield eq 'default';
74 45         102 push @err, [ $f->{$src}, $errfield, $f->{$errfield} ];
75 45         63 last;
76             }
77 92 100       200 $ret{$f->{$src}} = $f->{multi} ? \@values : $values[0];
78              
79             # check mincount/maxcount
80 92 100 100     148 push @err, [ $f->{$src}, 'mincount', $f->{mincount} ] if $f->{mincount} && @values < $f->{mincount};
81 92 100 100     198 push @err, [ $f->{$src}, 'maxcount', $f->{maxcount} ] if $f->{maxcount} && @values > $f->{maxcount};
82             }
83              
84 92 100       148 $ret{_err} = \@err if @err;
85 92         329 return \%ret;
86             }
87              
88              
89             sub _val_from_tpl {
90 896     896   1076 my($top_rules, $field, $tpls, $rules) = @_;
91 896 100       1575 return if !$rules->{template};
92 379         435 my $tpl = $tpls->{$rules->{template}};
93 379 100       472 if(exists $tpl->{$field}) {
94 17         36 $top_rules->{$field} = $tpl->{$field};
95             } else {
96 362         414 _val_from_tpl($top_rules, $field, $tpls, $tpl);
97             }
98             }
99              
100              
101             # Initial validation of a value. Same as _validate() below, but this one
102             # validates options that need to be checked only once. (The checks in
103             # _validate() may run several times when templates are used).
104             sub _validate_early { # value, \%rules
105 97     97   134 my($v, $r) = @_;
106              
107 97 100       181 $r->{required}++ if not exists $r->{required};
108 97 100       163 $r->{rmwhitespace}++ if not exists $r->{rmwhitespace};
109              
110             # remove whitespace
111 97 100 100     261 if($v && $r->{rmwhitespace}) {
112 75         137 $_[0] =~ s/\r//g;
113 75         182 $_[0] =~ s/^[\s\n]+//;
114 75         124 $_[0] =~ s/[\s\n]+$//;
115 75         95 $v = $_[0]
116             }
117              
118             # empty
119 97 100 100     278 if(!defined($v) || length($v) < 1) {
120 9 100       26 return 'required' if $r->{required};
121 3 100       8 $_[0] = $r->{default} if exists $r->{default};
122 3         7 return 'default';
123             }
124 88         200 return undef;
125             }
126              
127              
128             # Internal function used by kv_validate, checks one value on the validation
129             # rules, the name of the failed rule on error, undef otherwise
130             sub _validate { # value, \%templates, \%rules
131 155     155   195 my($v, $t, $r) = @_;
132              
133 155 50 66     332 croak "Template $r->{template} not defined." if $r->{template} && !$t->{$r->{template}};
134              
135             # length
136 155 100 100     235 return 'minlength' if $r->{minlength} && length $v < $r->{minlength};
137 153 100 100     282 return 'maxlength' if $r->{maxlength} && length $v > $r->{maxlength};
138             # enum
139 150 100 100     207 return 'enum' if $r->{enum} && !grep $_ eq $v, @{$r->{enum}};
  3         23  
140             # regex
141 149 100 100     611 return 'regex' if $r->{regex} && (ref($r->{regex}) eq 'ARRAY' ? ($v !~ m/$r->{regex}[0]/) : ($v !~ m/$r->{regex}/));
    100          
142             # template
143 126 100       177 if($r->{template}) {
144 67         94 my $in = $t->{$r->{template}}{inherit};
145 67 100       124 my %r = (($in ? (map exists($r->{$_}) ? ($_,$r->{$_}) : (), @$in) : ()), %{$t->{$r->{template}}});
  67 100       170  
146 67 100       136 return 'template' if _validate($_[0], $t, \%r);
147             }
148             # function
149 96 100 100     205 return 'func' if $r->{func} && (ref($r->{func}) eq 'ARRAY' ? !$r->{func}[0]->($_[0], $r) : !$r->{func}->($_[0], $r));
    100          
150             # passed validation
151 86         211 return undef;
152             }
153              
154              
155              
156              
157             sub formValidate {
158 0     0 1   my($self, @fields) = @_;
159             return kv_validate(
160 0     0     { post => sub { $self->reqPosts(shift) },
161 0     0     get => sub { $self->reqGets(shift) },
162 0     0     param => sub { $self->reqParams(shift) },
163 0     0     cookie => sub { $self->reqCookie(shift) },
164             }, $self->{_TUWF}{validate_templates} || {},
165             \@fields
166 0   0       );
167             }
168              
169              
170              
171             # A simple mail function, body and headers as arguments. Usage:
172             # $self->mail('body', header1 => 'value of header 1', ..);
173             sub mail {
174 0     0 1   my $self = shift;
175 0           my $body = shift;
176 0           my %hs = @_;
177              
178 0 0         croak "No To: specified!\n" if !$hs{To};
179 0 0         croak "No Subject: specified!\n" if !$hs{Subject};
180 0   0       $hs{'Content-Type'} ||= 'text/plain; charset=\'UTF-8\'';
181 0   0       $hs{From} ||= $self->{_TUWF}{mail_from};
182 0           $body =~ s/\r?\n/\n/g;
183              
184 0           my $mail = '';
185 0           foreach (keys %hs) {
186 0           $hs{$_} =~ s/[\r\n]//g;
187 0           $mail .= sprintf "%s: %s\n", $_, $hs{$_};
188             }
189 0           $mail .= sprintf "\n%s", $body;
190              
191 0 0         if(open(my $mailer, '|-:utf8', "$self->{_TUWF}{mail_sendmail} -t -f '$hs{From}'")) {
192 0           print $mailer $mail;
193 0 0         croak "Error running sendmail ($!)"
194             if !close($mailer);
195             } else {
196 0           croak "Error opening sendail ($!)";
197             }
198             }
199              
200              
201             1;