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