File Coverage

blib/lib/HTTP/Entity/Parser.pm
Criterion Covered Total %
statement 75 84 89.2
branch 19 30 63.3
condition 5 8 62.5
subroutine 9 9 100.0
pod 3 3 100.0
total 111 134 82.8


line stmt bran cond sub pod time code
1             package HTTP::Entity::Parser;
2              
3 4     4   207957 use 5.008001;
  4         42  
4 4     4   22 use strict;
  4         8  
  4         79  
5 4     4   20 use warnings;
  4         7  
  4         143  
6 4     4   1857 use Stream::Buffered;
  4         54570  
  4         109  
7 4     4   2078 use Module::Load;
  4         4813  
  4         24  
8              
9             our $VERSION = "0.25";
10              
11             our $BUFFER_LENGTH = 65536;
12              
13             our %LOADED;
14             our @DEFAULT_PARSER = qw/
15             OctetStream
16             UrlEncoded
17             MultiPart
18             JSON
19             /;
20             for my $parser ( @DEFAULT_PARSER ) {
21             load "HTTP::Entity::Parser::".$parser;
22             $LOADED{"HTTP::Entity::Parser::".$parser} = 1;
23             }
24              
25             sub new {
26 27     27 1 92113 my $class = shift;
27 27         100 my %args = (
28             buffer_length => $BUFFER_LENGTH,
29             @_,
30             );
31 27         165 bless [ [], $args{buffer_length} ], $class;
32             }
33              
34             sub register {
35 24     24 1 127 my ($self,$content_type, $klass, $opts) = @_;
36 24 50       102 if ( !$LOADED{$klass} ) {
37 0         0 load $klass;
38 0         0 $LOADED{$klass} = 1;
39             }
40 24         44 push @{$self->[0]}, [$content_type, $klass, $opts];
  24         114  
41             }
42              
43             sub parse {
44 26     26 1 147 my ($self, $env) = @_;
45              
46 26         53 my $buffer_length = $self->[1];
47 26   50     71 my $ct = $env->{CONTENT_TYPE} || '';
48              
49 26         40 my $parser;
50 26         43 for my $handler (@{$self->[0]}) {
  26         62  
51 24 100 100     132 if ( $ct eq $handler->[0] || index($ct, $handler->[0]) == 0) {
52 22         130 $parser = $handler->[1]->new($env, $handler->[2]);
53 22         45 last;
54             }
55             }
56              
57 26 100       427 if ( !$parser ) {
58 4         19 $parser = HTTP::Entity::Parser::OctetStream->new();
59             }
60              
61              
62 26         52 my $input = $env->{'psgi.input'};
63 26 100       59 if (!$input) {
64             # no input
65 2         11 return ([], []);
66             }
67              
68 24         34 my $buffer;
69 24 50       59 if ($env->{'psgix.input.buffered'}) {
70             # Just in case if input is read by middleware/apps beforehand
71 0         0 $input->seek(0, 0);
72             } else {
73 24         123 $buffer = Stream::Buffered->new();
74             }
75              
76 4     4   1610 my $chunked = do { no warnings; lc delete $env->{HTTP_TRANSFER_ENCODING} eq 'chunked' };
  4         8  
  4         1790  
  24         844  
  24         96  
77 24 100       68 if ( my $cl = $env->{CONTENT_LENGTH} ) {
    50          
78 21         37 my $spin = 0;
79 21         57 while ($cl > 0) {
80 21 50       108 $input->read(my $chunk, $cl < $buffer_length ? $cl : $buffer_length);
81 21         384 my $read = length $chunk;
82 21         46 $cl -= $read;
83 21         81 $parser->add($chunk);
84 21 50       460 $buffer->print($chunk) if $buffer;
85 21 50 33     462 if ($read == 0 && $spin++ > 2000) {
86 0         0 Carp::croak "Bad Content-Length: maybe client disconnect? ($cl bytes remaining)";
87             }
88             }
89             }
90             elsif ($chunked) {
91 3         26 my $chunk_buffer = '';
92 3         7 my $length;
93 3         8 my $spin = 0;
94 3         7 DECHUNK: while(1) {
95 3         15 $input->read(my $chunk, $buffer_length);
96 3         82 my $read = length $chunk;
97 3 50       16 if ($read == 0 ) {
98 0 0       0 Carp::croak "Malformed chunked request" if $spin++ > 2000;
99 0         0 next;
100             }
101 3         13 $chunk_buffer .= $chunk;
102 3         33 while ( $chunk_buffer =~ s/^(([0-9a-fA-F]+).*\015\012)// ) {
103 21         52 my $trailer = $1;
104 21         47 my $chunk_len = hex $2;
105 21 100       62 if ($chunk_len == 0) {
    50          
106 3         14 last DECHUNK;
107             } elsif (length $chunk_buffer < $chunk_len + 2) {
108 0         0 $chunk_buffer = $trailer . $chunk_buffer;
109 0         0 last;
110             }
111 18         42 my $loaded = substr $chunk_buffer, 0, $chunk_len, '';
112 18         64 $parser->add($loaded);
113 18         135 $buffer->print($loaded);
114 18         272 $chunk_buffer =~ s/^\015\012//;
115 18         71 $length += $chunk_len;
116             }
117             }
118 3         11 $env->{CONTENT_LENGTH} = $length;
119             }
120              
121 24 50       63 if ($buffer) {
122 24         47 $env->{'psgix.input.buffered'} = 1;
123 24         69 $env->{'psgi.input'} = $buffer->rewind;
124             } else {
125 0         0 $input->seek(0, 0);
126             }
127              
128 24         3269 $parser->finalize();
129             }
130              
131             1;
132             __END__