File Coverage

blib/lib/CSS/Adaptor/Whitelist.pm
Criterion Covered Total %
statement 50 59 84.7
branch 13 20 65.0
condition 0 3 0.0
subroutine 11 13 84.6
pod 7 9 77.7
total 81 104 77.8


line stmt bran cond sub pod time code
1             package CSS::Adaptor::Whitelist;
2              
3 1     1   43065 use strict;
  1         3  
  1         32  
4 1     1   5 use CSS::Adaptor;
  1         2  
  1         20  
5 1     1   783 use parent 'CSS::Adaptor';
  1         277  
  1         6  
6              
7             our $VERSION = '0.006';
8              
9             sub log {
10 2     2 1 5 my ($self, $msg) = @_;
11 1     1   88 no strict 'refs';
  1         2  
  1         3870  
12 2         20 push @CSS::Adaptor::Whitelist::message_log, {
13             timestamp => time,
14             message => $msg,
15             };
16             }
17              
18             sub list2hash {
19 17     17 1 27 return { map { $_ => 1 } @_ }
  56         12214  
20             }
21              
22             # this evaulates a string against a list of regular expressions
23             # like for the font or background properties
24             sub space_sep_res {
25 5     5 1 13 my ($str, @res) = @_;
26 5         25 while (@res) {
27 5         9 my $re1 = shift @res;
28 5 50       1454 if ($str =~ /^$re1(.*)/) {
29 5         13 my $rest = $1;
30 5 100       50 if (length($rest) == 0) {
    50          
31 1         20 return 1
32             }
33             elsif ($rest =~ s/^\s+//) {
34 4         18 return space_sep_res($rest, @res)
35             }
36             }
37             }
38 0         0 return 0
39             }
40              
41             # general
42             my $re_zero_to_one = qr/[01]\.?|0?\.\d+/;
43             my $re_percent = qr/(?: \d{1,2} \.? | \d{0,2} \. \d+ ) \%/x;
44             my $re_frac = qr/ \d* \. \d+ | \d+ \.? /x;
45             my $re_dim = qr/ $re_percent | (?:$re_frac) (?:p[ctx]|in|[cem]m|ex) \b | 0 \b /x;
46             my $re_ndim = qr/(?:-?$re_dim)/;
47             my $re_color_name = qr/(?i-xsm:\b(?:A(?:qua(?:marine)?|(?:liceBlu|ntiqueWhit|zur)e)|B(?:l(?:a(?:ck|nchedAlmond)|ue(?:Violet)?)|(?:eig|isqu)e|rown|urlyWood)|C(?:h(?:artreus|ocolat)e|or(?:n(?:flowerBlue|silk)|al)|adetBlue|(?:rimso|ya)n)|D(?:ark(?:G(?:r(?:ay|een)|oldenRod)|O(?:liveGreen|rchid)|S(?:late(?:Blue|Gray)|(?:almo|eaGree)n)|(?:Blu|orang|Turquois)e|Cyan|Khaki|Magenta|Red|Violet)|eep(?:Pink|SkyBlue)|imGray|odgerBlue)|F(?:ireBrick|loralWhite|orestGreen|uchsia)|G(?:old(?:enRod)?|r(?:een(?:Yellow)?|ay)|ainsboro|hostWhite)|Ho(?:neyDew|tPink)|I(?:ndi(?:anRed|go)|vory)|L(?:a(?:vender(?:Blush)?|wnGreen)|i(?:ght(?:C(?:oral|yan)|G(?:re(?:y|en)|oldenRodYellow)|S(?:(?:almo|eaGree)n|(?:ky|teel)Blue|lateGray)|Blue|Pink|Yellow)|me(?:Green)?|nen)|emonChiffon)|M(?:a(?:genta|roon)|edium(?:S(?:(?:ea|pring)Green|lateBlue)|(?:AquaMarin|Blu|Purpl|Turquois)e|(?:Orchi|VioletRe)d)|i(?:(?:dnightBlu|styRos)e|ntCream)|occasin)|Nav(?:ajoWhite|y)|O(?:l(?:ive(?:Drab)?|dLace)|r(?:ange(?:Red)?|chid))|P(?:a(?:le(?:G(?:oldenRod|reen)|Turquoise|VioletRed)|payaWhip)|e(?:achPuff|ru)|ink|lum|(?:owderBlu|urpl)e)|R(?:o(?:syBrown|yalBlue)|ed)|S(?:a(?:(?:ddle|ndy)Brow|lmo)n|ea(?:Green|Shell)|i(?:enna|lver)|late(?:Blue|Gray)|(?:ky|teel)Blue|now|pringGreen)|T(?:an|eal|(?:histl|urquois)e|omato)|Wh(?:ite(?:Smoke)?|eat)|Yellow(?:Green)?|Khaki|Violet)\b)/;
48             my $re_color = qr/(?:
49             transparent \b
50             | $re_color_name # Blue
51             | \#[\da-fA-F]{6} \b # #FF00FF
52             | \#[\da-fA-F]{3} \b # #F0F
53             | rgb\( # rgb(255,0,255), rgb(255,0,255,0.3)
54             (?: \d{1,3} | $re_percent ),
55             (?: \d{1,3} | $re_percent ),
56             (?: \d{1,3} | $re_percent )
57             (?: , (?:$re_zero_to_one | $re_percent) )?
58             \)
59             )/x;
60             my $re_url = qr{url\((?:http://[-\w+.]+/[-/\w.?%#]+)\)};
61             sub set_url_re {
62 0     0 1 0 my ($new_re) = @_;
63 0 0       0 if (ref($new_re) ne 'Regexp') {
64 0         0 die 'set_url_re requires a compiled regular expression, e.g. qr/url(http:.*?)/'
65             }
66             else {
67 0         0 $re_url = $new_re;
68             }
69             }
70              
71             # background
72             my $re_image = qr/(?:
73             none \b
74             | $re_url
75             )/x;
76             my $re_xy_pos = qr/(?:
77             (?: left | center | right | $re_ndim ) \b
78             (?: \s+
79             (?: top | center | bottom | $re_ndim ) \b
80             )?
81             )/x;
82             my $re_bg_attach = qr/(?:scroll\b|fixed\b)/;
83             my $re_bg_repeat = qr/(?:repeat(?:-[xy])?\b|no-repeat\b)/;
84              
85             # border
86             my $re_border_width = qr/(?: thin\b | medium\b | thick\b | $re_dim )/x;
87             my $re_border_style = qr/(?: (?:none|hidden|dotted|dashed|solid|double|groove|ridge|inset|outset) \b )/x;
88             sub ck_border {
89 0     0 0 0 space_sep_res(shift, $re_border_width, $re_border_style, $re_color)
90             }
91             my $re_border_radius = qr/(?: $re_dim ( \s+ $re_dim )? )/x;
92              
93             # margin, padding
94             my $re_margin = qr/(?: auto \b | $re_ndim )/x;
95             my $re_margin_all = qr/(?: $re_margin ( \s+ $re_margin ){0,3} )/x;
96             my $re_padding_all = qr/(?: $re_ndim ( \s+ $re_ndim ){0,3} )/x;
97              
98             # font
99             my $re_font_family = qr/(?: [-a-zA-Z0-9 ,"']+ \b )/x; # maybe too generous, should we list possible families?
100             my $re_font_size = qr/(?: (?:x?x-)?(?:small|large)\b | small(?:er)? \b | larger? \b | medium \b | $re_dim )/x;
101             my $re_font_style = qr/(?: normal \b | oblique \b | italic \b )/x;
102             my $re_font_variant = qr/(?: normal \b | small-caps \b )/x;
103             my $re_font_weight = qr/(?: (?: normal | lighter | bold(?:er)? | \d{3} ) \b )/x;
104              
105             # list style
106             my $re_list_style_position = qr/(?: (?:in|out)side \b )/x;
107             my $re_list_style_type = qr/(?: (?:
108             none | circle | disc | square | armenian
109             | decimal(?:-leading-zero)? | georgian | lower-greek | (?:lower|upper)-(?:alpha|latin|roman)
110             ) \b )/x;
111              
112             # various
113             my $re_cursor = qr/(?:
114             (?: $re_url (?: \s*,\s* $re_url )* \s* , )?
115             (?: auto | crosshair | default | help | move | pointer | progress | text | wait
116             | (?:[news]|[ns][ew])-resize
117             ) \b
118             )/x;
119              
120             our %whitelist = (
121             background => sub {
122             space_sep_res(shift, $re_color, $re_image, $re_bg_repeat, $re_bg_attach, $re_xy_pos)
123             },
124             'background-color' => qr/^$re_color$/,
125             'background-image' => qr/^$re_image$/,
126             'background-position' => qr/^$re_xy_pos$/,
127             'background-attachment' => qr/^$re_bg_attach$/,
128             'background-repeat' => qr/^$re_bg_repeat$/,
129            
130             border => \&ck_border,
131             'border-color' => qr/^$re_color$/,
132             'border-style' => qr/^$re_border_style$/,
133             'border-width' => qr/^$re_border_width$/,
134             'border-collapse' => list2hash(qw(collapse separate)),
135             'border-spacing' => qr/^ $re_dim (?: \s+ $re_dim )? $/x,
136             'border-top' => \&ck_border,
137             'border-top-color' => qr/^$re_color$/,
138             'border-top-style' => qr/^$re_border_style$/,
139             'border-top-width' => qr/^$re_border_width$/,
140             'border-bottom' => \&ck_border,
141             'border-bottom-color' => qr/^$re_color$/,
142             'border-bottom-style' => qr/^$re_border_style$/,
143             'border-bottom-width' => qr/^$re_border_width$/,
144             'border-left' => \&ck_border,
145             'border-left-color' => qr/^$re_color$/,
146             'border-left-style' => qr/^$re_border_style$/,
147             'border-left-width' => qr/^$re_border_width$/,
148             'border-right' => \&ck_border,
149             'border-right-color' => qr/^$re_color$/,
150             'border-right-style' => qr/^$re_border_style$/,
151             'border-right-width' => qr/^$re_border_width$/,
152             '-webkit-border-radius' => qr/^$re_border_radius$/,
153             '-moz-border-radius' => qr/^$re_border_radius$/,
154             '-o-border-radius' => qr/^$re_border_radius$/,
155             'border-radius' => qr/^$re_border_radius$/,
156            
157             outline => \&ck_border,
158             'outline-color' => qr/^$re_color$/,
159             'outline-style' => qr/^$re_border_style$/,
160             'outline-width' => qr/^$re_border_width$/,
161            
162             margin => qr/^$re_margin_all$/x,
163             'margin-top' => qr/$re_margin$/,
164             'margin-bottom' => qr/$re_margin$/,
165             'margin-left' => qr/$re_margin$/,
166             'margin-right' => qr/$re_margin$/,
167            
168             padding => qr/^$re_padding_all$/,
169             'padding-top' => qr/^$re_ndim$/,
170             'padding-bottom' => qr/^$re_ndim$/,
171             'padding-left' => qr/^$re_ndim$/,
172             'padding-right' => qr/^$re_ndim$/,
173            
174             color => qr/^$re_color$/,
175             font => sub {
176             my $str = shift;
177             return (
178             list2hash(
179             qw/caption icon menu message-box small-caption status-bar/
180             )->{$str}
181             ||
182             space_sep_res(
183             $str, $re_font_style, $re_font_variant, $re_font_weight, $re_font_size
184             )
185             )
186             },
187             'font-family' => qr/^$re_font_family$/,
188             'font-size' => qr/^$re_font_size$/,
189             'font-style' => qr/^$re_font_style$/,
190             'font-variant' => qr/^$re_font_variant$/,
191             'font-weight' => qr/^$re_font_weight$/,
192            
193             'list-style' => sub {
194             space_sep_res(shift, $re_list_style_type, $re_list_style_position, $re_image)
195             },
196             'list-style-image' => qr/^$re_image$/,
197             'list-style-type' => qr/^$re_list_style_type$/,
198             'list-style-position' => qr/^$re_list_style_position$/,
199              
200             position => list2hash(qw/absolute fixed relative static/),
201             top => qr/^$re_ndim$/,
202             bottom => qr/^$re_ndim$/,
203             left => qr/^$re_ndim$/,
204             right => qr/^$re_ndim$/,
205            
206             display => qr/^(?: (?:
207             none | block | inline(?:-block|-table)? | list-item | run-in
208             | table(?:- (:? caption | cell | (?:footer|header)-group | (?:column|row)(?:-group)? ) )?
209             ) \b )$/x,
210             visibility => list2hash(qw(visible hidden collapse)),
211             overflow => list2hash(qw(visible hidden scroll auto)),
212             float => list2hash(qw(left right none)),
213             clear => list2hash(qw(left right none both)),
214            
215             clip => qr/^(?:auto\b|rect\(\s*$re_dim(?:\s*,\s*$re_dim){3}\s*\))$/,
216             cursor => qr/^$re_cursor$/,
217             direction => list2hash(qw(ltr trl)),
218            
219             height => qr/^(?:auto\b|$re_ndim)$/,
220             width => qr/^(?:auto\b|$re_ndim)$/,
221             'min-width' => qr/^$re_ndim$/,
222             'min-height' => qr/^$re_ndim$/,
223             'max-width' => qr/^$re_ndim$/,
224             'max-height' => qr/^$re_ndim$/,
225             'line-height' => qr/^(?:normal\b|$re_frac|$re_dim)$/,
226            
227             'text-align' => list2hash(qw(left right center justify)),
228             'text-decoration' => sub {
229             my $str = shift;
230             if ($str !~ /\S/) { return 0 }
231             if ($str eq 'none') { return 1 }
232             my %vals = %{ list2hash(qw(underline overline line-through blink)) };
233             for (split /\s+/, $str) {
234             if (not $vals{$_}) { return 0 }
235             }
236             return 1
237             },
238             'text-indent' => qr/^$re_ndim$/,
239             'text-shadow' => qr/^ $re_ndim \s+ $re_ndim (?: \s+ $re_dim )? (?: \s+ $re_color )? $/x,
240             'text-transform' => list2hash(qw(none capitalize uppercase lowercase)),
241            
242             'letter-spacing' => qr/^(?:normal\b|$re_ndim)$/,
243             'word-spacing' => qr/^(?:normal\b|$re_ndim)$/,
244             'caption-side' => list2hash(qw(top bottom)),
245             'empty-cells' => list2hash(qw(hide show)),
246             'table-layout' => list2hash(qw(auto fixed)),
247             'unicode-bidi' => list2hash(qw(normal embed bidi-override)),
248             'vertical-align' => qr/^(?: $re_ndim | baseline \b | middle \b | su(?:b|per) \b | (?:text-)?(?:top|bottom) \b )$/x,
249             'white-space' => list2hash(qw(normal nowrap pre pre-line pre-wrap)),
250             'z-index' => qr/^(?: auto \b | -?\d+ \b )$/x,
251            
252             orphans => qr/^\d+\b$/,
253             widows => qr/^\d+\b$/,
254             'page-break-after' => list2hash(qw(auto always avoid left right)),
255             'page-break-before' => list2hash(qw(auto always avoid left right)),
256             'page-break-inside' => list2hash(qw(auto avoid)),
257             );
258             sub value_ok {
259 10     10 0 19 my ($value, $property) = @_;
260 10         17 $value =~ s/\s+!important$//;
261 10 50       27 if ($value eq 'inherit') { return 1 }
  0         0  
262 10         18 my $w = $whitelist{ $property };
263 10 100       31 if (ref $w eq 'Regexp') {
    50          
    50          
264 9         119 return $value =~ $w
265             }
266             elsif (ref $w eq 'HASH') {
267 0   0     0 return exists($w->{$value}) && $w->{$value}
268             }
269             elsif (ref $w eq 'CODE') {
270 1         5 return $w->($value)
271             }
272             else {
273 0         0 return 0
274             }
275             }
276              
277             sub output_rule {
278 5     5 1 9873 my ($self, $rule) = @_;
279 5         27 my $s = $rule->selectors;
280 5         642 return "$s {\n".$rule->properties."}\n";
281             }
282             sub output_properties {
283 5     5 1 634 my ($self, $assignments) = @_;
284 5         8 my @out;
285 5         12 for my $assignment (@$assignments) {
286 11         24 my $property = $assignment->{property};
287 11 100       41 if ( $whitelist{$property} ) {
288 10         33 my $values = $assignment->values;
289 10 100       24 if (value_ok($values, $property)) {
290 9         54 push @out, " $property: $values;\n";
291             }
292             else {
293 1         6 $self->log("filtered out value: $property: $values;");
294             }
295             }
296             else {
297 1         6 $self->log("filtered out property: $property;");
298             }
299             }
300 5         56 return join '', @out;
301             }
302             sub output_values {
303 10     10 1 410 my ($self, $values) = @_;
304 10         13 my @out;
305 10         36 for my $value (map $_->{value}, @$values) {
306 10         33 push @out, $value
307             }
308 10         71 return join('', @out);
309             }
310              
311             1
312              
313             __END__