File Coverage

blib/lib/HTTP/Body.pm
Criterion Covered Total %
statement 105 108 97.2
branch 41 50 82.0
condition 5 8 62.5
subroutine 19 21 90.4
pod 16 16 100.0
total 186 203 91.6


line stmt bran cond sub pod time code
1             package HTTP::Body;
2             {
3             $HTTP::Body::VERSION = '1.19';
4             }
5              
6 7     7   293967 use strict;
  7         17  
  7         366  
7              
8 7     7   41 use Carp qw[ ];
  7         15  
  7         667  
9              
10             our $TYPES = {
11             'application/octet-stream' => 'HTTP::Body::OctetStream',
12             'application/x-www-form-urlencoded' => 'HTTP::Body::UrlEncoded',
13             'multipart/form-data' => 'HTTP::Body::MultiPart',
14             'multipart/related' => 'HTTP::Body::XFormsMultipart',
15             'application/xml' => 'HTTP::Body::XForms',
16             'application/json' => 'HTTP::Body::OctetStream',
17             };
18              
19             require HTTP::Body::OctetStream;
20             require HTTP::Body::UrlEncoded;
21             require HTTP::Body::MultiPart;
22             require HTTP::Body::XFormsMultipart;
23             require HTTP::Body::XForms;
24              
25 7     7   6857 use HTTP::Headers;
  7         91866  
  7         264  
26 7     7   7497 use HTTP::Message;
  7         110198  
  7         10028  
