File Coverage

blib/lib/HTTP/Body/MultiPart.pm
Criterion Covered Total %
statement 116 124 93.5
branch 36 44 81.8
condition 6 6 100.0
subroutine 19 19 100.0
pod 13 13 100.0
total 190 206 92.2


line stmt bran cond sub pod time code
1             package HTTP::Body::MultiPart;
2             $HTTP::Body::MultiPart::VERSION = '1.23';
3 10     10   67 use strict;
  10         40  
  10         459  
4 10     10   56 use base 'HTTP::Body';
  10         18  
  10         1464  
5 10     10   94 use bytes;
  10         19  
  10         82  
6              
7 10     10   5924 use IO::File;
  10         11678  
  10         1638  
8 10     10   83 use File::Temp 0.14;
  10         206  
  10         1104  
9 10     10   73 use File::Spec;
  10         19  
  10         21196  
10              
11             =head1 NAME
12              
13             HTTP::Body::MultiPart - HTTP Body Multipart Parser
14              
15             =head1 SYNOPSIS
16              
17             use HTTP::Body::MultiPart;
18              
19             =head1 DESCRIPTION
20              
21             HTTP Body Multipart Parser.
22              
23             =head1 METHODS
24              
25             =over 4
26              
27             =item init
28              
29             =cut
30              
31             sub init {
32 23     23 1 74 my $self = shift;
33              
34 23 50       125 unless ( $self->content_type =~ /boundary=\"?([^\";]+)\"?/ ) {
35 0         0 my $content_type = $self->content_type;
36 0         0 Carp::croak("Invalid boundary in content_type: '$content_type'");
37             }
38              
39 23         116 $self->{boundary} = $1;
40 23         65 $self->{state} = 'preamble';
41              
42 23         120 return $self;
43             }
44              
45             =item spin
46              
47             =cut
48              
49             sub spin {
50 44     44 1 93 my $self = shift;
51              
52 44         79 while (1) {
53              
54 607 50       2827 if ( $self->{state} =~ /^(preamble|boundary|header|body)$/ ) {
55 607         1772 my $method = "parse_$1";
56 607 100       1843 return unless $self->$method;
57             }
58              
59             else {
60 0         0 Carp::croak('Unknown state');
61             }
62             }
63             }
64              
65             =item boundary
66              
67             =cut
68              
69             sub boundary {
70 658     658 1 3043 return shift->{boundary};
71             }
72              
73             =item boundary_begin
74              
75             =cut
76              
77             sub boundary_begin {
78 658     658 1 1421 return "--" . shift->boundary;
79             }
80              
81             =item boundary_end
82              
83             =cut
84              
85             sub boundary_end {
86 58     58 1 131 return shift->boundary_begin . "--";
87             }
88              
89             =item crlf
90              
91             =cut
92              
93             sub crlf () {
94 1073     1073 1 2966 return "\x0d\x0a";
95             }
96              
97             =item delimiter_begin
98              
99             =cut
100              
101             sub delimiter_begin {
102 577     577 1 949 my $self = shift;
103 577         1184 return $self->crlf . $self->boundary_begin;
104             }
105              
106             =item delimiter_end
107              
108             =cut
109              
110             sub delimiter_end {
111 58     58 1 107 my $self = shift;
112 58         157 return $self->crlf . $self->boundary_end;
113             }
114              
115             =item parse_preamble
116              
117             =cut
118              
119             sub parse_preamble {
120 23     23 1 83 my $self = shift;
121              
122 23         98 my $index = index( $self->{buffer}, $self->boundary_begin );
123              
124 23 50       105 unless ( $index >= 0 ) {
125 0         0 return 0;
126             }
127              
128             # replace preamble with CRLF so we can match dash-boundary as delimiter
129 23         105 substr( $self->{buffer}, 0, $index, $self->crlf );
130              
131 23         57 $self->{state} = 'boundary';
132              
133 23         83 return 1;
134             }
135              
136             =item parse_boundary
137              
138             =cut
139              
140             sub parse_boundary {
141 204     204 1 346 my $self = shift;
142              
143 204 100       617 if ( index( $self->{buffer}, $self->delimiter_begin . $self->crlf ) == 0 ) {
144              
145 180         507 substr( $self->{buffer}, 0, length( $self->delimiter_begin ) + 2, '' );
146 180         518 $self->{part} = {};
147 180         365 $self->{state} = 'header';
148              
149 180         613 return 1;
150             }
151              
152 24 100       94 if ( index( $self->{buffer}, $self->delimiter_end . $self->crlf ) == 0 ) {
153              
154 21         74 substr( $self->{buffer}, 0, length( $self->delimiter_end ) + 2, '' );
155 21         86 $self->{part} = {};
156 21         51 $self->{state} = 'done';
157              
158 21         121 return 0;
159             }
160              
161 3         25 return 0;
162             }
163              
164             =item parse_header
165              
166             =cut
167              
168             sub parse_header {
169 187     187 1 338 my $self = shift;
170              
171 187         447 my $crlf = $self->crlf;
172 187         628 my $index = index( $self->{buffer}, $crlf . $crlf );
173              
174 187 100       519 unless ( $index >= 0 ) {
175 7         52 return 0;
176             }
177              
178 180         502 my $header = substr( $self->{buffer}, 0, $index );
179              
180 180         436 substr( $self->{buffer}, 0, $index + 4, '' );
181              
182 180         314 my @headers;
183 180         1318 for ( split /$crlf/, $header ) {
184 270 50       960 if (s/^[ \t]+//) {
185 0         0 $headers[-1] .= $_;
186             }
187             else {
188 270         769 push @headers, $_;
189             }
190             }
191              
192 180         859 my $token = qr/[^][\x00-\x1f\x7f()<>@,;:\\"\/?={} \t]+/;
193              
194 180         394 for my $header (@headers) {
195              
196 270         2461 $header =~ s/^($token):[\t ]*//;
197              
198 270         1498 ( my $field = $1 ) =~ s/\b(\w)/uc($1)/eg;
  540         1978  
199              
200 270 50       977 if ( exists $self->{part}->{headers}->{$field} ) {
201 0         0 for ( $self->{part}->{headers}->{$field} ) {
202 0 0       0 $_ = [$_] unless ref($_) eq "ARRAY";
203 0         0 push( @$_, $header );
204             }
205             }
206             else {
207 270         969 $self->{part}->{headers}->{$field} = $header;
208             }
209             }
210              
211 180         377 $self->{state} = 'body';
212              
213 180         849 return 1;
214             }
215              
216             =item parse_body
217              
218             =cut
219              
220             sub parse_body {
221 193     193 1 349 my $self = shift;
222              
223 193         536 my $index = index( $self->{buffer}, $self->delimiter_begin );
224              
225 193 100       536 if ( $index < 0 ) {
226              
227             # make sure we have enough buffer to detect end delimiter
228 13         64 my $length = length( $self->{buffer} ) - ( length( $self->delimiter_end ) + 2 );
229              
230 13 100       50 unless ( $length > 0 ) {
231 7         57 return 0;
232             }
233              
234 6         26 $self->{part}->{data} .= substr( $self->{buffer}, 0, $length, '' );
235 6         14 $self->{part}->{size} += $length;
236 6         17 $self->{part}->{done} = 0;
237              
238 6         21 $self->handler( $self->{part} );
239              
240 6         35 return 0;
241             }
242              
243 180         717 $self->{part}->{data} .= substr( $self->{buffer}, 0, $index, '' );
244 180         435 $self->{part}->{size} += $index;
245 180         435 $self->{part}->{done} = 1;
246              
247 180         620 $self->handler( $self->{part} );
248              
249 180         453 $self->{state} = 'boundary';
250              
251 180         602 return 1;
252             }
253              
254             =item handler
255              
256             =cut
257              
258             our $basename_regexp = qr/[^.]+(\.[^\\\/]+)$/;
259             our $file_temp_suffix = '.upload';
260             our $file_temp_template;
261             our %file_temp_parameters;
262              
263             sub handler {
264 186     186 1 424 my ( $self, $part ) = @_;
265              
266 186 100       488 unless ( exists $part->{name} ) {
267              
268 177         407 my $disposition = $part->{headers}->{'Content-Disposition'};
269 177         1064 my ($name) = $disposition =~ / name="?([^\";]+)"?/;
270 177         626 my ($filename) = $disposition =~ / filename="?([^\"]*)"?/;
271             # Need to match empty filenames above, so this part is flagged as an upload type
272              
273 177         436 $part->{name} = $name;
274              
275 177 100       458 if ( defined $filename ) {
276 88         268 $part->{filename} = $filename;
277              
278 88 100       247 if ( $filename ne "" ) {
279 72         1170 my $basename = (File::Spec->splitpath($filename))[2];
280 72 100       764 my $suffix = $basename =~ $basename_regexp ? $1 : q{};
281              
282 72 50 100     328 my $fh = File::Temp->new(
283             UNLINK => 0, DIR => $self->tmpdir, SUFFIX => ($file_temp_suffix||$suffix),
284             ( $file_temp_template ? ( TEMPLATE => $file_temp_template ) : () ),
285             %file_temp_parameters,
286             );
287              
288 72         41181 $part->{fh} = $fh;
289 72         261 $part->{tempname} = $fh->filename;
290             }
291             }
292             }
293              
294 186 100 100     1317 if ( $part->{fh} && ( my $length = length( $part->{data} ) ) ) {
295 71         1090 $part->{fh}->write( substr( $part->{data}, 0, $length, '' ), $length );
296             }
297              
298 186 100       2300 if ( $part->{done} ) {
299              
300 180 100       498 if ( exists $part->{filename} ) {
301 90 100       335 if ( $part->{filename} ne "" ) {
302 74 100       378 $part->{fh}->close if defined $part->{fh};
303              
304 74         4344 delete @{$part}{qw[ data done fh ]};
  74         566  
305              
306 74         3854 $self->upload( $part->{name}, $part );
307             }
308             }
309             # If we have more than the content-disposition, we need to create a
310             # data key so that we don't waste the headers.
311             else {
312 90         437 $self->param( $part->{name}, $part->{data} );
313 90         318 $self->part_data( $part->{name}, $part )
314             }
315             }
316             }
317              
318             =back
319              
320             =head1 SUPPORT
321              
322             See L<HTTP::Body>
323              
324             =head1 AUTHOR
325              
326             Christian Hansen, C<ch@ngmedia.com>
327              
328             =head1 LICENSE
329              
330             This library is free software . You can redistribute it and/or modify
331             it under the same terms as perl itself.
332              
333             =cut
334              
335             1;