File Coverage

blib/lib/Net/WebSocket/Parser.pm
Criterion Covered Total %
statement 80 86 93.0
branch 33 38 86.8
condition 4 5 80.0
subroutine 10 10 100.0
pod 1 2 50.0
total 128 141 90.7


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::Read->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 only a partial frame is ready, undef is returned.
33              
34             =back
35              
36             =head1 I/O DETAILS
37              
38             L was born out of work on this module; see that module’s
39             documentation for the particulars of working with it. In particular,
40             note the exceptions L and
41             L. (As described in L’s
42             documentation, you can use an equivalent interface for frame chunking if you
43             wish.)
44              
45             =head1 CUSTOM FRAMES SUPPORT
46              
47             To support reception of custom frame types you’ll probably want to subclass
48             this module and define a specific custom constant for each supported opcode,
49             e.g.:
50              
51             package My::WebSocket::Parser;
52              
53             use parent qw( Net::WebSocket::Parser );
54              
55             use constant OPCODE_CLASS_3 => 'My::WebSocket::Frame::booya';
56              
57             … where C is itself a subclass of
58             C.
59              
60             You can also use this to override the default
61             classes for built-in frame types; e.g., C will override
62             L as the class will be used for pong frames
63             that this module receives. That could be useful, e.g., for compression
64             extensions, where you might want the C method to
65             decompress so that that detail is abstracted away.
66              
67             =cut
68              
69 6     6   376043 use strict;
  6         30  
  6         142  
70 6     6   28 use warnings;
  6         9  
  6         121  
71              
72 6     6   263 use Module::Load ();
  6         814  
  6         71  
73              
74 6     6   1133 use Net::WebSocket::Constants ();
  6         12  
  6         88  
75 6     6   1104 use Net::WebSocket::X ();
  6         13  
  6         169  
76              
77             use constant {
78 6         3679 OPCODE_CLASS_0 => 'Net::WebSocket::Frame::continuation',
79             OPCODE_CLASS_1 => 'Net::WebSocket::Frame::text',
80             OPCODE_CLASS_2 => 'Net::WebSocket::Frame::binary',
81             OPCODE_CLASS_8 => 'Net::WebSocket::Frame::close',
82             OPCODE_CLASS_9 => 'Net::WebSocket::Frame::ping',
83             OPCODE_CLASS_10 => 'Net::WebSocket::Frame::pong',
84 6     6   28 };
  6         11  
