File Coverage

blib/lib/HTML/EscapeEvil.pm
Criterion Covered Total %
statement 124 179 69.2
branch 34 58 58.6
condition 16 45 35.5
subroutine 24 29 82.7
pod 18 20 90.0
total 216 331 65.2


line stmt bran cond sub pod time code
1             package HTML::EscapeEvil;
2              
3 5     5   105663 use strict;
  5         11  
  5         186  
4 5     5   28 use base qw(HTML::Filter Class::Accessor);
  5         11  
  5         5331  
5 5     5   101484 use HTML::Element;
  5         149855  
  5         38  
6 5     5   238 use Carp;
  5         12  
  5         1146  
7              
8             our ( $ENTITY_REGEXP, %JS_EVENT, $VERSION );
9              
10             __PACKAGE__->mk_accessors(
11             qw(allow_comment allow_declaration allow_process allow_entity_reference allow_script allow_style collection_process)
12             );
13             __PACKAGE__->mk_ro_accessors(qw(processes));
14              
15             BEGIN {
16              
17 5     5   13 $VERSION = 0.05;
18              
19 5         19 my @allow_entity_references =
20             ( "amp", "lt", "gt", "quot", "apos", "#039", "nbsp", "copy", "reg" );
21 5         31 $ENTITY_REGEXP = "&(" . ( join "|", @allow_entity_references ) . ")(;)";
22              
23             # ============================================================================= #
24             # allow javascript event handler setting
25             # cite : javascript in href. e.g. hello
26             # ============================================================================= #
27 5         11571 %JS_EVENT = (
28             cite => 0,
29             onblur => 0,
30             onchange => 0,
31             onclick => 0,
32             ondblclick => 0,
33             onerror => 0,
34             onfocus => 0,
35             onkeydown => 0,
36             onkeypress => 0,
37             onkeyup => 0,
38             onload => 0,
39             onmousedown => 0,
40             onmousemove => 0,
41             onmouseout => 0,
42             onmouseover => 0,
43             onmouseup => 0,
44             onreset => 0,
45             onselect => 0,
46             onsubmit => 0,
47             onunload => 0,
48             );
49             }
50              
51             sub new {
52              
53 4     4 1 94 my ( $class, %args ) = @_;
54 4         60 my $self = $class->SUPER::new;
55              
56 4         360 foreach (
57             qw(allow_comment allow_declaration allow_process allow_style allow_script collection_process)
58             )
59             {
60              
61 24 100       114 $self->{$_} = ( $args{$_} ) ? 1 : 0;
62             }
63              
64 4 50 66     34 if ( $args{allow_entity_reference} ne ""
65             && $args{allow_entity_reference} == 0 )
66             {
67              
68 0         0 $self->{allow_entity_reference} = 0;
69             }
70             else {
71              
72 4         15 $self->{allow_entity_reference} = 1;
73             }
74              
75 4         13 $self->{processes} = [];
76              
77 4         13 $self->{_content} = [];
78 4         10 $self->{_allow_tags} = {};
79              
80 4   33     26 bless $self, ref $class || $class;
81              
82 4 100       20 if ( $args{allow_tags} ) {
83              
84 2         10 $self->add_allow_tags( ( ref( $args{allow_tags} ) eq "ARRAY" )
85 2 50       8 ? @{ $args{allow_tags} }
86             : $args{allow_tags} );
87             }
88              
89 4         20 return $self;
90             }
91              
92             sub set_allow_tags {
93              
94 0     0 1 0 my $self = shift;
95 0         0 $self->{_allow_tags} = {};
96 0         0 $self->{_current_tag} = undef;
97              
98 0         0 $self->{allow_script} = 0;
99 0         0 $self->{allow_style} = 0;
100 0         0 $self->{allow_comment} = 0;
101 0         0 $self->{allow_declaration} = 0;
102 0         0 $self->{allow_process} = 0;
103 0         0 $self->{allow_entity_reference} = 1;
104 0         0 $self->{collection_process} = 0;
105              
106 0         0 $self->clear_content;
107 0         0 $self->clear_process;
108 0         0 $self->add_allow_tags(@_);
109             }
110              
111             sub add_allow_tags {
112              
113 3     3 1 856 my ( $self, @tags ) = @_;
114 3         8 foreach my $tag (@tags) {
115              
116 10         14 $tag = lc $tag;
117 10 50 33     45 if ( $tag eq "script" || $tag eq "style" ) {
118              
119 0         0 $self->{"allow_$tag"} = 1;
120 0         0 next;
121             }
122 10         27 $self->{_allow_tags}->{$tag} = 1;
123             }
124              
125             }
126              
127             sub deny_tags {
128              
129 2     2 1 5 my ( $self, @tags ) = @_;
130 2         5 foreach my $tag (@tags) {
131              
132 2         5 $tag = lc $tag;
133 2 50 33     16 if ( $tag eq "script" || $tag eq "style" ) {
134              
135 0         0 $self->{"allow_$tag"} = 0;
136 0         0 next;
137             }
138 2         11 delete $self->{_allow_tags}->{$tag};
139             }
140             }
141              
142             sub get_allow_tags {
143              
144 1     1 1 6 my $self = shift;
145 1         1 my @tags = keys %{ $self->{_allow_tags} };
  1         4  
146 1 50       5 push @tags, "script" if $self->{allow_script};
147 1 50       18 push @tags, "style" if $self->{allow_style};
148 1         5 return sort { $a cmp $b } @tags;
  11         16  
149             }
150              
151             sub is_allow_tags {
152              
153 52     52 1 71 my ( $self, $tag ) = @_;
154 52         46 my $flag;
155 52         60 $tag = lc $tag;
156 52 50 33     197 if ( $tag eq "script" || $tag eq "style" ) {
157              
158 0         0 $flag = $self->{"allow_$tag"};
159             }
160             else {
161              
162 52 100       97 $flag = ( exists $self->{_allow_tags}->{$tag} ) ? 1 : 0;
163             }
164 52 100       138 return ($flag) ? 1 : 0;
165             }
166              
167             sub deny_all {
168              
169 0     0 1 0 my $self = shift;
170 0         0 $self->{_allow_tags} = {};
171 0         0 $self->{_current_tag} = undef;
172 0         0 $self->{allow_script} = 0;
173 0         0 $self->{allow_style} = 0;
174 0         0 $self->{allow_comment} = 0;
175 0         0 $self->{allow_declaration} = 0;
176 0         0 $self->{allow_process} = 0;
177 0         0 $self->{allow_entity_reference} = 0;
178             }
179              
180             sub filtered_html {
181              
182 4     4 1 9 my $self = shift;
183 4         5 my $content = join "", @{ $self->{_content} };
  4         17  
184 4         10 $self->clear_content;
185 4         45 return $content;
186             }
187              
188             sub filtered_file {
189              
190 2     2 1 4 my $self = shift;
191 2         3 my $fh;
192 2 50 33     209 ( ref( $_[0] ) eq "GLOB" || ref( \$_[0] ) eq "GLOB" )
      33        
193             ? ( $fh = $_[0] )
194             : ( open $fh, "> $_[0]" or croak($!) );
195 2         9 print $fh $self->filtered_html;
196 2         92 close $fh;
197             }
198              
199             sub filtered {
200              
201 2     2 1 3 my $self = shift;
202 2         2 my $content;
203 2 100 66     26 if ( -e $_[0] || ref( $_[0] ) eq "GLOB" || ref( \$_[0] ) eq "GLOB" ) {
    50 66        
204              
205 1         4 $self->parse_file( $_[0] );
206             }
207             elsif ( $_[0] ne "" ) {
208              
209 1         9 $self->parse( $_[0] );
210             }
211             else {
212              
213 0         0 croak("content is empty");
214             }
215              
216 2 100       5 if ( $_[1] ) {
217              
218 1         4 $self->filtered_file( $_[1] );
219 1         1 $content = 1;
220             }
221             else {
222              
223 1         4 $content = $self->filtered_html;
224             }
225              
226 2         7 $self->eof;
227 2         3 $self->{_current_tag} = undef;
228              
229 2         8 return $content;
230             }
231              
232             sub clear {
233              
234 7     7 1 257 my $self = shift;
235 7         53 $self->eof;
236 7         22 $self->clear_process;
237 7         20 $self->clear_content;
238 7         448 $self->{_current_tag} = undef;
239             }
240              
241             sub clear_content {
242              
243 11     11 0 17 my $self = shift;
244 11 100       10 $self->{_content} = [] if scalar @{ $self->{_content} };
  11         42  
245             }
246              
247             sub clear_process {
248              
249 7     7 1 13 my $self = shift;
250 7 100       9 $self->{processes} = [] if scalar @{ $self->{processes} };
  7         43  
251             }
252              
253             sub DESTROY {
254              
255 4     4   338 my $self = shift;
256 4         11 $self->clear;
257             }
258              
259             sub _escape {
260              
261 53     53   57 my $string = shift;
262 53         64 $string =~ s/&/&/g;
263 53         86 $string =~ s/
264 53         86 $string =~ s/>/>/g;
265 53         54 $string =~ s/\"/"/g;
266 53         46 $string =~ s/\'/'/g;
267 53         122 return $string;
268             }
269              
270             sub _unescape {
271              
272 0     0   0 my $string = shift;
273 0         0 $string =~ s/&/&/g;
274 0         0 $string =~ s/</
275 0         0 $string =~ s/>/>/g;
276 0         0 $string =~ s/"/\"/g;
277 0         0 $string =~ s/'/\'/g;
278 0         0 $string =~ s/'/\'/g;
279 0         0 return $string;
280             }
281              
282             sub _unescape_entities {
283              
284 39     39   38 my $string = shift;
285 39         148 $string =~ s/$ENTITY_REGEXP/\&$1$2/g;
286 39         78 return $string;
287             }
288              
289             # ============================== override method start ============================== #
290              
291             sub declaration {
292              
293 0     0 1 0 my ( $self, $declaration ) = @_;
294 0         0 $declaration = "";
295 0 0       0 $self->output( ( $self->{allow_declaration} )
296             ? $declaration
297             : &_escape($declaration) );
298             }
299              
300             sub process {
301              
302 3     3 1 6 my ( $self, $process, $process_text ) = @_;
303 3 100       9 if ( $self->{collection_process} ) {
304              
305 1         1 my $tmp_process = $process;
306 1         4 $tmp_process =~ s/\?$//;
307 1         2 push @{ $self->{processes} }, $tmp_process;
  1         3  
308             }
309 3 50       20 $self->SUPER::process( $process,
310             ( $self->{allow_process} ) ? $process_text : &_escape($process_text) );
311             }
312              
313             sub start {
314              
315 25     25 1 919 my ( $self, $tagname, $attr, $attrseq, $text ) = @_;
316 25         41 $self->{_current_tag} = lc $tagname;
317 25 100       48 if ( $self->is_allow_tags($tagname) ) {
318              
319 18 50       54 if ( !$self->allow_script ) {
320             ## change javascript event handler(1 : allow) e.g =>
321 0         0 foreach ( keys %{$attr} ) {
  0         0  
322              
323 0         0 my $event = lc $_;
324 0 0 0     0 if ( exists $JS_EVENT{$event} && !$JS_EVENT{$event} ) {
325              
326             #delete $attr->{$event};
327 0         0 $attr->{$event} = "void(0)";
328             }
329             }
330              
331             ## change javascript =>
332 0 0 0     0 if ( !$JS_EVENT{cite} && $attr->{href} =~ /^(java|vb)script:/i ) {
333              
334 0         0 $attr->{href} = "javascript:void(0)";
335             }
336             ## tag is generated again
337 0         0 my $element = HTML::Element->new( $tagname, %{$attr} );
  0         0  
338 0         0 $text = $element->starttag;
339 0         0 $element->delete;
340 0         0 $element = undef;
341             }
342             }
343             else {
344 7         28 $text = &_escape($text);
345             }
346 25         217 $self->SUPER::start( $tagname, $attr, $attrseq, $text );
347             }
348              
349             sub end {
350              
351 25     25 1 35 my ( $self, $tagname, $text ) = @_;
352 25         31 $self->{_current_tag} = undef;
353 25 100       37 $text = &_escape($text) if !$self->is_allow_tags($tagname);
354 25         74 $self->SUPER::end( $tagname, $text );
355             }
356              
357             sub comment {
358              
359 0     0 1 0 my ( $self, $comment ) = @_;
360 0         0 $comment = "";
361 0 0       0 $self->output( ( $self->{allow_comment} ) ? $comment : &_escape($comment) );
362             }
363              
364             sub text {
365              
366 39     39 1 93 my ( $self, $text, $is_cdata ) = @_;
367 39         51 $text = &_escape($text);
368 39 50       105 $text = &_unescape_entities($text) if $self->{allow_entity_reference};
369 39 0 33     86 $text = &_unescape($text)
      33        
370             if $is_cdata
371             && $self->{_current_tag} eq "script"
372             && $self->{allow_script};
373 39 0 33     63 $text = &_unescape($text)
      33        
374             if $is_cdata && $self->{_current_tag} eq "style" && $self->{allow_style};
375 39         104 $self->SUPER::text( $text, $is_cdata );
376             }
377              
378             sub output {
379              
380 92     92 0 285 my ( $self, $content ) = @_;
381 92         82 push @{ $self->{_content} }, $content;
  92         597  
382             }
383              
384             1;
385              
386             __END__