HTTP::Body 1.04, patch from jgoulah for tmpdir() accessor
[catagits/HTTP-Body.git] / lib / HTTP / Body.pm
CommitLineData
32b29b79 1package HTTP::Body;
2
3use strict;
4
348fdd5a 5use Carp qw[ ];
32b29b79 6
3debb7c0 7our $VERSION = '1.04';
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
71processing them.
72
aac7ca02 73=head1 METHODS
74
6153c112 75=over 4
76
77=item new
78
79Constructor. Takes content type and content length as parameters,
80returns a L<HTTP::Body> object.
aac7ca02 81
82=cut
83
32b29b79 84sub new {
85 my ( $class, $content_type, $content_length ) = @_;
86
0a66fd23 87 unless ( @_ >= 2 ) {
88 Carp::croak( $class, '->new( $content_type, [ $content_length ] )' );
32b29b79 89 }
7e2df1d9 90
27ee4e94 91 my $type;
92 foreach my $supported ( keys %{$TYPES} ) {
93 if ( index( lc($content_type), $supported ) >= 0 ) {
94 $type = $supported;
95 }
96 }
97
7e2df1d9 98 my $body = $TYPES->{ $type || 'application/octet-stream' };
99
32b29b79 100 eval "require $body";
7e2df1d9 101
102 if ($@) {
32b29b79 103 die $@;
104 }
7e2df1d9 105
32b29b79 106 my $self = {
107 buffer => '',
0a66fd23 108 chunk_buffer => '',
44761c00 109 body => undef,
0a66fd23 110 chunked => !defined $content_length,
111 content_length => defined $content_length ? $content_length : -1,
32b29b79 112 content_type => $content_type,
58050177 113 length => 0,
7e2df1d9 114 param => {},
115 state => 'buffering',
3debb7c0 116 upload => {},
117 tmpdir => File::Spec->tmpdir(),
32b29b79 118 };
119
120 bless( $self, $body );
7e2df1d9 121
32b29b79 122 return $self->init;
123}
124
aac7ca02 125=item add
126
4deaf0f0 127Add string to internal buffer. Will call spin unless done. returns
6153c112 128length before adding self.
129
aac7ca02 130=cut
131
32b29b79 132sub add {
58050177 133 my $self = shift;
304dca13 134
0a66fd23 135 if ( $self->{chunked} ) {
136 $self->{chunk_buffer} .= $_[0];
137
138 while ( $self->{chunk_buffer} =~ m/^([\da-fA-F]+).*\x0D\x0A/ ) {
139 my $chunk_len = hex($1);
140
141 if ( $chunk_len == 0 ) {
142 # Strip chunk len
143 $self->{chunk_buffer} =~ s/^([\da-fA-F]+).*\x0D\x0A//;
144
145 # End of data, there may be trailing headers
146 if ( my ($headers) = $self->{chunk_buffer} =~ m/(.*)\x0D\x0A/s ) {
147 if ( my $message = HTTP::Message->parse( $headers ) ) {
148 $self->{trailing_headers} = $message->headers;
149 }
150 }
151
152 $self->{chunk_buffer} = '';
153
154 # Set content_length equal to the amount of data we read,
155 # so the spin methods can finish up.
156 $self->{content_length} = $self->{length};
157 }
158 else {
159 # Make sure we have the whole chunk in the buffer (+CRLF)
160 if ( length( $self->{chunk_buffer} ) >= $chunk_len ) {
161 # Strip chunk len
162 $self->{chunk_buffer} =~ s/^([\da-fA-F]+).*\x0D\x0A//;
163
164 # Pull chunk data out of chunk buffer into real buffer
165 $self->{buffer} .= substr $self->{chunk_buffer}, 0, $chunk_len, '';
166
167 # Strip remaining CRLF
168 $self->{chunk_buffer} =~ s/^\x0D\x0A//;
169
170 $self->{length} += $chunk_len;
171 }
172 else {
173 # Not enough data for this chunk, wait for more calls to add()
174 return;
175 }
176 }
177
178 unless ( $self->{state} eq 'done' ) {
179 $self->spin;
180 }
181 }
182
183 return;
184 }
185
304dca13 186 my $cl = $self->content_length;
7e2df1d9 187
58050177 188 if ( defined $_[0] ) {
7e2df1d9 189 $self->{length} += length( $_[0] );
304dca13 190
191 # Don't allow buffer data to exceed content-length
192 if ( $self->{length} > $cl ) {
193 $_[0] = substr $_[0], 0, $cl - $self->{length};
194 $self->{length} = $cl;
195 }
196
197 $self->{buffer} .= $_[0];
58050177 198 }
aac7ca02 199
7e2df1d9 200 unless ( $self->state eq 'done' ) {
201 $self->spin;
202 }
203
304dca13 204 return ( $self->length - $cl );
32b29b79 205}
206
aac7ca02 207=item body
208
6153c112 209accessor for the body.
210
aac7ca02 211=cut
212
32b29b79 213sub body {
214 my $self = shift;
215 $self->{body} = shift if @_;
216 return $self->{body};
217}
218
0a66fd23 219=item chunked
aac7ca02 220
0a66fd23 221Returns 1 if the request is chunked.
6153c112 222
aac7ca02 223=cut
224
0a66fd23 225sub chunked {
226 return shift->{chunked};
58050177 227}
228
aac7ca02 229=item content_length
230
0a66fd23 231Returns the content-length for the body data if known.
232Returns -1 if the request is chunked.
6153c112 233
aac7ca02 234=cut
235
32b29b79 236sub content_length {
237 return shift->{content_length};
238}
239
aac7ca02 240=item content_type
241
0a66fd23 242Returns the content-type of the body data.
6153c112 243
aac7ca02 244=cut
245
32b29b79 246sub content_type {
247 return shift->{content_type};
248}
249
aac7ca02 250=item init
251
6153c112 252return self.
253
aac7ca02 254=cut
255
58050177 256sub init {
257 return $_[0];
258}
259
aac7ca02 260=item length
261
0a66fd23 262Returns the total length of data we expect to read if known.
263In the case of a chunked request, returns the amount of data
264read so far.
6153c112 265
aac7ca02 266=cut
267
58050177 268sub length {
269 return shift->{length};
270}
271
0a66fd23 272=item trailing_headers
273
274If a chunked request body had trailing headers, trailing_headers will
275return an HTTP::Headers object populated with those headers.
276
277=cut
278
279sub trailing_headers {
280 return shift->{trailing_headers};
281}
282
aac7ca02 283=item spin
284
6153c112 285Abstract method to spin the io handle.
286
aac7ca02 287=cut
288
58050177 289sub spin {
290 Carp::croak('Define abstract method spin() in implementation');
291}
292
aac7ca02 293=item state
294
0a66fd23 295Returns the current state of the parser.
6153c112 296
aac7ca02 297=cut
298
7e2df1d9 299sub state {
300 my $self = shift;
301 $self->{state} = shift if @_;
aac7ca02 302 return $self->{state};
303}
304
aac7ca02 305=item param
306
0a66fd23 307Get/set body parameters.
6153c112 308
aac7ca02 309=cut
310
32b29b79 311sub param {
312 my $self = shift;
313
314 if ( @_ == 2 ) {
315
316 my ( $name, $value ) = @_;
317
318 if ( exists $self->{param}->{$name} ) {
319 for ( $self->{param}->{$name} ) {
320 $_ = [$_] unless ref($_) eq "ARRAY";
321 push( @$_, $value );
322 }
323 }
324 else {
325 $self->{param}->{$name} = $value;
326 }
327 }
328
329 return $self->{param};
330}
331
aac7ca02 332=item upload
333
0a66fd23 334Get/set file uploads.
335
aac7ca02 336=cut
337
32b29b79 338sub upload {
339 my $self = shift;
340
341 if ( @_ == 2 ) {
342
343 my ( $name, $upload ) = @_;
344
345 if ( exists $self->{upload}->{$name} ) {
346 for ( $self->{upload}->{$name} ) {
347 $_ = [$_] unless ref($_) eq "ARRAY";
348 push( @$_, $upload );
349 }
350 }
351 else {
352 $self->{upload}->{$name} = $upload;
353 }
354 }
355
356 return $self->{upload};
357}
358
3debb7c0 359=item tmpdir
360
361Specify a different path for temporary files. Defaults to the system temporary path.
362
363=cut
364
365sub tmpdir {
366 my $self = shift;
367 $self->{tmpdir} = shift if @_;
368 return $self->{tmpdir};
369}
370
aac7ca02 371=back
372
373=head1 AUTHOR
374
375Christian Hansen, C<ch@ngmedia.com>
17c3e9b3 376
377Sebastian Riedel, C<sri@cpan.org>
aac7ca02 378
0a66fd23 379Andy Grundman, C<andy@hybridized.org>
380
aac7ca02 381=head1 LICENSE
382
17c3e9b3 383This library is free software. You can redistribute it and/or modify
aac7ca02 384it under the same terms as perl itself.
385
386=cut
387
32b29b79 3881;