27              
28             =head1 NAME
29              
30             HTTP::Body - HTTP Body Parser
31              
32             =head1 SYNOPSIS
33              
34             use HTTP::Body;
35            
36             sub handler : method {
37             my ( $class, $r ) = @_;
38              
39             my $content_type = $r->headers_in->get('Content-Type');
40             my $content_length = $r->headers_in->get('Content-Length');
41            
42             my $body = HTTP::Body->new( $content_type, $content_length );
43             my $length = $content_length;
44              
45             while ( $length ) {
46              
47             $r->read( my $buffer, ( $length < 8192 ) ? $length : 8192 );
48              
49             $length -= length($buffer);
50            
51             $body->add($buffer);
52             }
53            
54             my $uploads = $body->upload; # hashref
55             my $params = $body->param; # hashref
56             my $param_order = $body->param_order # arrayref
57             my $body = $body->body; # IO::Handle
58             }
59              
60             =head1 DESCRIPTION
61              
62             HTTP::Body parses chunks of HTTP POST data and supports
63             application/octet-stream, application/json, application/x-www-form-urlencoded,
64             and multipart/form-data.
65              
66             Chunked bodies are supported by not passing a length value to new().
67              
68             It is currently used by L to parse POST bodies.
69              
70             =head1 NOTES
71              
72             When parsing multipart bodies, temporary files are created to store any
73             uploaded files. You must delete these temporary files yourself after
74             processing them, or set $body->cleanup(1) to automatically delete them
75             at DESTROY-time.
76              
77             =head1 METHODS
78              
79             =over 4
80              
81             =item new
82              
83             Constructor. Takes content type and content length as parameters,
84             returns a L object.
85              
86             =cut
87              
88             sub new {
89 28     28 1 353764 my ( $class, $content_type, $content_length ) = @_;
90              
91 28 50       160 unless ( @_ >= 2 ) {
92 0         0 Carp::croak( $class, '->new( $content_type, [ $content_length ] )' );
93             }
94              
95 28         64 my $type;
96             my $earliest_index;
97 28         52 foreach my $supported ( keys %{$TYPES} ) {
  28         212  
98 168         366 my $index = index( lc($content_type), $supported );
99 168 50 33     761 if ($index >= 0 && (!defined $earliest_index || $index < $earliest_index)) {
      66        
100 26         55 $type = $supported;
101 26         66 $earliest_index = $index;
102             }
103             }
104              
105 28   100     705 my $body = $TYPES->{ $type || 'application/octet-stream' };
106              
107 28 100       2352 my $self = {
108             cleanup => 0,
109             buffer => '',
110             chunk_buffer => '',
111             body => undef,
112             chunked => !defined $content_length,
113             content_length => defined $content_length ? $content_length : -1,
114             content_type => $content_type,
115             length => 0,
116             param => {},
117             param_order => [],
118             state => 'buffering',
119             upload => {},
120             tmpdir => File::Spec->tmpdir(),
121             };
122              
123 28         129 bless( $self, $body );
124              
125 28         184 return $self->init;
126             }
127              
128             sub DESTROY {
129 28     28   2190 my $self = shift;
130            
131 28 100       679 if ( $self->{cleanup} ) {
132 8         21 my @temps = ();
133 8         16 for my $upload ( values %{ $self->{upload} } ) {
  8         37  
134 22 50       89 push @temps, map { $_->{tempname} || () }
  2         4  
135 20 100       59 ( ref $upload eq 'ARRAY' ? @{$upload} : $upload );
136             }
137            
138 8         34 unlink map { $_ } grep { -e $_ } @temps;
  22         2274  
  22         481  
139             }
140             }
141              
142             =item add
143              
144             Add string to internal buffer. Will call spin unless done. returns
145             length before adding self.
146              
147             =cut
148              
149             sub add {
150 44     44 1 100675 my $self = shift;
151            
152 44 100       214 if ( $self->{chunked} ) {
153 5         23 $self->{chunk_buffer} .= $_[0];
154            
155 5         53 while ( $self->{chunk_buffer} =~ m/^([\da-fA-F]+).*\x0D\x0A/ ) {
156 23         56 my $chunk_len = hex($1);
157            
158 23 100       50 if ( $chunk_len == 0 ) {
159             # Strip chunk len
160 3         18 $self->{chunk_buffer} =~ s/^([\da-fA-F]+).*\x0D\x0A//;
161            
162             # End of data, there may be trailing headers
163 3 100       21 if ( my ($headers) = $self->{chunk_buffer} =~ m/(.*)\x0D\x0A/s ) {
164 1 50       12 if ( my $message = HTTP::Message->parse( $headers ) ) {
165 1         129 $self->{trailing_headers} = $message->headers;
166             }
167             }
168            
169 3         20 $self->{chunk_buffer} = '';
170            
171             # Set content_length equal to the amount of data we read,
172             # so the spin methods can finish up.
173 3         8 $self->{content_length} = $self->{length};
174             }
175             else {
176             # Make sure we have the whole chunk in the buffer (+CRLF)
177 20 100       62 if ( length( $self->{chunk_buffer} ) >= $chunk_len ) {
178             # Strip chunk len
179 18         75 $self->{chunk_buffer} =~ s/^([\da-fA-F]+).*\x0D\x0A//;
180            
181             # Pull chunk data out of chunk buffer into real buffer
182 18         53 $self->{buffer} .= substr $self->{chunk_buffer}, 0, $chunk_len, '';
183            
184             # Strip remaining CRLF
185 18         48 $self->{chunk_buffer} =~ s/^\x0D\x0A//;
186            
187 18         32 $self->{length} += $chunk_len;
188             }
189             else {
190             # Not enough data for this chunk, wait for more calls to add()
191 2         13 return;
192             }
193             }
194            
195 21 100       115 unless ( $self->{state} eq 'done' ) {
196 20         65 $self->spin;
197             }
198             }
199            
200 3         23 return;
201             }
202            
203 39         155 my $cl = $self->content_length;
204              
205 39 50       132 if ( defined $_[0] ) {
206 39         113 $self->{length} += length( $_[0] );
207            
208             # Don't allow buffer data to exceed content-length
209 39 100       262 if ( $self->{length} > $cl ) {
210 6         33 $_[0] = substr $_[0], 0, $cl - $self->{length};
211 6         20 $self->{length} = $cl;
212             }
213            
214 39         158 $self->{buffer} .= $_[0];
215             }
216              
217 39 50       289 unless ( $self->state eq 'done' ) {
218 39         176 $self->spin;
219             }
220              
221 39         254 return ( $self->length - $cl );
222             }
223              
224             =item body
225              
226             accessor for the body.
227              
228             =cut
229              
230             sub body {
231 50     50 1 13968 my $self = shift;
232 50 100       138 $self->{body} = shift if @_;
233 50         1205 return $self->{body};
234             }
235              
236             =item chunked
237              
238             Returns 1 if the request is chunked.
239              
240             =cut
241              
242             sub chunked {
243 0     0 1 0 return shift->{chunked};
244             }
245              
246             =item cleanup
247              
248             Set to 1 to enable automatic deletion of temporary files at DESTROY-time.
249              
250             =cut
251              
252             sub cleanup {
253 8     8 1 18 my $self = shift;
254 8 50       34 $self->{cleanup} = shift if @_;
255 8         22 return $self->{cleanup};
256             }
257              
258             =item content_length
259              
260             Returns the content-length for the body data if known.
261             Returns -1 if the request is chunked.
262              
263             =cut
264              
265             sub content_length {
266 85     85 1 427 return shift->{content_length};
267             }
268              
269             =item content_type
270              
271             Returns the content-type of the body data.
272              
273             =cut
274              
275             sub content_type {
276 19     19 1 357 return shift->{content_type};
277             }
278              
279             =item init
280              
281             return self.
282              
283             =cut
284              
285             sub init {
286 11     11 1 362 return $_[0];
287             }
288              
289             =item length
290              
291             Returns the total length of data we expect to read if known.
292             In the case of a chunked request, returns the amount of data
293             read so far.
294              
295             =cut
296              
297             sub length {
298 87     87 1 450 return shift->{length};
299             }
300              
301             =item trailing_headers
302              
303             If a chunked request body had trailing headers, trailing_headers will
304             return an HTTP::Headers object populated with those headers.
305              
306             =cut
307              
308             sub trailing_headers {
309 1     1 1 157 return shift->{trailing_headers};
310             }
311              
312             =item spin
313              
314             Abstract method to spin the io handle.
315              
316             =cut
317              
318             sub spin {
319 0     0 1 0 Carp::croak('Define abstract method spin() in implementation');
320             }
321              
322             =item state
323              
324             Returns the current state of the parser.
325              
326             =cut
327              
328             sub state {
329 66     66 1 2856 my $self = shift;
330 66 100       1394 $self->{state} = shift if @_;
331 66         376 return $self->{state};
332             }
333              
334             =item param
335              
336             Get/set body parameters.
337              
338             =cut
339              
340             sub param {
341 118     118 1 165 my $self = shift;
342              
343 118 100       295 if ( @_ == 2 ) {
344              
345 97         158 my ( $name, $value ) = @_;
346              
347 97 100       238 if ( exists $self->{param}->{$name} ) {
348 17         55 for ( $self->{param}->{$name} ) {
349 17 50       82 $_ = [$_] unless ref($_) eq "ARRAY";
350 17         59 push( @$_, $value );
351             }
352             }
353             else {
354 80         293 $self->{param}->{$name} = $value;
355             }
356              
357 97         236 push @{$self->{param_order}}, $name;
  97         607  
358             }
359              
360 118         441 return $self->{param};
361             }
362              
363             =item upload
364              
365             Get/set file uploads.
366              
367             =cut
368              
369             sub upload {
370 123     123 1 54196 my $self = shift;
371              
372 123 100       336 if ( @_ == 2 ) {
373              
374 53         102 my ( $name, $upload ) = @_;
375              
376 53 100       159 if ( exists $self->{upload}->{$name} ) {
377 11         36 for ( $self->{upload}->{$name} ) {
378 11 50       56 $_ = [$_] unless ref($_) eq "ARRAY";
379 11         47 push( @$_, $upload );
380             }
381             }
382             else {
383 42         122 $self->{upload}->{$name} = $upload;
384             }
385             }
386              
387 123         485 return $self->{upload};
388             }
389              
390             =item tmpdir
391              
392             Specify a different path for temporary files. Defaults to the system temporary path.
393              
394             =cut
395              
396             sub tmpdir {
397 71     71 1 11542 my $self = shift;
398 71 100       204 $self->{tmpdir} = shift if @_;
399 71         472 return $self->{tmpdir};
400             }
401              
402             =item param_order
403              
404             Returns the array ref of the param keys in the order how they appeared on the body
405              
406             =cut
407              
408             sub param_order {
409 21     21 1 83 return shift->{param_order};
410             }
411              
412             =back
413              
414             =head1 SUPPORT
415              
416             Since its original creation this module has been taken over by the Catalyst
417             development team. If you want to contribute patches, these will be your
418             primary contact points:
419              
420             IRC:
421              
422             Join #catalyst-dev on irc.perl.org.
423              
424             Mailing Lists:
425              
426             http://lists.scsys.co.uk/cgi-bin/mailman/listinfo/catalyst-dev
427              
428             =head1 AUTHOR
429              
430             Christian Hansen, C
431              
432             Sebastian Riedel, C
433              
434             Andy Grundman, C
435              
436             =head1 CONTRIBUTORS
437              
438             Simon Elliott C
439              
440             Kent Fredric
441              
442             Christian Walde
443              
444             Torsten Raudssus
445              
446             =head1 LICENSE
447              
448             This library is free software. You can redistribute it and/or modify
449             it under the same terms as perl itself.
450              
451             =cut
452              
453             1;