| blib/lib/Labyrinth/MLUtils.pm | |||
|---|---|---|---|
| Criterion | Covered | Total | % |
| statement | 25 | 27 | 92.5 |
| branch | n/a | ||
| condition | n/a | ||
| subroutine | 9 | 9 | 100.0 |
| pod | n/a | ||
| total | 34 | 36 | 94.4 |
| line | stmt | bran | cond | sub | pod | time | code | |
|---|---|---|---|---|---|---|---|---|
| 1 | package Labyrinth::MLUtils; | |||||||
| 2 | ||||||||
| 3 | 8 | 8 | 34023 | use warnings; | ||||
| 8 | 12 | |||||||
| 8 | 203 | |||||||
| 4 | 8 | 8 | 27 | use strict; | ||||
| 8 | 9 | |||||||
| 8 | 143 | |||||||
| 5 | 8 | 8 | 26 | use utf8; | ||||
| 8 | 9 | |||||||
| 8 | 33 | |||||||
| 6 | ||||||||
| 7 | 8 | 8 | 140 | use vars qw($VERSION @ISA %EXPORT_TAGS @EXPORT @EXPORT_OK); | ||||
| 8 | 7 | |||||||
| 8 | 865 | |||||||
| 8 | $VERSION = '5.32'; | |||||||
| 9 | ||||||||
| 10 | =head1 NAME | |||||||
| 11 | ||||||||
| 12 | Labyrinth::MLUtils - Markup Language Utilities for Labyrinth. | |||||||
| 13 | ||||||||
| 14 | =head1 SYNOPSIS | |||||||
| 15 | ||||||||
| 16 | use Labyrinth::MLUtils; | |||||||
| 17 | ||||||||
| 18 | =cut | |||||||
| 19 | ||||||||
| 20 | # ------------------------------------- | |||||||
| 21 | # Export Details | |||||||
| 22 | ||||||||
| 23 | require Exporter; | |||||||
| 24 | @ISA = qw(Exporter); | |||||||
| 25 | %EXPORT_TAGS = ( 'all' => [ qw( | |||||||
| 26 | LegalTag LegalTags CleanTags | |||||||
| 27 | CleanHTML SafeHTML CleanLink CleanWords LinkTitles | |||||||
| 28 | DropDownList DropDownListText | |||||||
| 29 | DropDownRows DropDownRowsText | |||||||
| 30 | DropDownMultiList DropDownMultiRows | |||||||
| 31 | ErrorText ErrorSymbol | |||||||
| 32 | LinkSpam | |||||||
| 33 | ||||||||
| 34 | create_inline_styles | |||||||
| 35 | demoroniser | |||||||
| 36 | process_html escape_html | |||||||
| 37 | ) ] ); | |||||||
| 38 | ||||||||
| 39 | @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } ); | |||||||
| 40 | @EXPORT = ( @{ $EXPORT_TAGS{'all'} } ); | |||||||
| 41 | ||||||||
| 42 | # ------------------------------------- | |||||||
| 43 | # Library Modules | |||||||
| 44 | ||||||||
| 45 | 8 | 8 | 3117 | use Encode::ZapCP1252; | ||||
| 8 | 65087 | |||||||
| 8 | 413 | |||||||
| 46 | 8 | 8 | 3314 | use HTML::Entities; | ||||
| 8 | 32061 | |||||||
| 8 | 526 | |||||||
| 47 | 8 | 8 | 3523 | use Regexp::Common qw /profanity/; | ||||
| 8 | 13389 | |||||||
| 8 | 24 | |||||||
| 48 | ||||||||
| 49 | 8 | 8 | 6331 | use Labyrinth::Audit; | ||||
| 8 | 9 | |||||||
| 8 | 947 | |||||||
| 50 | 8 | 8 | 3009 | use Labyrinth::Variables; | ||||
| 0 | ||||||||
| 0 | ||||||||
| 51 | ||||||||
| 52 | # ------------------------------------- | |||||||
| 53 | # Variables | |||||||
| 54 | ||||||||
| 55 | my $DEFAULTTAGS = 'p,a,br,b,strong,center,hr,ol,ul,li,i,img,u,em,strike,h1,h2,h3,h4,h5,h6,table,thead,tr,th,tbody,td,sup,address,pre'; | |||||||
| 56 | my ($HTMLTAGS,%HTMLTAGS); | |||||||
| 57 | ||||||||
| 58 | # ------------------------------------- | |||||||
| 59 | # The Public Interface Subs | |||||||
| 60 | ||||||||
| 61 | =head1 FUNCTIONS | |||||||
| 62 | ||||||||
| 63 | =head2 HTML Tag handling | |||||||
| 64 | ||||||||
| 65 | =over 4 | |||||||
| 66 | ||||||||
| 67 | =item LegalTag | |||||||
| 68 | ||||||||
| 69 | Returns TRUE or FALSE as to whether the given HTML tag is accepted by the | |||||||
| 70 | system. | |||||||
| 71 | ||||||||
| 72 | =item LegalTags | |||||||
| 73 | ||||||||
| 74 | Returns the list of HTML tags that are accepted by the system. | |||||||
| 75 | ||||||||
| 76 | =item CleanTags | |||||||
| 77 | ||||||||
| 78 | For a given text string, attempts to clean the use of any HTML tags. Any HTML | |||||||
| 79 | tags found that are not accepted by the system are encoded into HTML entities. | |||||||
| 80 | ||||||||
| 81 | =item CleanHTML | |||||||
| 82 | ||||||||
| 83 | For a given text string, removes all existence of any HTML tag. Mostly used in | |||||||
| 84 | input text box cleaning. | |||||||
| 85 | ||||||||
| 86 | =item SafeHTML | |||||||
| 87 | ||||||||
| 88 | For a given text string, encodes all HTML tags to HTML entities. Mostly used in | |||||||
| 89 | input textarea edit preparation. | |||||||
| 90 | ||||||||
| 91 | =item CleanLink | |||||||
| 92 | ||||||||
| 93 | Attempts to remove known spam style links. | |||||||
| 94 | ||||||||
| 95 | =item CleanWords | |||||||
| 96 | ||||||||
| 97 | Attempts to remove known profanity words. | |||||||
| 98 | ||||||||
| 99 | =item LinkTitles | |||||||
| 100 | ||||||||
| 101 | Given a XHTML snippet, will look for basic links and add title attributes. | |||||||
| 102 | Titles are of rhe format 'External Site: $domain', where $domain is the domain | |||||||
| 103 | used in the link. | |||||||
| 104 | ||||||||
| 105 | =back | |||||||
| 106 | ||||||||
| 107 | =cut | |||||||
| 108 | ||||||||
| 109 | sub LegalTag { | |||||||
| 110 | my $tag = lc shift; | |||||||
| 111 | ||||||||
| 112 | my %tags = _buildtags(); | |||||||
| 113 | return 1 if($tags{$tag}); | |||||||
| 114 | return 0; | |||||||
| 115 | } | |||||||
| 116 | ||||||||
| 117 | sub LegalTags { | |||||||
| 118 | my %tags = _buildtags(); | |||||||
| 119 | my $tags = join(", ", sort keys %tags); | |||||||
| 120 | $tags =~ s/, ([^,]+)$/ and $1/; | |||||||
| 121 | return $tags; | |||||||
| 122 | } | |||||||
| 123 | ||||||||
| 124 | sub CleanTags { | |||||||
| 125 | my $text = shift; | |||||||
| 126 | return '' unless($text); | |||||||
| 127 | ||||||||
| 128 | $text =~ s!?(span|tbody)[^>]*>!!sig; | |||||||
| 129 | $text =~ s!<(br|hr)>!<$1 />!sig; | |||||||
| 130 | $text =~ s! (?:\s| )+(?: )?<(table|p|ul|ol|div|pre)!<$1!sig; |
|||||||
| 131 | $text =~ s!\s+&\s+! & !sg; | |||||||
| 132 | $text =~ s!&[lr]squo;!"!mg; | |||||||
| 133 | $text =~ s{&(?!\#\d+;|[a-z0-9]+;)}{&}sig; | |||||||
| 134 | ||||||||
| 135 | # decode TinyMCE encodings | |||||||
| 136 | $text =~ s!<(.*?)>!<$1>!sig; | |||||||
| 137 | ||||||||
| 138 | # clean paragraphs | |||||||
| 139 | $text =~ s!\s+ ! !sig; |
|||||||
| 140 | $text =~ s!\s* \s*! !sig; |
|||||||
| 141 | ||||||||
| 142 | my %tags = _buildtags(); | |||||||
| 143 | my @found = ($text =~ m!?(\w+)(?:\s+[^>]*)?>!gm); | |||||||
| 144 | for my $tag (@found) { | |||||||
| 145 | $tag = lc $tag; | |||||||
| 146 | next if($tags{$tag}); | |||||||
| 147 | ||||||||
| 148 | $text =~ s!<(/?$tag(?:[^>]*)?)>!<$1>!igm; | |||||||
| 149 | $tags{$tag} = 1; | |||||||
| 150 | } | |||||||
| 151 | ||||||||
| 152 | process_html($text,0,1); | |||||||
| 153 | } | |||||||
| 154 | ||||||||
| 155 | sub CleanHTML { | |||||||
| 156 | my $text = shift; | |||||||
| 157 | return '' unless($text); | |||||||
| 158 | ||||||||
| 159 | $text =~ s!<[^>]+>!!gm; # remove any tags | |||||||
| 160 | $text =~ s!\s{2,}! !mg; | |||||||
| 161 | $text =~ s!&[lr]squo;!"!mg; | |||||||
| 162 | $text =~ s{&(?!\#\d+;|[a-z0-9]+;)}{&}sig; | |||||||
| 163 | ||||||||
| 164 | process_html($text,0,0); | |||||||
| 165 | } | |||||||
| 166 | ||||||||
| 167 | sub SafeHTML { | |||||||
| 168 | my $text = shift; | |||||||
| 169 | return '' unless($text); | |||||||
| 170 | ||||||||
| 171 | $text =~ s! | |||||||
| 172 | $text =~ s!>!>!gm; | |||||||
| 173 | $text =~ s!\s+&\s+! & !mg; | |||||||
| 174 | $text =~ s!&[lr]squo;!"!mg; | |||||||
| 175 | $text =~ s{&(?!\#\d+;|[a-z0-9]+;)}{&}sig; | |||||||
| 176 | ||||||||
| 177 | process_html($text,0,0); | |||||||
| 178 | } | |||||||
| 179 | ||||||||
| 180 | sub CleanLink { | |||||||
| 181 | my $text = shift; | |||||||
| 182 | return '' unless($text); | |||||||
| 183 | ||||||||
| 184 | # remove embedded script tags | |||||||
| 185 | $text =~ s! |
|||||||
| 186 | $text =~ s! | |||||||
| 187 | $text =~ s!.*/script>!!gis; # close, but on open, removed from te beginning of string | |||||||
| 188 | ||||||||
| 189 | # remove anything that looks like a link | |||||||
| 190 | $text =~ s!https?://[^\s]*!!gis; | |||||||
| 191 | $text =~ s! |
|||||||
| 192 | $text =~ s!\[url.*?url\]!!gis; | |||||||
| 193 | $text =~ s!\[link.*?link\]!!gis; | |||||||
| 194 | # $text =~ s!$settings{urlregex}!!gis; | |||||||
| 195 | ||||||||
| 196 | CleanTags($text); | |||||||
| 197 | } | |||||||
| 198 | ||||||||
| 199 | sub CleanWords { | |||||||
| 200 | my $text = shift; | |||||||
| 201 | ||||||||
| 202 | $text =~ s/$RE{profanity}//gis; | |||||||
| 203 | my $filter = join("|", map {$_->[1]} $dbi->GetQuery('array','AllBadWords')); | |||||||
| 204 | $text =~ s/$filter//gis; | |||||||
| 205 | ||||||||
| 206 | return $text; | |||||||
| 207 | } | |||||||
| 208 | ||||||||
| 209 | sub LinkTitles { | |||||||
| 210 | my $text = shift; | |||||||
| 211 | ||||||||
| 212 | for my $href ($text =~ m!()!g) { | |||||||
| 213 | my ($link1,$path,$link2) = ($href =~ m!(!); | |||||||
| 214 | $href =~ s!([\\\?\+\-\.()\[\]])!\\$1!sig; | |||||||
| 215 | ||||||||
| 216 | my $title; | |||||||
| 217 | $title ||= $settings{pathmap}{$path} if($settings{pathmap}{$path}); | |||||||
| 218 | $title ||= $settings{titlemap}{$link2} if($settings{titlemap}{$link2}); | |||||||
| 219 | $title ||= "External Site: $link2"; | |||||||
| 220 | $text =~ s!$href!$link1$path" title="$title">!sgi; | |||||||
| 221 | } | |||||||
| 222 | ||||||||
| 223 | return $text; | |||||||
| 224 | } | |||||||
| 225 | ||||||||
| 226 | sub _buildtags { | |||||||
| 227 | return %HTMLTAGS if(%HTMLTAGS); | |||||||
| 228 | ||||||||
| 229 | if(defined $settings{htmltags} && $settings{htmltags} =~ /^\+(.*)/) { | |||||||
| 230 | $settings{htmltags} = $1 . ',' . $DEFAULTTAGS; | |||||||
| 231 | } elsif(!$settings{htmltags}) { | |||||||
| 232 | $settings{htmltags} = $DEFAULTTAGS; | |||||||
| 233 | } | |||||||
| 234 | ||||||||
| 235 | %HTMLTAGS = map {$_ => 1} split(",",$settings{htmltags}); | |||||||
| 236 | return %HTMLTAGS; | |||||||
| 237 | } | |||||||
| 238 | ||||||||
| 239 | =head2 Drop Down Boxes | |||||||
| 240 | ||||||||
| 241 | =over 4 | |||||||
| 242 | ||||||||
| 243 | =item DropDownList($opt,$name,@items) | |||||||
| 244 | ||||||||
| 245 | Returns a dropdown selection box given a list of numbers. Can optionally pass | |||||||
| 246 | a option value to be pre-selected. The name of the form element is used as | |||||||
| 247 | both the element name and id. | |||||||
| 248 | ||||||||
| 249 | =item DropDownListText($opt,$name,@items) | |||||||
| 250 | ||||||||
| 251 | Returns a dropdown selection box given a list of strings. Can optionally pass | |||||||
| 252 | a option value to be pre-selected. The name of the form element is used as | |||||||
| 253 | both the element name and id. | |||||||
| 254 | ||||||||
| 255 | =item DropDownRows($opt,$name,$index,$value,@items) | |||||||
| 256 | ||||||||
| 257 | Returns a dropdown selection box given a list of rows. Can optionally pass | |||||||
| 258 | a option value to be pre-selected. The name of the form element is used as | |||||||
| 259 | both the element name and id. The 'index' and 'value' refence the field names | |||||||
| 260 | within each row hash. | |||||||
| 261 | ||||||||
| 262 | =item DropDownRowsText($opt,$name,$index,$value,@items) | |||||||
| 263 | ||||||||
| 264 | Returns a dropdown selection box given a list of strings. Can optionally pass | |||||||
| 265 | a option value to be pre-selected. The name of the form element is used as | |||||||
| 266 | both the element name and id. The 'index' and 'value' refence the field names | |||||||
| 267 | within each row hash. | |||||||
| 268 | ||||||||
| 269 | =item DropDownMultiList($opts,$name,$count,@items) | |||||||
| 270 | ||||||||
| 271 | Returns a dropdown multi-selection box given a list of strings. The name of the | |||||||
| 272 | form element is used as both the element name and id. The default number of | |||||||
| 273 | rows visible is 5, but this can be changed by providing a value for 'count'. | |||||||
| 274 | ||||||||
| 275 | Can optionally pass an option value to be pre-selected. The option can be a | |||||||
| 276 | comma separated list (as a single string) of values or an arrayref to a list | |||||||
| 277 | of values. | |||||||
| 278 | ||||||||
| 279 | =item DropDownMultiRows($opts,$name,$index,$value,$count,@items) | |||||||
| 280 | ||||||||
| 281 | Returns a dropdown multi-selection box given a list of rows. The name of the | |||||||
| 282 | form element is used as both the element name and id. The default number of | |||||||
| 283 | rows visible is 5, but this can be changed by providing a value for 'count'. | |||||||
| 284 | The 'index' and 'value' refence the field names within each row hash. | |||||||
| 285 | ||||||||
| 286 | Can optionally pass an option value to be pre-selected. The option can be a | |||||||
| 287 | comma separated list (as a single string) of values or an arrayref to a list | |||||||
| 288 | of values. | |||||||
| 289 | ||||||||
| 290 | =back | |||||||
| 291 | ||||||||
| 292 | =cut | |||||||
| 293 | ||||||||
| 294 | sub DropDownList { | |||||||
| 295 | my ($opt,$name,@items) = @_; | |||||||
| 296 | $opt = undef if(defined $opt && $opt !~ /^\d+$/); # opt must be a number | |||||||
| 297 | ||||||||
| 298 | return qq| | |||||||
| 299 | join("",(map { qq| | |||||||
| 300 | (defined $opt && $opt == $_ ? ' selected="selected"' : ''). | |||||||
| 301 | ">$_" } @items)) . | |||||||
| 302 | ""; | |||||||
| 303 | } | |||||||
| 304 | ||||||||
| 305 | sub DropDownListText { | |||||||
| 306 | my ($opt,$name,@items) = @_; | |||||||
| 307 | ||||||||
| 308 | return qq| | |||||||
| 309 | join("",(map { qq| | |||||||
| 310 | (defined $opt && $opt eq $_ ? ' selected="selected"' : ''). | |||||||
| 311 | ">$_" } @items)) . | |||||||
| 312 | ""; | |||||||
| 313 | } | |||||||
| 314 | ||||||||
| 315 | sub DropDownRows { | |||||||
| 316 | my ($opt,$name,$index,$value,@items) = @_; | |||||||
| 317 | $opt = undef if(defined $opt && $opt !~ /^\d+$/); # opt must be a number | |||||||
| 318 | ||||||||
| 319 | return qq| | |||||||
| 320 | join("",(map { qq| | |||||||
| 321 | (defined $opt && $opt == $_->{$index} ? ' selected="selected"' : ''). | |||||||
| 322 | ">$_->{$value}" } @items)) . | |||||||
| 323 | ""; | |||||||
| 324 | } | |||||||
| 325 | ||||||||
| 326 | sub DropDownRowsText { | |||||||
| 327 | my ($opt,$name,$index,$value,@items) = @_; | |||||||
| 328 | ||||||||
| 329 | return qq| | |||||||
| 330 | join("",(map { qq| | |||||||
| 331 | (defined $opt && $opt eq $_->{$index} ? ' selected="selected"' : ''). | |||||||
| 332 | ">$_->{$value}" } @items)) . | |||||||
| 333 | ""; | |||||||
| 334 | } | |||||||
| 335 | ||||||||
| 336 | sub DropDownMultiList { | |||||||
| 337 | my ($opts,$name,$count,@items) = @_; | |||||||
| 338 | my %opts; | |||||||
| 339 | ||||||||
| 340 | if(defined $opts) { | |||||||
| 341 | if(ref($opts) eq 'ARRAY') { | |||||||
| 342 | %opts = map {$_ => 1} @$opts; | |||||||
| 343 | } elsif($opts =~ /,/) { | |||||||
| 344 | %opts = map {$_ => 1} split(/,/,$opts); | |||||||
| 345 | } elsif($opts) { | |||||||
| 346 | %opts = ("$opts" => 1); | |||||||
| 347 | } | |||||||
| 348 | } | |||||||
| 349 | ||||||||
| 350 | return qq| | |||||||
| 351 | join("",(map { qq| | |||||||
| 352 | (defined $opts && $opts{$_} ? ' selected="selected"' : ''). | |||||||
| 353 | ">$_" } @items)) . | |||||||
| 354 | ""; | |||||||
| 355 | } | |||||||
| 356 | ||||||||
| 357 | sub DropDownMultiRows { | |||||||
| 358 | my ($opts,$name,$index,$value,$count,@items) = @_; | |||||||
| 359 | my %opts; | |||||||
| 360 | ||||||||
| 361 | if(defined $opts) { | |||||||
| 362 | if(ref($opts) eq 'ARRAY') { | |||||||
| 363 | %opts = map {$_ => 1} @$opts; | |||||||
| 364 | } elsif($opts =~ /,/) { | |||||||
| 365 | %opts = map {$_ => 1} split(/,/,$opts); | |||||||
| 366 | } elsif($opts) { | |||||||
| 367 | %opts = ("$opts" => 1); | |||||||
| 368 | } | |||||||
| 369 | } | |||||||
| 370 | ||||||||
| 371 | return qq| | |||||||
| 372 | join("",(map { qq| | |||||||
| 373 | (defined $opts && $opts{$_->{$index}} ? ' selected="selected"' : ''). | |||||||
| 374 | ">$_->{$value}" } @items)) . | |||||||
| 375 | ""; | |||||||
| 376 | } | |||||||
| 377 | ||||||||
| 378 | =head2 Error Functions | |||||||
| 379 | ||||||||
| 380 | =over 4 | |||||||
| 381 | ||||||||
| 382 | =item ErrorText | |||||||
| 383 | ||||||||
| 384 | Returns the given error string in a HTML span tag, with the configured error | |||||||
| 385 | class, which by default is called "alert". In your CSS sytle sheet you will | |||||||
| 386 | need to specify an appropriate class declaration, such as: | |||||||
| 387 | ||||||||
| 388 | .alert { color: red; font-weight: bold; } | |||||||
| 389 | ||||||||
| 390 | Set the value of 'errorclass' in your site config file to change the class | |||||||
| 391 | name used. | |||||||
| 392 | ||||||||
| 393 | =item ErrorSymbol | |||||||
| 394 | ||||||||
| 395 | Flags to the system that an error has occured and returns the configured error | |||||||
| 396 | symbol, which by is the 'empty' symbol '∅', which can then be used as the | |||||||
| 397 | error field indicator. | |||||||
| 398 | ||||||||
| 399 | Set the value of 'errorsymbol' in your site config file to change the symbol | |||||||
| 400 | used. | |||||||
| 401 | ||||||||
| 402 | =back | |||||||
| 403 | ||||||||
| 404 | =cut | |||||||
| 405 | ||||||||
| 406 | sub ErrorText { | |||||||
| 407 | my $text = shift; | |||||||
| 408 | $settings{errorclass} ||= 'alert'; | |||||||
| 409 | return qq!$text!; | |||||||
| 410 | } | |||||||
| 411 | ||||||||
| 412 | sub ErrorSymbol { | |||||||
| 413 | $tvars{errmess} = 1; | |||||||
| 414 | $tvars{errcode} = 'ERROR'; | |||||||
| 415 | return $settings{errorsymbol} || '∅'; | |||||||
| 416 | } | |||||||
| 417 | ||||||||
| 418 | =head2 Protection Functions | |||||||
| 419 | ||||||||
| 420 | =over 4 | |||||||
| 421 | ||||||||
| 422 | =item LinkSpam | |||||||
| 423 | ||||||||
| 424 | Checks whether any links exist in the given text that could indicate comment spam. | |||||||
| 425 | ||||||||
| 426 | =back | |||||||
| 427 | ||||||||
| 428 | =cut | |||||||
| 429 | ||||||||
| 430 | sub LinkSpam { | |||||||
| 431 | my $text = shift; | |||||||
| 432 | return 1 if($text =~ m!https?://[^\s]*!is); | |||||||
| 433 | return 1 if($text =~ m! |
|||||||
| 434 | return 1 if($text =~ m!\[url.*?url\]!is); | |||||||
| 435 | return 1 if($text =~ m!\[link.*?link\]!is); | |||||||
| 436 | return 1 if($text =~ m!$settings{urlregex}!is); | |||||||
| 437 | return 0; | |||||||
| 438 | } | |||||||
| 439 | ||||||||
| 440 | =head2 CSS Handling Code | |||||||
| 441 | ||||||||
| 442 | =over 4 | |||||||
| 443 | ||||||||
| 444 | =item create_inline_styles ( HASHREF ) | |||||||
| 445 | ||||||||
| 446 | Create inline CSS style sheet block. Key value pairs should match the label | |||||||
| 447 | (tag, identifier or class patterns) and its contents. For example: | |||||||
| 448 | ||||||||
| 449 | my %css = ( '#label p' => 'font-weight: normal; color: #fff;' ); | |||||||
| 450 | ||||||||
| 451 | or | |||||||
| 452 | ||||||||
| 453 | my %css = ( '#label p' => { 'font-weight' => 'normal', 'color' => '#fff' } ); | |||||||
| 454 | ||||||||
| 455 | ||||||||
| 456 | The exception to this is the label 'media', which can be used to specify the | |||||||
| 457 | medium for which the CSS will be used. Typically these are 'screen' or 'print'. | |||||||
| 458 | ||||||||
| 459 | =back | |||||||
| 460 | ||||||||
| 461 | =cut | |||||||
| 462 | ||||||||
| 463 | sub create_inline_styles { | |||||||
| 464 | my $hash = shift || return; | |||||||
| 465 | my $media = $hash->{media} ? ' media="' . $hash->{media} . '"' : ''; | |||||||
| 466 | ||||||||
| 467 | my $text = qq|\n|; | |||||||
| 483 | return $text; | |||||||
| 484 | } | |||||||
| 485 | ||||||||
| 486 | =head2 HTML Demoroniser Code | |||||||
| 487 | ||||||||
| 488 | =over 4 | |||||||
| 489 | ||||||||
| 490 | =item demoroniser ( INPUT ) | |||||||
| 491 | ||||||||
| 492 | Given a string, will replace the Microsoft "smart" characters with sensible | |||||||
| 493 | ACSII versions. | |||||||
| 494 | ||||||||
| 495 | =back | |||||||
| 496 | ||||||||
| 497 | =cut | |||||||
| 498 | ||||||||
| 499 | sub demoroniser { | |||||||
| 500 | my $str = shift; | |||||||
| 501 | ||||||||
| 502 | zap_cp1252($str); | |||||||
| 503 | ||||||||
| 504 | $str =~ s/\xE2\x80\x9A/,/g; # 82 | |||||||
| 505 | $str =~ s/\xE2\x80\x9E/,,/g; # 84 | |||||||
| 506 | $str =~ s/\xE2\x80\xA6/.../g; # 85 | |||||||
| 507 | ||||||||
| 508 | $str =~ s/\xCB\x86/^/g; # 88 | |||||||
| 509 | ||||||||
| 510 | $str =~ s/\xE2\x80\x98/`/g; # 91 | |||||||
| 511 | $str =~ s/\xE2\x80\x99/'/g; # 92 | |||||||
| 512 | $str =~ s/\xE2\x80\x9C/"/g; # 93 | |||||||
| 513 | $str =~ s/\xE2\x80\x9D/"/g; # 94 | |||||||
| 514 | $str =~ s/\xE2\x80\xA2/*/g; # 95 | |||||||
| 515 | $str =~ s/\xE2\x80\x93/-/g; # 96 | |||||||
| 516 | $str =~ s/\xE2\x80\x94/-/g; # 97 | |||||||
| 517 | ||||||||
| 518 | $str =~ s/\xE2\x80\xB9/ | |||||||
| 519 | $str =~ s/\xE2\x80\xBA/>/g; # 9B | |||||||
| 520 | ||||||||
| 521 | return $str; | |||||||
| 522 | } | |||||||
| 523 | ||||||||
| 524 | =head2 HTML Handling Code | |||||||
| 525 | ||||||||
| 526 | The following functions disassemble and reassemble the HTML code snippets, | |||||||
| 527 | validating and cleaning the code to fix any errors that may exist between the | |||||||
| 528 | template and content of the database. | |||||||
| 529 | ||||||||
| 530 | =over 4 | |||||||
| 531 | ||||||||
| 532 | =item process_html ( INPUT [,LINE_BREAKS [,ALLOW]] ) | |||||||
| 533 | ||||||||
| 534 | =item escape_html ( INPUT ) | |||||||
| 535 | ||||||||
| 536 | =item unescape_html ( INPUT ) | |||||||
| 537 | ||||||||
| 538 | =item cleanup_attr_style | |||||||
| 539 | ||||||||
| 540 | =item cleanup_attr_number | |||||||
| 541 | ||||||||
| 542 | =item cleanup_attr_multilength | |||||||
| 543 | ||||||||
| 544 | =item cleanup_attr_text | |||||||
| 545 | ||||||||
| 546 | =item cleanup_attr_length | |||||||
| 547 | ||||||||
| 548 | =item cleanup_attr_color | |||||||
| 549 | ||||||||
| 550 | =item cleanup_attr_uri | |||||||
| 551 | ||||||||
| 552 | =item cleanup_attr_tframe | |||||||
| 553 | ||||||||
| 554 | =item cleanup_attr_trules | |||||||
| 555 | ||||||||
| 556 | =item cleanup_html | |||||||
| 557 | ||||||||
| 558 | =item cleanup_tag | |||||||
| 559 | ||||||||
| 560 | =item cleanup_close | |||||||
| 561 | ||||||||
| 562 | =item cleanup_cdata | |||||||
| 563 | ||||||||
| 564 | =item cleanup_no_number | |||||||
| 565 | ||||||||
| 566 | =item check_url_valid | |||||||
| 567 | ||||||||
| 568 | =item cleanup_attr_inputtype | |||||||
| 569 | ||||||||
| 570 | =item cleanup_attr_method | |||||||
| 571 | ||||||||
| 572 | =item cleanup_attr_scriptlang | |||||||
| 573 | ||||||||
| 574 | =item cleanup_attr_scripttype | |||||||
| 575 | ||||||||
| 576 | =item strip_nonprintable | |||||||
| 577 | ||||||||
| 578 | =back | |||||||
| 579 | ||||||||
| 580 | =cut | |||||||
| 581 | ||||||||
| 582 | # Configuration | |||||||
| 583 | my $allow_html = 0; | |||||||
| 584 | my $line_breaks = 1; | |||||||
| 585 | # End configuration | |||||||
| 586 | ||||||||
| 587 | ################################################################## | |||||||
| 588 | # | |||||||
| 589 | # HTML handling code | |||||||
| 590 | # | |||||||
| 591 | # The code below provides some functions for manipulating HTML. | |||||||
| 592 | # | |||||||
| 593 | # process_html ( INPUT [,LINE_BREAKS [,ALLOW]] ) | |||||||
| 594 | # | |||||||
| 595 | # Returns a modified version of the HTML string INPUT, with | |||||||
| 596 | # any potentially malicious HTML constructs (such as java, | |||||||
| 597 | # javascript and IMG tags) removed. | |||||||
| 598 | # | |||||||
| 599 | # If the LINE_BREAKS parameter is present and true then | |||||||
| 600 | # line breaks in the input will be converted to html |
|||||||
| 601 | # tags in the output. | |||||||
| 602 | # | |||||||
| 603 | # If the ALLOW parameter is present and true then most | |||||||
| 604 | # harmless tags will be left in, otherwise all tags will be | |||||||
| 605 | # removed. | |||||||
| 606 | # | |||||||
| 607 | # escape_html ( INPUT ) | |||||||
| 608 | # | |||||||
| 609 | # Returns a copy of the string INPUT with any HTML | |||||||
| 610 | # metacharacters replaced with character escapes. | |||||||
| 611 | # | |||||||
| 612 | # unescape_html ( INPUT ) | |||||||
| 613 | # | |||||||
| 614 | # Returns a copy of the string INPUT with HTML character | |||||||
| 615 | # entities converted to literal characters where possible. | |||||||
| 616 | # Note that some entites have no 8-bit character equivalent, | |||||||
| 617 | # see "http://www.w3.org/TR/xhtml1/DTD/xhtml-symbol.ent" | |||||||
| 618 | # for some examples. unescape_html() leaves these entities | |||||||
| 619 | # in their encoded form. | |||||||
| 620 | # | |||||||
| 621 | ||||||||
| 622 | use vars qw(%html_entities $html_safe_chars %escape_html_map $escape_html_map); | |||||||
| 623 | use vars qw(%safe_tags %safe_style %tag_is_empty %closetag_is_optional | |||||||
| 624 | %closetag_is_dependent %force_closetag %transpose_tag | |||||||
| 625 | $convert_nl %auto_deinterleave $auto_deinterleave_pattern); | |||||||
| 626 | ||||||||
| 627 | # check the validity of a URL. | |||||||
| 628 | ||||||||
| 629 | sub process_html { | |||||||
| 630 | my ($text, $line_breaks, $allow_html) = @_; | |||||||
| 631 | ||||||||
| 632 | # cleanup erroneous XHTML patterns | |||||||
| 633 | if($text) { | |||||||
| 634 | $text =~ s!! |
|||||||
| 635 | $text =~ s!
!
|
|||||||
| 636 | $text =~ s! \s*!!gsi; |
|||||||
| 637 | $text =~ s!
|
|||||||
| 638 | $text =~ s!
|
|||||||
| 639 | } | |||||||
| 640 | ||||||||
| 641 | # clean text of any nasties | |||||||
| 642 | #$text =~ s/[\x201A\x2018\x2019`]/'/g; # nasty single quotes | |||||||
| 643 | #$text =~ s/[\x201E\x201C\x201D]/"/g; # nasty double quotes | |||||||
| 644 | ||||||||
| 645 | cleanup_html( $text, $line_breaks, ($allow_html ? \%safe_tags : {})); | |||||||
| 646 | } | |||||||
| 647 | ||||||||
| 648 | BEGIN | |||||||
| 649 | { | |||||||
| 650 | %html_entities = ( | |||||||
| 651 | 'lt' => '<', | |||||||
| 652 | 'gt' => '>', | |||||||
| 653 | 'quot' => '"', | |||||||
| 654 | 'amp' => '&', | |||||||
| 655 | ||||||||
| 656 | 'nbsp' => "\240", 'iexcl' => "\241", | |||||||
| 657 | 'cent' => "\242", 'pound' => "\243", | |||||||
| 658 | 'curren' => "\244", 'yen' => "\245", | |||||||
| 659 | 'brvbar' => "\246", 'sect' => "\247", | |||||||
| 660 | 'uml' => "\250", 'copy' => "\251", | |||||||
| 661 | 'ordf' => "\252", 'laquo' => "\253", | |||||||
| 662 | 'not' => "\254", 'shy' => "\255", | |||||||
| 663 | 'reg' => "\256", 'macr' => "\257", | |||||||
| 664 | 'deg' => "\260", 'plusmn' => "\261", | |||||||
| 665 | 'sup2' => "\262", 'sup3' => "\263", | |||||||
| 666 | 'acute' => "\264", 'micro' => "\265", | |||||||
| 667 | 'para' => "\266", 'middot' => "\267", | |||||||
| 668 | 'cedil' => "\270", 'supl' => "\271", | |||||||
| 669 | 'ordm' => "\272", 'raquo' => "\273", | |||||||
| 670 | 'frac14' => "\274", 'frac12' => "\275", | |||||||
| 671 | 'frac34' => "\276", 'iquest' => "\277", | |||||||
| 672 | ||||||||
| 673 | 'Agrave' => "\300", 'Aacute' => "\301", | |||||||
| 674 | 'Acirc' => "\302", 'Atilde' => "\303", | |||||||
| 675 | 'Auml' => "\304", 'Aring' => "\305", | |||||||
| 676 | 'AElig' => "\306", 'Ccedil' => "\307", | |||||||
| 677 | 'Egrave' => "\310", 'Eacute' => "\311", | |||||||
| 678 | 'Ecirc' => "\312", 'Euml' => "\313", | |||||||
| 679 | 'Igrave' => "\314", 'Iacute' => "\315", | |||||||
| 680 | 'Icirc' => "\316", 'Iuml' => "\317", | |||||||
| 681 | 'ETH' => "\320", 'Ntilde' => "\321", | |||||||
| 682 | 'Ograve' => "\322", 'Oacute' => "\323", | |||||||
| 683 | 'Ocirc' => "\324", 'Otilde' => "\325", | |||||||
| 684 | 'Ouml' => "\326", 'times' => "\327", | |||||||
| 685 | 'Oslash' => "\330", 'Ugrave' => "\331", | |||||||
| 686 | 'Uacute' => "\332", 'Ucirc' => "\333", | |||||||
| 687 | 'Uuml' => "\334", 'Yacute' => "\335", | |||||||
| 688 | 'THORN' => "\336", 'szlig' => "\337", | |||||||
| 689 | ||||||||
| 690 | 'agrave' => "\340", 'aacute' => "\341", | |||||||
| 691 | 'acirc' => "\342", 'atilde' => "\343", | |||||||
| 692 | 'auml' => "\344", 'aring' => "\345", | |||||||
| 693 | 'aelig' => "\346", 'ccedil' => "\347", | |||||||
| 694 | 'egrave' => "\350", 'eacute' => "\351", | |||||||
| 695 | 'ecirc' => "\352", 'euml' => "\353", | |||||||
| 696 | 'igrave' => "\354", 'iacute' => "\355", | |||||||
| 697 | 'icirc' => "\356", 'iuml' => "\357", | |||||||
| 698 | 'eth' => "\360", 'ntilde' => "\361", | |||||||
| 699 | 'ograve' => "\362", 'oacute' => "\363", | |||||||
| 700 | 'ocirc' => "\364", 'otilde' => "\365", | |||||||
| 701 | 'ouml' => "\366", 'divide' => "\367", | |||||||
| 702 | 'oslash' => "\370", 'ugrave' => "\371", | |||||||
| 703 | 'uacute' => "\372", 'ucirc' => "\373", | |||||||
| 704 | 'uuml' => "\374", 'yacute' => "\375", | |||||||
| 705 | 'thorn' => "\376", 'yuml' => "\377", | |||||||
| 706 | ); | |||||||
| 707 | ||||||||
| 708 | # | |||||||
| 709 | # Build a map for representing characters in HTML. | |||||||
| 710 | # | |||||||
| 711 | $html_safe_chars = '()[]{}/?.,\\|;:@#~=+-_*^%$! ' . "\'\r\n\t"; | |||||||
| 712 | $escape_html_map = qr{[\w\(\)\[\]\{\}\/\?\.\,\\\|;:\@#~=\+\-\*\^\%\$\!\s\']+}; | |||||||
| 713 | %escape_html_map = | |||||||
| 714 | map {$_,$_} ( 'A'..'Z', 'a'..'z', '0'..'9', | |||||||
| 715 | split(//, $html_safe_chars) | |||||||
| 716 | ); | |||||||
| 717 | foreach my $ent (keys %html_entities) { | |||||||
| 718 | $escape_html_map{$html_entities{$ent}} = "&$ent;"; | |||||||
| 719 | } | |||||||
| 720 | foreach my $c (0..255) { | |||||||
| 721 | unless ( exists $escape_html_map{chr $c} ) { | |||||||
| 722 | $escape_html_map{chr $c} = sprintf '%d;', $c; | |||||||
| 723 | } | |||||||
| 724 | } | |||||||
| 725 | ||||||||
| 726 | # | |||||||
| 727 | # Tables for use by cleanup_html() (below). | |||||||
| 728 | # | |||||||
| 729 | # The main table is %safe_tags, which is a hash by tag name of | |||||||
| 730 | # all the tags that it's safe to leave in. The value for each | |||||||
| 731 | # tag is another hash, and each key of that hash defines an | |||||||
| 732 | # attribute that the tag is allowed to have. | |||||||
| 733 | # | |||||||
| 734 | # The values in the tag attribute hash can be undef (for an | |||||||
| 735 | # attribute that takes no value, for example the nowrap | |||||||
| 736 | # attribute in the tag | ) or they can | ||||||
| 737 | # be coderefs pointing to subs for cleaning up the attribute | |||||||
| 738 | # values. | |||||||
| 739 | # | |||||||
| 740 | # These subs will called with the attribute value in $_, and | |||||||
| 741 | # they can return either a cleaned attribute value or undef. | |||||||
| 742 | # If undef is returned then the attribute will be deleted | |||||||
| 743 | # from the tag. | |||||||
| 744 | # | |||||||
| 745 | # The list of tags and attributes was taken from | |||||||
| 746 | # "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd" | |||||||
| 747 | # | |||||||
| 748 | # The %tag_is_empty table defines the set of tags that have | |||||||
| 749 | # no corresponding close tag. | |||||||
| 750 | # | |||||||
| 751 | # cleanup_html() moves close tags around to force all tags to | |||||||
| 752 | # be closed in the correct sequence. For example, the text | |||||||
| 753 | # "foobar" will be converted to the text |
|||||||
| 754 | # "foobar". |
|||||||
| 755 | # | |||||||
| 756 | # The %auto_deinterleave table defines the set of tags which | |||||||
| 757 | # should be automatically reopened if they're closed early | |||||||
| 758 | # in this way. All the tags involved must be in | |||||||
| 759 | # %auto_deinterleave for the tag to be reopened. For example, | |||||||
| 760 | # the text "bbbiii" will be converted into the | |||||||
| 761 | # text "bbbiii" rather than into the | |||||||
| 762 | # text "bbbiii", because *both* "b" and "i" are | |||||||
| 763 | # in %auto_deinterleave. | |||||||
| 764 | # | |||||||
| 765 | %tag_is_empty = ( | |||||||
| 766 | 'hr' => 1, 'link' => 1, 'param' => 1, 'img' => 1, | |||||||
| 767 | 'br' => 1, 'area' => 1, 'input' => 1, 'basefont' => 1 | |||||||
| 768 | ); | |||||||
| 769 | %closetag_is_optional = ( ); | |||||||
| 770 | %closetag_is_dependent = ( ); | |||||||
| 771 | %force_closetag = ( | |||||||
| 772 | 'pre' => { 'p' => 1, 'h1' => 1, 'h2' => 1, 'h3' => 1, 'h4' => 1, 'h5' => 1, 'h6' => 1, 'pre' => 1, 'ul' => 1, 'ol' => 1 }, | |||||||
| 773 | 'p' => { 'p' => 1, 'h1' => 1, 'h2' => 1, 'h3' => 1, 'h4' => 1, 'h5' => 1, 'h6' => 1, 'pre' => 1, 'ul' => 1, 'ol' => 1 }, | |||||||
| 774 | 'h1' => { 'p' => 1, 'h1' => 1, 'h2' => 1, 'h3' => 1, 'h4' => 1, 'h5' => 1, 'h6' => 1, 'pre' => 1, 'ul' => 1, 'ol' => 1 }, | |||||||
| 775 | 'h2' => { 'p' => 1, 'h1' => 1, 'h2' => 1, 'h3' => 1, 'h4' => 1, 'h5' => 1, 'h6' => 1, 'pre' => 1, 'ul' => 1, 'ol' => 1 }, | |||||||
| 776 | 'h3' => { 'p' => 1, 'h1' => 1, 'h2' => 1, 'h3' => 1, 'h4' => 1, 'h5' => 1, 'h6' => 1, 'pre' => 1, 'ul' => 1, 'ol' => 1 }, | |||||||
| 777 | 'h4' => { 'p' => 1, 'h1' => 1, 'h2' => 1, 'h3' => 1, 'h4' => 1, 'h5' => 1, 'h6' => 1, 'pre' => 1, 'ul' => 1, 'ol' => 1 }, | |||||||
| 778 | 'h5' => { 'p' => 1, 'h1' => 1, 'h2' => 1, 'h3' => 1, 'h4' => 1, 'h5' => 1, 'h6' => 1, 'pre' => 1, 'ul' => 1, 'ol' => 1 }, | |||||||
| 779 | 'h6' => { 'p' => 1, 'h1' => 1, 'h2' => 1, 'h3' => 1, 'h4' => 1, 'h5' => 1, 'h6' => 1, 'pre' => 1, 'ul' => 1, 'ol' => 1 }, | |||||||
| 780 | 'table' => { 'p' => 1, 'h1' => 1, 'h2' => 1, 'h3' => 1, 'h4' => 1, 'h5' => 1, 'h6' => 1, 'pre' => 1, 'ul' => 1, 'ol' => 1 }, | |||||||
| 781 | 'ul' => { 'p' => 1, 'h1' => 1, 'h2' => 1, 'h3' => 1, 'h4' => 1, 'h5' => 1, 'h6' => 1 }, | |||||||
| 782 | 'ol' => { 'p' => 1, 'h1' => 1, 'h2' => 1, 'h3' => 1, 'h4' => 1, 'h5' => 1, 'h6' => 1 }, | |||||||
| 783 | 'li' => { 'p' => 1, 'h1' => 1, 'h2' => 1, 'h3' => 1, 'h4' => 1, 'h5' => 1, 'h6' => 1, 'li' => 1 }, | |||||||
| 784 | 'form' => { 'p' => 1, 'h1' => 1, 'h2' => 1, 'h3' => 1, 'h4' => 1, 'h5' => 1, 'h6' => 1 }, | |||||||
| 785 | ); | |||||||
| 786 | %transpose_tag = ( 'b' => 'strong', 'u' => 'em' ); | |||||||
| 787 | %auto_deinterleave = map {$_,1} qw( | |||||||
| 788 | tt i b big small u s strike font basefont | |||||||
| 789 | em strong dfn code q sub sup samp kbd var | |||||||
| 790 | cite abbr acronym span | |||||||
| 791 | ); | |||||||
| 792 | $auto_deinterleave_pattern = join '|', keys %auto_deinterleave; | |||||||
| 793 | my %attr = ( | |||||||
| 794 | 'style' => \&cleanup_attr_style, | |||||||
| 795 | 'name' => \&cleanup_attr_text, | |||||||
| 796 | 'id' => \&cleanup_attr_text, | |||||||
| 797 | 'class' => \&cleanup_attr_text, | |||||||
| 798 | 'title' => \&cleanup_attr_text, | |||||||
| 799 | 'onmouseover' => \&cleanup_attr_text, | |||||||
| 800 | 'onmouseout' => \&cleanup_attr_text, | |||||||
| 801 | 'onclick' => \&cleanup_attr_text, | |||||||
| 802 | 'onfocus' => \&cleanup_attr_text, | |||||||
| 803 | 'ondblclick' => \&cleanup_attr_text, | |||||||
| 804 | ); | |||||||
| 805 | my %font_attr = ( | |||||||
| 806 | %attr, | |||||||
| 807 | size => sub { /^([-+]?\d{1,3})$/ ? $1 : undef }, | |||||||
| 808 | face => sub { /^([\w\-, ]{2,100})$/ ? $1 : undef }, | |||||||
| 809 | color => \&cleanup_attr_color, | |||||||
| 810 | ); | |||||||
| 811 | my %insdel_attr = ( | |||||||
| 812 | %attr, | |||||||
| 813 | 'cite' => \&cleanup_attr_uri, | |||||||
| 814 | 'datetime' => \&cleanup_attr_text, | |||||||
| 815 | ); | |||||||
| 816 | my %texta_attr = ( | |||||||
| 817 | %attr, | |||||||
| 818 | align => sub { s/middle/center/i; | |||||||
| 819 | /^(left|center|right|justify)$/i ? lc $1 : undef | |||||||
| 820 | }, | |||||||
| 821 | ); | |||||||
| 822 | my %cellha_attr = ( | |||||||
| 823 | align => sub { s/middle/center/i; | |||||||
| 824 | /^(left|center|right|justify|char)$/i | |||||||
| 825 | ? lc $1 : undef | |||||||
| 826 | }, | |||||||
| 827 | char => sub { /^([\w\-])$/ ? $1 : undef }, | |||||||
| 828 | charoff => \&cleanup_attr_length, | |||||||
| 829 | ); | |||||||
| 830 | my %cellva_attr = ( | |||||||
| 831 | valign => sub { s/center/middle/i; | |||||||
| 832 | /^(top|middle|bottom|baseline)$/i ? lc $1 : undef | |||||||
| 833 | }, | |||||||
| 834 | ); | |||||||
| 835 | my %cellhv_attr = ( %attr, %cellha_attr, %cellva_attr ); | |||||||
| 836 | my %col_attr = ( | |||||||
| 837 | %attr, | |||||||
| 838 | width => \&cleanup_attr_multilength, | |||||||
| 839 | span => \&cleanup_attr_number, | |||||||
| 840 | %cellhv_attr, | |||||||
| 841 | ); | |||||||
| 842 | my %thtd_attr = ( | |||||||
| 843 | %attr, | |||||||
| 844 | abbr => \&cleanup_attr_text, | |||||||
| 845 | axis => \&cleanup_attr_text, | |||||||
| 846 | headers => \&cleanup_attr_text, | |||||||
| 847 | scope => sub { /^(row|col|rowgroup|colgroup)$/i ? lc $1 : undef }, | |||||||
| 848 | rowspan => \&cleanup_attr_number, | |||||||
| 849 | colspan => \&cleanup_attr_number, | |||||||
| 850 | %cellhv_attr, | |||||||
| 851 | nowrap => undef, | |||||||
| 852 | bgcolor => \&cleanup_attr_color, | |||||||
| 853 | width => \&cleanup_attr_number, | |||||||
| 854 | height => \&cleanup_attr_number, | |||||||
| 855 | ); | |||||||
| 856 | my $none = {}; | |||||||
| 857 | %safe_tags = ( | |||||||
| 858 | # FORM CONTROLS | |||||||
| 859 | 'form' => { %attr, | |||||||
| 860 | 'method' => \&cleanup_attr_method, | |||||||
| 861 | 'action' => \&cleanup_attr_text, | |||||||
| 862 | 'enctype' => \&cleanup_attr_text, | |||||||
| 863 | 'onsubmit' => \&cleanup_attr_text, | |||||||
| 864 | }, | |||||||
| 865 | 'button' => { %attr, | |||||||
| 866 | 'type' => \&cleanup_attr_inputtype, | |||||||
| 867 | }, | |||||||
| 868 | 'input' => { %attr, | |||||||
| 869 | 'type' => \&cleanup_attr_inputtype, | |||||||
| 870 | 'size' => \&cleanup_attr_number, | |||||||
| 871 | 'maxlength' => \&cleanup_attr_number, | |||||||
| 872 | 'value' => \&cleanup_attr_text, | |||||||
| 873 | 'checked' => \&cleanup_attr_text, | |||||||
| 874 | 'readonly' => \&cleanup_attr_text, | |||||||
| 875 | 'disabled' => \&cleanup_attr_text, | |||||||
| 876 | 'src' => \&cleanup_attr_uri, | |||||||
| 877 | 'width' => \&cleanup_attr_length, | |||||||
| 878 | 'height' => \&cleanup_attr_length, | |||||||
| 879 | 'alt' => \&cleanup_attr_text, | |||||||
| 880 | 'onchange' => \&cleanup_attr_text, | |||||||
| 881 | }, | |||||||
| 882 | 'select' => { %attr, | |||||||
| 883 | 'size' => \&cleanup_attr_number, | |||||||
| 884 | 'title' => \&cleanup_attr_text, | |||||||
| 885 | 'value' => \&cleanup_attr_text, | |||||||
| 886 | 'multiple' => \&cleanup_attr_text, | |||||||
| 887 | 'disabled' => \&cleanup_attr_text, | |||||||
| 888 | 'onchange' => \&cleanup_attr_text, | |||||||
| 889 | }, | |||||||
| 890 | 'option' => { %attr, | |||||||
| 891 | 'value' => \&cleanup_attr_text, | |||||||
| 892 | 'selected' => \&cleanup_attr_text, | |||||||
| 893 | }, | |||||||
| 894 | 'textarea' => { %attr, | |||||||
| 895 | 'rows' => \&cleanup_attr_number, | |||||||
| 896 | 'cols' => \&cleanup_attr_number, | |||||||
| 897 | }, | |||||||
| 898 | 'label' => { %attr, | |||||||
| 899 | 'for' => \&cleanup_attr_text, | |||||||
| 900 | }, | |||||||
| 901 | ||||||||
| 902 | # LAYOUT STYLE | |||||||
| 903 | 'style' => { | |||||||
| 904 | 'type' => \&cleanup_attr_text, | |||||||
| 905 | }, | |||||||
| 906 | 'br' => { 'clear' => sub { /^(left|right|all|none)$/i ? lc $1 : undef } | |||||||
| 907 | }, | |||||||
| 908 | 'hr' => \%attr, | |||||||
| 909 | 'em' => \%attr, | |||||||
| 910 | 'strong' => \%attr, | |||||||
| 911 | 'dfn' => \%attr, | |||||||
| 912 | 'code' => \%attr, | |||||||
| 913 | 'samp' => \%attr, | |||||||
| 914 | 'kbd' => \%attr, | |||||||
| 915 | 'var' => \%attr, | |||||||
| 916 | 'cite' => \%attr, | |||||||
| 917 | 'abbr' => \%attr, | |||||||
| 918 | 'acronym' => \%attr, | |||||||
| 919 | 'q' => { %attr, 'cite' => \&cleanup_attr_uri }, | |||||||
| 920 | 'blockquote' => { %attr, 'cite' => \&cleanup_attr_uri }, | |||||||
| 921 | 'sub' => \%attr, | |||||||
| 922 | 'sup' => \%attr, | |||||||
| 923 | 'tt' => \%attr, | |||||||
| 924 | 'i' => \%attr, | |||||||
| 925 | 'b' => \%attr, | |||||||
| 926 | 'big' => \%attr, | |||||||
| 927 | 'small' => \%attr, | |||||||
| 928 | 'u' => \%attr, | |||||||
| 929 | 's' => \%attr, | |||||||
| 930 | 'font' => \%font_attr, | |||||||
| 931 | 'h1' => \%texta_attr, | |||||||
| 932 | 'h2' => \%texta_attr, | |||||||
| 933 | 'h3' => \%texta_attr, | |||||||
| 934 | 'h4' => \%texta_attr, | |||||||
| 935 | 'h5' => \%texta_attr, | |||||||
| 936 | 'h6' => \%texta_attr, | |||||||
| 937 | 'p' => \%texta_attr, | |||||||
| 938 | 'div' => \%texta_attr, | |||||||
| 939 | 'span' => \%texta_attr, | |||||||
| 940 | 'ul' => { %attr, | |||||||
| 941 | 'type' => sub { /^(disc|square|circle)$/i ? lc $1 : undef }, | |||||||
| 942 | 'compact' => undef, | |||||||
| 943 | }, | |||||||
| 944 | 'ol' => { %attr, | |||||||
| 945 | 'type' => \&cleanup_attr_text, | |||||||
| 946 | 'compact' => undef, | |||||||
| 947 | 'start' => \&cleanup_attr_number, | |||||||
| 948 | }, | |||||||
| 949 | 'li' => { %attr, | |||||||
| 950 | 'type' => \&cleanup_attr_text, | |||||||
| 951 | 'value' => \&cleanup_no_number, | |||||||
| 952 | }, | |||||||
| 953 | 'dl' => { %attr, 'compact' => undef }, | |||||||
| 954 | 'dt' => \%attr, | |||||||
| 955 | 'dd' => \%attr, | |||||||
| 956 | 'address' => \%attr, | |||||||
| 957 | 'pre' => { %attr, 'width' => \&cleanup_attr_number }, | |||||||
| 958 | 'center' => \%attr, | |||||||
| 959 | 'nobr' => $none, | |||||||
| 960 | ||||||||
| 961 | # FUNCTIONAL TAGS | |||||||
| 962 | 'iframe' => { %attr, | |||||||
| 963 | 'src' => \&cleanup_attr_uri, | |||||||
| 964 | 'width' => \&cleanup_attr_length, | |||||||
| 965 | 'height' => \&cleanup_attr_length, | |||||||
| 966 | 'border' => \&cleanup_attr_number, | |||||||
| 967 | 'alt' => \&cleanup_attr_text, | |||||||
| 968 | 'align' => sub { s/middle/center/i; | |||||||
| 969 | /^(left|center|right)$/i ? lc $1 : undef | |||||||
| 970 | }, | |||||||
| 971 | 'title' => \&cleanup_attr_text, | |||||||
| 972 | }, | |||||||
| 973 | 'img' => { %attr, | |||||||
| 974 | 'src' => \&cleanup_attr_uri, | |||||||
| 975 | 'width' => \&cleanup_attr_length, | |||||||
| 976 | 'height' => \&cleanup_attr_length, | |||||||
| 977 | 'border' => \&cleanup_attr_number, | |||||||
| 978 | 'alt' => \&cleanup_attr_text, | |||||||
| 979 | 'align' => sub { s/middle/center/i; | |||||||
| 980 | /^(left|center|right)$/i ? lc $1 : undef | |||||||
| 981 | }, | |||||||
| 982 | 'title' => \&cleanup_attr_text, | |||||||
| 983 | 'usemap' => \&cleanup_attr_text, | |||||||
| 984 | }, | |||||||
| 985 | 'map' => { %attr, | |||||||
| 986 | }, | |||||||
| 987 | 'area' => { %attr, | |||||||
| 988 | 'shape' => \&cleanup_attr_text, | |||||||
| 989 | 'coords' => \&cleanup_attr_text, | |||||||
| 990 | 'href' => \&cleanup_attr_uri, | |||||||
| 991 | }, | |||||||
| 992 | 'table' => { %attr, | |||||||
| 993 | 'frame' => \&cleanup_attr_tframe, | |||||||
| 994 | 'rules' => \&cleanup_attr_trules, | |||||||
| 995 | %texta_attr, | |||||||
| 996 | 'bgcolor' => \&cleanup_attr_color, | |||||||
| 997 | 'width' => \&cleanup_attr_length, | |||||||
| 998 | 'cellspacing' => \&cleanup_attr_length, | |||||||
| 999 | 'cellpadding' => \&cleanup_attr_length, | |||||||
| 1000 | 'border' => \&cleanup_attr_number, | |||||||
| 1001 | 'summary' => \&cleanup_attr_text, | |||||||
| 1002 | }, | |||||||
| 1003 | 'caption' => { %attr, | |||||||
| 1004 | 'align' => sub { /^(top|bottom|left|right)$/i ? lc $1 : undef }, | |||||||
| 1005 | }, | |||||||
| 1006 | 'colgroup' => \%col_attr, | |||||||
| 1007 | 'col' => \%col_attr, | |||||||
| 1008 | 'thead' => \%cellhv_attr, | |||||||
| 1009 | 'tfoot' => \%cellhv_attr, | |||||||
| 1010 | 'tbody' => \%cellhv_attr, | |||||||
| 1011 | 'tr' => { %attr, | |||||||
| 1012 | bgcolor => \&cleanup_attr_color, | |||||||
| 1013 | %cellhv_attr, | |||||||
| 1014 | }, | |||||||
| 1015 | 'th' => \%thtd_attr, | |||||||
| 1016 | 'td' => \%thtd_attr, | |||||||
| 1017 | 'ins' => \%insdel_attr, | |||||||
| 1018 | 'del' => \%insdel_attr, | |||||||
| 1019 | 'a' => { %attr, | |||||||
| 1020 | href => \&cleanup_attr_uri, | |||||||
| 1021 | style => \&cleanup_attr_text, | |||||||
| 1022 | target => \&cleanup_attr_text, | |||||||
| 1023 | rel => \&cleanup_attr_text, | |||||||
| 1024 | }, | |||||||
| 1025 | ||||||||
| 1026 | 'script' => { | |||||||
| 1027 | language => \&cleanup_attr_scriptlang, | |||||||
| 1028 | type => \&cleanup_attr_scripttype, | |||||||
| 1029 | src => \&cleanup_attr_uri, | |||||||
| 1030 | }, | |||||||
| 1031 | 'noscript' => { %attr, | |||||||
| 1032 | }, | |||||||
| 1033 | 'link' => { %attr, | |||||||
| 1034 | href => \&cleanup_attr_uri, | |||||||
| 1035 | 'rel' => \&cleanup_attr_text, | |||||||
| 1036 | 'type' => \&cleanup_attr_text, | |||||||
| 1037 | 'media' => \&cleanup_attr_text, | |||||||
| 1038 | }, | |||||||
| 1039 | 'object' => { %attr, | |||||||
| 1040 | 'width' => \&cleanup_attr_length, | |||||||
| 1041 | 'height' => \&cleanup_attr_length, | |||||||
| 1042 | style => \&cleanup_attr_text, | |||||||
| 1043 | type => \&cleanup_attr_text, | |||||||
| 1044 | data => \&cleanup_attr_text, | |||||||
| 1045 | classid => \&cleanup_attr_text, | |||||||
| 1046 | codebase => \&cleanup_attr_text, | |||||||
| 1047 | }, | |||||||
| 1048 | 'param' => { | |||||||
| 1049 | name => \&cleanup_attr_text, | |||||||
| 1050 | value => \&cleanup_attr_text, | |||||||
| 1051 | }, | |||||||
| 1052 | 'embed' => { %attr, | |||||||
| 1053 | 'src' => \&cleanup_attr_uri, | |||||||
| 1054 | 'bgcolor' => \&cleanup_attr_color, | |||||||
| 1055 | 'width' => \&cleanup_attr_length, | |||||||
| 1056 | 'height' => \&cleanup_attr_length, | |||||||
| 1057 | 'pluginspage' => \&cleanup_attr_uri, | |||||||
| 1058 | flashvars => \&cleanup_attr_text, | |||||||
| 1059 | type => \&cleanup_attr_text, | |||||||
| 1060 | quality => \&cleanup_attr_text, | |||||||
| 1061 | allowScriptAccess => \&cleanup_attr_text, | |||||||
| 1062 | allowNetworking => \&cleanup_attr_text, | |||||||
| 1063 | }, | |||||||
| 1064 | ); | |||||||
| 1065 | ||||||||
| 1066 | %safe_style = ( | |||||||
| 1067 | 'animation' => \&cleanup_attr_text, | |||||||
| 1068 | 'animation-name' => \&cleanup_attr_text, | |||||||
| 1069 | 'animation-duration' => \&cleanup_attr_text, | |||||||
| 1070 | 'animation-timing-function' => \&cleanup_attr_text, | |||||||
| 1071 | 'animation-delay' => \&cleanup_attr_text, | |||||||
| 1072 | 'animation-iteration-count' => \&cleanup_attr_text, | |||||||
| 1073 | 'animation-direction' => \&cleanup_attr_text, | |||||||
| 1074 | 'animation-play-state' => \&cleanup_attr_text, | |||||||
| 1075 | 'appearance' => \&cleanup_attr_text, | |||||||
| 1076 | 'backface-visibility' => \&cleanup_attr_text, | |||||||
| 1077 | 'background' => \&cleanup_attr_text, | |||||||
| 1078 | 'background-attachment' => \&cleanup_attr_text, | |||||||
| 1079 | 'background-color' => \&cleanup_attr_color, | |||||||
| 1080 | 'background-image' => \&cleanup_attr_text, | |||||||
| 1081 | 'background-position' => \&cleanup_attr_text, | |||||||
| 1082 | 'background-repeat' => \&cleanup_attr_text, | |||||||
| 1083 | 'background-clip' => \&cleanup_attr_text, | |||||||
| 1084 | 'background-origin' => \&cleanup_attr_text, | |||||||
| 1085 | 'background-size' => \&cleanup_attr_text, | |||||||
| 1086 | 'border' => \&cleanup_attr_text, | |||||||
| 1087 | 'border-bottom' => \&cleanup_attr_text, | |||||||
| 1088 | 'border-bottom-color' => \&cleanup_attr_color, | |||||||
| 1089 | 'border-bottom-style' => \&cleanup_attr_text, | |||||||
| 1090 | 'border-bottom-width' => \&cleanup_attr_length, | |||||||
| 1091 | 'border-collapse' => \&cleanup_attr_text, | |||||||
| 1092 | 'border-color' => \&cleanup_attr_color, | |||||||
| 1093 | 'border-left' => \&cleanup_attr_text, | |||||||
| 1094 | 'border-left-color' => \&cleanup_attr_color, | |||||||
| 1095 | 'border-left-style' => \&cleanup_attr_text, | |||||||
| 1096 | 'border-left-width' => \&cleanup_attr_length, | |||||||
| 1097 | 'border-right' => \&cleanup_attr_text, | |||||||
| 1098 | 'border-right-color' => \&cleanup_attr_color, | |||||||
| 1099 | 'border-right-style' => \&cleanup_attr_text, | |||||||
| 1100 | 'border-right-width' => \&cleanup_attr_length, | |||||||
| 1101 | 'border-spacing' => \&cleanup_attr_text, | |||||||
| 1102 | 'border-style' => \&cleanup_attr_text, | |||||||
| 1103 | 'border-top' => \&cleanup_attr_text, | |||||||
| 1104 | 'border-top-color' => \&cleanup_attr_color, | |||||||
| 1105 | 'border-top-style' => \&cleanup_attr_text, | |||||||
| 1106 | 'border-top-width' => \&cleanup_attr_length, | |||||||
| 1107 | 'border-width' => \&cleanup_attr_length, | |||||||
| 1108 | 'border-bottom-left-radius' => \&cleanup_attr_text, | |||||||
| 1109 | 'border-bottom-right-radius' => \&cleanup_attr_text, | |||||||
| 1110 | 'border-image' => \&cleanup_attr_text, | |||||||
| 1111 | 'border-image-outset' => \&cleanup_attr_text, | |||||||
| 1112 | 'border-image-repeat' => \&cleanup_attr_text, | |||||||
| 1113 | 'border-image-slice' => \&cleanup_attr_text, | |||||||
| 1114 | 'border-image-source' => \&cleanup_attr_text, | |||||||
| 1115 | 'border-image-width' => \&cleanup_attr_length, | |||||||
| 1116 | 'border-radius' => \&cleanup_attr_text, | |||||||
| 1117 | 'border-top-left-radius' => \&cleanup_attr_text, | |||||||
| 1118 | 'border-top-right-radius' => \&cleanup_attr_text, | |||||||
| 1119 | 'bottom' => \&cleanup_attr_text, | |||||||
| 1120 | 'box' => \&cleanup_attr_text, | |||||||
| 1121 | 'box-align' => \&cleanup_attr_text, | |||||||
| 1122 | 'box-direction' => \&cleanup_attr_text, | |||||||
| 1123 | 'box-flex' => \&cleanup_attr_text, | |||||||
| 1124 | 'box-flex-group' => \&cleanup_attr_text, | |||||||
| 1125 | 'box-lines' => \&cleanup_attr_text, | |||||||
| 1126 | 'box-ordinal-group' => \&cleanup_attr_text, | |||||||
| 1127 | 'box-orient' => \&cleanup_attr_text, | |||||||
| 1128 | 'box-pack' => \&cleanup_attr_text, | |||||||
| 1129 | 'box-sizing' => \&cleanup_attr_text, | |||||||
| 1130 | 'box-shadow' => \&cleanup_attr_text, | |||||||
| 1131 | 'caption-side' => \&cleanup_attr_text, | |||||||
| 1132 | 'clear' => \&cleanup_attr_text, | |||||||
| 1133 | 'clip' => \&cleanup_attr_text, | |||||||
| 1134 | 'color' => \&cleanup_attr_color, | |||||||
| 1135 | 'column' => \&cleanup_attr_text, | |||||||
| 1136 | 'column-count' => \&cleanup_attr_text, | |||||||
| 1137 | 'column-fill' => \&cleanup_attr_text, | |||||||
| 1138 | 'column-gap' => \&cleanup_attr_text, | |||||||
| 1139 | 'column-rule' => \&cleanup_attr_text, | |||||||
| 1140 | 'column-rule-color' => \&cleanup_attr_text, | |||||||
| 1141 | 'column-rule-style' => \&cleanup_attr_text, | |||||||
| 1142 | 'column-rule-width' => \&cleanup_attr_length, | |||||||
| 1143 | 'column-span' => \&cleanup_attr_text, | |||||||
| 1144 | 'column-width' => \&cleanup_attr_length, | |||||||
| 1145 | 'columns' => \&cleanup_attr_text, | |||||||
| 1146 | 'content' => \&cleanup_attr_text, | |||||||
| 1147 | 'counter-increment' => \&cleanup_attr_text, | |||||||
| 1148 | 'counter-reset' => \&cleanup_attr_text, | |||||||
| 1149 | 'cursor' => \&cleanup_attr_text, | |||||||
| 1150 | 'direction' => \&cleanup_attr_text, | |||||||
| 1151 | 'display' => \&cleanup_attr_text, | |||||||
| 1152 | 'empty-cells' => \&cleanup_attr_text, | |||||||
| 1153 | 'float' => \&cleanup_attr_text, | |||||||
| 1154 | 'font' => \&cleanup_attr_text, | |||||||
| 1155 | 'font-family' => \&cleanup_attr_text, | |||||||
| 1156 | 'font-size' => \&cleanup_attr_text, | |||||||
| 1157 | 'font-style' => \&cleanup_attr_text, | |||||||
| 1158 | 'font-variant' => \&cleanup_attr_text, | |||||||
| 1159 | 'font-weight' => \&cleanup_attr_length, | |||||||
| 1160 | '@font-face' => \&cleanup_attr_text, | |||||||
| 1161 | 'font-size-adjust' => \&cleanup_attr_text, | |||||||
| 1162 | 'font-stretch' => \&cleanup_attr_text, | |||||||
| 1163 | 'grid-columns' => \&cleanup_attr_text, | |||||||
| 1164 | 'grid-rows' => \&cleanup_attr_text, | |||||||
| 1165 | 'hanging-punctuation' => \&cleanup_attr_text, | |||||||
| 1166 | 'height' => \&cleanup_attr_length, | |||||||
| 1167 | 'icon' => \&cleanup_attr_text, | |||||||
| 1168 | '@keyframes' => \&cleanup_attr_text, | |||||||
| 1169 | 'left' => \&cleanup_attr_length, | |||||||
| 1170 | 'letter-spacing' => \&cleanup_attr_text, | |||||||
| 1171 | 'line-height' => \&cleanup_attr_text, | |||||||
| 1172 | 'list-style' => \&cleanup_attr_text, | |||||||
| 1173 | 'list-style-image' => \&cleanup_attr_text, | |||||||
| 1174 | 'list-style-position' => \&cleanup_attr_text, | |||||||
| 1175 | 'list-style-type' => \&cleanup_attr_text, | |||||||
| 1176 | 'margin' => \&cleanup_attr_text, | |||||||
| 1177 | 'margin-bottom' => \&cleanup_attr_length, | |||||||
| 1178 | 'margin-left' => \&cleanup_attr_length, | |||||||
| 1179 | 'margin-right' => \&cleanup_attr_length, | |||||||
| 1180 | 'margin-top' => \&cleanup_attr_length, | |||||||
| 1181 | 'max-height' => \&cleanup_attr_length, | |||||||
| 1182 | 'max-width' => \&cleanup_attr_length, | |||||||
| 1183 | 'min-height' => \&cleanup_attr_length, | |||||||
| 1184 | 'min-width' => \&cleanup_attr_length, | |||||||
| 1185 | 'nav' => \&cleanup_attr_text, | |||||||
| 1186 | 'nav-down' => \&cleanup_attr_text, | |||||||
| 1187 | 'nav-index' => \&cleanup_attr_text, | |||||||
| 1188 | 'nav-left' => \&cleanup_attr_text, | |||||||
| 1189 | 'nav-right' => \&cleanup_attr_text, | |||||||
| 1190 | 'nav-up' => \&cleanup_attr_text, | |||||||
| 1191 | 'opacity' => \&cleanup_attr_text, | |||||||
| 1192 | 'outline' => \&cleanup_attr_text, | |||||||
| 1193 | 'outline-color' => \&cleanup_attr_color, | |||||||
| 1194 | 'outline-offset' => \&cleanup_attr_text, | |||||||
| 1195 | 'outline-style' => \&cleanup_attr_text, | |||||||
| 1196 | 'outline-width' => \&cleanup_attr_length, | |||||||
| 1197 | 'overflow' => \&cleanup_attr_text, | |||||||
| 1198 | 'overflow-x' => \&cleanup_attr_text, | |||||||
| 1199 | 'overflow-y' => \&cleanup_attr_text, | |||||||
| 1200 | 'padding' => \&cleanup_attr_text, | |||||||
| 1201 | 'padding-bottom' => \&cleanup_attr_length, | |||||||
| 1202 | 'padding-left' => \&cleanup_attr_length, | |||||||
| 1203 | 'padding-right' => \&cleanup_attr_length, | |||||||
| 1204 | 'padding-top' => \&cleanup_attr_length, | |||||||
| 1205 | 'page-break' => \&cleanup_attr_text, | |||||||
| 1206 | 'page-break-after' => \&cleanup_attr_text, | |||||||
| 1207 | 'page-break-before' => \&cleanup_attr_text, | |||||||
| 1208 | 'page-break-inside' => \&cleanup_attr_text, | |||||||
| 1209 | 'perspective' => \&cleanup_attr_text, | |||||||
| 1210 | 'perspective-origin' => \&cleanup_attr_text, | |||||||
| 1211 | 'position' => \&cleanup_attr_text, | |||||||
| 1212 | 'punctuation-trim' => \&cleanup_attr_text, | |||||||
| 1213 | 'quotes' => \&cleanup_attr_text, | |||||||
| 1214 | 'resize' => \&cleanup_attr_text, | |||||||
| 1215 | 'right' => \&cleanup_attr_length, | |||||||
| 1216 | 'rotation' => \&cleanup_attr_text, | |||||||
| 1217 | 'rotation-point' => \&cleanup_attr_text, | |||||||
| 1218 | 'table-layout' => \&cleanup_attr_text, | |||||||
| 1219 | 'target' => \&cleanup_attr_text, | |||||||
| 1220 | 'target-name' => \&cleanup_attr_text, | |||||||
| 1221 | 'target-new' => \&cleanup_attr_text, | |||||||
| 1222 | 'target-position' => \&cleanup_attr_text, | |||||||
| 1223 | 'text' => \&cleanup_attr_text, | |||||||
| 1224 | 'text-align' => \&cleanup_attr_text, | |||||||
| 1225 | 'text-decoration' => \&cleanup_attr_text, | |||||||
| 1226 | 'text-indent' => \&cleanup_attr_text, | |||||||
| 1227 | 'text-justify' => \&cleanup_attr_text, | |||||||
| 1228 | 'text-outline' => \&cleanup_attr_text, | |||||||
| 1229 | 'text-overflow' => \&cleanup_attr_text, | |||||||
| 1230 | 'text-shadow' => \&cleanup_attr_text, | |||||||
| 1231 | 'text-transform' => \&cleanup_attr_text, | |||||||
| 1232 | 'text-wrap' => \&cleanup_attr_text, | |||||||
| 1233 | 'top' => \&cleanup_attr_length, | |||||||
| 1234 | 'transform' => \&cleanup_attr_text, | |||||||
| 1235 | 'transform-origin' => \&cleanup_attr_text, | |||||||
| 1236 | 'transform-style' => \&cleanup_attr_text, | |||||||
| 1237 | 'transition' => \&cleanup_attr_text, | |||||||
| 1238 | 'transition-property' => \&cleanup_attr_text, | |||||||
| 1239 | 'transition-duration' => \&cleanup_attr_text, | |||||||
| 1240 | 'transition-timing-function' => \&cleanup_attr_text, | |||||||
| 1241 | 'transition-delay' => \&cleanup_attr_text, | |||||||
| 1242 | 'vertical-align' => \&cleanup_attr_text, | |||||||
| 1243 | 'visibility' => \&cleanup_attr_text, | |||||||
| 1244 | 'width' => \&cleanup_attr_length, | |||||||
| 1245 | 'white-space' => \&cleanup_attr_text, | |||||||
| 1246 | 'word-spacing' => \&cleanup_attr_text, | |||||||
| 1247 | 'word-break' => \&cleanup_attr_text, | |||||||
| 1248 | 'word-wrap' => \&cleanup_attr_text, | |||||||
| 1249 | 'z-index' => \&cleanup_attr_text | |||||||
| 1250 | ); | |||||||
| 1251 | } | |||||||
| 1252 | ||||||||
| 1253 | ||||||||
| 1254 | sub cleanup_attr_style { | |||||||
| 1255 | my @clean = (); | |||||||
| 1256 | foreach my $elt (split /;/, $_) { | |||||||
| 1257 | next if $elt =~ m#^\s*$#; | |||||||
| 1258 | if ( $elt =~ m#^\s*([\w\-]+)\s*:\s*(.+?)\s*$#s ) { | |||||||
| 1259 | my ($key, $val) = (lc $1, $2); | |||||||
| 1260 | local $_ = $val; | |||||||
| 1261 | my $sub = $safe_style{$key}; | |||||||
| 1262 | if (defined $sub) { | |||||||
| 1263 | my $cleanval = &{$sub}(); | |||||||
| 1264 | if (defined $cleanval) { | |||||||
| 1265 | push @clean, "$key:$val"; | |||||||
| 1266 | } | |||||||
| 1267 | } | |||||||
| 1268 | } | |||||||
| 1269 | } | |||||||
| 1270 | return join '; ', @clean; | |||||||
| 1271 | } | |||||||
| 1272 | sub cleanup_attr_number { | |||||||
| 1273 | /^(\d+)$/ ? $1 : undef; | |||||||
| 1274 | } | |||||||
| 1275 | sub cleanup_attr_method { | |||||||
| 1276 | /^(get|post)$/i ? lc $1 : 'post'; | |||||||
| 1277 | } | |||||||
| 1278 | sub cleanup_attr_inputtype { | |||||||
| 1279 | /^(text|password|checkbox|radio|submit|reset|file|hidden|image|button)$/i ? lc $1 : undef; | |||||||
| 1280 | } | |||||||
| 1281 | sub cleanup_attr_multilength { | |||||||
| 1282 | /^(\d+(?:\.\d+)?[*%]?)$/ ? $1 : undef; | |||||||
| 1283 | } | |||||||
| 1284 | sub cleanup_attr_text { | |||||||
| 1285 | tr/-a-zA-Z0-9_()[]{}\/?.,\\|;:&@#~=+*^%$'! \xc0-\xff//dc; | |||||||
| 1286 | $_; | |||||||
| 1287 | } | |||||||
| 1288 | sub cleanup_attr_length { | |||||||
| 1289 | /^(\d+(\%|px|em)?)$/ ? $1 : undef; | |||||||
| 1290 | } | |||||||
| 1291 | sub cleanup_attr_color { | |||||||
| 1292 | /^(\w{2,20}|#[\da-fA-F]{3}|#[\da-fA-F]{6})$/ or die "color <<$_>> bad"; | |||||||
| 1293 | /^(\w{2,20}|#[\da-fA-F]{3}|#[\da-fA-F]{6})$/ ? $1 : undef; | |||||||
| 1294 | } | |||||||
| 1295 | sub cleanup_attr_uri { | |||||||
| 1296 | check_url_valid($_) ? $_ : undef; | |||||||
| 1297 | } | |||||||
| 1298 | sub cleanup_attr_tframe { | |||||||
| 1299 | /^(void|above|below|hsides|lhs|rhs|vsides|box|border)$/i | |||||||
| 1300 | ? lc $1 : undef; | |||||||
| 1301 | } | |||||||
| 1302 | sub cleanup_attr_trules { | |||||||
| 1303 | /^(none|groups|rows|cols|all)$/i ? lc $1 : undef; | |||||||
| 1304 | } | |||||||
| 1305 | ||||||||
| 1306 | sub cleanup_attr_scriptlang { | |||||||
| 1307 | /^(javascript)$/i ? lc $1 : undef; | |||||||
| 1308 | } | |||||||
| 1309 | sub cleanup_attr_scripttype { | |||||||
| 1310 | /^(text\/javascript)$/i ? lc $1 : undef; | |||||||
| 1311 | } | |||||||
| 1312 | ||||||||
| 1313 | use vars qw(@stack $safe_tags $convert_nl); | |||||||
| 1314 | sub cleanup_html { | |||||||
| 1315 | local ($_, $convert_nl, $safe_tags) = @_; | |||||||
| 1316 | local @stack = (); | |||||||
| 1317 | ||||||||
| 1318 | return '' unless($_); | |||||||
| 1319 | ||||||||
| 1320 | my $ignore_comments = 0; | |||||||
| 1321 | if($ignore_comments) { | |||||||
| 1322 | s[ | |||||||
| 1323 | (?: ) | | |||||||
| 1324 | (?: <[?!].*?> ) | | |||||||
| 1325 | (?: <([a-z0-9]+)\b((?:[^>'"]|"[^"]*"|'[^']*')*)> ) | | |||||||
| 1326 | (?: ([a-z0-9]+)> ) | | |||||||
| 1327 | (?: (.[^<]*) ) | |||||||
| 1328 | ][ | |||||||
| 1329 | defined $1 ? cleanup_tag(lc $1, $2) : | |||||||
| 1330 | defined $3 ? cleanup_close(lc $3) : | |||||||
| 1331 | defined $4 ? cleanup_cdata($4) : | |||||||
| 1332 | '' | |||||||
| 1333 | ]igesx; | |||||||
| 1334 | } else { | |||||||
| 1335 | s[ | |||||||
| 1336 | (?: () ) | | |||||||
| 1337 | (?: ) | | |||||||
| 1338 | (?: <[?!].*?> ) | | |||||||
| 1339 | (?: <([a-z0-9]+)\b((?:[^>'"]|"[^"]*"|'[^']*')*)> ) | | |||||||
| 1340 | (?: ([a-z0-9]+)> ) | | |||||||
| 1341 | (?: (.[^<]*) ) | |||||||
| 1342 | ][ | |||||||
| 1343 | defined $1 ? $1 : | |||||||
| 1344 | defined $2 ? cleanup_tag(lc $2, $3) : | |||||||
| 1345 | defined $4 ? cleanup_close(lc $4) : | |||||||
| 1346 | defined $5 ? cleanup_cdata($5) : | |||||||
| 1347 | '' | |||||||
| 1348 | ]igesx; | |||||||
| 1349 | } | |||||||
| 1350 | ||||||||
| 1351 | # Close anything that was left open | |||||||
| 1352 | $_ .= join '', map "$_->{NAME}>", @stack; | |||||||
| 1353 | ||||||||
| 1354 | # Where we turned foo into foo, | |||||||
| 1355 | # take out the pointless . | |||||||
| 1356 | 1 while s#<($auto_deinterleave_pattern)\b[^>]*>( |\s)*\1>##go; | |||||||
| 1357 | ||||||||
| 1358 | # cleanup p elements | |||||||
| 1359 | s!\s+!!g; | |||||||
| 1360 | s!!!g; | |||||||
| 1361 | ||||||||
| 1362 | # Element pre is not declared in p list of possible children | |||||||
| 1363 | s! \s*( .*?)\s*!$1!g; |
|||||||
| 1364 | ||||||||
| 1365 | return $_; | |||||||
| 1366 | } | |||||||
| 1367 | ||||||||
| 1368 | sub cleanup_tag { | |||||||
| 1369 | my ($tag, $attrs) = @_; | |||||||
| 1370 | unless (exists $safe_tags->{$tag}) { | |||||||
| 1371 | return ''; | |||||||
| 1372 | } | |||||||
| 1373 | ||||||||
| 1374 | # for XHTML conformity | |||||||
| 1375 | $tag = $transpose_tag{$tag} if($transpose_tag{$tag}); | |||||||
| 1376 | ||||||||
| 1377 | my $html = ''; | |||||||
| 1378 | if($force_closetag{$tag}) { | |||||||
| 1379 | while (scalar @stack and $force_closetag{$tag}{$stack[0]{NAME}}) { | |||||||
| 1380 | $html = cleanup_close($stack[0]{NAME}); | |||||||
| 1381 | } | |||||||
| 1382 | } | |||||||
| 1383 | ||||||||
| 1384 | my $t = $safe_tags->{$tag}; | |||||||
| 1385 | my $safe_attrs = ''; | |||||||
| 1386 | while ($attrs =~ s#^\s*(\w+)(?:\s*=\s*(?:([^"'>\s]+)|"([^"]*)"|'([^']*)'))?##) { | |||||||
| 1387 | my $attr = lc $1; | |||||||
| 1388 | my $val = ( defined $2 ? $2 : | |||||||
| 1389 | defined $3 ? unescape_html($3) : | |||||||
| 1390 | defined $4 ? unescape_html($4) : | |||||||
| 1391 | '$attr' | |||||||
| 1392 | ); | |||||||
| 1393 | unless (exists $t->{$attr}) { | |||||||
| 1394 | next; | |||||||
| 1395 | } | |||||||
| 1396 | if (defined $t->{$attr}) { | |||||||
| 1397 | local $_ = $val; | |||||||
| 1398 | my $cleaned = &{ $t->{$attr} }(); | |||||||
| 1399 | if (defined $cleaned) { | |||||||
| 1400 | $safe_attrs .= qq| $attr="${\( escape_html($cleaned) )}"|; | |||||||
| 1401 | } | |||||||
| 1402 | } else { | |||||||
| 1403 | $safe_attrs .= " $attr"; | |||||||
| 1404 | } | |||||||
| 1405 | } | |||||||
| 1406 | ||||||||
| 1407 | my $str; | |||||||
| 1408 | if (exists $tag_is_empty{$tag}) { | |||||||
| 1409 | $str = "$html<$tag$safe_attrs />"; | |||||||
| 1410 | } elsif (exists $closetag_is_optional{$tag}) { | |||||||
| 1411 | $str = "$html<$tag$safe_attrs>"; | |||||||
| 1412 | # } elsif (exists $closetag_is_dependent{$tag} && $safe_attrs =~ /$closetag_is_dependent{$tag}=/) { | |||||||
| 1413 | # return "$html<$tag$safe_attrs />"; | |||||||
| 1414 | } else { | |||||||
| 1415 | my $full = "<$tag$safe_attrs>"; | |||||||
| 1416 | unshift @stack, { NAME => $tag, FULL => $full }; | |||||||
| 1417 | $str = "$html$full"; | |||||||
| 1418 | } | |||||||
| 1419 | #LogDebug("cleanup_tag: str=$str"); | |||||||
| 1420 | return $str; | |||||||
| 1421 | } | |||||||
| 1422 | ||||||||
| 1423 | sub cleanup_close { | |||||||
| 1424 | my $tag = shift; | |||||||
| 1425 | ||||||||
| 1426 | # for XHTML conformity | |||||||
| 1427 | $tag = $transpose_tag{$tag} if($transpose_tag{$tag}); | |||||||
| 1428 | ||||||||
| 1429 | # Ignore a close without an open | |||||||
| 1430 | unless (grep {$_->{NAME} eq $tag} @stack) { | |||||||
| 1431 | return ''; | |||||||
| 1432 | } | |||||||
| 1433 | ||||||||
| 1434 | # Close open tags up to the matching open | |||||||
| 1435 | my @close = (); | |||||||
| 1436 | while (scalar @stack and $stack[0]{NAME} ne $tag) { | |||||||
| 1437 | push @close, shift @stack; | |||||||
| 1438 | } | |||||||
| 1439 | push @close, shift @stack; | |||||||
| 1440 | ||||||||
| 1441 | my $html = join '', map {"$_->{NAME}>"} @close; | |||||||
| 1442 | ||||||||
| 1443 | # Reopen any we closed early if all that were closed are | |||||||
| 1444 | # configured to be auto deinterleaved. | |||||||
| 1445 | unless (grep {! exists $auto_deinterleave{$_->{NAME}} } @close) { | |||||||
| 1446 | pop @close; | |||||||
| 1447 | $html .= join '', map {$_->{FULL}} reverse @close; | |||||||
| 1448 | unshift @stack, @close; | |||||||
| 1449 | } | |||||||
| 1450 | ||||||||
| 1451 | return $html; | |||||||
| 1452 | } | |||||||
| 1453 | ||||||||
| 1454 | sub cleanup_cdata { | |||||||
| 1455 | local $_ = shift; | |||||||
| 1456 | ||||||||
| 1457 | return $_ if(scalar @stack and $stack[0]{NAME} eq 'script'); | |||||||
| 1458 | ||||||||
| 1459 | s[ (?: & ( | |||||||
| 1460 | [a-zA-Z0-9]{2,15} | | |||||||
| 1461 | [#][0-9]{2,6} | | |||||||
| 1462 | [#][xX][a-fA-F0-9]{2,6} | ) \b ;? | |||||||
| 1463 | ) | ($escape_html_map) | (.) | |||||||
| 1464 | ][ | |||||||
| 1465 | defined $1 ? "&$1;" : defined $2 ? $2 : $3 | |||||||
| 1466 | ]gesx; | |||||||
| 1467 | ||||||||
| 1468 | # substitute newlines in the input for html line breaks if required. | |||||||
| 1469 | s%\cM?\n% \n%g if $convert_nl; |
|||||||
| 1470 | ||||||||
| 1471 | return $_; | |||||||
| 1472 | } | |||||||
| 1473 | ||||||||
| 1474 | # subroutine to escape the necessary characters to the appropriate HTML | |||||||
| 1475 | # entities | |||||||
| 1476 | ||||||||
| 1477 | sub escape_html { | |||||||
| 1478 | my $str = shift or return ''; | |||||||
| 1479 | $str = encode_entities($str); | |||||||
| 1480 | $str =~ s/&(#x?\d+;)/&$1/g; # avoid double encoding of hex/dec characters | |||||||
| 1481 | return $str; | |||||||
| 1482 | } | |||||||
| 1483 | ||||||||
| 1484 | # subroutine to unescape escaped HTML entities. Note that some entites | |||||||
| 1485 | # have no 8-bit character equivalent, see | |||||||
| 1486 | # "http://www.w3.org/TR/xhtml1/DTD/xhtml-symbol.ent" for some examples. | |||||||
| 1487 | # unescape_html() leaves these entities in their encoded form. | |||||||
| 1488 | ||||||||
| 1489 | sub unescape_html { | |||||||
| 1490 | my $str = shift or return ''; | |||||||
| 1491 | $str = decode_entities($str); | |||||||
| 1492 | return strip_nonprintable($str); | |||||||
| 1493 | } | |||||||
| 1494 | ||||||||
| 1495 | sub check_url_valid { | |||||||
| 1496 | my $url = shift; | |||||||
| 1497 | ||||||||
| 1498 | $url = "$tvars{cgipath}/$tvars{script}$url" if($url =~ /^\?/); | |||||||
| 1499 | ||||||||
| 1500 | # allow in page URLs | |||||||
| 1501 | return 1 if $url =~ m!^\#!; | |||||||
| 1502 | ||||||||
| 1503 | # allow relative URLs with sane values | |||||||
| 1504 | return 1 if $url =~ m!^[a-z0-9_\-\.\,\+\/#]+$!i; | |||||||
| 1505 | ||||||||
| 1506 | # allow mailto email addresses | |||||||
| 1507 | return 1 if $url =~ m#mailto:([-+=\w\'.\&\\//]+)@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([a-zA-Z0-9\-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)#i; | |||||||
| 1508 | ||||||||
| 1509 | # allow javascript calls | |||||||
| 1510 | return 1 if $url =~ m#^javascript:#i; | |||||||
| 1511 | ||||||||
| 1512 | # $url =~ m< ^ ((?:ftp|http|https):// [\w\-\.]+ (?:\:\d+)?)? | |||||||
| 1513 | # (?: /? [\w\-.!~*'(|);/\@+\$,%#]* )? | |||||||
| 1514 | # (?: \? [\w\-.!~*'(|);/\@&=+\$,%#]* )? | |||||||
| 1515 | # $ | |||||||
| 1516 | # >x ? 1 : 0; | |||||||
| 1517 | return $url =~ m< ^ $settings{urlregex} $ >x ? 1 : 0; | |||||||
| 1518 | } | |||||||
| 1519 | ||||||||
| 1520 | sub strip_nonprintable { | |||||||
| 1521 | my $text = shift; | |||||||
| 1522 | return '' unless defined $text; | |||||||
| 1523 | ||||||||
| 1524 | $text=~ tr#\t\n\040-\176\241-\377# #cs; | |||||||
| 1525 | return $text; | |||||||
| 1526 | } | |||||||
| 1527 | ||||||||
| 1528 | # | |||||||
| 1529 | # End of HTML handling code | |||||||
| 1530 | # | |||||||
| 1531 | ################################################################## | |||||||
| 1532 | ||||||||
| 1533 | 1; | |||||||
| 1534 | ||||||||
| 1535 | __END__ |