| blib/lib/HTML/Scrubber/StripScripts.pm | |||
|---|---|---|---|
| Criterion | Covered | Total | % |
| statement | 35 | 35 | 100.0 |
| branch | 14 | 14 | 100.0 |
| condition | 3 | 3 | 100.0 |
| subroutine | 5 | 5 | 100.0 |
| pod | 1 | 1 | 100.0 |
| total | 58 | 58 | 100.0 |
| line | stmt | bran | cond | sub | pod | time | code |
|---|---|---|---|---|---|---|---|
| 1 | package HTML::Scrubber::StripScripts; | ||||||
| 2 | 7 | 7 | 27877 | use strict; | |||
| 7 | 14 | ||||||
| 7 | 301 | ||||||
| 3 | |||||||
| 4 | 7 | 7 | 44 | use vars qw($VERSION); | |||
| 7 | 13 | ||||||
| 7 | 558 | ||||||
| 5 | $VERSION = '0.02'; | ||||||
| 6 | |||||||
| 7 | =head1 NAME | ||||||
| 8 | |||||||
| 9 | HTML::Scrubber::StripScripts - strip scripting from HTML | ||||||
| 10 | |||||||
| 11 | =head1 SYNOPSIS | ||||||
| 12 | |||||||
| 13 | use HTML::Scrubber::StripScripts; | ||||||
| 14 | |||||||
| 15 | my $hss = HTML::Scrubber::StripScripts->new( | ||||||
| 16 | Allow_src => 1, | ||||||
| 17 | Allow_href => 1, | ||||||
| 18 | Allow_a_mailto => 1, | ||||||
| 19 | Whole_document => 1, | ||||||
| 20 | Block_tags => ['hr'], | ||||||
| 21 | ); | ||||||
| 22 | |||||||
| 23 | my $clean_html = $hss->scrub($dirty_html); | ||||||
| 24 | |||||||
| 25 | =head1 DESCRIPTION | ||||||
| 26 | |||||||
| 27 | This module provides a preworked configuration for L |
||||||
| 28 | configuring it to leave as much non-scripting markup in place as | ||||||
| 29 | possible while being certain to eliminate all scripting constructs. | ||||||
| 30 | This allows web applications to display HTML originating from an | ||||||
| 31 | untrusted source without introducing XSS (cross site scripting) | ||||||
| 32 | vulnerabilities. | ||||||
| 33 | |||||||
| 34 | =head1 CONSTRUCTORS | ||||||
| 35 | |||||||
| 36 | =over | ||||||
| 37 | |||||||
| 38 | =item new ( CONFIG ) | ||||||
| 39 | |||||||
| 40 | Returns a new C |
||||||
| 41 | policy based on a whitelist of XSS-free tags and attributes. If | ||||||
| 42 | present, the CONFIG parameter must be a hashref. The following keys | ||||||
| 43 | are recognized (unrecognized keys will be silently ignored). | ||||||
| 44 | |||||||
| 45 | =over | ||||||
| 46 | |||||||
| 47 | =item C |
||||||
| 48 | |||||||
| 49 | By default, the scrubber won't be configured to allow constructs | ||||||
| 50 | that cause the browser to fetch things automatically, such as C |
||||||
| 51 | attributes in C |
||||||
| 52 | those constructs will be allowed. | ||||||
| 53 | |||||||
| 54 | =item C |
||||||
| 55 | |||||||
| 56 | By default, the scrubber won't be configured to allow constructs | ||||||
| 57 | that cause the browser to fetch things if the user clicks on | ||||||
| 58 | something, such as the C |
||||||
| 59 | option to a true value to allow this type of construct. | ||||||
| 60 | |||||||
| 61 | =item C |
||||||
| 62 | |||||||
| 63 | By default, the scrubber won't be configured to allow C |
||||||
| 64 | URLs in C |
||||||
| 65 | value to allow them. Ignored unless C |
||||||
| 66 | |||||||
| 67 | =item C |
||||||
| 68 | |||||||
| 69 | By default, the scrubber will be configured to deal with a snippet | ||||||
| 70 | of HTML to be placed inside another document after scrubbing, and | ||||||
| 71 | won't allow C and C tags and so on. | ||||||
| 72 | |||||||
| 73 | Set this option to a true value if an entire HTML document is being | ||||||
| 74 | scrubbed. | ||||||
| 75 | |||||||
| 76 | =item C |
||||||
| 77 | |||||||
| 78 | If present, this must be an array ref holding a list of lower case | ||||||
| 79 | tag names. These tags will be removed from the allowed list. | ||||||
| 80 | |||||||
| 81 | For example, a guestbook CGI that uses C tags to separate posts |
||||||
| 82 | might wish to disallow the C tag in posts, even though C |
||||||
| 83 | presents no XSS hazard. | ||||||
| 84 | |||||||
| 85 | =back | ||||||
| 86 | |||||||
| 87 | =cut | ||||||
| 88 | |||||||
| 89 | require 5.005; # for qr// | ||||||
| 90 | 7 | 7 | 6590 | use HTML::Scrubber; | |||
| 7 | 71891 | ||||||
| 7 | 251 | ||||||
| 91 | |||||||
| 92 | 7 | 7 | 70 | use vars qw(%re); | |||
| 7 | 15 | ||||||
| 7 | 21681 | ||||||
| 93 | %re = ( | ||||||
| 94 | size => qr#^[+-]?\d+(?:\./d+)?[%*]?$#, | ||||||
| 95 | color => qr#^(?:\w{2,20}|\#[\da-fA-F]{6})$#, | ||||||
| 96 | word => qw#^\w*$#, | ||||||
| 97 | wordlist => qr#(?:[\w\-\, ]{1,200})$#, | ||||||
| 98 | text => qr#^[^\0]*$#, | ||||||
| 99 | url => qr# (?:^ (?:https?|ftp) :// ) | (?:^ [\w\.,/-]+ $) #ix, | ||||||
| 100 | a_mailto => qr# (?:^ (?:https?|ftp) :// ) | (?:^ [\w\.,/-]+ $) | (?:^ mailto: [\w\-\.\+\=\*]+\@[\w\-\.]+ $) #ix, | ||||||
| 101 | ); | ||||||
| 102 | |||||||
| 103 | sub new { | ||||||
| 104 | 7 | 7 | 1 | 5319 | my ($pkg, %cfg) = @_; | ||
| 105 | |||||||
| 106 | 7 | 17 | my (@cite, @href, @src, @background); | ||||
| 107 | 7 | 100 | 63 | @cite = ( cite => $re{'url'} ) if $cfg{Allow_href}; | |||
| 108 | 7 | 100 | 30 | @href = ( href => $re{'url'} ) if $cfg{Allow_href}; | |||
| 109 | 7 | 100 | 50 | @src = ( src => $re{'url'} ) if $cfg{Allow_src}; | |||
| 110 | 7 | 100 | 28 | @background = ( background => $re{'url'} ) if $cfg{Allow_src}; | |||
| 111 | |||||||
| 112 | 7 | 15 | my %empty = (); | ||||
| 113 | |||||||
| 114 | 7 | 38 | my %font_attr = ( | ||||
| 115 | 'size' => $re{'size'}, | ||||||
| 116 | 'face' => $re{'wordlist'}, | ||||||
| 117 | 'color' => $re{'color'}, | ||||||
| 118 | ); | ||||||
| 119 | |||||||
| 120 | 7 | 27 | my %insdel_attr = ( | ||||
| 121 | @cite, | ||||||
| 122 | 'datetime' => $re{'text'}, | ||||||
| 123 | ); | ||||||
| 124 | |||||||
| 125 | 7 | 24 | my %texta_attr = ( | ||||
| 126 | 'align' => $re{'word'}, | ||||||
| 127 | ); | ||||||
| 128 | |||||||
| 129 | 7 | 31 | my %cellha_attr = ( | ||||
| 130 | 'align' => $re{'word'}, | ||||||
| 131 | 'char' => $re{'word'}, | ||||||
| 132 | 'charoff' => $re{'size'}, | ||||||
| 133 | ); | ||||||
| 134 | |||||||
| 135 | 7 | 21 | my %cellva_attr = ( | ||||
| 136 | 'valign' => $re{'word'}, | ||||||
| 137 | ); | ||||||
| 138 | |||||||
| 139 | 7 | 36 | my %cellhv_attr = ( %cellha_attr, %cellva_attr ); | ||||
| 140 | |||||||
| 141 | 7 | 48 | my %col_attr = ( | ||||
| 142 | %cellhv_attr, | ||||||
| 143 | 'width' => $re{'size'}, | ||||||
| 144 | 'span' => $re{'number'}, | ||||||
| 145 | ); | ||||||
| 146 | |||||||
| 147 | 7 | 116 | my %thtd_attr = ( | ||||
| 148 | 'abbr' => $re{'text'}, | ||||||
| 149 | 'axis' => $re{'text'}, | ||||||
| 150 | 'headers' => $re{'text'}, | ||||||
| 151 | 'scope' => $re{'word'}, | ||||||
| 152 | 'rowspan' => $re{'size'}, | ||||||
| 153 | 'colspan' => $re{'size'}, | ||||||
| 154 | %cellhv_attr, | ||||||
| 155 | 'nowrap' => $re{'word'}, | ||||||
| 156 | 'bgcolor' => $re{'color'}, | ||||||
| 157 | 'width' => $re{'size'}, | ||||||
| 158 | 'height' => $re{'size'}, | ||||||
| 159 | 'bordercolor' => $re{'color'}, | ||||||
| 160 | 'bordercolorlight' => $re{'color'}, | ||||||
| 161 | 'bordercolordark' => $re{'color'}, | ||||||
| 162 | ); | ||||||
| 163 | |||||||
| 164 | 7 | 100 | 567 | my %rules = ( | |||
| 165 | 'br' => { 'clear' => $re{'word'} }, | ||||||
| 166 | 'em' => \%empty, | ||||||
| 167 | 'strong' => \%empty, | ||||||
| 168 | 'dfn' => \%empty, | ||||||
| 169 | 'code' => \%empty, | ||||||
| 170 | 'samp' => \%empty, | ||||||
| 171 | 'kbd' => \%empty, | ||||||
| 172 | 'var' => \%empty, | ||||||
| 173 | 'cite' => \%empty, | ||||||
| 174 | 'abbr' => \%empty, | ||||||
| 175 | 'acronym' => \%empty, | ||||||
| 176 | 'q' => { @cite }, | ||||||
| 177 | 'blockquote' => { @cite }, | ||||||
| 178 | 'sub' => \%empty, | ||||||
| 179 | 'sup' => \%empty, | ||||||
| 180 | 'tt' => \%empty, | ||||||
| 181 | 'i' => \%empty, | ||||||
| 182 | 'b' => \%empty, | ||||||
| 183 | 'big' => \%empty, | ||||||
| 184 | 'small' => \%empty, | ||||||
| 185 | 'u' => \%empty, | ||||||
| 186 | 's' => \%empty, | ||||||
| 187 | 'strike' => \%empty, | ||||||
| 188 | 'font' => \%font_attr, | ||||||
| 189 | 'table' => { 'frame' => $re{'word'}, | ||||||
| 190 | 'rules' => $re{'word'}, | ||||||
| 191 | %texta_attr, | ||||||
| 192 | 'bgcolor' => $re{'color'}, | ||||||
| 193 | @background, | ||||||
| 194 | 'width' => $re{'size'}, | ||||||
| 195 | 'height' => $re{'size'}, | ||||||
| 196 | 'cellspacing' => $re{'size'}, | ||||||
| 197 | 'cellpadding' => $re{'size'}, | ||||||
| 198 | 'border' => $re{'size'}, | ||||||
| 199 | 'bordercolor' => $re{'color'}, | ||||||
| 200 | 'bordercolorlight' => $re{'color'}, | ||||||
| 201 | 'bordercolordark' => $re{'color'}, | ||||||
| 202 | 'summary' => $re{'text'}, | ||||||
| 203 | }, | ||||||
| 204 | 'caption' => { 'align' => $re{'word'} }, | ||||||
| 205 | 'colgroup' => \%col_attr, | ||||||
| 206 | 'col' => \%col_attr, | ||||||
| 207 | 'thead' => \%cellhv_attr, | ||||||
| 208 | 'tfoot' => \%cellhv_attr, | ||||||
| 209 | 'tbody' => \%cellhv_attr, | ||||||
| 210 | 'tr' => { bgcolor => $re{'color'}, | ||||||
| 211 | %cellhv_attr, | ||||||
| 212 | }, | ||||||
| 213 | 'th' => \%thtd_attr, | ||||||
| 214 | 'td' => \%thtd_attr, | ||||||
| 215 | 'ins' => \%insdel_attr, | ||||||
| 216 | 'del' => \%insdel_attr, | ||||||
| 217 | 'a' => { @href }, | ||||||
| 218 | 'h1' => \%texta_attr, | ||||||
| 219 | 'h2' => \%texta_attr, | ||||||
| 220 | 'h3' => \%texta_attr, | ||||||
| 221 | 'h4' => \%texta_attr, | ||||||
| 222 | 'h5' => \%texta_attr, | ||||||
| 223 | 'h6' => \%texta_attr, | ||||||
| 224 | 'p' => \%texta_attr, | ||||||
| 225 | 'div' => \%texta_attr, | ||||||
| 226 | 'span' => \%texta_attr, | ||||||
| 227 | 'ul' => { 'type' => $re{'word'}, | ||||||
| 228 | 'compact' => $re{'word'}, | ||||||
| 229 | }, | ||||||
| 230 | 'ol' => { 'type' => $re{'text'}, | ||||||
| 231 | 'compact' => $re{'word'}, | ||||||
| 232 | 'start' => $re{'size'}, | ||||||
| 233 | }, | ||||||
| 234 | 'li' => { 'type' => $re{'text'}, | ||||||
| 235 | 'value' => $re{'size'}, | ||||||
| 236 | }, | ||||||
| 237 | 'dl' => { 'compact' => $re{'word'} }, | ||||||
| 238 | 'dt' => \%empty, | ||||||
| 239 | 'dd' => \%empty, | ||||||
| 240 | 'address' => \%empty, | ||||||
| 241 | 'hr' => { %texta_attr, | ||||||
| 242 | 'width' => $re{'size'}, | ||||||
| 243 | 'size ' => $re{'size'}, | ||||||
| 244 | 'noshade' => $re{'word'}, | ||||||
| 245 | }, | ||||||
| 246 | 'pre' => { 'width' => $re{'size'} }, | ||||||
| 247 | 'center' => \%empty, | ||||||
| 248 | 'nobr' => \%empty, | ||||||
| 249 | 'img' => { @src, | ||||||
| 250 | 'alt' => $re{'text'}, | ||||||
| 251 | 'width' => $re{'size'}, | ||||||
| 252 | 'height' => $re{'size'}, | ||||||
| 253 | 'border' => $re{'size'}, | ||||||
| 254 | 'hspace' => $re{'size'}, | ||||||
| 255 | 'vspace' => $re{'size'}, | ||||||
| 256 | 'align' => $re{'word'}, | ||||||
| 257 | }, | ||||||
| 258 | ( $cfg{Whole_document} ? | ||||||
| 259 | ( 'body' => { 'bgcolor' => $re{'color'}, | ||||||
| 260 | @background, | ||||||
| 261 | 'link' => $re{'color'}, | ||||||
| 262 | 'vlink' => $re{'color'}, | ||||||
| 263 | 'alink' => $re{'color'}, | ||||||
| 264 | 'text' => $re{'color'}, | ||||||
| 265 | }, | ||||||
| 266 | 'head' => {}, | ||||||
| 267 | 'title' => {}, | ||||||
| 268 | 'html' => {}, | ||||||
| 269 | ) : () | ||||||
| 270 | ), | ||||||
| 271 | ); | ||||||
| 272 | |||||||
| 273 | 7 | 100 | 100 | 65 | if ( $cfg{Allow_href} and $cfg{Allow_a_mailto} ) { | ||
| 274 | 1 | 4 | $rules{'a'}{'href'} = $re{'a_mailto'}; | ||||
| 275 | } | ||||||
| 276 | |||||||
| 277 | 7 | 100 | 29 | if ( $cfg{Block_tags} ) { | |||
| 278 | 1 | 2 | foreach my $block (@{ $cfg{Block_tags} }) { | ||||
| 1 | 3 | ||||||
| 279 | 3 | 10 | delete $rules{$block}; | ||||
| 280 | } | ||||||
| 281 | } | ||||||
| 282 | |||||||
| 283 | 7 | 171 | return HTML::Scrubber->new( | ||||
| 284 | rules => [%rules], | ||||||
| 285 | comment => 0, | ||||||
| 286 | process => 0, | ||||||
| 287 | ); | ||||||
| 288 | } | ||||||
| 289 | |||||||
| 290 | =head1 BUGS | ||||||
| 291 | |||||||
| 292 | =over | ||||||
| 293 | |||||||
| 294 | =item | ||||||
| 295 | |||||||
| 296 | All scripting is safely removed, but no attempt is made to ensure that | ||||||
| 297 | there is a matching end tag for each start tag. That could be a problem | ||||||
| 298 | if the scrubbed HTML is to be inserted into a larger HTML document, since | ||||||
| 299 | C tags and so on could be maliciously left open. | ||||||
| 300 | |||||||
| 301 | If that's a big problem for you, consider using the more heavyweight | ||||||
| 302 | (and probably much slower) L |
||||||
| 303 | |||||||
| 304 | =back | ||||||
| 305 | |||||||
| 306 | =head1 SEE ALSO | ||||||
| 307 | |||||||
| 308 | L |
||||||
| 309 | |||||||
| 310 | =head1 AUTHOR | ||||||
| 311 | |||||||
| 312 | Nick Cleaton E |
||||||
| 313 | |||||||
| 314 | =head1 COPYRIGHT | ||||||
| 315 | |||||||
| 316 | Copyright (C) 2003 Nick Cleaton. All Rights Reserved. | ||||||
| 317 | |||||||
| 318 | This module is free software; you can redistribute it and/or modify it | ||||||
| 319 | under the same terms as Perl itself. | ||||||
| 320 | |||||||
| 321 | =cut | ||||||
| 322 | |||||||
| 323 | 1; | ||||||
| 324 |