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