85              
86             sub new {
87 21     21 0 11150 my ($class, $reader) = @_;
88              
89 21 50       121 if (!(ref $reader)->can('read')) {
90 0         0 die "“$reader” needs a read() method!";
91             }
92              
93 21         69 return bless {
94             _reader => $reader,
95             }, $class;
96             }
97              
98             sub get_next_frame {
99 65908     65908 1 3054737 my ($self) = @_;
100              
101 65908         78651 local $@;
102              
103 65908 100       115321 if (!exists $self->{'_partial_frame'}) {
104 21         38 $self->{'_partial_frame'} = q<>;
105             }
106              
107             #It is really, really inconvenient that Perl has no “or” operator
108             #that considers q<> falsey but '0' truthy. :-/
109             #That aside, if indeed all we read is '0', then we know that’s not
110             #enough, and we can return.
111 65908 100       98806 my $first2 = $self->_read_with_buffer(2) or return undef;
112              
113             #Now that we’ve read our header bytes, we’ll read some more.
114             #There may not actually be anything to read, though, in which case
115             #some readers will error (e.g., EAGAIN from a non-blocking filehandle).
116             #From a certain ideal we’d return #on each individual read to allow
117             #the reader to wait until there is more data ready; however, for
118             #practicality (and speed) let’s go ahead and try to read the rest of
119             #the frame. That means we need to set some flag to let the reader know
120             #not to die() if there’s no more data currently, as we’re probably
121             #expecting more soon to complete the frame.
122 65903         110082 local $self->{'_reading_frame'} = 1;
123              
124 65903         133687 my ($oct1, $oct2) = unpack('CC', $first2 );
125              
126 65903         95854 my $len = $oct2 & 0x7f;
127              
128 65903   100     98380 my $mask_size = ($oct2 & 0x80) && 4;
129              
130 65903 100       114315 my $len_len = ($len == 0x7e) ? 2 : ($len == 0x7f) ? 8 : 0;
    100          
131 65903         77567 my $len_buf = q<>;
132              
133 65903         79940 my ($longs, $long);
134              
135 65903 100       86910 if ($len_len) {
136 65804 100       97807 $len_buf = $self->_read_with_buffer($len_len) or do {
137 10         18 substr( $self->{'_partial_frame'}, 0, 0, $first2 );
138 10         23 return undef;
139             };
140              
141 65794 100       104282 if ($len_len == 2) {
142 257         454 ($longs, $long) = ( 0, unpack('n', $len_buf) );
143             }
144             else {
145 65537         119799 ($longs, $long) = ( unpack('NN', $len_buf) );
146             }
147             }
148             else {
149 99         139 ($longs, $long) = ( 0, $len );
150             }
151              
152 65893         85777 my $mask_buf;
153 65893 100       88065 if ($mask_size) {
154 71 100       113 $mask_buf = $self->_read_with_buffer($mask_size) or do {
155 4         8 substr( $self->{'_partial_frame'}, 0, 0, $first2 . $len_buf );
156 4         13 return undef;
157             };
158             }
159             else {
160 65822         80880 $mask_buf = q<>;
161             }
162              
163 65889         83068 my $payload = q<>;
164              
165 65889         117063 for ( 1 .. $longs ) {
166              
167             #32-bit systems don’t know what 2**32 is.
168             #MacOS, at least, also chokes on sysread( 2**31, … )
169             #(Is their size_t signed??), even on 64-bit.
170 0         0 for ( 1 .. 4 ) {
171 0 0       0 $self->_append_chunk( 2**30, \$payload ) or do {
172 0         0 substr( $self->{'_partial_frame'}, 0, 0, $first2 . $len_buf . $mask_buf . $payload );
173 0         0 return undef;
174             };
175             }
176             }
177              
178 65889 100       97513 if ($long) {
179 65881 100       101581 $self->_append_chunk( $long, \$payload ) or do {
180 65806         128216 substr( $self->{'_partial_frame'}, 0, 0, $first2 . $len_buf . $mask_buf . $payload );
181 65806         175233 return undef;
182             };
183             }
184              
185 83         136 $self->{'_partial_frame'} = q<>;
186              
187 83         129 my $opcode = $oct1 & 0xf;
188              
189 83   66     219 my $frame_class = $self->{'_opcode_class'}{$opcode} ||= do {
190 25         29 my $class;
191 25 50       132 if (my $cr = $self->can("OPCODE_CLASS_$opcode")) {
192 25         59 $class = $cr->();
193             }
194             else {
195              
196             #Untyped because this is a coding error.
197 0         0 die "$self: Unrecognized frame opcode: “$opcode”";
198             }
199              
200 25 100       190 Module::Load::load($class) if !$class->can('new');
201              
202 25         151 $class;
203             };
204              
205 83         307 return $frame_class->create_from_parse(\$first2, \$len_len, \$mask_buf, \$payload);
206             }
207              
208             #This will only return exactly the number of bytes requested.
209             #If fewer than we want are available, then we return undef.
210             sub _read_with_buffer {
211 197664     197664   247523 my ($self, $length) = @_;
212              
213             #Prioritize the case where we read everything we need.
214              
215 197664 100       312500 if ( length($self->{'_partial_frame'}) < $length ) {
216 66045         83415 my $deficit = $length - length($self->{'_partial_frame'});
217 66045         120577 my $read = $self->{'_reader'}->read($deficit);
218              
219 66045 100       1386354 if (!defined $read) {
220 65824         95305 return undef;
221             }
222              
223 221         676 return substr($self->{'_partial_frame'}, 0, length($self->{'_partial_frame'}), q<>) . $read;
224             }
225              
226 131619         312877 return substr( $self->{'_partial_frame'}, 0, $length, q<> );
227             }
228              
229             sub _append_chunk {
230 65881     65881   88222 my ($self, $length, $buf_sr) = @_;
231              
232 65881         82514 my $start_buf_len = length $$buf_sr;
233              
234 65881         73542 my $cur_buf;
235              
236 65881         74226 while (1) {
237 65881         79243 my $read_so_far = length($$buf_sr) - $start_buf_len;
238              
239 65881         101144 $cur_buf = $self->_read_with_buffer($length - $read_so_far);
240 65881 100       140250 return undef if !defined $cur_buf;
241              
242 75         163 $$buf_sr .= $cur_buf;
243              
244 75 50       163 last if (length($$buf_sr) - $start_buf_len) >= $length;
245             }
246              
247 75         137 return 1;
248             }
249              
250             1;