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