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