File Coverage

blib/lib/Net/Inspect/L7/HTTP.pm
Criterion Covered Total %
statement 426 603 70.6
branch 256 466 54.9
condition 76 241 31.5
subroutine 24 35 68.5
pod 14 17 82.3
total 796 1362 58.4


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