| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | ############################################################################ | 
| 2 |  |  |  |  |  |  | # WebSocket support | 
| 3 |  |  |  |  |  |  | ############################################################################ | 
| 4 |  |  |  |  |  |  |  | 
| 5 | 1 |  |  | 1 |  | 14581 | use strict; | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 24 |  | 
| 6 | 1 |  |  | 1 |  | 4 | use warnings; | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 46 |  | 
| 7 |  |  |  |  |  |  | package Net::Inspect::L7::HTTP::WebSocket; | 
| 8 | 1 |  |  | 1 |  | 9 | use Scalar::Util 'weaken'; | 
|  | 1 |  |  |  |  | 3 |  | 
|  | 1 |  |  |  |  | 50 |  | 
| 9 | 1 |  |  | 1 |  | 5 | use Carp 'croak'; | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 37 |  | 
| 10 | 1 |  |  | 1 |  | 452 | use Digest::SHA 'sha1_base64'; | 
|  | 1 |  |  |  |  | 2525 |  | 
|  | 1 |  |  |  |  | 1308 |  | 
| 11 |  |  |  |  |  |  |  | 
| 12 |  |  |  |  |  |  | sub upgrade_websocket { | 
| 13 | 1 |  |  | 1 | 0 | 2 | my ($self,$conn,$req,$rsp) = @_; | 
| 14 |  |  |  |  |  |  |  | 
| 15 |  |  |  |  |  |  | # Websocket: RFC6455, Sec.4.1 Page 16ff | 
| 16 | 1 |  | 50 |  |  | 5 | my $wskey = $req->{fields}{'sec-websocket-key'} || []; | 
| 17 | 1 | 50 |  |  |  | 4 | if (@$wskey > 1) { | 
| 18 | 0 |  |  |  |  | 0 | my %x; | 
| 19 | 0 | 0 |  |  |  | 0 | $wskey = [ map { $x{$_}++ ? ():($_) } @$wskey ]; | 
|  | 0 |  |  |  |  | 0 |  | 
| 20 |  |  |  |  |  |  | } | 
| 21 |  |  |  |  |  |  |  | 
| 22 |  |  |  |  |  |  | # Check request | 
| 23 | 1 | 50 |  |  |  | 3 | die "no sec-websocket-key given in request" if ! @$wskey; | 
| 24 | 1 | 50 |  |  |  | 10 | die "multiple sec-websocket-key given in request" if @$wskey > 1; | 
| 25 |  |  |  |  |  |  | die "method must be GET but is $req->{method}" | 
| 26 | 1 | 50 |  |  |  | 4 | if $req->{method} ne 'GET'; | 
| 27 | 1 |  |  |  |  | 3 | my $v = $req->{fields}{'sec-websocket-version'}; | 
| 28 | 1 | 50 |  |  |  | 6 | die "no sec-websocket-version field in request" if !$v; | 
| 29 |  |  |  |  |  |  | die "sec-websocket-version must be 13 not '@$v'" | 
| 30 | 1 | 50 |  |  |  | 3 | if grep { $_ ne '13' } @$v; | 
|  | 1 |  |  |  |  | 5 |  | 
| 31 |  |  |  |  |  |  |  | 
| 32 |  |  |  |  |  |  | # Check response | 
| 33 | 1 |  |  |  |  | 2 | my $wsa = $rsp->{fields}{'sec-websocket-accept'}; | 
| 34 | 1 | 50 |  |  |  | 3 | if (@$wsa > 1) { | 
| 35 | 0 |  |  |  |  | 0 | my %x; | 
| 36 | 0 | 0 |  |  |  | 0 | $wsa = [ map { $x{$_}++ ? ():($_) } @$wsa ]; | 
|  | 0 |  |  |  |  | 0 |  | 
| 37 |  |  |  |  |  |  | } | 
| 38 | 1 | 50 |  |  |  | 4 | die "no sec-websocket-accept given in response" if ! @$wsa; | 
| 39 | 1 | 50 |  |  |  | 3 | die "multiple sec-websocket-accept given in response" if @$wsa > 1; | 
| 40 |  |  |  |  |  |  |  | 
| 41 |  |  |  |  |  |  | # Check that sec-websocket-accept in response matches sec-websocket-key. | 
| 42 |  |  |  |  |  |  | # Beware its magic! see RFC6455 page 7. | 
| 43 |  |  |  |  |  |  | # sha1_base64 does no padding, so we need to add a single '=' (pad to 4*7 | 
| 44 |  |  |  |  |  |  | # byte) at the end for comparison. | 
| 45 | 1 | 50 | 33 |  |  | 62 | if ( @$wsa != 1 or $wsa->[0] ne sha1_base64( | 
| 46 |  |  |  |  |  |  | $wskey->[0].'258EAFA5-E914-47DA-95CA-C5AB0DC85B11').'=') { | 
| 47 | 0 |  |  |  |  | 0 | die "sec-websocket-accept does not match sec-websocket-key"; | 
| 48 |  |  |  |  |  |  | } | 
| 49 |  |  |  |  |  |  |  | 
| 50 |  |  |  |  |  |  |  | 
| 51 | 1 |  |  |  |  | 4 | my @sub; | 
| 52 | 1 |  |  |  |  | 6 | weaken($self); | 
| 53 | 1 |  |  |  |  | 4 | for my $dir (0,1) { | 
| 54 | 2 |  |  |  |  | 5 | my $dir = $dir; # old $dir is only alias | 
| 55 | 2 |  |  |  |  | 4 | my $rbuf = ''; | 
| 56 |  |  |  |  |  |  |  | 
| 57 |  |  |  |  |  |  | # If $clen is defined we are inside a frame ($current_frame). | 
| 58 |  |  |  |  |  |  | # If $clen is not defined all other variables here do not matter. | 
| 59 |  |  |  |  |  |  | # Since control messages might be in-between fragmented data messages we | 
| 60 |  |  |  |  |  |  | # need to keep this information for an open data message. | 
| 61 | 2 |  |  |  |  | 6 | my ($clen,$clenhi,$current_frame,$data_frame,$ctl_frame,$got_close); | 
| 62 |  |  |  |  |  |  |  | 
| 63 |  |  |  |  |  |  | $sub[$dir] = sub { | 
| 64 | 18 |  |  | 18 |  | 38 | my ($data,$eof,$time) = @_; | 
| 65 | 18 |  |  |  |  | 29 | my $err; | 
| 66 |  |  |  |  |  |  |  | 
| 67 |  |  |  |  |  |  | # Handle data gaps. These are only allowed inside data frames. | 
| 68 |  |  |  |  |  |  | ############################################################ | 
| 69 | 18 | 100 |  |  |  | 44 | if (ref($data)) { | 
| 70 | 5 | 50 |  |  |  | 14 | croak "unknown type $data->[0]" if $data->[0] ne 'gap'; | 
| 71 | 5 |  |  |  |  | 8 | my $gap = $data->[1]; | 
| 72 | 5 | 50 |  |  |  | 14 | if (!defined $clen) { | 
| 73 | 0 |  |  |  |  | 0 | $err = "gap outside websocket frame"; | 
| 74 | 0 |  |  |  |  | 0 | goto bad; | 
| 75 |  |  |  |  |  |  | } | 
| 76 | 5 | 50 | 33 |  |  | 35 | if (!$data_frame || $current_frame != $data_frame) { | 
| 77 | 0 |  |  |  |  | 0 | $err = "gap inside control frame"; | 
| 78 | 0 |  |  |  |  | 0 | goto bad; | 
| 79 |  |  |  |  |  |  | } | 
| 80 | 5 |  |  |  |  | 10 | my $eom = 0; # end of message on end-of-frame + FIN frame | 
| 81 | 5 |  |  |  |  | 12 | while ($gap>0) { | 
| 82 | 5 | 50 |  |  |  | 15 | if ($clen == 0) { | 
|  |  | 50 |  |  |  |  |  | 
| 83 | 0 | 0 |  |  |  | 0 | if (!$clenhi) { | 
| 84 | 0 |  |  |  |  | 0 | $err = "gap larger than frame size"; | 
| 85 | 0 |  |  |  |  | 0 | goto bad; | 
| 86 |  |  |  |  |  |  | } | 
| 87 | 0 |  |  |  |  | 0 | $clenhi--; | 
| 88 | 0 |  |  |  |  | 0 | $clen = 0xffffffff; | 
| 89 | 0 |  |  |  |  | 0 | $gap--; | 
| 90 |  |  |  |  |  |  | $current_frame->{mask_offset} | 
| 91 | 0 |  | 0 |  |  | 0 | = (($current_frame->{mask_offset}||0) + 1) % 4; | 
| 92 |  |  |  |  |  |  |  | 
| 93 |  |  |  |  |  |  | } elsif ($gap > $clen) { | 
| 94 | 0 |  |  |  |  | 0 | $gap -= $clen; | 
| 95 |  |  |  |  |  |  | $current_frame->{mask_offset} | 
| 96 | 0 |  | 0 |  |  | 0 | = (($current_frame->{mask_offset}||0) + $clen) % 4; | 
| 97 | 0 |  |  |  |  | 0 | $clen = 0; | 
| 98 |  |  |  |  |  |  | } else { # $gap <= $clen | 
| 99 | 5 |  |  |  |  | 9 | $clen -= $gap; | 
| 100 |  |  |  |  |  |  | $current_frame->{mask_offset} | 
| 101 | 5 |  | 100 |  |  | 15 | = (($current_frame->{mask_offset}||0) + $gap) % 4; | 
| 102 | 5 |  |  |  |  | 12 | $gap = 0; | 
| 103 |  |  |  |  |  |  | } | 
| 104 |  |  |  |  |  |  | } | 
| 105 | 5 | 0 | 33 |  |  | 12 | if (!$clen && !$clenhi) { | 
| 106 |  |  |  |  |  |  | # frame done | 
| 107 | 0 | 0 |  |  |  | 0 | $eom = $data_frame->{fin} ? 1:0; | 
| 108 | 0 |  |  |  |  | 0 | $clen = undef; | 
| 109 |  |  |  |  |  |  | } | 
| 110 |  |  |  |  |  |  |  | 
| 111 | 5 | 50 |  |  |  | 10 | if (defined $clen) { | 
| 112 | 5 |  |  |  |  | 11 | $data_frame->{bytes_left} = [$clenhi,$clen]; | 
| 113 |  |  |  |  |  |  | } else { | 
| 114 | 0 |  |  |  |  | 0 | delete $data_frame->{bytes_left}; | 
| 115 |  |  |  |  |  |  | } | 
| 116 | 5 |  |  |  |  | 18 | $self->in_wsdata($dir,$data,$eom,$time,$data_frame); | 
| 117 | 5 | 50 |  |  |  | 50 | if ($eom) { | 
| 118 | 0 |  |  |  |  | 0 | $data_frame = $current_frame = undef; | 
| 119 | 0 |  |  |  |  | 0 | $conn->set_gap_diff($dir,undef); | 
| 120 |  |  |  |  |  |  | } else { | 
| 121 | 5 |  |  |  |  | 9 | delete $data_frame->{init}; | 
| 122 | 5 |  |  |  |  | 7 | delete $data_frame->{header}; | 
| 123 |  |  |  |  |  |  | } | 
| 124 | 5 |  |  |  |  | 12 | return; | 
| 125 |  |  |  |  |  |  | } | 
| 126 |  |  |  |  |  |  |  | 
| 127 | 13 |  |  |  |  | 22 | $rbuf .= $data; | 
| 128 |  |  |  |  |  |  |  | 
| 129 |  |  |  |  |  |  | PARSE_DATA: | 
| 130 |  |  |  |  |  |  |  | 
| 131 |  |  |  |  |  |  | # data for existing frame | 
| 132 |  |  |  |  |  |  | ############################################################ | 
| 133 | 21 | 100 |  |  |  | 46 | if (defined $clen) { | 
| 134 | 10 |  |  |  |  | 14 | my $size = length($rbuf); | 
| 135 | 10 | 50 | 33 |  |  | 29 | if (!$size and $clen || $clenhi and $eof) { | 
|  |  |  | 66 |  |  |  |  | 
|  |  |  | 33 |  |  |  |  | 
| 136 | 0 |  |  |  |  | 0 | $err = "eof inside websocket frame"; | 
| 137 | 0 |  |  |  |  | 0 | goto bad; | 
| 138 |  |  |  |  |  |  | } | 
| 139 | 10 |  |  |  |  | 18 | my $fwd = ''; | 
| 140 | 10 |  |  |  |  | 13 | my $eom = 0; | 
| 141 | 10 |  |  |  |  | 23 | while ($size>0) { | 
| 142 | 9 | 50 |  |  |  | 22 | if ($clen == 0) { | 
|  |  | 50 |  |  |  |  |  | 
| 143 | 0 | 0 |  |  |  | 0 | last if !$clenhi; | 
| 144 | 0 |  |  |  |  | 0 | $clenhi--; | 
| 145 | 0 |  |  |  |  | 0 | $clen = 0xffffffff; | 
| 146 | 0 |  |  |  |  | 0 | $size--; | 
| 147 | 0 |  |  |  |  | 0 | $fwd .= substr($rbuf,0,1,''); | 
| 148 |  |  |  |  |  |  | } elsif ($size > $clen) { | 
| 149 | 0 |  |  |  |  | 0 | $size -= $clen; | 
| 150 | 0 |  |  |  |  | 0 | $fwd .= substr($rbuf,0,$clen,''); | 
| 151 | 0 |  |  |  |  | 0 | $clen = 0; | 
| 152 |  |  |  |  |  |  | } else {  # $size < $clen | 
| 153 | 9 |  |  |  |  | 15 | $clen -= $size; | 
| 154 | 9 |  |  |  |  | 13 | $size = 0; | 
| 155 | 9 |  |  |  |  | 18 | $fwd .= $rbuf; | 
| 156 | 9 |  |  |  |  | 21 | $rbuf = ''; | 
| 157 |  |  |  |  |  |  | } | 
| 158 |  |  |  |  |  |  | } | 
| 159 | 10 | 50 | 66 |  |  | 40 | if (!$clen && !$clenhi) { | 
| 160 |  |  |  |  |  |  | # frame done | 
| 161 | 8 | 100 |  |  |  | 19 | $eom = $current_frame->{fin} ? 1:0; | 
| 162 | 8 |  |  |  |  | 13 | $clen = undef; | 
| 163 |  |  |  |  |  |  | } | 
| 164 | 10 | 100 | 100 |  |  | 45 | if ($data_frame && $current_frame == $data_frame) { | 
| 165 | 6 | 100 |  |  |  | 14 | if (defined $clen) { | 
| 166 | 2 |  |  |  |  | 6 | $data_frame->{bytes_left} = [$clenhi,$clen]; | 
| 167 |  |  |  |  |  |  | } else { | 
| 168 | 4 |  |  |  |  | 10 | delete $data_frame->{bytes_left}; | 
| 169 |  |  |  |  |  |  | } | 
| 170 | 6 |  |  |  |  | 23 | $self->in_wsdata($dir,$fwd,$eom,$time,$data_frame); | 
| 171 | 6 | 100 |  |  |  | 46 | if ($eom) { | 
| 172 | 2 |  |  |  |  | 4 | $data_frame = undef; | 
| 173 |  |  |  |  |  |  | } else { | 
| 174 | 4 |  |  |  |  | 6 | delete $data_frame->{init}; | 
| 175 | 4 |  |  |  |  | 7 | delete $data_frame->{header}; | 
| 176 |  |  |  |  |  |  | $current_frame->{mask_offset} | 
| 177 | 4 | 100 | 50 |  |  | 16 | = (($current_frame->{mask_offset}||0) + length($fwd)) % 4 | 
| 178 |  |  |  |  |  |  | if defined $clen; | 
| 179 |  |  |  |  |  |  | } | 
| 180 |  |  |  |  |  |  | } else { | 
| 181 |  |  |  |  |  |  | # Control frames are read in full and we make sure about | 
| 182 |  |  |  |  |  |  | # this when reading the header already. | 
| 183 | 4 | 50 |  |  |  | 9 | die "expected to read full control frame" if defined $clen; | 
| 184 |  |  |  |  |  |  |  | 
| 185 | 4 | 100 |  |  |  | 9 | if ($current_frame->{opcode} == 0x8) { | 
| 186 |  |  |  |  |  |  | # extract status + reason for close | 
| 187 | 2 | 50 |  |  |  | 7 | if ($fwd eq '') { | 
|  |  | 50 |  |  |  |  |  | 
| 188 | 0 |  |  |  |  | 0 | $current_frame->{status} = 1005; # RFC6455, 7.1.5 | 
| 189 |  |  |  |  |  |  | } elsif (length($fwd) < 2) { | 
| 190 |  |  |  |  |  |  | # if payload it must be at least 2 byte for status | 
| 191 | 0 |  |  |  |  | 0 | $err = "invalid length for close control frame"; | 
| 192 | 0 |  |  |  |  | 0 | goto bad; | 
| 193 |  |  |  |  |  |  | } else { | 
| 194 |  |  |  |  |  |  | ($current_frame->{status},$current_frame->{reason}) | 
| 195 | 2 |  |  |  |  | 6 | = unpack("na*",$current_frame->unmask($fwd)); | 
| 196 |  |  |  |  |  |  | } | 
| 197 |  |  |  |  |  |  | } | 
| 198 | 4 |  |  |  |  | 16 | $self->in_wsctl($dir,$fwd,$time,$current_frame); | 
| 199 |  |  |  |  |  |  | } | 
| 200 | 10 | 50 |  |  |  | 139 | goto done if !$size; | 
| 201 | 0 |  |  |  |  | 0 | goto PARSE_DATA; | 
| 202 |  |  |  |  |  |  | } | 
| 203 |  |  |  |  |  |  |  | 
| 204 |  |  |  |  |  |  | # start of new frame: read frame header | 
| 205 |  |  |  |  |  |  | ############################################################ | 
| 206 | 11 | 100 |  |  |  | 25 | goto done if $eof; | 
| 207 | 9 | 50 |  |  |  | 21 | goto hdr_need_more if length($rbuf)<2; | 
| 208 |  |  |  |  |  |  |  | 
| 209 | 9 |  |  |  |  | 26 | (my $flags,$clen) = unpack("CC",$rbuf); | 
| 210 | 9 |  |  |  |  | 17 | my $mask = $clen & 0x80; | 
| 211 | 9 |  |  |  |  | 14 | $clen &= 0x7f; | 
| 212 | 9 |  |  |  |  | 12 | $clenhi = 0; | 
| 213 | 9 |  |  |  |  | 14 | my $off = 2; | 
| 214 |  |  |  |  |  |  |  | 
| 215 | 9 | 100 |  |  |  | 27 | if ($clen == 126) { | 
|  |  | 100 |  |  |  |  |  | 
| 216 | 1 | 50 |  |  |  | 7 | goto hdr_need_more if length($rbuf)<4; | 
| 217 | 1 |  |  |  |  | 7 | ($clen) = unpack("xxn",$rbuf); | 
| 218 | 1 | 50 |  |  |  | 3 | goto bad_length if $clen<126; | 
| 219 | 1 |  |  |  |  | 3 | $off = 4; | 
| 220 |  |  |  |  |  |  | } elsif ($clen == 127) { | 
| 221 | 2 | 50 |  |  |  | 7 | goto hdr_need_more if length($rbuf)<10; | 
| 222 | 2 |  |  |  |  | 6 | ($clenhi,$clen) = unpack("xxNN",$rbuf); | 
| 223 | 2 | 50 | 33 |  |  | 12 | goto bad_length if !$clenhi && $clen<2**16; | 
| 224 | 2 |  |  |  |  | 3 | $off = 10; | 
| 225 |  |  |  |  |  |  | } | 
| 226 | 9 | 100 |  |  |  | 19 | if ($mask) { | 
| 227 | 7 | 100 |  |  |  | 19 | goto hdr_need_more if length($rbuf)<$off+4; | 
| 228 | 6 |  |  |  |  | 19 | ($mask) = unpack("x${off}a4",$rbuf); | 
| 229 | 6 |  |  |  |  | 12 | $off+=4; | 
| 230 |  |  |  |  |  |  | } else { | 
| 231 | 2 |  |  |  |  | 4 | $mask = undef; | 
| 232 |  |  |  |  |  |  | } | 
| 233 |  |  |  |  |  |  |  | 
| 234 | 8 |  |  |  |  | 11 | my $opcode = $flags & 0b00001111; | 
| 235 | 8 |  |  |  |  | 15 | my $fin    = $flags & 0b10000000; | 
| 236 | 8 | 50 |  |  |  | 17 | goto reserved_flag if $flags & 0b01110000; | 
| 237 |  |  |  |  |  |  |  | 
| 238 | 8 | 100 |  |  |  | 20 | if ($opcode >= 0x8) { | 
|  |  | 100 |  |  |  |  |  | 
| 239 |  |  |  |  |  |  | # control frame | 
| 240 | 4 | 50 |  |  |  | 9 | goto reserved_opcode if $opcode >= 0xb; | 
| 241 | 4 | 50 |  |  |  | 10 | if (!$fin) { | 
| 242 | 0 |  |  |  |  | 0 | $err = "fragmented control frames are forbidden"; | 
| 243 | 0 |  |  |  |  | 0 | goto bad; | 
| 244 |  |  |  |  |  |  | } | 
| 245 | 4 | 50 | 33 |  |  | 19 | if ($clenhi || $clen>125) { | 
| 246 | 0 |  |  |  |  | 0 | $err = "control frames should be <= 125 bytes"; | 
| 247 | 0 |  |  |  |  | 0 | goto bad; | 
| 248 |  |  |  |  |  |  | } | 
| 249 |  |  |  |  |  |  | # We like to forward control frames as a single entity, so make | 
| 250 |  |  |  |  |  |  | # sure we get the whole (small) frame at once. | 
| 251 | 4 | 50 |  |  |  | 9 | goto hdr_need_more if $off+$clen > length($rbuf); | 
| 252 |  |  |  |  |  |  |  | 
| 253 | 4 |  | 66 |  |  | 15 | $current_frame = $ctl_frame | 
| 254 |  |  |  |  |  |  | ||= Net::Inspect::L7::HTTP::WebSocket::_WSFrame->new; | 
| 255 | 4 | 100 |  |  |  | 20 | %$current_frame = ( | 
| 256 |  |  |  |  |  |  | opcode => $opcode, | 
| 257 |  |  |  |  |  |  | defined($mask) ? ( mask => $mask ):() | 
| 258 |  |  |  |  |  |  | ); | 
| 259 | 4 | 100 |  |  |  | 13 | $got_close = 1 if $opcode == 0x8; | 
| 260 |  |  |  |  |  |  |  | 
| 261 |  |  |  |  |  |  | } elsif ($opcode>0) { | 
| 262 |  |  |  |  |  |  | # data frame, but no continuation | 
| 263 | 2 | 50 |  |  |  | 5 | goto reserved_opcode if $opcode >= 0x3; | 
| 264 | 2 | 50 |  |  |  | 8 | if ($got_close) { | 
| 265 | 0 |  |  |  |  | 0 | $err = "data frame after close"; | 
| 266 | 0 |  |  |  |  | 0 | goto bad; | 
| 267 |  |  |  |  |  |  | } | 
| 268 | 2 | 50 |  |  |  | 5 | if ($data_frame) { | 
| 269 | 0 |  |  |  |  | 0 | $err = "new data message before end of previous message"; | 
| 270 | 0 |  |  |  |  | 0 | goto bad; | 
| 271 |  |  |  |  |  |  | } | 
| 272 | 2 |  |  |  |  | 9 | $current_frame = $data_frame | 
| 273 |  |  |  |  |  |  | = Net::Inspect::L7::HTTP::WebSocket::_WSFrame->new; | 
| 274 | 2 | 100 |  |  |  | 17 | %$current_frame = ( | 
|  |  | 100 |  |  |  |  |  | 
| 275 |  |  |  |  |  |  | opcode => $opcode, | 
| 276 |  |  |  |  |  |  | $fin ? ( fin => 1 ):(), | 
| 277 |  |  |  |  |  |  | init => 1,  # initial data | 
| 278 |  |  |  |  |  |  | defined($mask) ? ( mask => $mask ):() | 
| 279 |  |  |  |  |  |  | ); | 
| 280 |  |  |  |  |  |  |  | 
| 281 |  |  |  |  |  |  | } else { | 
| 282 |  |  |  |  |  |  | # continuation frame | 
| 283 | 2 | 50 |  |  |  | 6 | if (!$data_frame) { | 
| 284 | 0 |  |  |  |  | 0 | $err = "continuation frame without previous data frame"; | 
| 285 | 0 |  |  |  |  | 0 | goto bad; | 
| 286 |  |  |  |  |  |  | } | 
| 287 | 2 |  |  |  |  | 5 | $current_frame = $data_frame; | 
| 288 |  |  |  |  |  |  | %$current_frame = ( | 
| 289 |  |  |  |  |  |  | opcode => $data_frame->{opcode}, | 
| 290 | 2 | 100 |  |  |  | 11 | $fin ? ( fin => 1 ):(), | 
|  |  | 50 |  |  |  |  |  | 
| 291 |  |  |  |  |  |  | defined($mask) ? ( mask => $mask ):() | 
| 292 |  |  |  |  |  |  | ); | 
| 293 |  |  |  |  |  |  | } | 
| 294 |  |  |  |  |  |  |  | 
| 295 |  |  |  |  |  |  | # done with frame header | 
| 296 | 8 |  |  |  |  | 26 | $current_frame->{header} = substr($rbuf,0,$off,''); | 
| 297 | 8 |  |  |  |  | 16 | goto PARSE_DATA; | 
| 298 |  |  |  |  |  |  |  | 
| 299 |  |  |  |  |  |  | # Done | 
| 300 |  |  |  |  |  |  | ############################################################ | 
| 301 |  |  |  |  |  |  |  | 
| 302 | 1 |  |  |  |  | 2 | hdr_need_more: | 
| 303 |  |  |  |  |  |  | $clen = undef; # re-read from start if frame next time | 
| 304 | 1 |  |  |  |  | 2 | return; | 
| 305 |  |  |  |  |  |  |  | 
| 306 |  |  |  |  |  |  | done: | 
| 307 | 12 | 100 |  |  |  | 33 | if ($eof) { | 
|  |  | 100 |  |  |  |  |  | 
| 308 |  |  |  |  |  |  | # forward eof as special wsctl with no frame | 
| 309 |  |  |  |  |  |  | # FIXME: complain if we have eof but the current frame is not | 
| 310 |  |  |  |  |  |  | # done yet. | 
| 311 | 2 |  |  |  |  | 5 | $self->in_wsctl($dir,'',$time); | 
| 312 |  |  |  |  |  |  | } elsif (defined $clen) { | 
| 313 |  |  |  |  |  |  | # We have at least the header of a data frame (control frames | 
| 314 |  |  |  |  |  |  | # are read as a single entity) and might need more data | 
| 315 |  |  |  |  |  |  | # (clen>0). Set gap_diff. | 
| 316 | 2 | 50 |  |  |  | 12 | $clen>0 and $conn->set_gap_diff($dir, | 
|  |  | 50 |  |  |  |  |  | 
| 317 |  |  |  |  |  |  | ! $clenhi ? $clen :           # len <=32 bit | 
| 318 |  |  |  |  |  |  | 1 << 32 == 1 ? 0xffffffff :   # maxint on 32-bit platform | 
| 319 |  |  |  |  |  |  | ($clenhi << 32) + $clen       # full 64 bit | 
| 320 |  |  |  |  |  |  | ); | 
| 321 |  |  |  |  |  |  | } | 
| 322 | 12 |  |  |  |  | 36 | return; | 
| 323 |  |  |  |  |  |  |  | 
| 324 | 0 |  | 0 |  |  | 0 | bad_length: | 
| 325 |  |  |  |  |  |  | $err ||= "non-minimal length representation in websocket frame"; | 
| 326 | 0 |  | 0 |  |  | 0 | reserved_flag: | 
| 327 |  |  |  |  |  |  | $err ||= "extensions using reserved flags are not supported"; | 
| 328 | 0 |  | 0 |  |  | 0 | reserved_opcode: | 
| 329 |  |  |  |  |  |  | $err ||= "no support for opcode $opcode"; | 
| 330 |  |  |  |  |  |  |  | 
| 331 |  |  |  |  |  |  | bad: | 
| 332 | 0 |  |  |  |  | 0 | $conn->{error} = 1; | 
| 333 | 0 |  |  |  |  | 0 | $self->fatal($err,$dir,$time); | 
| 334 | 0 |  |  |  |  | 0 | return; | 
| 335 | 2 |  |  |  |  | 24 | }; | 
| 336 |  |  |  |  |  |  | } | 
| 337 |  |  |  |  |  |  |  | 
| 338 |  |  |  |  |  |  | return sub { | 
| 339 | 18 |  |  | 18 |  | 32 | my $dir = shift; | 
| 340 | 18 |  |  |  |  | 26 | goto &{$sub[$dir]}; | 
|  | 18 |  |  |  |  | 56 |  | 
| 341 |  |  |  |  |  |  | } | 
| 342 | 1 |  |  |  |  | 10 | } | 
| 343 |  |  |  |  |  |  |  | 
| 344 |  |  |  |  |  |  | { | 
| 345 |  |  |  |  |  |  | package Net::Inspect::L7::HTTP::WebSocket::_WSFrame; | 
| 346 | 4 |  |  | 4 |  | 13 | sub new { bless {}, shift }; | 
| 347 |  |  |  |  |  |  | sub unmask { | 
| 348 | 11 |  |  | 11 |  | 64 | my ($self,$data) = @_; | 
| 349 | 11 | 100 | 66 |  |  | 50 | return $data if $data eq '' or ! $self->{mask}; | 
| 350 | 8 |  |  |  |  | 12 | my $l = length($data); | 
| 351 | 8 |  | 100 |  |  | 55 | $data ^= substr($self->{mask} x int($l/4+2),$self->{mask_offset}||0,$l); | 
| 352 | 8 |  |  |  |  | 20 | return $data; | 
| 353 |  |  |  |  |  |  | }; | 
| 354 |  |  |  |  |  |  | } | 
| 355 |  |  |  |  |  |  |  | 
| 356 |  |  |  |  |  |  | 1; | 
| 357 |  |  |  |  |  |  |  | 
| 358 |  |  |  |  |  |  | __END__ |