File Coverage

blib/lib/Class/DBI/Plugin/AutoUntaint.pm
Criterion Covered Total %
statement 16 53 30.1
branch 0 30 0.0
condition 0 18 0.0
subroutine 5 6 83.3
pod 1 1 100.0
total 22 108 20.3


line stmt bran cond sub pod time code
1             package Class::DBI::Plugin::AutoUntaint;
2 1     1   29343 use Carp();
  1         3  
  1         21  
3              
4 1     1   5 use warnings;
  1         2  
  1         29  
5 1     1   4 use strict;
  1         6  
  1         153  
6              
7             =head1 NAME
8              
9             Class::DBI::Plugin::AutoUntaint - untaint columns automatically
10              
11             =cut
12              
13             our $VERSION = 0.1;
14              
15             our %TypesMap = ( varchar => 'printable',
16             char => 'printable', # includes MySQL enum and set
17             blob => 'printable', # includes MySQL text
18             text => 'printable',
19            
20             integer => 'integer',
21             bigint => 'integer',
22             smallint => 'integer',
23             tinyint => 'integer',
24             int => 'integer',
25            
26             # loads Date::Manip, which is powerful, but big and slow
27             date => 'date',
28            
29             # normally you want to skip untainting a timestamp column...
30             #timestamp => 'date',
31            
32             # someone should write CGI::Untaint::number
33             double => 'printable',
34             float => 'printable',
35             decimal => 'printable',
36             );
37              
38             =head1 SYNOPSIS
39              
40             package Film;
41             use Class::DBI::FromCGI;
42             use Class::DBI::Plugin::Type;
43             use Class::DBI::Plugin::AutoUntaint;
44             use base 'Class::DBI';
45             # set up as any other Class::DBI class.
46            
47             # instead of this:
48             #__PACKAGE__->untaint_columns(
49             # printable => [qw/Title Director/],
50             # integer => [qw/DomesticGross NumExplodingSheep],
51             # date => [qw/OpeningDate/],
52             # );
53            
54             # say this:
55             __PACKAGE__->auto_untaint;
56            
57             =head1 DESCRIPTION
58              
59             Automatically detects suitable default untaint methods for most column types.
60             Accepts arguments for overriding the default untaint types.
61              
62             =head1 METHODS
63              
64             =over 4
65            
66             =cut
67              
68             sub import
69             {
70 1     1   10 my ( $class ) = @_;
71            
72 1         3 my $caller = caller;
73            
74 1     1   5 no strict 'refs';
  1         2  
  1         697  
75 1         2 *{"$caller\::auto_untaint"} = \&auto_untaint;
  1         14  
76             }
77              
78             =item auto_untaint( [ %args ] )
79              
80             The following options can be set in C<%args>:
81              
82             =over 4
83              
84             =item untaint_columns
85              
86             Specify untaint types for specific columns:
87              
88             untaint_columns => { printable => [ qw( name title ) ],
89             date => [ qw( birthday ) ],
90             }
91            
92             =item skip_columns
93              
94             List of columns that will not be untainted:
95              
96             skip_columns => [ qw( secret_stuff internal_data ) ]
97              
98             =item match_columns
99              
100             Use regular expressions matching groups of columns to specify untaint
101             types:
102              
103             match_columns => { qr(^(first|last)_name$) => 'printable',
104             qr(^.+_event$) => 'date',
105             qr(^count_.+$) => 'integer',
106             }
107            
108             =item untaint_types
109              
110             Untaint according to SQL data types:
111              
112             untaint_types => { char => 'printable',
113             }
114            
115             Defaults are taken from the package global C<%TypesMap>.
116            
117             =item match_types
118              
119             Use a regular expression to map SQL data types to untaint types:
120              
121             match_types => { qr(^.*int$) => 'integer',
122             }
123            
124             =item debug
125            
126             Control how much detail to report (via C) during setup. Set to 1 for brief
127             info, and 2 for a list of each column's untaint type.
128              
129             =item strict
130              
131             If set to 1, will die if an untaint type cannot be determined for any column.
132             Default is to issue warnings and not untaint these column(s).
133            
134             =back
135              
136             =back
137              
138             =head2 timestamp
139              
140             The default behaviour is to skip untainting C columns. A warning will be issued
141             if the C parameter is set to 2.
142              
143             =head2 Failures
144              
145             The default mapping of column types to untaint types is set in C<%Class::DBI::Plugin::AutoUntaint::TypesMap>, and is probably incomplete. If you come across any failures, you can add suitable entries to the hash before calling C. However, B email me with any failures so the hash can be updated for everyone.
146              
147             =cut
148              
149             sub auto_untaint
150             { # plugged-into class i.e. CDBI class
151 0     0 1   my ( $class, %args ) = @_;
152            
153 0 0         warn "Untainting $class\n" if $args{debug} == 1;
154            
155 0   0       my $untaint_cols = $args{untaint_columns} || {};
156 0   0       my $skip_cols = $args{skip_columns} || [];
157 0   0       my $match_cols = $args{match_columns} || {};
158 0   0       my $ut_types = $args{untaint_types} || {};
159 0   0       my $match_types = $args{match_types} || {};
160            
161 0           my %skip = map { $_ => 1 } @$skip_cols;
  0            
162            
163 0           my %ut_cols;
164            
165 0           foreach my $as ( keys %$untaint_cols )
166             {
167 0           $ut_cols{ $_ } = $as for @{ $untaint_cols->{ $as } };
  0            
168             }
169            
170 0           my %untaint;
171            
172             # $col->name preserves case - stringifying doesn't
173 0           foreach my $col ( map { $_->name } $class->columns )
  0            
174             {
175 0 0         next if $skip{ $col };
176            
177 0           my $type = $class->column_type( $col );
178            
179 0 0         die "No type detected for column $col ($class)" unless $type;
180              
181 0   0       my $ut = $ut_cols{ $col } || $ut_types->{ $type } || $TypesMap{ $type } || '';
182            
183 0           foreach my $regex ( keys %$match_types )
184             {
185 0 0         last if $ut;
186 0 0         $ut = $match_types->{ $regex } if $type =~ $regex;
187             }
188            
189 0           foreach my $regex ( keys %$match_cols )
190             {
191 0 0         last if $ut;
192 0 0         $ut = $match_cols->{ $regex } if $col =~ $regex;
193             }
194            
195 0   0       my $skip_ts = ( ( $type eq 'timestamp' ) && ! $ut );
196            
197 0 0 0       warn "Skipping $class $col [timestamp]\n"
198             if ( $skip_ts and $args{debug} > 1 );
199            
200 0 0         next if $skip_ts;
201            
202 0 0         my $fail = "No untaint type detected for column $col, type $type in $class"
203             unless $ut;
204            
205 0 0         $fail and $args{strict} ? die $fail : warn $fail;
    0          
206            
207 0           my $type2 = substr( $type, 0, 25 );
208 0 0         $type2 .= '...' unless $type2 eq $type;
209            
210 0 0         warn sprintf "Untainting %s %s [%s] as %s\n",
211             $class, $col, $type2, $ut
212             if $args{debug} > 1;
213            
214 0 0         push( @{ $untaint{ $ut } }, $col ) if $ut;
  0            
215             }
216            
217 0           $class->untaint_columns( %untaint );
218             }
219              
220             =head1 TODO
221              
222             Tests!
223              
224             =head1 SEE ALSO
225              
226             L.
227              
228             =head1 AUTHOR
229              
230             David Baird, C<< >>
231              
232             =head1 BUGS
233              
234             Please report any bugs or feature requests to
235             C, or through the web interface at
236             L.
237             I will be notified, and then you'll automatically be notified of progress on
238             your bug as I make changes.
239              
240             =head1 COPYRIGHT & LICENSE
241              
242             Copyright 2005 David Baird, All Rights Reserved.
243              
244             This program is free software; you can redistribute it and/or modify it
245             under the same terms as Perl itself.
246              
247             =cut
248              
249             1; # End of Class::DBI::Plugin::AutoUntaint