File Coverage

blib/lib/Net/WebSocket/Parser.pm
Criterion Covered Total %
statement 83 90 92.2
branch 39 50 78.0
condition 4 5 80.0
subroutine 10 10 100.0
pod 1 2 50.0
total 137 157 87.2


line stmt bran cond sub pod time code
1             package Net::WebSocket::Parser;
2              
3             =encoding utf-8
4              
5             =head1 NAME
6              
7             Net::WebSocket::Parser - Parse WebSocket from a filehandle
8              
9             =head1 SYNOPSIS
10              
11             my $iof = IO::Framed->new($fh);
12              
13             my $parse = Net::WebSocket::Parser->new($iof);
14              
15             #See below for error responses
16             my $frame = $parse->get_next_frame();
17              
18             C<$iof> should normally be an instance of L. You’re free to
19             pass in anything with a C method, but that method must implement
20             the same behavior as C.
21              
22             =head1 METHODS
23              
24             =head2 I->get_next_frame()
25              
26             A call to this method yields one of the following:
27              
28             =over
29              
30             =item * If a frame can be read, it will be returned.
31              
32             =item * If we hit an empty read (i.e., indicative of end-of-file),
33             empty string is returned.
34              
35             =item * If only a partial frame is ready, undef is returned.
36              
37             =back
38              
39             =head1 I/O DETAILS
40              
41             L was born out of work on this module; see that module’s
42             documentation for the particulars of working with it. In particular,
43             note the exceptions L and
44             L.
45              
46             Again, you can use an equivalent interface for frame chunking if you wish.
47              
48             =head1 CONCERNING EMPTY READS
49              
50             An empty read is how we detect that a file handle (or socket, etc.) has no
51             more data to read. Generally we shouldn’t get this in WebSocket since it
52             means that a peer endpoint has gone away without sending a close frame.
53             It is thus recommended that applications regard an empty read on a WebSocket
54             stream as an error condition; e.g., if you’re using L,
55             you should NOT enable the C behavior.
56              
57             Nevertheless, this module (and L) do work when
58             that flag is enabled.
59              
60             =head1 CUSTOM FRAMES SUPPORT
61              
62             To support reception of custom frame types you’ll probably want to subclass
63             this module and define a specific custom constant for each supported opcode,
64             e.g.:
65              
66             package My::WebSocket::Parser;
67              
68             use parent qw( Net::WebSocket::Parser );
69              
70             use constant OPCODE_CLASS_3 => 'My::WebSocket::Frame::booya';
71              
72             … where C is itself a subclass of
73             C.
74              
75             You can also use this to override the default
76             classes for built-in frame types; e.g., C will override
77             L as the class will be used for pong frames
78             that this module receives. That could be useful, e.g., for compression
79             extensions, where you might want the C method to
80             decompress so that that detail is abstracted away.
81              
82             =cut
83              
84 14     14   768025 use strict;
  14         103  
  14         368  
85 14     14   61 use warnings;
  14         23  
  14         270  
86              
87 14     14   492 use Module::Load ();
  14         1016  
  14         177  
88              
89 14     14   2755 use Net::WebSocket::Constants ();
  14         24  
  14         191  
90 14     14   2494 use Net::WebSocket::X ();
  14         29  
  14         404  
91              
92             use constant {
93 14         9857 OPCODE_CLASS_0 => 'Net::WebSocket::Frame::continuation',
94             OPCODE_CLASS_1 => 'Net::WebSocket::Frame::text',
95             OPCODE_CLASS_2 => 'Net::WebSocket::Frame::binary',
96             OPCODE_CLASS_8 => 'Net::WebSocket::Frame::close',
97             OPCODE_CLASS_9 => 'Net::WebSocket::Frame::ping',
98             OPCODE_CLASS_10 => 'Net::WebSocket::Frame::pong',
99 14     14   75 };
  14         37  
