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