File Coverage

blib/lib/Net/Inspect/L7/HTTP.pm
Criterion Covered Total %
statement 425 601 70.7
branch 255 464 54.9
condition 78 238 32.7
subroutine 24 35 68.5
pod 14 17 82.3
total 796 1355 58.7


line stmt bran cond sub pod time code
1             ############################################################################
2             # finds HTTP requests + responses in tcp connection
3             # chunked HTTP responses are supported
4             ############################################################################
5 1     1   512 use strict;
  1         2  
  1         22  
6 1     1   4 use warnings;
  1         2  
  1         30  
7             package Net::Inspect::L7::HTTP;
8 1     1   4 use base 'Net::Inspect::Flow';
  1         1  
  1         355  
9 1     1   301 use Net::Inspect::Debug qw(:DEFAULT $DEBUG %TRACE);
  1         3  
  1         3  
10 1     1   522 use Hash::Util 'lock_ref_keys';
  1         1988  
  1         5  
11 1     1   94 use Carp 'croak';
  1         2  
  1         36  
12 1     1   4 use Scalar::Util 'weaken';
  1         2  
  1         41  
13             use fields (
14 1         6 'replay', # collected and replayed in guess_protocol
15             'meta', # meta data from connection
16             'requests', # list of open requests, see _in0 for fields
17             'error', # connection has error like server sending data w/o request
18             'upgrade', # true if got upgrade, CONNECT, WebSockets..
19             'connid', # connection id
20             'lastreqid',# id of last request
21             'offset', # offset in data stream
22             'gap_upto', # up to which offset we could manage a gap, that is where we
23             # only get body data (no header, chunked info..).
24             # [off,off] similar to offset and off is set to -1 if umlimited
25             # (i.e. body ends with end of file)
26             'hdr_maxsz',# maximum header size for request(0), response(1) and
27             # chunk header(2). Defaults to 64k, 16k and 2k.
28 1     1   4 );
  1         2  
29              
30 1     1   107 use Exporter 'import';
  1         1  
  1         107  
31             our (@EXPORT_OK,%EXPORT_TAGS);
32             {
33             %EXPORT_TAGS = (
34             need_body => [qw(
35             METHODS_WITHOUT_RQBODY METHODS_WITH_RQBODY METHODS_WITHOUT_RPBODY
36             CODE_WITHOUT_RPBODY
37             )]
38             );
39             push @EXPORT_OK,@$_ for (values %EXPORT_TAGS);
40             push @EXPORT_OK,'parse_hdrfields','parse_reqhdr','parse_rsphdr';
41             }
42              
43             use constant {
44 1         108 METHODS_WITHOUT_RQBODY => [qw(GET HEAD DELETE CONNECT)],
45             METHODS_WITH_RQBODY => [qw(POST PUT)],
46             METHODS_WITHOUT_RPBODY => [qw(HEAD CONNECT)],
47             CODE_WITHOUT_RPBODY => [100..199, 204, 205, 304],
48 1     1   5 };
  1         2  
49              
50             use constant {
51 1         5868 RQHDR_DONE => 0b00001,
52             RQBDY_DONE => 0b00010,
53             RQ_ERROR => 0b00100,
54             RPHDR_DONE => 0b01000,
55             RPBDY_DONE_ON_EOF => 0b10000,
56 1     1   5 };
  1         2  
