File Coverage

blib/lib/HTTP/Body/MultiPart.pm
Criterion Covered Total %
statement 116 124 93.5
branch 35 42 83.3
condition 3 3 100.0
subroutine 19 19 100.0
pod 13 13 100.0
total 186 201 92.5


line stmt bran cond sub pod time code
1             package HTTP::Body::MultiPart;
2             $HTTP::Body::MultiPart::VERSION = '1.22';
3 8     8   33 use strict;
  8         12  
  8         269  
4 8     8   34 use base 'HTTP::Body';
  8         9  
  8         512  
5 8     8   53 use bytes;
  8         13  
  8         34  
6              
7 8     8   4368 use IO::File;
  8         6898  
  8         1027  
8 8     8   48 use File::Temp 0.14;
  8         128  
  8         671  
9 8     8   44 use File::Spec;
  8         10  
  8         10537  
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 18     18 1 34 my $self = shift;
33              
34 18 50       87 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 18         76 $self->{boundary} = $1;
40 18         45 $self->{state} = 'preamble';
41              
42 18         60 return $self;
43             }
44              
45             =item spin
46              
47             =cut
48              
49             sub spin {
50 35     35 1 47 my $self = shift;
51              
52 35         42 while (1) {
53              
54 482 50       1954 if ( $self->{state} =~ /^(preamble|boundary|header|body)$/ ) {
55 482         1057 my $method = "parse_$1";
56 482 100       1155 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 519     519 1 2106 return shift->{boundary};
71             }
72              
73             =item boundary_begin
74              
75             =cut
76              
77             sub boundary_begin {
78 519     519 1 834 return "--" . shift->boundary;
79             }
80              
81             =item boundary_end
82              
83             =cut
84              
85             sub boundary_end {
86 44     44 1 86 return shift->boundary_begin . "--";
87             }
88              
89             =item crlf
90              
91             =cut
92              
93             sub crlf () {
94 850     850 1 1832 return "\x0d\x0a";
95             }
96              
97             =item delimiter_begin
98              
99             =cut
100              
101             sub delimiter_begin {
102 457     457 1 504 my $self = shift;
103 457         632 return $self->crlf . $self->boundary_begin;
104             }
105              
106             =item delimiter_end
107              
108             =cut
109              
110             sub delimiter_end {
111 44     44 1 56 my $self = shift;
112 44         87 return $self->crlf . $self->boundary_end;
113             }
114              
115             =item parse_preamble
116              
117             =cut
118              
119             sub parse_preamble {
120 18     18 1 35 my $self = shift;
121              
122 18         70 my $index = index( $self->{buffer}, $self->boundary_begin );
123              
124 18 50       64 unless ( $index >= 0 ) {
125 0         0 return 0;
126             }
127              
128             # replace preamble with CRLF so we can match dash-boundary as delimiter
129 18         87 substr( $self->{buffer}, 0, $index, $self->crlf );
130              
131 18         47 $self->{state} = 'boundary';
132              
133 18         74 return 1;
134             }
135              
136             =item parse_boundary
137              
138             =cut
139              
140             sub parse_boundary {
141 162     162 1 198 my $self = shift;
142              
143 162 100       346 if ( index( $self->{buffer}, $self->delimiter_begin . $self->crlf ) == 0 ) {
144              
145 143         276 substr( $self->{buffer}, 0, length( $self->delimiter_begin ) + 2, '' );
146 143         331 $self->{part} = {};
147 143         244 $self->{state} = 'header';
148              
149 143         595 return 1;
150             }
151              
152 19 100       78 if ( index( $self->{buffer}, $self->delimiter_end . $self->crlf ) == 0 ) {
153              
154 16         50 substr( $self->{buffer}, 0, length( $self->delimiter_end ) + 2, '' );
155 16         43 $self->{part} = {};
156 16         34 $self->{state} = 'done';
157              
158 16         77 return 0;
159             }
160              
161 3         17 return 0;
162             }
163              
164             =item parse_header
165              
166             =cut
167              
168             sub parse_header {
169 150     150 1 171 my $self = shift;
170              
171 150         240 my $crlf = $self->crlf;
172 150         337 my $index = index( $self->{buffer}, $crlf . $crlf );
173              
174 150 100       321 unless ( $index >= 0 ) {
175 7         46 return 0;
176             }
177              
178 143         275 my $header = substr( $self->{buffer}, 0, $index );
179              
180 143         222 substr( $self->{buffer}, 0, $index + 4, '' );
181              
182 143         138 my @headers;
183 143         616 for ( split /$crlf/, $header ) {
184 209 50       505 if (s/^[ \t]+//) {
185 0         0 $headers[-1] .= $_;
186             }
187             else {
188 209         413 push @headers, $_;
189             }
190             }
191              
192 143         546 my $token = qr/[^][\x00-\x1f\x7f()<>@,;:\\"\/?={} \t]+/;
193              
194 143         246 for my $header (@headers) {
195              
196 209         1505 $header =~ s/^($token):[\t ]*//;
197              
198 209         866 ( my $field = $1 ) =~ s/\b(\w)/uc($1)/eg;
  418         1009  
199              
200 209 50       672 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 209         742 $self->{part}->{headers}->{$field} = $header;
208             }
209             }
210              
211 143         233 $self->{state} = 'body';
212              
213 143         594 return 1;
214             }
215              
216             =item parse_body
217              
218             =cut
219              
220             sub parse_body {
221 152     152 1 170 my $self = shift;
222              
223 152         304 my $index = index( $self->{buffer}, $self->delimiter_begin );
224              
225 152 100       389 if ( $index < 0 ) {
226              
227             # make sure we have enough buffer to detect end delimiter
228 9         40 my $length = length( $self->{buffer} ) - ( length( $self->delimiter_end ) + 2 );
229              
230 9 100       29 unless ( $length > 0 ) {
231 4         21 return 0;
232             }
233              
234 5         28 $self->{part}->{data} .= substr( $self->{buffer}, 0, $length, '' );
235 5         14 $self->{part}->{size} += $length;
236 5         14 $self->{part}->{done} = 0;
237              
238 5         16 $self->handler( $self->{part} );
239              
240 5         27 return 0;
241             }
242              
243 143         499 $self->{part}->{data} .= substr( $self->{buffer}, 0, $index, '' );
244 143         254 $self->{part}->{size} += $index;
245 143         234 $self->{part}->{done} = 1;
246              
247 143         381 $self->handler( $self->{part} );
248              
249 143         204 $self->{state} = 'boundary';
250              
251 143         396 return 1;
252             }
253              
254             =item handler
255              
256             =cut
257              
258             our $basename_regexp = qr/[^.]+(\.[^\\\/]+)$/;
259             #our $basename_regexp = qr/(\.\w+(?:\.\w+)*)$/;
260              
261             sub handler {
262 148     148 1 196 my ( $self, $part ) = @_;
263              
264 148 100       417 unless ( exists $part->{name} ) {
265              
266 140         251 my $disposition = $part->{headers}->{'Content-Disposition'};
267 140         660 my ($name) = $disposition =~ / name="?([^\";]+)"?/;
268 140         374 my ($filename) = $disposition =~ / filename="?([^\"]*)"?/;
269             # Need to match empty filenames above, so this part is flagged as an upload type
270              
271 140         270 $part->{name} = $name;
272              
273 140 100       321 if ( defined $filename ) {
274 65         111 $part->{filename} = $filename;
275              
276 65 100       163 if ( $filename ne "" ) {
277 51         667 my $basename = (File::Spec->splitpath($filename))[2];
278 51 100       402 my $suffix = $basename =~ $basename_regexp ? $1 : q{};
279              
280 51         183 my $fh = File::Temp->new( UNLINK => 0, DIR => $self->tmpdir, SUFFIX => $suffix );
281              
282 51         17780 $part->{fh} = $fh;
283 51         168 $part->{tempname} = $fh->filename;
284             }
285             }
286             }
287              
288 148 100 100     819 if ( $part->{fh} && ( my $length = length( $part->{data} ) ) ) {
289 51         759 $part->{fh}->write( substr( $part->{data}, 0, $length, '' ), $length );
290             }
291              
292 148 100       1242 if ( $part->{done} ) {
293              
294 143 100       290 if ( exists $part->{filename} ) {
295 67 100       198 if ( $part->{filename} ne "" ) {
296 53 100       270 $part->{fh}->close if defined $part->{fh};
297              
298 53         1891 delete @{$part}{qw[ data done fh ]};
  53         319  
299              
300 53         1770 $self->upload( $part->{name}, $part );
301             }
302             }
303             # If we have more than the content-disposition, we need to create a
304             # data key so that we don't waste the headers.
305             else {
306 76         279 $self->param( $part->{name}, $part->{data} );
307 76         239 $self->part_data( $part->{name}, $part )
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;