patch for 1.10
[catagits/HTTP-Body.git] / lib / HTTP / Body.pm
CommitLineData
32b29b79 1package HTTP::Body;
2
3use strict;
4
348fdd5a 5use Carp qw[ ];
32b29b79 6
7e2df1d9 7our $TYPES = {
4f5db602 8 'application/octet-stream' => 'HTTP::Body::OctetStream',
9 'application/x-www-form-urlencoded' => 'HTTP::Body::UrlEncoded',
5940e4c7 10 'multipart/form-data' => 'HTTP::Body::MultiPart',
11 'multipart/related' => 'HTTP::Body::XFormsMultipart',
12 'application/xml' => 'HTTP::Body::XForms'
32b29b79 13};
14
b018320d 15require HTTP::Body::OctetStream;
16require HTTP::Body::UrlEncoded;
17require HTTP::Body::MultiPart;
5940e4c7 18require HTTP::Body::XFormsMultipart;
19require HTTP::Body::XForms;
b018320d 20
0a66fd23 21use HTTP::Headers;
22use HTTP::Message;
23
aac7ca02 24=head1 NAME
25
26HTTP::Body - HTTP Body Parser
27
28=head1 SYNOPSIS
29
30 use HTTP::Body;
17c3e9b3 31
32 sub handler : method {
33 my ( $class, $r ) = @_;
34
35 my $content_type = $r->headers_in->get('Content-Type');
36 my $content_length = $r->headers_in->get('Content-Length');
37
38 my $body = HTTP::Body->new( $content_type, $content_length );
39 my $length = $content_length;
40
41 while ( $length ) {
42
43 $r->read( my $buffer, ( $length < 8192 ) ? $length : 8192 );
44
45 $length -= length($buffer);
46
47 $body->add($buffer);
48 }
49
50 my $uploads = $body->upload; # hashref
51 my $params = $body->param; # hashref
52 my $body = $body->body; # IO::Handle
53 }
aac7ca02 54
55=head1 DESCRIPTION
56
6215b02b 57HTTP::Body parses chunks of HTTP POST data and supports
58application/octet-stream, application/x-www-form-urlencoded, and
59multipart/form-data.
60
0a66fd23 61Chunked bodies are supported by not passing a length value to new().
62
6215b02b 63It is currently used by L<Catalyst> to parse POST bodies.
aac7ca02 64
1ced50e0 65=head1 NOTES
66
67When parsing multipart bodies, temporary files are created to store any
68uploaded files. You must delete these temporary files yourself after
b1da105b 69processing them, or set $body->cleanup(1) to automatically delete them
70at DESTROY-time.
1ced50e0 71
aac7ca02 72=head1 METHODS
73
6153c112 74=over 4
75
76=item new
77
78Constructor. Takes content type and content length as parameters,
79returns a L<HTTP::Body> object.
aac7ca02 80
81=cut
82
32b29b79 83sub new {
84 my ( $class, $content_type, $content_length ) = @_;
85
0a66fd23 86 unless ( @_ >= 2 ) {
87 Carp::croak( $class, '->new( $content_type, [ $content_length ] )' );
32b29b79 88 }
7e2df1d9 89
27ee4e94 90 my $type;
91 foreach my $supported ( keys %{$TYPES} ) {
92 if ( index( lc($content_type), $supported ) >= 0 ) {
93 $type = $supported;
94 }
95 }
96
7e2df1d9 97 my $body = $TYPES->{ $type || 'application/octet-stream' };
98
32b29b79 99 my $self = {
b1da105b 100 cleanup => 0,
32b29b79 101 buffer => '',
0a66fd23 102 chunk_buffer => '',
44761c00 103 body => undef,
0a66fd23 104 chunked => !defined $content_length,
105 content_length => defined $content_length ? $content_length : -1,
32b29b79 106 content_type => $content_type,
58050177 107 length => 0,
7e2df1d9 108 param => {},
109 state => 'buffering',
3debb7c0 110 upload => {},
111 tmpdir => File::Spec->tmpdir(),
32b29b79 112 };
113
114 bless( $self, $body );
7e2df1d9 115
32b29b79 116 return $self->init;
117}
118
b1da105b 119sub DESTROY {
120 my $self = shift;
121
122 if ( $self->{cleanup} ) {
123 my @temps = ();
124 for my $upload ( values %{ $self->{upload} } ) {
125 push @temps, map { $_->{tempname} || () }
126 ( ref $upload eq 'ARRAY' ? @{$upload} : $upload );
127 }
128
129 unlink map { $_ } grep { -e $_ } @temps;
130 }
131}
132
aac7ca02 133=item add
134
4deaf0f0 135Add string to internal buffer. Will call spin unless done. returns
6153c112 136length before adding self.
137
aac7ca02 138=cut
139
32b29b79 140sub add {
58050177 141 my $self = shift;
304dca13 142
0a66fd23 143 if ( $self->{chunked} ) {
144 $self->{chunk_buffer} .= $_[0];
145
146 while ( $self->{chunk_buffer} =~ m/^([\da-fA-F]+).*\x0D\x0A/ ) {
147 my $chunk_len = hex($1);
148
149 if ( $chunk_len == 0 ) {
150 # Strip chunk len
151 $self->{chunk_buffer} =~ s/^([\da-fA-F]+).*\x0D\x0A//;
152
153 # End of data, there may be trailing headers
154 if ( my ($headers) = $self->{chunk_buffer} =~ m/(.*)\x0D\x0A/s ) {
155 if ( my $message = HTTP::Message->parse( $headers ) ) {
156 $self->{trailing_headers} = $message->headers;
157 }
158 }
159
160 $self->{chunk_buffer} = '';
161
162 # Set content_length equal to the amount of data we read,
163 # so the spin methods can finish up.
164 $self->{content_length} = $self->{length};
165 }
166 else {
167 # Make sure we have the whole chunk in the buffer (+CRLF)
168 if ( length( $self->{chunk_buffer} ) >= $chunk_len ) {
169 # Strip chunk len
170 $self->{chunk_buffer} =~ s/^([\da-fA-F]+).*\x0D\x0A//;
171
172 # Pull chunk data out of chunk buffer into real buffer
173 $self->{buffer} .= substr $self->{chunk_buffer}, 0, $chunk_len, '';
174
175 # Strip remaining CRLF
176 $self->{chunk_buffer} =~ s/^\x0D\x0A//;
177
178 $self->{length} += $chunk_len;
179 }
180 else {
181 # Not enough data for this chunk, wait for more calls to add()
182 return;
183 }
184 }
185
186 unless ( $self->{state} eq 'done' ) {
187 $self->spin;
188 }
189 }
190
191 return;
192 }
193
304dca13 194 my $cl = $self->content_length;
7e2df1d9 195
58050177 196 if ( defined $_[0] ) {
7e2df1d9 197 $self->{length} += length( $_[0] );
304dca13 198
199 # Don't allow buffer data to exceed content-length
200 if ( $self->{length} > $cl ) {
201 $_[0] = substr $_[0], 0, $cl - $self->{length};
202 $self->{length} = $cl;
203 }
204
205 $self->{buffer} .= $_[0];
58050177 206 }
aac7ca02 207
7e2df1d9 208 unless ( $self->state eq 'done' ) {
209 $self->spin;
210 }
211
304dca13 212 return ( $self->length - $cl );
32b29b79 213}
214
aac7ca02 215=item body
216
6153c112 217accessor for the body.
218
aac7ca02 219=cut
220
32b29b79 221sub body {
222 my $self = shift;
223 $self->{body} = shift if @_;
224 return $self->{body};
225}
226
0a66fd23 227=item chunked
aac7ca02 228
0a66fd23 229Returns 1 if the request is chunked.
6153c112 230
aac7ca02 231=cut
232
0a66fd23 233sub chunked {
234 return shift->{chunked};
58050177 235}
236
b1da105b 237=item cleanup
238
239Set to 1 to enable automatic deletion of temporary files at DESTROY-time.
240
241=cut
242
243sub cleanup {
244 my $self = shift;
245 $self->{cleanup} = shift if @_;
246 return $self->{cleanup};
247}
248
aac7ca02 249=item content_length
250
0a66fd23 251Returns the content-length for the body data if known.
252Returns -1 if the request is chunked.
6153c112 253
aac7ca02 254=cut
255
32b29b79 256sub content_length {
257 return shift->{content_length};
258}
259
aac7ca02 260=item content_type
261
0a66fd23 262Returns the content-type of the body data.
6153c112 263
aac7ca02 264=cut
265
32b29b79 266sub content_type {
267 return shift->{content_type};
268}
269
aac7ca02 270=item init
271
6153c112 272return self.
273
aac7ca02 274=cut
275
58050177 276sub init {
277 return $_[0];
278}
279
aac7ca02 280=item length
281
0a66fd23 282Returns the total length of data we expect to read if known.
283In the case of a chunked request, returns the amount of data
284read so far.
6153c112 285
aac7ca02 286=cut
287
58050177 288sub length {
289 return shift->{length};
290}
291
0a66fd23 292=item trailing_headers
293
294If a chunked request body had trailing headers, trailing_headers will
295return an HTTP::Headers object populated with those headers.
296
297=cut
298
299sub trailing_headers {
300 return shift->{trailing_headers};
301}
302
aac7ca02 303=item spin
304
6153c112 305Abstract method to spin the io handle.
306
aac7ca02 307=cut
308
58050177 309sub spin {
310 Carp::croak('Define abstract method spin() in implementation');
311}
312
aac7ca02 313=item state
314
0a66fd23 315Returns the current state of the parser.
6153c112 316
aac7ca02 317=cut
318
7e2df1d9 319sub state {
320 my $self = shift;
321 $self->{state} = shift if @_;
aac7ca02 322 return $self->{state};
323}
324
aac7ca02 325=item param
326
0a66fd23 327Get/set body parameters.
6153c112 328
aac7ca02 329=cut
330
32b29b79 331sub param {
332 my $self = shift;
333
334 if ( @_ == 2 ) {
335
336 my ( $name, $value ) = @_;
337
338 if ( exists $self->{param}->{$name} ) {
339 for ( $self->{param}->{$name} ) {
340 $_ = [$_] unless ref($_) eq "ARRAY";
341 push( @$_, $value );
342 }
343 }
344 else {
345 $self->{param}->{$name} = $value;
346 }
347 }
348
349 return $self->{param};
350}
351
aac7ca02 352=item upload
353
0a66fd23 354Get/set file uploads.
355
aac7ca02 356=cut
357
32b29b79 358sub upload {
359 my $self = shift;
360
361 if ( @_ == 2 ) {
362
363 my ( $name, $upload ) = @_;
364
365 if ( exists $self->{upload}->{$name} ) {
366 for ( $self->{upload}->{$name} ) {
367 $_ = [$_] unless ref($_) eq "ARRAY";
368 push( @$_, $upload );
369 }
370 }
371 else {
372 $self->{upload}->{$name} = $upload;
373 }
374 }
375
376 return $self->{upload};
377}
378
3debb7c0 379=item tmpdir
380
381Specify a different path for temporary files. Defaults to the system temporary path.
382
383=cut
384
385sub tmpdir {
386 my $self = shift;
387 $self->{tmpdir} = shift if @_;
388 return $self->{tmpdir};
389}
390
aac7ca02 391=back
392
e0c37f8e 393=head1 SUPPORT
394
395Since its original creation this module has been taken over by the Catalyst
396development team. If you want to contribute patches, these will be your
397primary contact points:
398
399IRC:
400
401 Join #catalyst-dev on irc.perl.org.
402
403Mailing Lists:
404
405 http://lists.scsys.co.uk/cgi-bin/mailman/listinfo/catalyst-dev
406
aac7ca02 407=head1 AUTHOR
408
f994d0c8 409Christian Hansen, C<chansen@cpan.org>
17c3e9b3 410
411Sebastian Riedel, C<sri@cpan.org>
aac7ca02 412
0a66fd23 413Andy Grundman, C<andy@hybridized.org>
414
e0c37f8e 415=head1 CONTRIBUTORS
416
417Simon Elliott C<cpan@papercreatures.com>
418
419Kent Fredric <kentnl@cpan.org>
420
421Christian Walde
422
aac7ca02 423=head1 LICENSE
424
17c3e9b3 425This library is free software. You can redistribute it and/or modify
aac7ca02 426it under the same terms as perl itself.
427
428=cut
429
32b29b79 4301;