57              
58             # rfc2616, 2.2
59             # token = 1*
60             # separators = "(" | ")" | "<" | ">" | "@"
61             # | "," | ";" | ":" | "\" | <">
62             # | "/" | "[" | "]" | "?" | "="
63             # | "{" | "}" | SP | HT
64              
65             my $separator = qr{[()<>@,;:\\"/\[\]?={} \t]};
66             my $token = qr{[^()<>@,;:\\"/\[\]?={}\x00-\x20\x7f]+};
67             my $token_value_cont = qr{
68             ($token): # key:
69             [\040\t]*([^\r\n]*?)[\040\t]* # value
70             ((?:\r?\n[\040\t][^\r\n]*)*) # continuation lines
71             \r?\n # (CR)LF
72             }x;
73              
74             # common error: "Last Modified" instead of "Last-Modified"
75             # squid seems to just strip invalid headers, try the same
76             my $xtoken = qr{[^()<>@,;:\\"/\[\]?={}\x00-\x20\x7f][^:[:^print:]]*};
77              
78             my %METHODS_WITHOUT_RQBODY = map { ($_,1) } @{METHODS_WITHOUT_RQBODY()};
79             my %METHODS_WITH_RQBODY = map { ($_,1) } @{METHODS_WITH_RQBODY()};
80             my %METHODS_WITHOUT_RPBODY = map { ($_,1) } @{METHODS_WITHOUT_RPBODY()};
81             my %CODE_WITHOUT_RPBODY = map { ($_,1) } @{CODE_WITHOUT_RPBODY()};
82              
83             sub guess_protocol {
84 0     0 1 0 my ($self,$guess,$dir,$data,$eof,$time,$meta) = @_;
85              
86 0 0       0 if ( $dir == 0 ) {
87 0   0     0 my $rp = $self->{replay} ||= [];
88 0         0 push @$rp,[$data,$eof,$time];
89 0         0 my $buf = join('',map { $_->[0] } @$rp);
  0         0  
90 0 0 0     0 if ( $buf =~m{
    0          
91             \A[\r\n]* # initial junk
92             [A-Z]{2,20}[\040\t]{1,3} # method
93             \S+[\040\t]{1,3} # path/URI
94             HTTP/1\.[01][\040\t]{0,3} # version
95             \r?\n # (CR)LF
96             (?:$xtoken:.*\r?\n(?:[\t\040].*\r?\n)* )* # field:..+cont
97             \r?\n # empty line
98             }xi) {
99             # looks like HTTP request
100 0         0 my $obj = $self->new_connection($meta);
101             # replay as one piece
102 0         0 my $n = $obj->in(0,$buf,$rp->[-1][1],$rp->[-1][2]);
103 0         0 undef $self->{replay};
104 0         0 $n += -length($buf) + length($data);
105 0 0       0 $n<=0 and die "object $obj did not consume alle replayed bytes";
106 0         0 debug("consumed $n of ".length($data)." bytes");
107 0         0 return ($obj,$n);
108              
109             } elsif ( $buf =~m{[^\n]\r?\n\r?\n}
110             or length($buf)>2**16 ) {
111             # does not look like a HTTP header for me
112 0         0 debug("does not look like HTTP header: $buf");
113 0         0 $guess->detach($self);
114             } else {
115 0         0 debug("need more data to decide if HTTP");
116 0         0 return;
117             }
118             } else {
119             # data from server but no request header from
120             # client yet - cannot be HTTP
121 0         0 debug("got data from server before getting request from client -> no HTTP");
122 0         0 $guess->detach($self);
123             }
124 0         0 return;
125             }
126              
127              
128             {
129             my $connid = 0;
130 0     0 0 0 sub syn { 1 }; # in case it is attached to Net::Inspect::Tcp
131             sub new_connection {
132 14     14 1 87 my ($self,$meta,%args) = @_;
133 14         42 my $obj = $self->new;
134 14         30 $obj->{meta} = $meta;
135 14         26 $obj->{requests} = [];
136 14         25 $obj->{connid} = ++$connid;
137 14         25 $obj->{lastreqid} = 0;
138 14         30 $obj->{offset} = [0,0];
139 14         26 $obj->{gap_upto} = [0,0];
140 14         25 $obj->{hdr_maxsz} = delete $args{header_maxsize};
141 14   50     71 $obj->{hdr_maxsz}[0] ||= 2**16;
142 14   50     57 $obj->{hdr_maxsz}[1] ||= 2**14;
143 14   50     58 $obj->{hdr_maxsz}[2] ||= 2**11;
144              
145 14         30 return $obj;
146             }
147             }
148              
149             sub in {
150 92     92 1 10093 my ($self,$dir,$data,$eof,$time) = @_;
151 92 0 0     201 $DEBUG && $self->xdebug("got %s bytes from %d, eof=%d",
    50          
152             ref($data) ? join(":",@$data): length($data),
153             $dir,$eof//0
154             );
155 92 100       249 my $bytes = $dir == 0
156             ? _in0($self,$data,$eof,$time)
157             : _in1($self,$data,$eof,$time);
158             #$self->dump_state if $DEBUG;
159 92         190 return $bytes;
160             }
161              
162             sub offset {
163 0     0 1 0 my $self = shift;
164 0 0       0 return @{ $self->{offset} }[wantarray ? @_:$_[0]];
  0         0  
165             }
166              
167             sub gap_diff {
168 29     29 1 2527 my $self = shift;
169 29         44 my @rv;
170 29         59 for(@_) {
171 58         110 my $off = $self->{gap_upto}[$_];
172             push @rv,
173             $off == -1 ? -1 :
174 58 100       153 ($off-=$self->{offset}[$_]) > 0 ? $off :
    100          
175             0;
176             }
177 29 50       86 return wantarray ? @rv : $rv[0];
178             }
179              
180             sub set_gap_diff {
181 2     2 1 4 my ($self,$dir,$diff) = @_;
182             $self->{gap_upto}[$dir] = defined($diff)
183 2 50       8 ? $self->{offset}[$dir] + $diff # add to offset
184             : 0; # reset gap_upto
185             }
186              
187             sub gap_offset {
188 16     16 1 788 my $self = shift;
189 16         23 my @rv;
190 16         30 for(@_) {
191 32         47 my $off = $self->{gap_upto}[$_];
192             push @rv,
193             $off == -1 ? -1 :
194 32 100       88 $off > $self->{offset}[$_] ? $off :
    100          
195             0
196             }
197 16 50       45 return wantarray ? @rv : $rv[0];
198             }
199              
200             # give requests a chance to cleanup before destroying connection
201             sub DESTROY {
202 14     14   8027 my $self = shift;
203 14         26 @{$self->{requests}} = ();
  14         132  
204             }
205              
206              
207             # process request data
208             sub _in0 {
209 55     55   107 my ($self,$data,$eof,$time) = @_;
210 55         82 my $bytes = 0; # processed bytes
211 55         98 my $rqs = $self->{requests};
212              
213 55 100       120 if ( ref($data)) {
214             # process gap in request data
215 7 50       19 croak "unknown type $data->[0]" if $data->[0] ne 'gap';
216 7         10 my $len = $data->[1];
217              
218 7 50       18 croak 'existing error in connection' if $self->{error};
219              
220 7         10 my $rqs = $self->{requests};
221             croak 'no open request' if ! @$rqs or
222 7 50 66     44 $rqs->[0]{state} & RQBDY_DONE && ! $self->{upgrade};
      33        
223 7 50       16 croak 'existing error in request' if $rqs->[0]{state} & RQ_ERROR;
224             croak "gap too large" if $self->{gap_upto}[0]>=0
225 7 50 33     37 && $self->{gap_upto}[0] < $self->{offset}[0] + $len;
226              
227 7 100       19 if (defined $rqs->[0]{rqclen}) {
228 2         5 $rqs->[0]{rqclen} -= $len;
229 2 50 33     12 if ( ! $rqs->[0]{rqclen} && ! $rqs->[0]{rqchunked} ) {
230 2         4 $rqs->[0]{state} |= RQBDY_DONE;
231             }
232             }
233              
234 7         12 $self->{offset}[0] += $len;
235 7         12 my $obj = $rqs->[0]{obj};
236 7 100       17 if ($self->{upgrade}) {
    50          
237 5         21 $self->{upgrade}(0,[ gap => $len ],$eof,$time);
238             } elsif ($obj) {
239             $obj->in_request_body(
240             [ gap => $len ],
241 2   33     14 $eof || ($rqs->[0]{state} & RQBDY_DONE ? 1:0),
242             $time
243             );
244             }
245 7         20 return $len;
246             }
247              
248             READ_DATA:
249              
250 85 50       192 if ($self->{error}) {
251 0 0       0 $DEBUG && $self->xdebug("no more data because of server side error");
252 0         0 return $bytes;
253             }
254              
255 85 100       172 if ($self->{upgrade}) {
256 10         19 $self->{offset}[0] += length($data);
257 10         28 $self->{upgrade}(0,$data,$eof,$time);
258 10         29 return $bytes + length($data);
259             }
260              
261 75 50 66     283 if (@$rqs and $rqs->[0]{state} & RQ_ERROR ) {
262             # error reading request
263 0 0       0 $DEBUG && $self->xdebug("no more data because of client side error");
264 0         0 return $bytes;
265             }
266              
267 75 50 100     374 if ( ( ! @$rqs or $rqs->[0]{state} & RQBDY_DONE )
      66        
268             and $data =~m{\A[\r\n]+}g ) {
269             # first request or previous request body done
270             # new request might follow but maybe we only have trailing lines after
271             # the last request: eat empty lines
272 0         0 my $n = pos($data);
273 0         0 $bytes += $n;
274 0         0 $self->{offset}[0] += $n;
275 0         0 substr($data,0,$n,'');
276 0 0       0 %TRACE && $self->xtrace("eat empty lines before request header");
277             }
278              
279 75 100       188 if ( $data eq '' ) {
280 28 50       60 $DEBUG && $self->xdebug("no data, eof=$eof, bytes=$bytes");
281 28 50       87 return $bytes if ! $eof; # need more data
282              
283             # handle EOF
284             # check if we got request body for last request
285 0 0 0     0 if ( @$rqs and not $rqs->[0]{state} & RQBDY_DONE ) {
286             # request body not done yet
287 0 0 0     0 %TRACE && ($rqs->[0]{obj}||$self)->xtrace("request body not done but eof");
288 0   0     0 ($rqs->[0]{obj}||$self)->fatal('eof but request body not done',0,$time);
289 0         0 $rqs->[0]{state} |= RQ_ERROR;
290 0         0 return $bytes;
291             }
292              
293 0         0 return $bytes; # request body done
294             }
295              
296             # create new request if no open request or last open request has the
297             # request body already done (pipelining)
298 47 100 100     165 if ( ! @$rqs or $rqs->[0]{state} & RQBDY_DONE ) {
299 17         24 my $reqid = ++$self->{lastreqid};
300             my $obj = $self->new_request({
301 17         31 %{$self->{meta}},
  17         62  
302             time => $time,
303             reqid => $reqid,
304             });
305 17         101 my $rq = {
306             obj => $obj,
307             # bitmask what is done: rpbody|rphdr|rqerror|rqbody|rqhdr
308             state => 0,
309             rqclen => undef, # open content-length request
310             rpclen => undef, # open content-length response
311             # chunked mode for request|response:
312             # false - no chunking
313             # 1,r[qp]clen == 0 - next will be chunk size
314             # 1,r[qp]clen > 0 - inside chunk data, need *clen
315             # 2 - next will be chunk
316             # 3 - after last chunk, next will be chunk trailer
317             rqchunked => undef, # chunked mode for request
318             rpchunked => undef, # chunked mode for response
319             request => undef, # result from parse_reqhdr
320             };
321              
322 17 50       42 if ($DEBUG) {
323 0         0 $rq->{reqid} = $reqid;
324 0         0 weaken($rq->{conn} = $self);
325 0         0 bless $rq, 'Net::Inspect::L7::HTTP::_DebugRequest';
326 0         0 $rq->xdebug("create new request");
327             }
328 17         46 lock_ref_keys($rq);
329 17         154 unshift @$rqs, $rq;
330             }
331              
332 47         76 my $rq = $rqs->[0];
333 47         73 my $obj = $rq->{obj};
334              
335             # read request header if not done
336 47 100       112 if ( not $rq->{state} & RQHDR_DONE ) {
337             # no request header yet, check if data contains it
338              
339             # leading newlines at beginning of request are legally ignored junk
340 20 50       64 if ( $data =~s{\A([\r\n]+)}{} ) {
341 0   0     0 ($obj||$self)->in_junk(0,$1,0,$time);
342             }
343              
344 20 50       42 $DEBUG && $rq->xdebug("need to read request header");
345 20 100       114 if ($data =~s{\A(\A.*?\n\r?\n)}{}s) {
    50          
    50          
    50          
346 17 50       49 $DEBUG && $rq->xdebug("got request header");
347 17         59 my $hdr = $1;
348 17         34 my $n = length($hdr);
349 17         28 $self->{offset}[0] += $n;
350 17         28 $bytes += $n;
351 17         31 $rq->{state} |= RQHDR_DONE; # rqhdr done
352              
353 17         27 my (%hdr,@warn);
354 17         45 my $err = parse_reqhdr($hdr,\%hdr,0);
355 17 50 66     48 if ($err and my $sub = $obj->can('fix_reqhdr')) {
356 0         0 $hdr = $sub->($obj,$hdr);
357 0         0 $err = parse_reqhdr($hdr,\%hdr,0);
358             }
359              
360 17 100       36 if ($err) {
361 1   33     6 ($obj||$self)->fatal($err,0,$time);
362 1         6 $rq->{state} |= RQ_ERROR;
363 1         4 return $bytes;
364             }
365              
366 16         20 my $body_done;
367 16 100       40 if ($hdr{chunked}) {
    100          
368 4         8 $rq->{rqchunked} = 1;
369             } elsif ($hdr{content_length}) {
370 3         6 $rq->{rqclen} = $hdr{content_length};
371 3         8 $self->{gap_upto}[0]= $self->{offset}[0] + $hdr{content_length};
372             } else {
373 9         13 $body_done = 1;
374             }
375              
376 16         28 $rq->{request} = \%hdr;
377              
378 16 0 0     40 %TRACE && $hdr{junk} && ($obj||$self)->xtrace(
      33        
379             "invalid request header data: $hdr{junk}");
380              
381 16 50       71 $obj && $obj->in_request_header($hdr,$time,\%hdr);
382              
383 16 100       80 if ($body_done) {
384 9 50       20 $DEBUG && $rq->xdebug("request done (no body)");
385 9         19 $rq->{state} |= RQBDY_DONE;
386 9 100 100     40 if ($hdr{method} eq 'CONNECT' || $hdr{upgrade}) {
387             # Don't propagate an empty request body:
388             # with CONNECT there will be no body and with Upgrade we
389             # will have no body if the upgrade succeeded. If it failed
390             # we submit the body later.
391             } else {
392 7 50       28 $obj && $obj->in_request_body('',1,$time);
393             }
394             }
395              
396             } elsif ($data =~m{[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]}) {
397             # junk data, maybe attempt to use SOCKS instead of proxy request
398 0   0     0 ($obj||$self)->fatal( sprintf(
399             "junk data instead of request header '%s...'",
400             substr($data,0,10)),0,$time);
401 0         0 $rq->{state} |= RQ_ERROR;
402 0         0 return $bytes;
403             } elsif ( length($data) > $self->{hdr_maxsz}[0] ) {
404 0   0     0 ($obj||$self)->fatal('request header too big',0,$time);
405 0         0 $rq->{state} |= RQ_ERROR;
406 0         0 return $bytes;
407             } elsif ( $eof ) {
408 0   0     0 ($obj||$self)->fatal('eof in request header',0,$time);
409 0         0 $rq->{state} |= RQ_ERROR;
410 0         0 return $bytes;
411             } else {
412             # will be called on new data from upper flow
413 3 50       14 $DEBUG && $rq->xdebug("need more bytes for request header");
414 3         11 return $bytes;
415             }
416             }
417              
418             # read request body if not done
419 43 100 66     181 if ( $data ne '' and not $rq->{state} & RQBDY_DONE ) {
420             # request body
421 27 100       56 if ( my $want = $rq->{rqclen} ) {
422 10         17 my $l = length($data);
423 10 100       25 if ( $l>=$want) {
424             # got all request body
425 8 50       19 $DEBUG && $rq->xdebug("need $want bytes, got all");
426 8         22 my $body = substr($data,0,$rq->{rqclen},'');
427 8         20 $self->{offset}[0] += $rq->{rqclen};
428 8         11 $bytes += $rq->{rqclen};
429 8         14 $rq->{rqclen} = 0;
430 8 100       15 if ( ! $rq->{rqchunked} ) {
431 1 50       6 $DEBUG && $rq->xdebug("request done (full clen)");
432 1         4 $rq->{state} |= RQBDY_DONE; # req body done
433 1 50       7 $obj && $obj->in_request_body($body,1,$time)
434             } else {
435 7 50       27 $obj && $obj->in_request_body($body,$eof,$time);
436 7         28 $rq->{rqchunked} = 2; # get CRLF after chunk
437             }
438             } else {
439             # only part
440 2 50       6 $DEBUG && $rq->xdebug("need $want bytes, got only $l");
441 2         6 my $body = substr($data,0,$l,'');
442 2         4 $self->{offset}[0] += $l;
443 2         3 $bytes += $l;
444 2         4 $rq->{rqclen} -= $l;
445 2 50       15 $obj && $obj->in_request_body($body,0,$time);
446             }
447              
448             # Chunking: rfc2616, 3.6.1
449             } else {
450             # [2] must get CRLF after chunk
451 17 100       44 if ( $rq->{rqchunked} == 2 ) {
452 7 50       14 $DEBUG && $rq->xdebug("want CRLF after chunk");
453 7 50       31 if ( $data =~m{\A\r?\n}g ) {
    0          
454 7         14 my $n = pos($data);
455 7         9 $self->{offset}[0] += $n;
456 7         13 $bytes += $n;
457 7         13 substr($data,0,$n,'');
458 7         10 $rq->{rqchunked} = 1; # get next chunk header
459 7 50       16 $DEBUG && $rq->xdebug("got CRLF after chunk");
460             } elsif ( length($data)>=2 ) {
461 0   0     0 ($obj||$self)->fatal("no CRLF after chunk",0,$time);
462 0         0 $self->{error} = 1;
463 0         0 return $bytes;
464             } else {
465             # need more
466 0         0 return $bytes;
467             }
468             }
469              
470             # [1] must read chunk header
471 17 50       35 if ( $rq->{rqchunked} == 1 ) {
472 17 50       35 $DEBUG && $rq->xdebug("want chunk header");
473 17 100 33     79 if ( $data =~m{\A([\da-fA-F]+)[ \t]*(?:;.*)?\r?\n}g ) {
    50          
474 11         30 $rq->{rqclen} = hex($1);
475 11         28 my $chdr = substr($data,0,pos($data),'');
476 11         17 $self->{offset}[0] += length($chdr);
477 11         22 $bytes += length($chdr);
478              
479             $self->{gap_upto}[0] = $self->{offset}[0] + $rq->{rqclen}
480 11 100       29 if $rq->{rqclen};
481              
482 11 50       39 $obj->in_chunk_header(0,$chdr,$time) if $obj;
483 11 50       53 $DEBUG && $rq->xdebug(
484             "got chunk header - want $rq->{rqclen} bytes");
485 11 100       26 if ( ! $rq->{rqclen} ) {
486             # last chunk
487 4         6 $rq->{rqchunked} = 3;
488 4 50       12 $obj && $obj->in_request_body('',1,$time);
489             }
490             } elsif ( $data =~m{\n} or length($data)>8192 ) {
491 0   0     0 ($obj||$self)->fatal("invalid chunk header",0,$time);
492 0         0 $self->{error} = 1;
493 0         0 return $bytes;
494             } else {
495             # need more data
496 6         16 return $bytes;
497             }
498             }
499              
500             # [3] must read chunk trailer
501 11 100       39 if ( $rq->{rqchunked} == 3 ) {
502 4 50       9 $DEBUG && $rq->xdebug("want chunk trailer");
503 4 50 0     14 if ( $data =~m{\A
    0          
    0          
504             (?:\w[\w\-]*:.*\r?\n(?:[\t\040].*\r?\n)* )* # field:..+cont
505             \r?\n
506             }xg) {
507 4 50       9 $DEBUG && $rq->xdebug("request done (chunk trailer)");
508 4         9 my $trailer = substr($data,0,pos($data),'');
509 4         7 $self->{offset}[0] += length($trailer);
510 4         7 $bytes += length($trailer);
511 4 50       21 $obj->in_chunk_trailer(0,$trailer,$time) if $obj;
512 4         17 $rq->{state} |= RQBDY_DONE; # request done
513             } elsif ( $data =~m{\n\r?\n}
514             or length($data) > $self->{hdr_maxsz}[2] ) {
515 0   0     0 ($obj||$self)->fatal("invalid chunk trailer",0,$time);
516 0         0 $self->{error} = 1;
517 0         0 return $bytes;
518             } elsif ( $eof ) {
519             # not fatal, because we got all data
520 0 0 0     0 %TRACE && ($obj||$self)->xtrace(
521             "eof before end of chunk trailer");
522 0         0 $self->{error} = 1;
523 0         0 return $bytes;
524             } else {
525             # need more
526 0 0       0 $DEBUG && $rq->xdebug("need more bytes for chunk trailer");
527 0         0 return $bytes
528             }
529             }
530             }
531             }
532              
533 37         81 goto READ_DATA;
534             }
535              
536              
537              
538             # process response data
539             sub _in1 {
540 37     37   74 my ($self,$data,$eof,$time) = @_;
541              
542 37         68 my $rqs = $self->{requests};
543 37         50 my $bytes = 0; # processed bytes
544              
545 37 100       79 if ( ref($data)) {
546             # process gap in response data
547 2 50       5 croak "unknown type $data->[0]" if $data->[0] ne 'gap';
548 2         5 my $len = $data->[1];
549              
550 2 50       5 croak 'existing error in connection' if $self->{error};
551              
552 2         3 my $rqs = $self->{requests};
553 2 50       6 croak 'no open response' if ! @$rqs;
554 2         4 my $rq = $rqs->[-1];
555 2 50       9 croak 'existing error in request' if $rq->{state} & RQ_ERROR;
556             croak "gap too large" if $self->{gap_upto}[1]>=0
557 2 50 33     12 && $self->{gap_upto}[1] < $self->{offset}[1] + $len;
558              
559 2 50       7 $rq->{rpclen} -= $len if defined $rq->{rpclen};
560 2         3 $self->{offset}[1] += $len;
561              
562 2         3 my $obj = $rq->{obj};
563 2 50 33     14 if ($self->{upgrade}) {
    50 33        
564 0         0 $self->{upgrade}(1,[ gap => $len ],$eof,$time);
565             } elsif ($rq->{rpclen}
566             or !defined $rq->{rpclen}
567             or $rq->{rpchunked}) {
568 0 0       0 $obj && $obj->in_response_body([ gap => $len ],$eof,$time);
569             } else {
570             # done with request
571 2 50       5 $DEBUG && $rq->xdebug("response done (last gap)");
572 2         5 pop(@$rqs);
573 2 50       8 $obj && $obj->in_response_body([ gap => $len ],1,$time);
574             }
575 2         16 return $len;
576             }
577              
578              
579             READ_DATA:
580              
581 60 50       138 return $bytes if $self->{error};
582 60 100 100     211 return $bytes if $data eq '' && !$eof;
583              
584 40 100       87 if ($self->{upgrade}) {
585 5         10 $self->{offset}[1] += length($data);
586 5         16 $self->{upgrade}(1,$data,$eof,$time);
587 5         53 return $bytes + length($data);
588             }
589              
590 35 100       72 if ( $data eq '' ) {
591 2 50       7 $DEBUG && $self->xdebug("no more data, eof=$eof bytes=$bytes");
592              
593             # handle EOF
594             # check if we got response body for last request
595 2 50 33     13 if ( @$rqs && $rqs->[-1]{state} & RPBDY_DONE_ON_EOF ) {
    0          
596             # response body done on eof
597 2         6 my $rq = pop(@$rqs);
598 2 50       7 $DEBUG && $rq->xdebug("response done (eof)");
599 2 50       14 $rq->{obj}->in_response_body('',1,$time) if $rq->{obj};
600              
601             } elsif ( @$rqs ) {
602             # response body not done yet
603 0         0 my $rq = pop(@$rqs);
604 0 0       0 $DEBUG && $rq->xdebug("response done (unexpected eof)");
605 0 0       0 if (($rq->{state} & RPHDR_DONE) == 0) {
606 0 0 0     0 if ($data eq '' and $self->{lastreqid}>1) {
    0          
607             # We had already a request and now the server closes while
608             # the client is still sending a new request. This happens
609             # with keep-alive connections and the client needs to handle
610             # this case with retrying the request. Signal this issue by
611             # calling in_request_header with empty header.
612 0   0     0 ($rq->{obj}||$self)->in_request_header('',$time);
613             } elsif ($data eq '') {
614 0   0     0 ($rq->{obj}||$self)->fatal(
615             'eof before receiving first response', 1,$time);
616             } else {
617 0 0 0     0 %TRACE && ($rq->{obj}||$self)->xtrace(
618             "eof within response header: '$data'");
619 0   0     0 ($rq->{obj}||$self)->fatal(
620             'eof within response header', 1,$time);
621             }
622             } else {
623             # eof inside a response or close for first request already.
624 0 0 0     0 %TRACE && ($rq->{obj}||$self)->xtrace("eof within response body");
625 0   0     0 ($rq->{obj}||$self)->fatal('eof within response body', 1,$time);
626             }
627             }
628              
629 2         21 return $bytes; # done
630             }
631              
632 33 50       71 if ( ! @$rqs ) {
633 0 0       0 if ( $data =~s{\A([\r\n]+)}{} ) {
634             # skip newlines after request because newlines at beginning of
635             # new request are allowed, stupid
636 0         0 $bytes += length($1);
637 0         0 goto READ_DATA;
638             }
639              
640 0         0 $self->fatal('data from server w/o request',1,$time);
641 0         0 $self->{error} = 1;
642 0         0 return $bytes;
643             }
644              
645 33         49 my $rq = $rqs->[-1];
646 33         53 my $obj = $rq->{obj};
647              
648             # read response header if not done
649 33 100       80 if ( not $rq->{state} & RPHDR_DONE ) {
650 15 50       31 $DEBUG && $rq->xdebug("response header not read yet");
651              
652             # leading newlines at beginning of response are legally ignored junk
653 15 50       46 if ( $data =~s{\A([\r\n]+)}{} ) {
654 0   0     0 ($obj||$self)->in_junk(1,$1,0,$time);
655             }
656              
657             # no response header yet, check if data contains it
658 15 100       74 if ( $data =~s{\A(.*?\n\r?\n)}{}s ) {
    50          
    50          
    50          
    50          
659 14         33 my $hdr = $1;
660 14         24 my $n = length($hdr);
661 14         19 $bytes += $n;
662 14         25 $self->{offset}[1] += $n;
663              
664 14         17 my %hdr;
665 14         35 my $err = parse_rsphdr($hdr,$rq->{request},\%hdr);
666 14 50 66     45 if ($err and my $sub = $obj->can('fix_rsphdr')) {
667 0         0 $hdr = $sub->($obj,$hdr);
668 0         0 $err = parse_rsphdr($hdr,$rq->{request},\%hdr);
669             }
670              
671 14 100       32 goto error if $err;
672 13 50       29 $DEBUG && $rq->xdebug("got response header");
673              
674 13 0 0     30 %TRACE && $hdr{junk} && ($obj||$self)->xtrace(
      33        
675             "invalid request header data: $hdr{junk}");
676              
677 13 100       29 if ($hdr{preliminary}) {
678             # Preliminary response. Wait for read real response.
679 1 50       5 $obj && $obj->in_response_header($hdr,$time,\%hdr);
680 1         23 goto READ_DATA;
681             }
682              
683 12         22 $rq->{state} |= RPHDR_DONE; # response header done
684              
685 12 100       35 if ($hdr{upgrade}) {
    50          
686             # Reset length to undef since we need to read until eof.
687 2         4 $rq->{rpclen} = undef;
688              
689             # If no object is given we just use a dummy function which
690             # returns the size of the data.
691             # If object has its own upgrade_XXXX method for the protocol we
692             # use this. Support for CONNECT has been traditionally built in
693             # but can be redefined with upgrade_CONNECT.
694              
695 2 50       26 if (!$obj) {
    100          
    50          
    50          
696 0     0   0 $self->{upgrade} = sub {};
697 0         0 @{$self->{gap_upto}} = (-1,-1);
  0         0  
698              
699             } elsif (my $sub = $obj->can('upgrade_'.$hdr{upgrade})) {
700             # $sub might throw an error if it is unwilling to upgrade
701             # the connection based on request and response.
702 1 50       2 unless ($self->{upgrade} = eval {
703 1         5 $sub->($obj,$self,$rq->{request},\%hdr)
704             }) {
705 0         0 $err = "invalid connection upgrade '$hdr{upgrade}': $@";
706 0         0 goto error;
707             }
708              
709             } elsif ($sub = $obj->can('upgrade_ANY')) {
710             # $sub might throw an error if it is unwilling to upgrade
711             # the connection based on request and response.
712 0 0       0 unless ($self->{upgrade} = eval {
713             $sub->($obj,$self,$rq->{request},\%hdr,$hdr{upgrade})
714 0         0 }) {
715 0         0 $err = "invalid connection upgrade '$hdr{upgrade}': $@";
716 0         0 goto error;
717             }
718              
719             } elsif ($hdr{upgrade} eq 'CONNECT') {
720             # Traditionally just calls in_data. If this is not available
721             # call dummy function.
722             $self->{upgrade} = $obj->can('in_data') && do {
723             weaken(my $wobj = $obj);
724 2     2   6 sub { $wobj->in_data(@_) }
725 1   50 0   5 } || sub {};
726 1         2 @{$self->{gap_upto}} = (-1,-1);
  1         3  
727              
728             } else {
729 0         0 $err = "unsupported connection upgrade '$hdr{upgrade}'";
730 0         0 goto error;
731             }
732              
733 2         23 goto done;
734             } elsif ($rq->{request}{upgrade}) {
735             # The client requested an upgrade which the server did not ack.
736             # Thus propagate the empty request body now.
737 0 0       0 $obj && $obj->in_request_body('',1,$time);
738             }
739              
740 10         15 my $body_done;
741 10 100       27 if ($hdr{chunked}) {
    100          
742 2         2 $rq->{rpchunked} = 1;
743             } elsif (defined $hdr{content_length}) {
744 6 100       14 if (($rq->{rpclen} = $hdr{content_length})) {
745             # content_length > 0, can do gaps
746             $self->{gap_upto}[1]= $self->{offset}[1]
747 3         8 + $hdr{content_length};
748             } else {
749 3         5 $body_done = 1;
750             }
751             } else {
752             # no length given but method supports body -> end with eof
753 2         3 $rq->{state} |= RPBDY_DONE_ON_EOF; # body done when eof
754 2         5 $self->{gap_upto}[1] = -1;
755             }
756              
757 12 50       52 done:
758             $obj && $obj->in_response_header($hdr,$time,\%hdr);
759 12 100       57 if ($body_done) {
760 3 50       7 $DEBUG && $rq->xdebug("response done (no body)");
761 3         5 pop(@$rqs);
762 3 50       9 $obj && $obj->in_response_body('',1,$time);
763             }
764 12         267 goto READ_DATA;
765              
766             error:
767 1         2 $self->{error} = 1;
768 1   33     10 ($obj||$self)->fatal($err,1,$time);
769 1         7 return $bytes;
770              
771             } elsif ($data =~m{[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]}) {
772 0   0     0 ($obj||$self)->fatal( sprintf(
773             "junk data instead of response header '%s...'",
774             substr($data,0,10)) ,1,$time);
775 0         0 $self->{error} = 1;
776 0         0 return $bytes;
777             } elsif ( $data =~m{[^\n]\r?\n\r?\n}g ) {
778 0   0     0 ($obj||$self)->fatal( sprintf("invalid response header syntax '%s'",
779             substr($data,0,pos($data))),1,$time);
780 0         0 $self->{error} = 1;
781 0         0 return $bytes;
782             } elsif ( length($data) > $self->{hdr_maxsz}[1] ) {
783 0   0     0 ($obj||$self)->fatal('response header too big',1,$time);
784 0         0 $self->{error} = 1;
785 0         0 return $bytes;
786             } elsif ( $eof ) {
787 0   0     0 ($obj||$self)->fatal('eof in response header',1,$time);
788 0         0 $self->{error} = 1;
789 0         0 return $bytes;
790             } else {
791             # will be called on new data from upper flow
792 1 50       3 $DEBUG && $rq->xdebug("need more data for response header");
793 1         3 return $bytes;
794             }
795             }
796              
797             # read response body
798 18 50       38 if ( $data ne '' ) {
799             # response body
800 18 50       38 $DEBUG && $rq->xdebug("response body data");
801              
802             # have content-length or within chunk
803 18 100       48 if ( my $want = $rq->{rpclen} ) {
    100          
    50          
804             # called for content-length or to read content from chunk
805             # with known length
806 6         9 my $l = length($data);
807 6 100       16 if ( $l >= $want ) {
808 5 50       12 $DEBUG && $rq->xdebug("need $want bytes, got all($l)");
809             # got all response body
810 5         13 my $body = substr($data,0,$want,'');
811 5         7 $self->{offset}[1] += $want;
812 5         7 $bytes += $want;
813 5         9 $rq->{rpclen} = 0;
814 5 100       11 if ( ! $rq->{rpchunked} ) {
815             # response done
816 1         2 pop(@$rqs);
817 1 50       4 $DEBUG && $rq->xdebug("response done (full clen received)");
818 1 50       5 $obj && $obj->in_response_body($body,1,$time);
819             } else {
820 4 50       14 $obj->in_response_body($body,0,$time) if $obj;
821 4         22 $rq->{rpchunked} = 2; # get CRLF after chunk
822             }
823             } else {
824             # only part
825 1 50       4 $DEBUG && $rq->xdebug("need $want bytes, got only $l");
826 1         3 my $body = substr($data,0,$l,'');
827 1         2 $self->{offset}[1] += $l;
828 1         3 $bytes += $l;
829 1         2 $rq->{rpclen} -= $l;
830 1 50       4 $obj->in_response_body($body,0,$time) if $obj;
831             }
832              
833             # no content-length, no chunk: must read until eof
834             } elsif ( $rq->{state} & RPBDY_DONE_ON_EOF ) {
835 2 50       5 $DEBUG && $rq->xdebug("read until eof");
836 2         3 $self->{offset}[1] += length($data);
837 2         4 $bytes += length($data);
838 2 50       5 if ($eof) {
839             # response done
840 0         0 pop(@$rqs);
841 0 0       0 $DEBUG && $rq->xdebug("response done (eof)");
842             }
843 2 50       11 $obj->in_response_body($data,$eof,$time) if $obj;
844 2         10 $data = '';
845 2         4 return $bytes;
846              
847             # Chunking: rfc2616, 3.6.1
848             } elsif ( ! $rq->{rpchunked} ) {
849             # should not happen
850 0         0 die "no content-length and no chunked - why we are here?";
851             } else {
852             # [2] must get CRLF after chunk
853 10 100       25 if ( $rq->{rpchunked} == 2 ) {
854 4 50       9 $DEBUG && $rq->xdebug("want CRLF after chunk");
855 4 50       19 if ( $data =~m{\A\r?\n}g ) {
    0          
856 4         8 my $n = pos($data);
857 4         5 $self->{offset}[1] += $n;
858 4         8 $bytes += $n;
859 4         8 substr($data,0,$n,'');
860 4         5 $rq->{rpchunked} = 1; # get next chunk header
861 4 50       11 $DEBUG && $rq->xdebug("got CRLF after chunk");
862             } elsif ( length($data)>=2 ) {
863 0   0     0 ($obj||$self)->fatal("no CRLF after chunk",1,$time);
864 0         0 $self->{error} = 1;
865 0         0 return $bytes;
866             } else {
867             # need more
868 0         0 return $bytes;
869             }
870             }
871              
872             # [1] must read chunk header
873 10 50       25 if ( $rq->{rpchunked} == 1 ) {
874 10 50       23 $DEBUG && $rq->xdebug("want chunk header");
875 10 100 33     51 if ( $data =~m{\A([\da-fA-F]+)[ \t]*(?:;.*)?\r?\n}g ) {
    50          
876 6         16 $rq->{rpclen} = hex($1);
877 6         16 my $chdr = substr($data,0,pos($data),'');
878 6         11 $self->{offset}[1] += length($chdr);
879 6         9 $bytes += length($chdr);
880             $self->{gap_upto}[1] = $self->{offset}[1] + $rq->{rpclen}
881 6 100       16 if $rq->{rpclen};
882              
883 6 50       30 $obj->in_chunk_header(1,$chdr,$time) if $obj;
884 6 50       30 $DEBUG && $rq->xdebug(
885             "got chunk header - want $rq->{rpclen} bytes");
886 6 100       14 if ( ! $rq->{rpclen} ) {
887             # last chunk
888 2         4 $rq->{rpchunked} = 3;
889 2 50       7 $obj && $obj->in_response_body('',1,$time);
890             }
891             } elsif ( $data =~m{\n} or length($data)>8192 ) {
892 0   0     0 ($obj||$self)->fatal("invalid chunk header",1,$time);
893 0         0 $self->{error} = 1;
894 0         0 return $bytes;
895             } else {
896             # need more data
897 4         11 return $bytes;
898             }
899             }
900              
901             # [3] must read chunk trailer
902 6 100       20 if ( $rq->{rpchunked} == 3 ) {
903 2 50       5 $DEBUG && $rq->xdebug("want chunk trailer");
904 2 50 0     13 if ( $data =~m{\A
    0          
905             (?:\w[\w\-]*:.*\r?\n(?:[\t\040].*\r?\n)* )* # field:..+cont
906             \r?\n
907             }xg) {
908 2 50       5 $DEBUG && $rq->xdebug("response done (chunk trailer)");
909 2         6 my $trailer = substr($data,0,pos($data),'');
910 2         5 $self->{offset}[1] += length($trailer);
911 2         3 $bytes += length($trailer);
912 2 50       9 $obj->in_chunk_trailer(1,$trailer,$time) if $obj;
913 2         10 pop(@$rqs); # done
914             } elsif ( $data =~m{\n\r?\n} or
915             length($data)>$self->{hdr_maxsz}[2] ) {
916 0   0     0 ($obj||$self)->fatal("invalid chunk trailer",1,$time);
917 0         0 $self->{error} = 1;
918 0         0 return $bytes;
919             } else {
920             # need more
921 0 0       0 $DEBUG && $rq->xdebug("need more bytes for chunk trailer");
922 0         0 return $bytes
923             }
924             }
925             }
926             }
927              
928 12         27 goto READ_DATA;
929             }
930              
931             # parse and normalize header
932             sub parse_hdrfields {
933 31     31 1 71 my ($hdr,$fields) = @_;
934 31 100       81 return '' if ! defined $hdr;
935 18         27 my $bad = '';
936             parse:
937 36         288 while ( $hdr =~m{\G$token_value_cont}gc ) {
938 24 50       66 if ($3 eq '') {
939             # no continuation line
940 24         40 push @{$fields->{ lc($1) }},$2;
  24         180  
941             } else {
942             # with continuation line
943 0         0 my ($k,$v) = ($1,$2.$3);
944             # value-part -> ' ' + value-part
945 0         0 $v =~s{[\r\n]+[ \t](.*?)[ \t]*}{ $1}g;
946 0         0 push @{$fields->{ lc($k) }},$v;
  0         0  
947             }
948             }
949 36 100 66     149 if (pos($hdr)//0 != length($hdr)) {
950             # bad line inside
951 18   50     55 substr($hdr,0,pos($hdr)//0,'');
952 18 50       44 $bad .= $1 if $hdr =~s{\A([^\n]*)\n}{};
953 18         61 goto parse;
954             }
955 18         42 return $bad;
956             }
957              
958             sub parse_reqhdr {
959 17     17 1 35 my ($data,$hdr,$external_length) = @_;
960 17 50       104 $data =~m{\A
961             ([A-Z]{2,20})[\040\t]+ # $1: method
962             (\S+)[\040\t]+ # $2: path/URI
963             HTTP/(1\.[01])[\40\t]* # $3: version
964             \r?\n # (CR)LF
965             ([^\r\n].*?\n)? # $4: fields
966             \r?\n # final (CR)LF
967             \Z}sx or return "invalid request header";
968              
969 17         39 my $version = $3;
970 17         30 my $method = $1;
971 17         93 %$hdr = (
972             method => $method,
973             url => $2,
974             version => $version,
975             info => "$method $2 HTTP/$version",
976             # fields - hash of fields
977             # junk - bad header fields
978             # expect - expectations from expect header
979             # upgrade - { websocket => key }
980             # content_length
981             # chunked
982             );
983              
984 17         23 my %kv;
985 17         39 my $bad = parse_hdrfields($4,\%kv);
986 17 50       44 $hdr->{junk} = $bad if $bad ne '';
987 17         35 $hdr->{fields} = \%kv;
988              
989 17 100 100     102 if ($version>=1.1 and $kv{expect}) {
990 1         3 for(@{$kv{expect}}) {
  1         4  
991             # ignore all but 100-continue
992 1 50       15 $hdr->{expect}{lc($1)} = 1 if m{\b(100-continue)\b}i
993             }
994             }
995              
996             # RFC2616 4.4.3:
997             # chunked transfer-encoding takes preferece before content-length
998 17 100 100     56 if ( $version >= 1.1 and
    100          
999 4         22 grep { m{(?:^|[ \t,])chunked(?:$|[ \t,;])}i }
1000 12 100       61 @{ $kv{'transfer-encoding'} || [] }
1001             ) {
1002 4         8 $hdr->{chunked} = 1;
1003              
1004             } elsif ( my $cl = $kv{'content-length'} ) {
1005             return "multiple different content-length header in request"
1006 5 50 33     15 if @$cl>1 and do { my %x; @x{@$cl} = (); keys(%x) } > 1;
  0         0  
  0         0  
  0         0  
1007 5 100       24 return "invalid content-length '$cl->[0]' in request"
1008             if $cl->[0] !~m{^(\d+)$};
1009 4         9 $hdr->{content_length} = $cl->[0];
1010             }
1011              
1012 16 100       47 if ( $METHODS_WITHOUT_RQBODY{$method} ) {
    50          
    0          
1013             # Complain if the client announced a body.
1014             return "no body allowed with $method"
1015 8 50 33     33 if $hdr->{content_length} or $hdr->{chunked};
1016              
1017             } elsif ( $METHODS_WITH_RQBODY{$method} ) {
1018             return "content-length or transfer-encoding chunked must be given with method $method"
1019             if ! $hdr->{chunked}
1020             and ! defined $hdr->{content_length}
1021 8 0 66     28 and ! $external_length;
      33        
1022              
1023             } elsif ( ! $hdr->{chunked} ) {
1024             # if not given content-length is considered 0
1025 0   0     0 $hdr->{content_length} ||= 0;
1026             }
1027              
1028             # Connection upgrade
1029 16 100 100     82 if ($version >= 1.1 and $kv{upgrade} and my %upgrade
      66        
1030 1         9 = map { lc($_) => 1 } map { m{($token)}g } @{$kv{upgrade}}) {
  1         36  
  1         4  
1031 1         3 $hdr->{upgrade} = \%upgrade;
1032             }
1033              
1034 16         39 return; # no error
1035             }
1036              
1037             sub parse_rsphdr {
1038 14     14 1 28 my ($data,$request,$hdr,$warn) = @_;
1039 14 50       70 $data =~ m{\A
1040             HTTP/(1\.[01])[\040\t]+ # $1: version
1041             (\d\d\d) # $2: code
1042             (?:[\040\t]+([^\r\n]*))? # $3: reason
1043             \r?\n
1044             ([^\r\n].*?\n)? # $4: fields
1045             \r?\n # empty line
1046             \Z}sx or return "invalid response header";
1047              
1048 14         33 my $version = $1;
1049 14         27 my $code = $2;
1050 14         58 %$hdr = (
1051             version => $version,
1052             code => $code,
1053             reason => $3,
1054             # fields
1055             # junk
1056             # content_length
1057             # chunked
1058             # upgrade
1059             # preliminary
1060             );
1061              
1062 14         21 my %kv;
1063 14         30 my $bad = parse_hdrfields($4,\%kv);
1064 14         31 $hdr->{fields} = \%kv;
1065 14 50       33 $hdr->{junk} = $bad if $bad ne '';
1066              
1067 14 100       40 if ($code<=199) {
1068             # Preliminary responses do not contain any body.
1069 2         5 $hdr->{preliminary} = 1;
1070 2         5 $hdr->{content_length} = 0;
1071 2 50 66     17 if ($code == 100 and $request->{expect}{'100-continue'}
      66        
      66        
1072             or $code == 102 or $code == 101) {
1073             # 100 should only happen with Expect: 100-continue from client
1074             } else {
1075 0 0       0 push @$warn,"unexpected intermediate status code $code" if $warn;
1076             }
1077             }
1078              
1079             # Switching Protocols
1080             # Any upgrade must have both a "Connection: upgrade" and a
1081             # "Upgrade: newprotocol" header.
1082 14 100       33 if ($code == 101) {
1083 1         2 my %proto;
1084 1 50 33     5 if ($request->{upgrade}
1085 1 50       7 and grep { m{\bUPGRADE\b}i } @{$kv{connection} || []}) {
  1         4  
1086 1 50       3 for(@{$kv{upgrade} || []}) {
  1         3  
1087 1         7 $proto{lc($_)} = 1 for split(m{\s*[,;]\s*});
1088             }
1089             }
1090              
1091 1 50       4 if (keys(%proto) == 1) {
1092 1         4 $hdr->{upgrade} = (keys %proto)[0];
1093 1         2 $hdr->{preliminary} = 0;
1094 1         3 $hdr->{content_length} = undef;
1095             } else {
1096 0         0 return "invalid or unsupported connection upgrade";
1097             }
1098             }
1099              
1100             # successful response to CONNECT
1101 14 50 66     44 if ($request->{method} eq 'CONNECT' and $code >= 200 and $code < 300) {
      66        
1102 1         2 $hdr->{upgrade} = 'CONNECT';
1103 1         3 $hdr->{content_length} = 0;
1104 1         2 delete $hdr->{chunked};
1105 1         2 return;
1106             }
1107              
1108             # RFC2616 4.4.3:
1109             # chunked transfer-encoding takes preferece before content-length
1110 13 100 100     50 if ( $version >= 1.1 and
    100          
1111 2         13 grep { m{(?:^|[ \t,])chunked(?:$|[ \t,;])}i }
1112 9 100       52 @{ $kv{'transfer-encoding'} || [] }
1113             ) {
1114 2         4 $hdr->{chunked} = 1;
1115              
1116             } elsif ( my $cl = $kv{'content-length'} ) {
1117             return "multiple different content-length header in response"
1118 5 50 33     19 if @$cl>1 and do { my %x; @x{@$cl} = (); keys(%x) } > 1;
  0         0  
  0         0  
  0         0  
1119 5 100       27 return "invalid content-length '$cl->[0]' in response"
1120             if $cl->[0] !~m{^(\d+)$};
1121 4         9 $hdr->{content_length} = $cl->[0];
1122             }
1123              
1124 12 100 66     52 if ($CODE_WITHOUT_RPBODY{$code}
1125             or $METHODS_WITHOUT_RPBODY{$request->{method}}) {
1126             # no content, even if specified
1127 4         9 $hdr->{content_length} = 0;
1128 4         7 delete $hdr->{chunked};
1129 4         10 return;
1130             }
1131              
1132 8         17 return;
1133             }
1134              
1135              
1136             sub new_request {
1137 17     17 1 30 my $self = shift;
1138 17         46 return $self->{upper_flow}->new_request(@_,$self)
1139             }
1140              
1141             # return open requests
1142             sub open_requests {
1143 0     0 1   my $self = shift;
1144 0 0         my @rq = @_ ? @{$self->{requests}}[@_] : @{$self->{requests}};
  0            
  0            
1145             return wantarray
1146 0 0         ? map { $_->{obj} ? ($_->{obj}):() } @rq
  0 0          
1147             : 0 + @rq;
1148             }
1149              
1150             sub fatal {
1151 0     0 1   my ($self,$reason,$dir,$time) = @_;
1152 0 0         %TRACE && $self->xtrace($reason);
1153             }
1154              
1155             sub xtrace {
1156 0     0 0   my $self = shift;
1157 0           my $msg = shift;
1158 0           $msg = "$$.$self->{connid} $msg";
1159 0           unshift @_,$msg;
1160 0           goto &trace;
1161             }
1162              
1163             sub xdebug {
1164 0 0   0 0   $DEBUG or return;
1165 0           my $self = shift;
1166 0           my $msg = shift;
1167 0           $msg = "$$.$self->{connid} $msg";
1168 0           unshift @_,$msg;
1169 0           goto &debug;
1170             }
1171              
1172             sub dump_state {
1173 0 0 0 0 1   $DEBUG or defined wantarray or return;
1174 0           my $self = shift;
1175 0           my $m = $self->{meta};
1176             my $msg = sprintf("%s.%d -> %s.%d ",
1177 0           $m->{saddr},$m->{sport},$m->{daddr},$m->{dport});
1178 0           my $rqs = $self->{requests};
1179 0           for( my $i=0;$i<@$rqs;$i++) {
1180             $msg .= sprintf("request#$i state=%05b %s",
1181 0           $rqs->[$i]{state},$rqs->[$i]{request}{info});
1182             }
1183 0 0         return $msg if defined wantarray;
1184 0           $self->xdebug($msg);
1185             }
1186              
1187              
1188             {
1189             package Net::Inspect::L7::HTTP::_DebugRequest;
1190             sub xdebug {
1191 0     0     my $rq = shift;
1192 0           my $msg = shift;
1193 0           unshift @_, $rq->{conn}, "#$rq->{reqid} $msg";
1194 0           goto &Net::Inspect::L7::HTTP::xdebug;
1195             }
1196             }
1197              
1198             1;
1199              
1200             __END__