File Coverage

blib/lib/Test/CGI/Multipart.pm
Criterion Covered Total %
statement 129 131 98.4
branch 17 20 85.0
condition n/a
subroutine 23 23 100.0
pod 7 7 100.0
total 176 181 97.2


line stmt bran cond sub pod time code
1             package Test::CGI::Multipart;
2              
3 11     11   423444 use warnings;
  11         29  
  11         388  
4 11     11   60 use strict;
  11         24  
  11         391  
5 11     11   56 use Carp;
  11         23  
  11         1127  
6 11     11   10403 use UNIVERSAL::require;
  11         19679  
  11         114  
7 11     11   11949 use Params::Validate qw(:all);
  11         127349  
  11         2748  
8 11     11   13716 use MIME::Entity;
  11         1659530  
  11         200  
9 11     11   11806 use Readonly;
  11         37135  
  11         751  
10             require 5.006_001; # we use 3-arg open in places
11              
12 11     11   10336 use version; our $VERSION = qv('0.0.3');
  11         25626  
  11         92  
13              
14             # Module implementation here
15              
16             # Make callbacks a package variable as then loading callbacks
17             # will be prettier.
18             my @callbacks;
19              
20             # Parameter specs
21             # Note the purpose of these spcs is to protect our data structures.
22             # It should not protect the code that will be tested
23             # as that must look after itself.
24             Readonly my $NAME_SPEC => {type=>SCALAR};
25             Readonly my $VALUE_SPEC => {type=>SCALAR|ARRAYREF};
26             Readonly my $UA_SPEC => {type=>SCALAR, default=> 'Test::CGI::Multipart'};
27             Readonly my $CGI_SPEC => {
28             type=>SCALAR,
29             default=>'CGI',
30             regex=> qr{
31             \A # start of string
32             (?:
33             \w
34             |(?:\:\:) # Module name separator
35             )+
36             \z # end of string
37             }xms
38             };
39             Readonly my $TYPE_SPEC => {
40             type=>SCALAR,
41             optional=>1,
42             regex=> qr{
43             \A # start of string
44             [\w\-]+ # major type
45             \/ # MIME type separator
46             [\w\-]+ # sub-type
47             \z # end of string
48             }xms
49             };
50             Readonly my $FILE_SPEC => {
51             type=>SCALAR,
52             };
53             Readonly my $MIME_SPEC => {
54             type=>OBJECT,
55             isa=>'MIME::Entity',
56             };
57             Readonly my $CODE_SPEC => {
58             type=>CODEREF,
59             };
60              
61             # MIME parsing states
62             Readonly my $TYPE_STATE => 0;
63             Readonly my $HEADER_STATE => 1;
64             Readonly my $DATA_STATE=> 2;
65             Readonly my $EOL => "\015\012";
66              
67             sub new {
68 10     10 1 15006 my $class = shift;
69 10         59 my $self = {
70             file_index=>0,
71             params=>{},
72             };
73 10         32 bless $self, $class;
74 10         32 return $self;
75             }
76              
77             sub set_param {
78 14     14 1 9317 my $self = shift;
79 14         96 my %params = validate(@_, {name=>$NAME_SPEC, value=>$VALUE_SPEC});
80 7         35 my @values = ref $params{value} eq 'ARRAY'
81 14 100       5745 ? @{$params{value}}
82             : $params{value}
83             ;
84 14         103 $self->{params}->{$params{name}} = \@values;
85 14         133 return;
86             }
87              
88             sub upload_file {
89 17     17 1 23895 my $self = shift;
90 17         94 my %params = @_;
91 17         31 my $params = \%params;
92              
93 17         58 foreach my $code (@callbacks) {
94 17         164 $params = &$code($params);
95             }
96              
97 15         112 $self->_upload_file(%$params);
98              
99 12         3445 return;
100             }
101              
102              
103             sub _upload_file {
104 15     15   24 my $self = shift;
105 15         133 my %params = validate(@_, {
106             name=>$NAME_SPEC,
107             value=>$VALUE_SPEC,
108             file=>$FILE_SPEC,
109             type=>$TYPE_SPEC
110             });
111 13         758 my $name = $params{name};
112              
113 13 100       67 if (!exists $self->{params}->{$name}) {
114 10         44 $self->{params}->{$name} = {};
115             }
116 13 100       65 if (ref $self->{params}->{$name} ne 'HASH') {
117 1         32 croak "mismatch: is $name a file upload or not";
118             }
119              
120 12         26 my $file_index = $self->{file_index};
121              
122 12         48 $self->{params}->{$name}->{$file_index} = \%params;
123              
124 12         34 $self->{file_index}++;
125              
126 12         36 return;
127             }
128              
129             sub get_param {
130 65     65 1 92131 my $self = shift;
131 65         458 my %params = validate(@_, {name=>$NAME_SPEC});
132 65         2051 my $name = $params{name};
133 65 100       406 if (ref $self->{params}->{$name} eq 'HASH') {
134 23         46 return values %{$self->{params}->{$name}};
  23         158  
135             }
136 42         65 return @{$self->{params}->{$name}};
  42         282  
137             }
138              
139             sub get_names {
140 46     46 1 9983 my $self = shift;
141 46         81 return keys %{$self->{params}};
  46         323  
142             }
143              
144             sub create_cgi {
145 11     11   32951 use autodie qw(open);
  11         238896  
  11         77  
146 20     20 1 21805 my $self = shift;
147 20         152 my %params = validate(@_, {cgi=>$CGI_SPEC, ua=>$UA_SPEC});
148              
149 20         1293 my $mime = $self->_mime_data;
150 18         135 my $mime_str = $mime->stringify;
151 18         127498 my $mime_string = $self->_normalize1($mime_str);
152 18         81 my $boundary = $mime->head->multipart_boundary;
153              
154 18         2416 $ENV{REQUEST_METHOD}='POST';
155 18         104 $ENV{CONTENT_TYPE}="multipart/form-data; boundary=$boundary";
156 18         97 $ENV{CONTENT_LENGTH}=length($mime_string);
157 18         164 $ENV{HTTP_USER_AGENT}=$params{ua};
158              
159             # Would like to localize these but this causes problems with CGI::Simple.
160 18         85 local *STDIN;
161 18         125 open(STDIN, '<', \$mime_string);
162 18         25945 binmode STDIN;
163              
164 18         223 $params{cgi}->require;
165              
166 18 50       132817 if ($params{cgi} eq 'CGI::Simple') {
167 0         0 $CGI::Simple::DISABLE_UPLOADS = 0;
168             }
169 18 50       105 if ($params{cgi} eq 'CGI') {
170 18         81 CGI::initialize_globals();
171             }
172 18 50       1735 if ($params{cgi} eq 'CGI::Minimal') {
173 0         0 CGI::Minimal::reset_globals();
174             }
175              
176 18         113 my $cgi = $params{cgi}->new;
177 18         263997 return $cgi;
178             }
179              
180             sub _normalize1 {
181 18     18   45 my $self = shift;
182 18         48 my $mime_string = shift;
183 18         462 $mime_string =~ s{([\w-]+:\s+[^\n]+)\n\n}{$1$EOL$EOL}xmsg;
184 18         2000 $mime_string =~ s{\n([\w-]+:\s+)}{$EOL$1}xmsg;
185 18         2265 $mime_string =~ s{\n(-------)}{$EOL$1}xmsg;
186 18         708 return $mime_string;
187             }
188              
189             sub _mime_data {
190 20     20   49 my $self = shift;
191              
192 20         96 my $mime = $self->_create_multipart;
193 20         32371 foreach my $name ($self->get_names) {
194 50         174 my $value = $self->{params}->{$name};
195 50 100       205 if (ref($value) eq "ARRAY") {
    100          
196 28         66 foreach my $v (@$value) {
197 70         299 $self->_attach_field(
198             mime=>$mime,
199             name=>$name,
200             value=>$v,
201             );
202             }
203             }
204             elsif(ref($value) eq "HASH") {
205 20         85 $self->_encode_upload(mime=>$mime,values=>$value);
206             }
207             else {
208 2         51 croak "unexpected data structure";
209             }
210             }
211              
212             # Required so at least we don't have an empty MIME structure.
213             # And lynx at least does send it.
214             # CGI.pm seems to strip it out where as the others seem to pass it on.
215             $self->_attach_field(
216 18         126 mime=>$mime,
217             name=>'.submit',
218             value=>'Submit',
219             );
220              
221 18         58 return $mime;
222             }
223              
224             sub _attach_field {
225 88     88   136 my $self = shift;
226 88         461 my %params = validate(@_, {
227             mime => $MIME_SPEC,
228             name=>$NAME_SPEC,
229             value=>$VALUE_SPEC,
230             }
231             );
232 88         5138 $params{mime}->attach(
233             'Content-Disposition'=>"form-data; name=\"$params{name}\"",
234             Data=>$params{value},
235             );
236 88         125528 return;
237             }
238              
239             sub _create_multipart {
240 20     20   45 my $self = shift;
241 20         187 my %params = validate(@_, {});
242 20         274 return MIME::Entity->build(
243             'Type'=>"multipart/form-data",
244             );
245             }
246              
247             sub _encode_upload {
248 20     20   33 my $self = shift;
249 20         356 my %params = validate(@_, {
250             mime => $MIME_SPEC,
251             values => {type=>HASHREF}
252             });
253 20         902 my %values = %{$params{values}};
  20         103  
254 20         59 foreach my $k (keys %values) {
255 24         183 $self->_attach_file(
256             mime=>$params{mime},
257 24         55 %{$values{$k}}
258             );
259             }
260 20         95 return;
261             }
262              
263             sub _attach_file {
264 24     24   41 my $self = shift;
265 24         110 my %params = validate(@_, {
266             mime => $MIME_SPEC,
267             file=>$FILE_SPEC,
268             type=>$TYPE_SPEC,
269             name=>$NAME_SPEC,
270             value=>$VALUE_SPEC,
271             }
272             );
273 24         1879 my %attach = (
274             'Content-Disposition'=>
275             "form-data; name=\"$params{name}\"; filename=\"$params{file}\"",
276             Data=>$params{value},
277             Encoding=>'binary',
278             );
279 24 100       99 if ($params{type}) {
280 15         38 $attach{Type} = $params{type};
281             }
282             $params{mime}->attach(
283 24         119 %attach
284             );
285 24         27916 return;
286             }
287              
288             sub register_callback {
289 4     4 1 10069 my $self = shift;
290 4         31 my %params = validate(@_, {
291             callback => $CODE_SPEC,
292             }
293             );
294 4         190 push @callbacks, $params{callback};
295 4         16 return;
296             }
297              
298              
299             1; # Magic true value required at end of module
300             __END__