100              
101             sub new {
102 36     36 0 431098 my ($class, $reader) = @_;
103              
104 36 50       231 if (!(ref $reader)->can('read')) {
105 0         0 die "“$reader” needs a read() method!";
106             }
107              
108 36         157 return bless {
109             _reader => $reader,
110             _partial_frame => q<>,
111             }, $class;
112             }
113              
114             #Create these out here so that we don’t create/destroy them on each frame.
115             #As long as we don’t access them prior to writing to them this is fine.
116             my ($oct1, $oct2, $len, $mask_size, $len_len, $longs, $long);
117              
118             sub get_next_frame {
119 65931     65931 1 3836597 my ($self) = @_;
120              
121 65931         81175 local $@;
122              
123             #It is really, really inconvenient that Perl has no “or” operator
124             #that considers q<> falsey but '0' truthy. :-/
125             #That aside, if indeed all we read is '0', then we know that’s not
126             #enough, and we can return.
127 65931         104607 my $first2 = $self->_read_with_buffer(2);
128 65931 100       116411 if (!$first2) {
129 6 100       24 return defined($first2) ? q<> : undef;
130             }
131              
132 65925         142250 ($oct1, $oct2) = unpack('CC', $first2 );
133              
134 65925         96022 $len = $oct2 & 0x7f;
135              
136 65925   100     107379 $mask_size = ($oct2 & 0x80) && 4;
137              
138 65925 100       120935 $len_len = ($len == 0x7e) ? 2 : ($len == 0x7f) ? 8 : 0;
    100          
139              
140 65925         80310 my ($len_buf, $mask_buf);
141              
142 65925 100       93371 if ($len_len) {
143 65810         100633 $len_buf = $self->_read_with_buffer($len_len);
144              
145 65810 100       115615 if (!$len_buf) {
146 10         21 substr( $self->{'_partial_frame'}, 0, 0, $first2 );
147 10 50       24 return defined($len_buf) ? q<> : undef;
148             };
149              
150 65800 100       96240 if ($len_len == 2) {
151 259         558 ($longs, $long) = ( 0, unpack('n', $len_buf) );
152             }
153             else {
154              
155             #Do it this way to support 32-bit systems.
156 65541         122215 ($longs, $long) = ( unpack('NN', $len_buf) );
157             }
158             }
159             else {
160 115         161 ($longs, $long) = ( 0, $len );
161 115         157 $len_buf = q<>;
162             }
163              
164 65915 100       96209 if ($mask_size) {
165 76         141 $mask_buf = $self->_read_with_buffer($mask_size);
166 76 100       135 if (!$mask_buf) {
167 4         10 substr( $self->{'_partial_frame'}, 0, 0, $first2 . $len_buf );
168 4 50       13 return defined($mask_buf) ? q<> : undef;
169             };
170             }
171             else {
172 65839         82542 $mask_buf = q<>;
173             }
174              
175 65911         75463 my $payload = q<>;
176              
177 65911         120573 for ( 1 .. $longs ) {
178              
179             #32-bit systems don’t know what 2**32 is.
180             #MacOS, at least, also chokes on sysread( 2**31, … )
181             #(Is their size_t signed??), even on 64-bit.
182 0         0 for ( 1 .. 4 ) {
183 0         0 my $append_ok = $self->_append_chunk( 2**30, \$payload );
184 0 0       0 if (!$append_ok) {
185 0         0 substr( $self->{'_partial_frame'}, 0, 0, $first2 . $len_buf . $mask_buf . $payload );
186 0 0       0 return defined($append_ok) ? q<> : undef;
187             };
188             }
189             }
190              
191 65911 100       101251 if ($long) {
192 65903         113353 my $append_ok = $self->_append_chunk( $long, \$payload );
193 65903 100       101968 if (!$append_ok) {
194 65806         133216 substr( $self->{'_partial_frame'}, 0, 0, $first2 . $len_buf . $mask_buf . $payload );
195 65806 50       176880 return defined($append_ok) ? q<> : undef;
196             }
197             }
198              
199 105         149 $self->{'_partial_frame'} = q<>;
200              
201 105         150 my $opcode = $oct1 & 0xf;
202              
203 105   66     288 my $frame_class = $self->{'_opcode_class'}{$opcode} ||= do {
204 42         58 my $class;
205 42 50       224 if (my $cr = $self->can("OPCODE_CLASS_$opcode")) {
206 42         99 $class = $cr->();
207             }
208             else {
209              
210             #Untyped because this is a coding error.
211 0         0 die "$self: Unrecognized frame opcode: “$opcode”";
212             }
213              
214 42 100       362 Module::Load::load($class) if !$class->can('new');
215              
216 42         223 $class;
217             };
218              
219 105         418 return $frame_class->create_from_parse(\$first2, \$len_buf, \$mask_buf, \$payload);
220             }
221              
222             #This will only return exactly the number of bytes requested.
223             #If fewer than we want are available, then we return undef.
224             #
225             #NB: This predates IO::Framed and might be due for a simplification.
226             #
227             sub _read_with_buffer {
228 197720     197720   260151 my ($self, $length) = @_;
229              
230             #Prioritize the case where we read everything we need.
231              
232 197720 100       330705 if ( length($self->{'_partial_frame'}) < $length ) {
233 66101         82002 my $deficit = $length - length($self->{'_partial_frame'});
234 66101         146642 my $read = $self->{'_reader'}->read($deficit);
235              
236 66101 100       1938342 if (!defined $read) {
    100          
237 65824         108366 return undef;
238             }
239             elsif (!length $read) {
240 2         6 return q<>;
241             }
242              
243 275         1264 return substr($self->{'_partial_frame'}, 0, length($self->{'_partial_frame'}), q<>) . $read;
244             }
245              
246 131619         274370 return substr( $self->{'_partial_frame'}, 0, $length, q<> );
247             }
248              
249             sub _append_chunk {
250 65903     65903   93418 my ($self, $length, $buf_sr) = @_;
251              
252 65903         85030 my $start_buf_len = length $$buf_sr;
253              
254 65903         72565 my $cur_buf;
255              
256 65903         74643 while (1) {
257 65903         83369 my $read_so_far = length($$buf_sr) - $start_buf_len;
258              
259 65903         111608 $cur_buf = $self->_read_with_buffer($length - $read_so_far);
260 65903 100       140589 return undef if !defined $cur_buf;
261              
262 97 50       175 return q<> if !length $cur_buf;
263              
264 97         506 $$buf_sr .= $cur_buf;
265              
266 97 50       207 last if (length($$buf_sr) - $start_buf_len) >= $length;
267             }
268              
269 97         147 return 1;
270             }
271              
272             1;