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.20';
3 8     8   33 use strict;
  8         9  
  8         243  
4 8     8   31 use base 'HTTP::Body';
  8         7  
  8         462  
5 8     8   48 use bytes;
  8         9  
  8         31  
6              
7 8     8   3590 use IO::File;
  8         6057  
  8         923  
8 8     8   41 use File::Temp 0.14;
  8         118  
  8         632  
9 8     8   39 use File::Spec;
  8         12  
  8         9225  
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 25 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         55 $self->{boundary} = $1;
40 18         43 $self->{state} = 'preamble';
41              
42 18         55 return $self;
43             }
44              
45             =item spin
46              
47             =cut
48              
49             sub spin {
50 35     35 1 40 my $self = shift;
51              
52 35         46 while (1) {
53              
54 482 50       1674 if ( $self->{state} =~ /^(preamble|boundary|header|body)$/ ) {
55 482         851 my $method = "parse_$1";
56 482 100       972 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 1470 return shift->{boundary};
71             }
72              
73             =item boundary_begin
74              
75             =cut
76              
77             sub boundary_begin {
78 519     519 1 780 return "--" . shift->boundary;
79             }
80              
81             =item boundary_end
82              
83             =cut
84              
85             sub boundary_end {
86 44     44 1 73 return shift->boundary_begin . "--";
87             }
88              
89             =item crlf
90              
91             =cut
92              
93             sub crlf () {
94 850     850 1 1429 return "\x0d\x0a";
95             }
96              
97             =item delimiter_begin
98              
99             =cut
100              
101             sub delimiter_begin {
102 457     457 1 511 my $self = shift;
103 457         530 return $self->crlf . $self->boundary_begin;
104             }
105              
106             =item delimiter_end
107              
108             =cut
109              
110             sub delimiter_end {
111 44     44 1 47 my $self = shift;
112 44         67 return $self->crlf . $self->boundary_end;
113             }
114              
115             =item parse_preamble
116              
117             =cut
118              
119             sub parse_preamble {
120 18     18 1 31 my $self = shift;
121              
122 18         85 my $index = index( $self->{buffer}, $self->boundary_begin );
123              
124 18 50       52 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         63 substr( $self->{buffer}, 0, $index, $self->crlf );
130              
131 18         30 $self->{state} = 'boundary';
132              
133 18         56 return 1;
134             }
135              
136             =item parse_boundary
137              
138             =cut
139              
140             sub parse_boundary {
141 162     162 1 150 my $self = shift;
142              
143 162 100       286 if ( index( $self->{buffer}, $self->delimiter_begin . $self->crlf ) == 0 ) {
144              
145 143         269 substr( $self->{buffer}, 0, length( $self->delimiter_begin ) + 2, '' );
146 143         280 $self->{part} = {};
147 143         216 $self->{state} = 'header';
148              
149 143         397 return 1;
150             }
151              
152 19 100       72 if ( index( $self->{buffer}, $self->delimiter_end . $self->crlf ) == 0 ) {
153              
154 16         37 substr( $self->{buffer}, 0, length( $self->delimiter_end ) + 2, '' );
155 16         38 $self->{part} = {};
156 16         28 $self->{state} = 'done';
157              
158 16         64 return 0;
159             }
160              
161 3         22 return 0;
162             }
163              
164             =item parse_header
165              
166             =cut
167              
168             sub parse_header {
169 150     150 1 146 my $self = shift;
170              
171 150         178 my $crlf = $self->crlf;
172 150         252 my $index = index( $self->{buffer}, $crlf . $crlf );
173              
174 150 100       260 unless ( $index >= 0 ) {
175 7         43 return 0;
176             }
177              
178 143         232 my $header = substr( $self->{buffer}, 0, $index );
179              
180 143         186 substr( $self->{buffer}, 0, $index + 4, '' );
181              
182 143         117 my @headers;
183 143         584 for ( split /$crlf/, $header ) {
184 209 50       422 if (s/^[ \t]+//) {
185 0         0 $headers[-1] .= $_;
186             }
187             else {
188 209         356 push @headers, $_;
189             }
190             }
191              
192 143         425 my $token = qr/[^][\x00-\x1f\x7f()<>@,;:\\"\/?={} \t]+/;
193              
194 143         247 for my $header (@headers) {
195              
196 209         1314 $header =~ s/^($token):[\t ]*//;
197              
198 209         730 ( my $field = $1 ) =~ s/\b(\w)/uc($1)/eg;
  418         930  
199              
200 209 50       514 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         617 $self->{part}->{headers}->{$field} = $header;
208             }
209             }
210              
211 143         187 $self->{state} = 'body';
212              
213 143         472 return 1;
214             }
215              
216             =item parse_body
217              
218             =cut
219              
220             sub parse_body {
221 152     152 1 151 my $self = shift;
222              
223 152         249 my $index = index( $self->{buffer}, $self->delimiter_begin );
224              
225 152 100       302 if ( $index < 0 ) {
226              
227             # make sure we have enough buffer to detect end delimiter
228 9         23 my $length = length( $self->{buffer} ) - ( length( $self->delimiter_end ) + 2 );
229              
230 9 100       20 unless ( $length > 0 ) {
231 4         15 return 0;
232             }
233              
234 5         18 $self->{part}->{data} .= substr( $self->{buffer}, 0, $length, '' );
235 5         16 $self->{part}->{size} += $length;
236 5         7 $self->{part}->{done} = 0;
237              
238 5         8 $self->handler( $self->{part} );
239              
240 5         23 return 0;
241             }
242              
243 143         376 $self->{part}->{data} .= substr( $self->{buffer}, 0, $index, '' );
244 143         192 $self->{part}->{size} += $index;
245 143         167 $self->{part}->{done} = 1;
246              
247 143         328 $self->handler( $self->{part} );
248              
249 143         171 $self->{state} = 'boundary';
250              
251 143         301 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 157 my ( $self, $part ) = @_;
263              
264 148 100       329 unless ( exists $part->{name} ) {
265              
266 140         193 my $disposition = $part->{headers}->{'Content-Disposition'};
267 140         573 my ($name) = $disposition =~ / name="?([^\";]+)"?/;
268 140         294 my ($filename) = $disposition =~ / filename="?([^\"]*)"?/;
269             # Need to match empty filenames above, so this part is flagged as an upload type
270              
271 140         178 $part->{name} = $name;
272              
273 140 100       262 if ( defined $filename ) {
274 65         94 $part->{filename} = $filename;
275              
276 65 100       117 if ( $filename ne "" ) {
277 51         555 my $basename = (File::Spec->splitpath($filename))[2];
278 51 100       326 my $suffix = $basename =~ $basename_regexp ? $1 : q{};
279              
280 51         145 my $fh = File::Temp->new( UNLINK => 0, DIR => $self->tmpdir, SUFFIX => $suffix );
281              
282 51         14911 $part->{fh} = $fh;
283 51         152 $part->{tempname} = $fh->filename;
284             }
285             }
286             }
287              
288 148 100 100     673 if ( $part->{fh} && ( my $length = length( $part->{data} ) ) ) {
289 51         659 $part->{fh}->write( substr( $part->{data}, 0, $length, '' ), $length );
290             }
291              
292 148 100       1065 if ( $part->{done} ) {
293              
294 143 100       244 if ( exists $part->{filename} ) {
295 67 100       172 if ( $part->{filename} ne "" ) {
296 53 100       187 $part->{fh}->close if defined $part->{fh};
297              
298 53         1613 delete @{$part}{qw[ data done fh ]};
  53         264  
299              
300 53         1520 $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         213 $self->param( $part->{name}, $part->{data} );
307 76         175 $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;