File Coverage

blib/lib/BBCode/Util.pm
Criterion Covered Total %
statement 334 455 73.4
branch 165 292 56.5
condition 20 54 37.0
subroutine 64 84 76.1
pod 0 27 0.0
total 583 912 63.9


line stmt bran cond sub pod time code
1             # $Id: Util.pm 284 2006-12-01 07:51:49Z chronos $
2             package BBCode::Util;
3 7     7   24599 use base qw(Exporter);
  7         15  
  7         835  
4 7     7   48 use Carp qw(croak);
  7         16  
  7         575  
5 7     7   6588 use HTML::Entities ();
  7         49575  
  7         292  
6 7     7   6899 use POSIX ();
  7         62450  
  7         200  
7 7     7   6933 use URI ();
  7         77798  
  7         186  
8 7     7   76 use strict;
  7         15  
  7         300  
9 7     7   39 use warnings;
  7         14  
  7         1713  
10              
11             our $VERSION = '0.34';
12             our @EXPORT;
13             our @EXPORT_OK;
14             our %EXPORT_TAGS;
15              
16             sub _export {
17 189     189   498 my $sym = shift;
18 189         829 $sym =~ s/^(?=\w)/&/;
19 189         413 unshift @_, 'ALL';
20 189         493 while(@_) {
21 371         593 my $tag = shift;
22 371 100       1060 $EXPORT_TAGS{$tag} = [] unless exists $EXPORT_TAGS{$tag};
23 371         383 push @{$EXPORT_TAGS{$tag}}, $sym;
  371         102379  
24             }
25             }
26              
27 7     7   29 BEGIN { _export qw(pkgFilename pkg) }
28             sub pkgFilename($) {
29 2135 50   2135 0 11170 if($_[0] =~ /^((?:\w+::)*\w+)$/) {
30 2135         4716 local $_ = $1;
31 2135         6931 s#::#/#g;
32 2135         6858 s/$/.pm/;
33 2135         5134 return $_;
34             }
35 0         0 return undef;
36             }
37              
38             my %userTags = (
39             'BODY' => 'BBCode::Body',
40             );
41              
42 7     7   27 BEGIN { _export qw(tagUserDefined tag) }
43             sub tagUserDefined($) {
44 0     0 0 0 my $pkg = shift;
45 0         0 my $file = pkgFilename($pkg);
46 0 0       0 croak qq(Invalid package name "$pkg") unless defined $file;
47 0         0 require $file;
48 0         0 my $obj = bless {}, $pkg;
49 0 0       0 croak qq(Package "$pkg" does not inherit from BBCode::Tag) unless UNIVERSAL::isa($obj,'BBCode::Tag');
50 0         0 $userTags{uc($obj->Tag)} = $pkg;
51             }
52              
53 7     7   33 BEGIN { _export qw(tagLoadPackage tag) }
54             sub tagLoadPackage($) {
55 2133     2133 0 2581 my($tag,$pkg);
56 2133 50       8551 croak qq(Invalid tag name "$_[0]") unless $_[0] =~ m#^/?(_?\w+)$#;
57 2133         4190 $tag = uc($1);
58 2133 100       4138 if(exists $userTags{$tag}) {
59 305         507 $pkg = $userTags{$tag};
60             } else {
61 1828         2243 $tag =~ s/^_/x/;
62 1828         2986 $pkg = "BBCode::Tag::$tag";
63             }
64 2133         4055 my $file = pkgFilename($pkg);
65 2133         50070 require $file;
66 2133         4801 return $pkg;
67             }
68              
69 7     7   52 BEGIN { _export qw(tagExists tag) }
70             sub tagExists($) {
71 154     154 0 281 my $tag = shift;
72 154 50       214 return 1 if eval {
73 154         324 tagLoadPackage($tag);
74 154         899 1;
75             };
76 0         0 return 0;
77             }
78              
79 7     7   23 BEGIN { _export qw(tagCanonical tag) }
80             sub tagCanonical($) {
81 3482     3482 0 6068 local $_ = shift;
82 3482 100       6369 if(ref $_) {
83 199 50       1115 return $_->Tag if UNIVERSAL::isa($_,'BBCode::Tag');
84 0         0 croak qq(Invalid reference);
85             } else {
86 3283 100       14116 return uc($1) if /^(:\w+)$/;
87 1301         2209 my $pkg = tagLoadPackage($_);
88 1301         4580 return $pkg->Tag;
89             }
90             }
91              
92 7     7   26 BEGIN { _export qw(tagObject tag) }
93             sub tagObject($) {
94 0     0 0 0 my $tag = shift;
95 0 0       0 if(ref $tag) {
96 0 0       0 return $tag if UNIVERSAL::isa($tag,'BBCode::Tag');
97 0         0 croak qq(Invalid reference);
98             } else {
99 0         0 my $pkg = tagLoadPackage($tag);
100 0         0 return bless {}, $pkg;
101             }
102             }
103              
104 7     7   26 BEGIN { _export qw(tagHierarchy tag) }
105             sub tagHierarchy($) {
106 469     469 0 1742 my $tag = tagCanonical(shift);
107 469 50       1237 return $tag if $tag =~ /^:/;
108 469         978 my $pkg = tagLoadPackage($tag);
109 469         1688 return ($pkg->Tag, map { ":$_" } ($pkg->Class, 'ALL'));
  1134         3746  
110             }
111              
112 7     7   31 BEGIN { _export qw(quoteQ quote) }
113             sub quoteQ($) {
114 59     59 0 1875 local $_ = $_[0];
115 59         133 s/([\\'])/\\$1/g;
116 59         253 return qq('$_');
117             }
118              
119 7     7   27 BEGIN { _export qw(quoteQQ quote) }
120             sub quoteQQ($) {
121 59     59 0 98 local $_ = $_[0];
122 59         121 s/([\\"])/\\$1/g;
123 59         219 return qq("$_");
124             }
125              
126 7     7   31 BEGIN { _export qw(quoteBS quote) }
127             sub quoteBS($) {
128 59     59 0 90 local $_ = $_[0];
129 59         183 s/([\\\[\]"'=,\s\n])/\\$1/g;
130 59         192 return $_;
131             }
132              
133 7     7   38 BEGIN { _export qw(quoteRaw quote) }
134             sub quoteRaw($) {
135 59     59 0 96 local $_ = $_[0];
136 59 100       236 return undef if /[\\\[\]"'=,\s\n]/;
137 49         131 return $_;
138             }
139              
140 7     7   24 BEGIN { _export qw(quote quote) }
141             sub quote($) {
142 207 50       642 my @q = sort {
143 216         508 (length($a) <=> length($b)) or ($a cmp $b)
144             } grep {
145 54     54 0 209 defined $_
146             } (quoteQ $_[0], quoteQQ $_[0], quoteBS $_[0], quoteRaw $_[0]);
147 54         326 return $q[0];
148             }
149              
150 7     7   23 BEGIN { _export qw(encodeHTML encode); }
151             sub encodeHTML($) {
152 122     122 0 372 local $_ = $_[0];
153 122 50       344 if(defined $_) {
154             # Basic HTML/XML escapes
155 122         7767 s/&/&/g;
156 122         227 s/
157 122         175 s/>/>/g;
158 122         146 s/"/"/g;
159             # ' is XML-only
160 122         182 s/'/'/g;
161             }
162 122         484 return $_;
163             }
164              
165 7     7   25 BEGIN { _export qw(decodeHTML encode); }
166             sub decodeHTML($) {
167 0     0 0 0 return HTML::Entities::decode($_[0]);
168             }
169              
170 7     7   23 BEGIN { _export qw(parseBool parse) }
171             sub parseBool($) {
172 17     17 0 4930 local $_ = $_[0];
173 17 50       57 return undef if not defined $_;
174 17 50 33     55 return $_->as_bool() if ref $_ and UNIVERSAL::can($_, 'as_bool');
175 17 100       124 return 1 if /^(?:
176             1 |
177             T | TR | TRU | TRUE |
178             Y | YE | YES |
179             ON
180             )$/ix;
181 4 50       29 return 0 if /^(?:
182             0 |
183             F | FA | FAL | FALS | FALSE |
184             N | NO |
185             OFF
186             )$/ix;
187 0 0       0 return $_ ? 1 : 0;
188             }
189              
190 7     7   32 BEGIN { _export qw(parseInt parse) }
191             sub parseInt($) {
192 53     53 0 4722 my $num = shift;
193 53 50       158 return undef if not defined $num;
194 53         106 $num =~ s/[\s,_]+//g;
195 53         75 $num =~ s/^\+//;
196 53 100       205 return 0 if $num =~ /^-?$/;
197 47 100       352 return 0+$1 if $num =~ /^ ( -? \d+ ) $/x;
198 1         7 return undef;
199             }
200              
201 7     7   26 BEGIN { _export qw(parseNum parse) }
202             sub parseNum($);
203             sub parseNum($) {
204 48     48 0 7829 my $num = shift;
205 48 50       120 return undef if not defined $num;
206 48         119 $num =~ s/[\s,_]+//g;
207 48 100       209 if($num =~ /^ (.*) e (.*) $/ix) {
208 4         14 my($m,$e) = ($1,$2);
209 4         12 $m = parseNum $m;
210 4         10 $e = parseNum $e;
211 4 50 33     118 return $m * (10 ** $e) if defined $m and defined $e;
212 0         0 return undef;
213             }
214 44 100       237 if($num =~ /^ ([^.]*) \. ([^.]*) $/x) {
215 13         40 my($i,$f) = ($1,$2);
216 13         27 $i = parseInt $i;
217 13 50       35 return undef unless defined $i;
218 13 50       49 return undef unless $f =~ /^(\d*)$/;
219 13         30 $num = "$i.$f";
220 13         25 $num =~ s/\.$//;
221 13         65 return 0+$num;
222             }
223 31         74 return parseInt($num);
224             }
225              
226 7     7   24 BEGIN { _export qw(parseEntity parse) }
227             sub parseEntity($);
228             sub parseEntity($) {
229 15     15 0 8435 local $_ = $_[0];
230 15 50       39 return undef unless defined $_;
231 15         75 s/^&(.*);$/$1/;
232 15         38 s/^#([xob])/0$1/i;
233 15         60 s/^#//;
234 15         23 s/^U\+/0x/;
235              
236 15         17 my $ch;
237 15 100       130 if(/^ 0x ([0-9A-F]+) $/xi) {
    100          
    100          
    50          
    100          
238 3         7 $ch = hex($1);
239             } elsif(/^ 0o ([0-7]+) $/xi) {
240 1         4 $ch = oct($1);
241             } elsif(/^ 0b ([01]+) $/xi) {
242 1         7 my $b = ("\0" x 4) . pack("B*", $1);
243 1         7 $ch = unpack "N", substr($b, -4);
244             } elsif(/^ 0 ([0-7]{3}) $/x) {
245 0         0 $ch = oct($1);
246             } elsif(/^ (\d+) $/x) {
247 1         4 $ch = 0+$1;
248             }
249 15 100       71 return sprintf "#x%X", $ch if defined $ch;
250              
251 9         92 my $decoded = HTML::Entities::decode("&$_;");
252 9 100       43 return undef if $decoded eq "&$_;";
253 8         38 return $_;
254             }
255              
256 7     7   26 BEGIN { _export qw(parseListType parse) }
257             my %listtype = (
258             '*' => [ qw(ul) ],
259             '1' => [ qw(ol decimal) ],
260             '01' => [ qw(ol decimal-leading-zero) ],
261             'A' => [ qw(ol upper-latin) ],
262             'a' => [ qw(ol lower-latin) ],
263             'I' => [ qw(ol upper-roman) ],
264             'i' => [ qw(ol lower-roman) ],
265             "\x{3B1}" => [ qw(ol lower-greek) ],
266             "\x{5D0}" => [ qw(ol hebrew) ],
267             "\x{3042}" => [ qw(ol hiragana) ],
268             "\x{3044}" => [ qw(ol hiragana-iroha) ],
269             "\x{30A2}" => [ qw(ol katakana) ],
270             "\x{30A4}" => [ qw(ol katakana-iroha) ],
271             );
272             sub parseListType($) {
273 9     9 0 18 local $_ = $_[0];
274 9         14 my @ret;
275 9 100       27 if(defined $_) {
276 1 50       11 if(/^(disc|circle|square|none)$/i) {
    50          
    50          
277 0         0 @ret = ('ul', lc $1);
278             } elsif(/^(
279             decimal(?:-leading-zero)? |
280             (?:upper|lower)-(?:roman|latin|alpha) |
281             lower-greek |
282             hebrew |
283             georgian |
284             armenian |
285             cjk-ideographic |
286             (?:hiragana|katakana)(?:-iroha)?
287             )$/ix) {
288 0         0 @ret = ('ol', lc $1);
289             } elsif(exists $listtype{$_}) {
290 1         2 @ret = @{$listtype{$_}};
  1         4  
291             }
292             }
293 9         35 return @ret;
294             }
295              
296             # Conversion factors from CSS units to points
297             my %conv = (
298             # Integer conversions within English units
299             pt => 1,
300             pc => 12,
301             in => 72,
302              
303             # Floating-point conversions from Metric units
304             mm => 72/25.4,
305             cm => 72/2.54,
306              
307             # Somewhat approximate, but the CSS standard is actually rather
308             # picky about how many pixels a 'pixel' is at different resolutions,
309             # so this is actually relatively reliable.
310             px => 0.75,
311             );
312              
313             # Emulation of ... from HTML 3.2
314             # See
315             # Tweaked slightly to be more logical
316             my @compat = qw(xx-small x-small small medium large x-large xx-large 300%);
317              
318 7     7   52 BEGIN { _export qw(parseFontSize parse) }
319             sub parseFontSize($;$$$);
320             sub parseFontSize($;$$$) {
321 45     45 0 31297 local $_ = shift;
322 45 50       200 return undef unless defined $_;
323 45         66 my($base,$lo,$hi) = @_;
324 45 100       165 $base = 12 if not defined $base;
325 45 100       91 $lo = 8 if not defined $lo;
326 45 100       87 $hi = 72 if not defined $hi;
327 45         181 s/\s+/ /g;
328 45         287 s/^\s|\s$//g;
329              
330             # CSS 2.1 15.7
331 45 100       151 if(/^( (?:xx?-)? (?:large|small) | medium )$/ix) {
332 3         18 return lc $1;
333             }
334              
335             # CSS 2.1 15.7
336             # Note: Since [FONT] is nestable and not readily computable before HTML
337             # rendering, this can allow a malicious user to escape the
338             # admin-defined font size limits
339 42 100       263 if(/^ ( larger | smaller ) $/ix) {
340 2         13 return lc $1;
341             }
342              
343             # CSS 2.1 4.3.2
344 40 100       175 if(/^ ( [\s\d._+-]+ ) ( [a-z]+ ) $/ix) {
345 17         66 my($n,$unit) = ($1,lc $2);
346 17         48 $n = parseNum $n;
347 17 50 33     102 if(defined $n and $n > 0) {
348 17         23 my $conv;
349 17 100       53 if(exists $conv{$unit}) {
    100          
    50          
350 15         31 $conv = $conv{$unit};
351             } elsif($unit =~ /^em$/i) {
352 1         3 $conv = $base;
353             } elsif($unit =~ /^ex$/i) {
354 1         5 $conv = $base * 0.5;
355             } else {
356 0         0 return undef;
357             }
358 17         122 my $n2 = $n * $conv;
359 17 100 66     252 if(defined $lo and $n2 < $lo) {
    100 66        
360 2         8 $n = $lo / $conv;
361             } elsif(defined $hi and $n2 > $hi) {
362 3         5 $n = $hi / $conv;
363             }
364 17         205 $n = sprintf "%.3f", $n;
365 17         122 $n =~ s/0+$//;
366 17         53 $n =~ s/\.$//;
367 17         91 return "$n$unit";
368             } else {
369 0         0 return undef;
370             }
371             }
372              
373             # CSS 2.1 4.3.3
374             # Note: The same concerns apply as for
375 23 100       76 if(/^ ( [\s\d._+-]+ ) % $/x) {
376 9         26 my $n = parseNum $1;
377 9 50 33     59 if(defined $n and $n > 0) {
378 9         15 $n *= 0.01;
379 9         18 my $n2 = $n * $base;
380 9 100 66     70 if(defined $lo and $n2 < $lo) {
    100 66        
381 1         5 $n = $lo / $base;
382             } elsif(defined $hi and $n2 > $hi) {
383 1         3 $n = $hi / $base;
384             }
385 9         11 $n *= 100;
386 9         76 $n = sprintf "%.3f", $n;
387 9         40 $n =~ s/0+$//;
388 9         27 $n =~ s/\.$//;
389 9         55 return "$n%";
390             } else {
391 0         0 return undef;
392             }
393             }
394              
395             # HTML 3.2
396             # See
397 14 100       61 if(/^ (\d+) $/x) {
398 8         24 my $n = 0+$1;
399 8 50 33     115 if($n >= 0 and $n < @compat) {
400 8         49 return $compat[$n];
401             } else {
402 0         0 return parseFontSize("$n pt",$base,$lo,$hi);
403             }
404             }
405              
406             # HTML 3.2
407 6 100       24 if(/^ \+ (\d+) $/x) {
408             # "+1" is roughly equivalent to CSS 2.1 "larger"
409 2         34 my $n = sprintf "%f%%", 100 * (1.25 ** $1);
410 2         18 return parseFontSize($n,$base,$lo,$hi);
411             }
412              
413             # HTML 3.2
414 4 100       17 if(/^ - (\d+) $/x) {
415             # "-1" is roughly equivalent to CSS 2.1 "smaller"
416 2         79 my $n = sprintf "%f%%", 100 * (0.85 ** $1);
417 2         7 return parseFontSize($n,$base,$lo,$hi);
418             }
419              
420 2         9 return undef;
421             }
422              
423             # Official CSS 2.1 colors are passed through as-is
424             my %cssColor = map { $_ => 1 } qw(
425             maroon red orange yellow olive
426             purple fuchsia white lime green
427             navy blue aqua teal
428             black silver gray
429             );
430              
431             # Other named colors must map to an official named color or an #RRGGBB color
432             my %extraColor = (
433             darkred => 'maroon',
434             darkblue => 'navy',
435             );
436              
437 7     7   27 BEGIN { _export qw(parseColor parse) }
438             sub parseColor($) {
439 8     8 0 20 local $_ = $_[0];
440 8 50       30 return undef unless defined $_;
441 8         21 s/\s+//g;
442 8         16 $_ = lc $_;
443              
444 8 100 100     73 return $1 if /^(\w+)$/ and exists $cssColor{$1};
445 3 50       14 return $extraColor{$_} if exists $extraColor{$_};
446              
447 3 100       13 if(s/^#//) {
448 1         10 s/^ ( [0-9a-f]{1,2} ) $/$1$1$1/x;
449 1         4 s/^ ([0-9a-f]) \1 ([0-9a-f]) \2 ([0-9a-f]) \3 $/$1$2$3/x;
450              
451 1 50       5 return "#$_" if /^ [0-9a-f]{3} $/x;
452 1 50       9 return "#$_" if /^ [0-9a-f]{6} $/x;
453             } else {
454 2 50       11 return $1 if /^( rgb \( (?: \d+ , ){2} \d+ \) )$/x;
455 2 50       11 return $1 if /^( rgba\( (?: \d+ , ){3} \d+ \) )$/x;
456 2 50       10 return $1 if /^( rgb \( (?: \d+% , ){2} \d+% \) )$/x;
457 2 100       17 return $1 if /^( rgba\( (?: \d+% , ){3} \d+% \) )$/x;
458             }
459 1         4 return undef;
460             }
461              
462             sub _url_parse_opaque($) {
463 35     35   62 local $_ = $_[0];
464 35         103 my @ret = (undef) x 3;
465              
466 35 50       139 $ret[2] = $1 if s/(#.*)$//;
467 35 100       252 $ret[0] = lc $1 if s/^([\w+-]+)://;
468 35         106 $ret[1] = $_;
469              
470 35 50       182 return @ret if wantarray;
471 0         0 return \@ret;
472             }
473              
474             sub _url_parse_query($) {
475 25     25   51 local $_ = $_[0];
476 25         60 my @ret = (undef) x 2;
477              
478 25 100       93 $ret[1] = $1 if s/(\?.*)$//;
479 25         46 $ret[0] = $_;
480              
481 25 50       89 return @ret if wantarray;
482 0         0 return \@ret;
483             }
484              
485             sub _url_parse_path($) {
486 19     19   39 local $_ = $_[0];
487 19         47 my @ret = (undef) x 2;
488              
489 19 50       99 if(s#^//##) {
    0          
490 19 50       113 $ret[0] = $1 if s#^([^/]+)##;
491 19         58 s#^$#/#;
492 19         44 $ret[1] = $_;
493             } elsif(m#^/#) {
494 0         0 $ret[1] = $_;
495             } else {
496 0 0       0 return () if wantarray;
497 0         0 return undef;
498             }
499              
500 19 50       96 return @ret if wantarray;
501 0         0 return \@ret;
502             }
503              
504             sub _url_parse_server($) {
505 19     19   50 local $_ = $_[0];
506 19         25 my($userpass,$hostport);
507              
508 19 50       76 if(/^ ([^@]*) \@ ([^@]*) $/x) {
509 0         0 ($userpass,$hostport) = ($1,$2);
510             } else {
511 19         35 $hostport = $_;
512             }
513              
514 19         54 my @ret = (undef) x 4;
515              
516 19         35 $_ = $userpass;
517 19 50       92 if(defined $_) {
518 0 0       0 if(/^ ([^:]*) : ([^:]*) $/x) {
519 0         0 @ret[0,1] = ($1,$2);
520             } else {
521 0         0 $ret[0] = $_;
522             }
523             }
524              
525 19         30 $_ = $hostport;
526 19 50       100 if(s/:(\d+)$//) {
    50          
527 0         0 $ret[3] = $1;
528             } elsif(s/:([\w+-]+)$//) {
529 0         0 $ret[3] = getservbyname($1,'tcp');
530 0 0       0 goto Failure if not defined $ret[3];
531             } else {
532 19         44 s/:$//;
533             }
534              
535 19         112 s/\.*$/./;
536 19 100       140 if(/^ ( (?: [\w-]+ \. )+ ) $/x) {
537 15         39 $ret[2] = $1;
538 15         57 $ret[2] =~ s/\.$//;
539             }
540              
541 19 100       65 goto Failure if not defined $ret[2];
542 15 50       73 return @ret if wantarray;
543 0         0 return \@ret;
544              
545 4 50       19 Failure:
546             return () if wantarray;
547 0         0 return undef;
548             }
549              
550             my %urltype = (
551             'http' => 3,
552             'https' => 3,
553             'ftp' => 3,
554              
555             'file' => 2,
556              
557             'mailto' => 1,
558              
559             'data' => 0,
560             'javascript' => 0,
561             );
562              
563             sub _url_parse($$) {
564 35     35   70 my($str,$schemes) = @_;
565              
566 35         108 my($scheme,$opaque,$fragment) = _url_parse_opaque($str);
567 35 100       119 return undef unless defined $scheme;
568 30 50       88 return undef unless exists $urltype{$scheme};
569              
570 30 100       137 if($urltype{$scheme} > 0) {
571 25         71 my($rest,$query) = _url_parse_query($opaque);
572              
573 25 100       80 if($urltype{$scheme} > 1) {
574 19         529 my($auth,$path) = _url_parse_path($rest);
575 19 50       66 return undef unless defined $path;
576              
577 19 50       59 if($urltype{$scheme} > 2) {
578 19 50       53 return undef unless defined $auth;
579 19         68 my($user,$pass,$host,$port) = _url_parse_server($auth);
580 19 100       73 return undef unless defined $host;
581              
582 15         28 $auth = '';
583 15 50       40 if(defined $user) {
584 0         0 $auth .= $user;
585 0 0       0 $auth .= ':'.$pass if defined $pass;
586 0         0 $auth .= '@';
587             }
588 15         28 $auth .= $host;
589 15 50       42 $auth .= ':'.$port if defined $port;
590             }
591              
592 15 50       28 $rest = join '', map { defined $_ ? $_ : '' } ('//',$auth,$path);
  45         149  
593             }
594              
595 21 100       63 $opaque = join '', map { defined $_ ? $_ : '' } ($rest,$query);
  42         145  
596             }
597 26 50       107 $str = $scheme.':'.$opaque.(defined $fragment ? $fragment : '');
598              
599 26         211 my $url = URI->new_abs($str, 'http://sanity.check.example.com/')->canonical;
600 26 50       61585 return undef unless defined $url->scheme;
601 26 100       470 return undef unless exists $$schemes{$url->scheme};
602 22 50       379 return undef if $url->as_string =~ /\bsanity\.check\.example\.com\b/i;
603 22 50 66     364 return undef if $url->can('userinfo') and defined $url->userinfo;
604 22 50 66     435 return undef if $url->can('host') and not defined $url->host;
605 22 100       406 if($url->scheme eq 'mailto') {
606 6         96 my %unsafe = $url->headers;
607 6         793 my %safe;
608 6         23 foreach my $key (keys %unsafe) {
609 6 50       39 if($key =~ /^(?:to|cc|bcc)$/i) {
610 6         25 my @to = split /,/, $unsafe{$key};
611 6         17 $key = lc $key;
612 6         13 foreach(@to) {
613 6 100       40 if(/^ ( [\w.+-]+ \@ (?: \w[\w-]*(?<=\w) \. )+ [a-z]{2,6} ) $/xi) {
614 2 50       21 if(exists $safe{$key}) {
615 0         0 $safe{$key} .= ",$1";
616             } else {
617 2         10 $safe{$key} = $1;
618             }
619             }
620             }
621 6         24 next;
622             }
623 0 0       0 if($key =~ /^subject$/i) {
624 0 0       0 if($unsafe{$key} =~ /^ ( [\x20-\x7E]+ ) $/x) {
625 0         0 $safe{subject} = $1;
626             }
627 0         0 next;
628             }
629             }
630 6 100       33 return undef unless exists $safe{to};
631 2         9 $url->headers(%safe);
632             }
633 18         447 return $url;
634             }
635              
636 7     7   50 BEGIN { _export qw(parseURL parse) }
637             my %schemes = map { $_ => 1 } qw(http https ftp mailto data);
638             sub parseURL($) {
639 20     20 0 60 foreach('%', 'http://%', 'mailto:%') {
640 32         53 my $str = $_;
641 32         197 $str =~ s/%/$_[0]/g;
642 32         143 my $url = _url_parse($str, \%schemes);
643 32 100       224 return $url if defined $url;
644             }
645 4         13 return undef;
646             }
647              
648 7     7   35 BEGIN { _export qw(parseMailURL parse) }
649             my %mail_schemes = (mailto => 1);
650             sub parseMailURL($) {
651 2     2 0 6 foreach('%', 'mailto:%') {
652 3         6 my $str = $_;
653 3         19 $str =~ s/%/$_[0]/g;
654 3         13 my $url = _url_parse($str, \%mail_schemes);
655 3 100       18 return $url if defined $url;
656             }
657 0         0 return undef;
658             }
659              
660 7     7   30 BEGIN { _export qw(multilineText text) }
661             sub multilineText {
662 518 50   518 0 1557 if(defined wantarray) {
663 518         1156 my $str = join "", @_;
664 518 100       2859 return $str unless wantarray;
665 4         34 return split /(?<=\n)/, $str;
666             }
667             }
668              
669 7     7   29 BEGIN { _export qw(textURL text) }
670             sub textURL($) {
671 0     0 0   my $url = shift;
672 0 0         $url = parseURL($url) if not ref $url;
673 0 0         return undef if not defined $url;
674 0 0         if($url->scheme eq 'mailto') {
675 0           return $url->to;
676             }
677 0 0 0       if($url->scheme eq 'http' or $url->scheme eq 'https') {
678 0 0 0       if(not defined $url->query or $url->query eq '') {
679 0 0 0       if($url->path eq '' or $url->path eq '/') {
680 0           return $url->host;
681             }
682 0           return $url->host.$url->path;
683             }
684             }
685 0 0         if($url->scheme eq 'ftp') {
686 0           return $url->path.' on '.$url->host.' (FTP)';
687             }
688 0 0         if($url->scheme eq 'data') {
689 0           my $m = $url->media_type;
690 0 0         if(defined $m) {
691 0           $m =~ s/;.*$//;
692 0           return "Inline data ($m)";
693             }
694 0           return "Inline data";
695             }
696 0           return $url->as_string;
697              
698             }
699              
700 7     7   25 BEGIN { _export qw(textALT text) }
701             sub textALT($) {
702 0     0 0   my $url = shift;
703 0 0         $url = parseURL($url) if not ref $url;
704 0 0         return undef if not defined $url;
705 0 0         if($url->scheme eq 'data') {
706 0           return "[Inline data]";
707             }
708 0           my $path = $url->path;
709 0           $path =~ s#^.*/##;
710 0           return "[$path]";
711             }
712              
713             sub _b10_len($) {
714 0     0     my $n = shift;
715 0 0         if($n > 0) {
716 0           return 1+POSIX::floor(log($n)/log(10));
717             }
718 0 0         if($n < 0) {
719 0           return 2+POSIX::floor(log(-$n)/log(10));
720             }
721 0           return 1;
722             }
723              
724             sub _max {
725 0     0     my $max;
726 0           while(@_) {
727 0           my $val = shift;
728 0 0 0       $max = $val if defined $val and (not defined $max or $val > $max);
      0        
729             }
730 0           return $max;
731             }
732              
733 7     7   28 BEGIN { _export qw(createListSequence) }
734             sub createListSequence($;$$) {
735 0     0 0   my($type,$start,$total) = @_;
736 0           my @list = parseListType($type);
737 0 0         $start = 1 unless defined $start;
738              
739 0 0 0       if(@list and $list[0] eq 'ol') {
740 0 0         my $type = (@list > 1) ? $list[1] : 'decimal';
741              
742 0           if(0) {
743             # Disabled until the generators can be split into separate packages
744             if($type =~ /^(upper|lower)-(alpha|latin|roman|greek)$/i) {
745             my $func = 'textOrder'.ucfirst(lc($2));
746             my $uc = $1 =~ /^upper$/i;
747             $func =~ s/Latin$/Alpha/;
748             {
749 7     7   93 no strict 'refs';
  7         12  
  7         1724  
750             $func = \&{$func};
751             }
752             if($uc) {
753 0     0     return sub { $func->($start++).'.' };
754             } else {
755 0     0     return sub { lc $func->($start++).'.' };
756             }
757             }
758             if($type =~ /^(hiragana|katakana)(?:-(iroha))?$/i) {
759             my $func = 'textOrder'.ucfirst(lc($1)).(defined $2 ? uc($2) : '');
760             {
761 7     7   41 no strict 'refs';
  7         12  
  7         4456  
762             $func = \&{$func};
763             }
764 0     0     return sub { $func->($start++).'.' };
765             }
766             if($type =~ /^cjk-ideographic$/i) {
767 0     0     return sub { textOrderCJK($start++).'.' };
768             }
769             if($type =~ /^hebrew$/i) {
770 0     0     return sub { textOrderHebrew($start++).'.' };
771             }
772             if($type =~ /^georgian$/i) {
773 0     0     return sub { textOrderGeorgian($start++).'.' };
774             }
775             if($type =~ /^armenian$/i) {
776 0     0     return sub { textOrderArmenian($start++).'.' };
777             }
778             }
779              
780 0 0         if($type =~ /^decimal-leading-zero$/i) {
781 0 0         if(defined $total) {
782 0           my $end = $total + $start - 1;
783 0           my $len = _max 3, 1+_b10_len(abs $start), 1+_b10_len(abs $end);
784 0           my $fmt = sprintf '%% 0%dd.', $len;
785 0     0     return sub { sprintf($fmt,$start++) };
  0            
786             } else {
787 0     0     return sub { sprintf("% 03d.", $start++) };
  0            
788             }
789             }
790              
791 0 0         if(defined $total) {
792 0           my $end = $total + $start - 1;
793 0           my $len = _max _b10_len $start, _b10_len $end;
794 0           my $fmt = sprintf '%%%dd.', $len;
795 0     0     return sub { sprintf($fmt,$start++) };
  0            
796             } else {
797 0     0     return sub { sprintf("%d.",$start++) };
  0            
798             }
799             }
800 0     0     return sub { '*' };
  0            
801             }
802              
803             BEGIN {
804 7 50   7   45 push @EXPORT_OK, @{$EXPORT_TAGS{ALL}} if exists $EXPORT_TAGS{ALL};
  7         130  
805 7 50       349 push @EXPORT, @{$EXPORT_TAGS{DEFAULT}} if exists $EXPORT_TAGS{DEFAULT};
  0         0  
806             }
807              
808             1;