File Coverage

blib/lib/CGI/Expand.pm
Criterion Covered Total %
statement 93 94 98.9
branch 37 44 84.0
condition 3 3 100.0
subroutine 15 15 100.0
pod 7 7 100.0
total 155 163 95.0


line stmt bran cond sub pod time code
1             package CGI::Expand;
2             $VERSION = '2.05';
3 1     1   65524 use strict;
  1         2  
  1         46  
4 1     1   4 use warnings;
  1         1  
  1         90  
5              
6             # NOTE: Exporter is not actually used
7             our @EXPORT = qw(expand_cgi);
8             our @EXPORT_OK = qw(expand_hash collapse_hash);
9             my %is_exported = map { $_ => 1 } @EXPORT, @EXPORT_OK;
10              
11 1     1   5 use Carp qw(croak carp);
  1         5  
  1         281  
12              
13             sub import {
14 9     9   9815 my $from_pkg = shift;
15 9         37 my $to_pkg = caller;
16              
17 9 100       179 if(@_) {
18 5         9 for my $sub (@_) {
19 10 50       37 croak "Can't export symbol $sub" unless $is_exported{$sub};
20             }
21             } else {
22 4         15 @_ = @EXPORT;
23             }
24              
25 9         24 _export_curried($from_pkg, $to_pkg, @_);
26             }
27              
28             sub _export_curried {
29 9     9   19 my $from_pkg = shift;
30 9         12 my $to_pkg = shift;
31              
32 1     1   13 no strict 'refs';
  1         2  
  1         1616  
33 9         18 for my $sub (@_) {
34             # export requested subs with class arg curried
35 14     59   58 *{$to_pkg.'::'.$sub} = sub { $from_pkg->$sub(@_) };
  14         90  
  59         120004  
36             # get inherited implementation with interface backward compatibility
37             }
38             }
39              
40             sub separator {
41 258 100   258 1 1567 if( defined $CGI::Expand::Separator ) {
42 18 50       38 carp '$CGI::Expand::Separator is deprecated'
43             unless $CGI::Expand::BackCompat;
44 18         46 return $CGI::Expand::Separator;
45             }
46 240         442 return '.';
47             }
48              
49             sub max_array {
50 186 100   186 1 336 if( defined $CGI::Expand::Max_Array ) {
51 17 50       36 carp '$CGI::Expand::Max_Array is deprecated'
52             unless $CGI::Expand::BackCompat;
53 17         97 return $CGI::Expand::Max_Array;
54             }
55 169         1757 return 100;
56             }
57              
58             sub expand_cgi {
59 14     14 1 31 my $class = shift;
60 14         20 my $cgi = shift; # CGI or Apache::Request
61 14         24 my %args;
62              
63             # permit multiple values CGI style
64 14         44 for ($cgi->param) {
65 53 100       372 next if (/\.[xy]$/); # img_submit=val & img_submit.x=20 -> clash
66 51         126 my @vals = $cgi->param($_);
67 51 100       821 $args{$_} = @vals > 1 ? \@vals : $vals[0];
68             }
69 14         66 return $class->expand_hash(\%args);
70             }
71              
72             sub split_name {
73 133     133 1 155 my $class = shift;
74 133         159 my $name = shift;
75 133         254 my $sep = $class->separator();
76 133         209 $sep = "\Q$sep";
77              
78             # These next two regexes are the escaping aware equivalent
79             # to the following:
80             # my ($first, @segments) = split(/\./, $name, -1);
81              
82             # m// splits on unescaped '.' chars. Can't fail b/c \G on next
83             # non ./ * -> escaped anything -> non ./ *
84 133         3136 $name =~ m/^ ( [^\\$sep]* (?: \\(?:.|$) [^\\$sep]* )* ) /gx;
85 133         257 my $first = $1;
86 133         843 $first =~ s/\\(.)/$1/g; # remove escaping
87              
88 133         869 my (@segments) = $name =~
89             # . -> ( non ./ * -> escaped anything -> non ./ * )
90             m/\G (?:[$sep]) ( [^\\$sep]* (?: \\(?:.|$) [^\\$sep]* )* ) /gx;
91             # Escapes removed later, can be used to avoid using as array index
92              
93 133         509 return ($first, @segments);
94             }
95              
96             sub expand_hash {
97 51     51 1 1482 my $class = shift;
98 51         63 my $flat = shift;
99 51         78 my $deep = {};
100 51         165 my $sep = $class->separator;
101              
102 51         182 for my $name (keys %$flat) {
103              
104 141         308 my ($first, @segments) = $class->split_name($name);
105              
106 141         399 my $box_ref = \$deep->{$first};
107 141         257 for (@segments) {
108 133 100 100     2730 if($class->max_array && /^(0|[1-9]\d*)$/) {
109 67 100       148 croak "CGI param array limit exceeded $1 for $name=$_"
110             if($1 >= $class->max_array);
111 65 100       167 $$box_ref = [] unless defined $$box_ref;
112 65 100       220 croak "CGI param clash for $name=$_"
113             unless ref $$box_ref eq 'ARRAY';
114 61         1905 $box_ref = \($$box_ref->[$1]);
115             } else {
116 66 50       1341 s/\\(.)/$1/g if $sep; # remove escaping
117 66 100       161 $$box_ref = {} unless defined $$box_ref;
118 66 100       212 croak "CGI param clash for $name=$_"
119             unless ref $$box_ref eq 'HASH';
120 63         217 $box_ref = \($$box_ref->{$_});
121             }
122             }
123 132 100       382 croak "CGI param clash for $name value $flat->{$name}"
124             if defined $$box_ref;
125 129         315 $$box_ref = $flat->{$name};
126             }
127 39         319 return $deep;
128             }
129              
130             {
131              
132             sub collapse_hash {
133 10     10 1 20 my $class = shift;
134 10         24 my $deep = shift;
135 10         18 my $flat = {};
136              
137 10         59 $class->_collapse_hash($deep, $flat, () );
138 10         63 return $flat;
139             }
140              
141             sub join_name {
142 48     48 1 60 my $class = shift;
143 48         8930 my $sep = substr($class->separator, 0, 1);
144 48         148 return join $sep, @_;
145             }
146              
147             sub _collapse_hash {
148 102     102   126 my $class = shift;
149 102         116 my $deep = shift;
150 102         1368 my $flat = shift;
151             # @_ is now segments
152              
153 102 100       337 if(! ref $deep) {
    100          
    50          
154 56         137 my $name = $class->join_name(@_);
155 56         294 $flat->{$name} = $deep;
156             } elsif(ref $deep eq 'HASH') {
157 34         107 for (keys %$deep) {
158             # escape \ and separator chars (once only, at this level)
159 72         95 my $name = $_;
160 72 50       160 if (defined (my $sep = $class->separator)) {
161 72         137 $sep = "\Q$sep";
162 72         350 $name =~ s/([\\$sep])/\\$1/g
163             }
164 72         220 $class->_collapse_hash($deep->{$_}, $flat, @_, $name);
165             }
166             } elsif(ref $deep eq 'ARRAY') {
167 12 50       38 croak "CGI param array limit exceeded $#$deep for ",
168             $class->join_name(@_)
169             if($#$deep+1 >= $class->max_array);
170              
171 12         29 for (0 .. $#$deep) {
172 24 100       90 $class->_collapse_hash($deep->[$_], $flat, @_, $_)
173             if defined $deep->[$_];
174             }
175             } else {
176 0           croak "Unknown reference type for ",$class->join_name(@_),":",ref $deep;
177             }
178             }
179              
180             }
181              
182             1;
183             __END__