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