File Coverage

blib/lib/HTTP/Body/MultiPart.pm
Criterion Covered Total %
statement 115 123 93.5
branch 35 42 83.3
condition 3 3 100.0
subroutine 19 19 100.0
pod 13 13 100.0
total 185 200 92.5


line stmt bran cond sub pod time code
1             package HTTP::Body::MultiPart;
2             {
3             $HTTP::Body::MultiPart::VERSION = '1.19';
4             }
5              
6 7     7   39 use strict;
  7         14  
  7         262  
7 7     7   36 use base 'HTTP::Body';
  7         13  
  7         553  
8 7     7   40 use bytes;
  7         13  
  7         51  
9              
10 7     7   15094 use IO::File;
  7         7811  
  7         1209  
11 7     7   47 use File::Temp 0.14;
  7         146  
  7         704  
12 7     7   42 use File::Spec;
  7         12  
  7         11683  
13              
14             =head1 NAME
15              
16             HTTP::Body::MultiPart - HTTP Body Multipart Parser
17              
18             =head1 SYNOPSIS
19              
20             use HTTP::Body::Multipart;
21              
22             =head1 DESCRIPTION
23              
24             HTTP Body Multipart Parser.
25              
26             =head1 METHODS
27              
28             =over 4
29              
30             =item init
31              
32             =cut
33              
34             sub init {
35 18     18 1 34 my $self = shift;
36              
37 18 50       216 unless ( $self->content_type =~ /boundary=\"?([^\";]+)\"?/ ) {
38 0         0 my $content_type = $self->content_type;
39 0         0 Carp::croak("Invalid boundary in content_type: '$content_type'");
40             }
41              
42 18         78 $self->{boundary} = $1;
43 18         45 $self->{state} = 'preamble';
44              
45 18         69 return $self;
46             }
47              
48             =item spin
49              
50             =cut
51              
52             sub spin {
53 35     35 1 57 my $self = shift;
54              
55 35         51 while (1) {
56              
57 482 50       2695 if ( $self->{state} =~ /^(preamble|boundary|header|body)$/ ) {
58 482         1158 my $method = "parse_$1";
59 482 100       1548 return unless $self->$method;
60             }
61              
62             else {
63 0         0 Carp::croak('Unknown state');
64             }
65             }
66             }
67              
68             =item boundary
69              
70             =cut
71              
72             sub boundary {
73 519     519 1 2577 return shift->{boundary};
74             }
75              
76             =item boundary_begin
77              
78             =cut
79              
80             sub boundary_begin {
81 519     519 1 974 return "--" . shift->boundary;
82             }
83              
84             =item boundary_end
85              
86             =cut
87              
88             sub boundary_end {
89 44     44 1 108 return shift->boundary_begin . "--";
90             }
91              
92             =item crlf
93              
94             =cut
95              
96             sub crlf () {
97 850     850 1 2503 return "\x0d\x0a";
98             }
99              
100             =item delimiter_begin
101              
102             =cut
103              
104             sub delimiter_begin {
105 457     457 1 601 my $self = shift;
106 457         835 return $self->crlf . $self->boundary_begin;
107             }
108              
109             =item delimiter_end
110              
111             =cut
112              
113             sub delimiter_end {
114 44     44 1 66 my $self = shift;
115 44         96 return $self->crlf . $self->boundary_end;
116             }
117              
118             =item parse_preamble
119              
120             =cut
121              
122             sub parse_preamble {
123 18     18 1 39 my $self = shift;
124              
125 18         68 my $index = index( $self->{buffer}, $self->boundary_begin );
126              
127 18 50       60 unless ( $index >= 0 ) {
128 0         0 return 0;
129             }
130              
131             # replace preamble with CRLF so we can match dash-boundary as delimiter
132 18         75 substr( $self->{buffer}, 0, $index, $self->crlf );
133              
134 18         36 $self->{state} = 'boundary';
135              
136 18         63 return 1;
137             }
138              
139             =item parse_boundary
140              
141             =cut
142              
143             sub parse_boundary {
144 162     162 1 197 my $self = shift;
145              
146 162 100       433 if ( index( $self->{buffer}, $self->delimiter_begin . $self->crlf ) == 0 ) {
147              
148 143         378 substr( $self->{buffer}, 0, length( $self->delimiter_begin ) + 2, '' );
149 143         356 $self->{part} = {};
150 143         372 $self->{state} = 'header';
151              
152 143         641 return 1;
153             }
154              
155 19 100       84 if ( index( $self->{buffer}, $self->delimiter_end . $self->crlf ) == 0 ) {
156              
157 16         51 substr( $self->{buffer}, 0, length( $self->delimiter_end ) + 2, '' );
158 16         46 $self->{part} = {};
159 16         33 $self->{state} = 'done';
160              
161 16         288 return 0;
162             }
163              
164 3         20 return 0;
165             }
166              
167             =item parse_header
168              
169             =cut
170              
171             sub parse_header {
172 150     150 1 1785 my $self = shift;
173              
174 150         265 my $crlf = $self->crlf;
175 150         364 my $index = index( $self->{buffer}, $crlf . $crlf );
176              
177 150 100       329 unless ( $index >= 0 ) {
178 7         49 return 0;
179             }
180              
181 143         511 my $header = substr( $self->{buffer}, 0, $index );
182              
183 143         253 substr( $self->{buffer}, 0, $index + 4, '' );
184              
185 143         160 my @headers;
186 143         4443 for ( split /$crlf/, $header ) {
187 209 50       1136 if (s/^[ \t]+//) {
188 0         0 $headers[-1] .= $_;
189             }
190             else {
191 209         766 push @headers, $_;
192             }
193             }
194              
195 143         531 my $token = qr/[^][\x00-\x1f\x7f()<>@,;:\\"\/?={} \t]+/;
196              
197 143         4499 for my $header (@headers) {
198              
199 209         10307 $header =~ s/^($token):[\t ]*//;
200              
201 209         1393 ( my $field = $1 ) =~ s/\b(\w)/uc($1)/eg;
  418         1873  
202              
203 209 50       1141 if ( exists $self->{part}->{headers}->{$field} ) {
204 0         0 for ( $self->{part}->{headers}->{$field} ) {
205 0 0       0 $_ = [$_] unless ref($_) eq "ARRAY";
206 0         0 push( @$_, $header );
207             }
208             }
209             else {
210 209         1022 $self->{part}->{headers}->{$field} = $header;
211             }
212             }
213              
214 143         275 $self->{state} = 'body';
215              
216 143         980 return 1;
217             }
218              
219             =item parse_body
220              
221             =cut
222              
223             sub parse_body {
224 152     152 1 181 my $self = shift;
225              
226 152         462 my $index = index( $self->{buffer}, $self->delimiter_begin );
227              
228 152 100       573 if ( $index < 0 ) {
229              
230             # make sure we have enough buffer to detect end delimiter
231 9         40 my $length = length( $self->{buffer} ) - ( length( $self->delimiter_end ) + 2 );
232              
233 9 100       32 unless ( $length > 0 ) {
234 4         24 return 0;
235             }
236              
237 5         22 $self->{part}->{data} .= substr( $self->{buffer}, 0, $length, '' );
238 5         13 $self->{part}->{size} += $length;
239 5         9 $self->{part}->{done} = 0;
240              
241 5         16 $self->handler( $self->{part} );
242              
243 5         25 return 0;
244             }
245              
246 143         739 $self->{part}->{data} .= substr( $self->{buffer}, 0, $index, '' );
247 143         272 $self->{part}->{size} += $index;
248 143         263 $self->{part}->{done} = 1;
249              
250 143         354 $self->handler( $self->{part} );
251              
252 143         322 $self->{state} = 'boundary';
253              
254 143         416 return 1;
255             }
256              
257             =item handler
258              
259             =cut
260              
261             our $basename_regexp = qr/[^.]+(\.[^\\\/]+)$/;
262             #our $basename_regexp = qr/(\.\w+(?:\.\w+)*)$/;
263              
264             sub handler {
265 148     148 1 219 my ( $self, $part ) = @_;
266              
267 148 100       433 unless ( exists $part->{name} ) {
268              
269 140         445 my $disposition = $part->{headers}->{'Content-Disposition'};
270 140         800 my ($name) = $disposition =~ / name="?([^\";]+)"?/;
271 140         584 my ($filename) = $disposition =~ / filename="?([^\"]*)"?/;
272             # Need to match empty filenames above, so this part is flagged as an upload type
273              
274 140         358 $part->{name} = $name;
275              
276 140 100       325 if ( defined $filename ) {
277 65         128 $part->{filename} = $filename;
278              
279 65 100       160 if ( $filename ne "" ) {
280 51         841 my $basename = (File::Spec->splitpath($filename))[2];
281 51 100       600 my $suffix = $basename =~ $basename_regexp ? $1 : q{};
282              
283 51         202 my $fh = File::Temp->new( UNLINK => 0, DIR => $self->tmpdir, SUFFIX => $suffix );
284              
285 51         27857 $part->{fh} = $fh;
286 51         289 $part->{tempname} = $fh->filename;
287             }
288             }
289             }
290              
291 148 100 100     1499 if ( $part->{fh} && ( my $length = length( $part->{data} ) ) ) {
292 51         915 $part->{fh}->write( substr( $part->{data}, 0, $length, '' ), $length );
293             }
294              
295 148 100       1414 if ( $part->{done} ) {
296              
297 143 100       379 if ( exists $part->{filename} ) {
298 67 100       195 if ( $part->{filename} ne "" ) {
299 53 100       346 $part->{fh}->close if defined $part->{fh};
300              
301 53         2810 delete @{$part}{qw[ data done fh ]};
  53         319  
302              
303 53         1977 $self->upload( $part->{name}, $part );
304             }
305             }
306             else {
307 76         331 $self->param( $part->{name}, $part->{data} );
308             }
309             }
310             }
311              
312             =back
313              
314             =head1 AUTHOR
315              
316             Christian Hansen, C
317              
318             =head1 LICENSE
319              
320             This library is free software . You can redistribute it and/or modify
321             it under the same terms as perl itself.
322              
323             =cut
324              
325             1;