File Coverage

blib/lib/Net/WebSocket/Handshake.pm
Criterion Covered Total %
statement 86 108 79.6
branch 31 46 67.3
condition 2 3 66.6
subroutine 17 19 89.4
pod 5 5 100.0
total 141 181 77.9


line stmt bran cond sub pod time code
1             package Net::WebSocket::Handshake;
2              
3 5     5   2004 use strict;
  5         23  
  5         124  
4 5     5   23 use warnings;
  5         7  
  5         158  
5              
6             =encoding utf-8
7              
8             =head1 NAME
9              
10             Net::WebSocket::Handshake - base class for handshake objects
11              
12             =head1 DESCRIPTION
13              
14             This base class’s L and
15             L subclasses implement
16             WebSocket’s handshake logic. They handle the basics of a WebSocket
17             handshake and, optionally, subprotocol and extension negotiation.
18              
19             This base class is NOT directly instantiable.
20              
21             =cut
22              
23 5     5   2054 use Digest::SHA1 ();
  5         2771  
  5         98  
24 5     5   1920 use HTTP::Headers::Util ();
  5         4121  
  5         104  
25              
26 5     5   1678 use Net::WebSocket::HTTP ();
  5         12  
  5         84  
27 5     5   30 use Net::WebSocket::X ();
  5         32  
  5         132  
28              
29             use constant {
30 5         5322 _WS_MAGIC_CONSTANT => '258EAFA5-E914-47DA-95CA-C5AB0DC85B11',
31             CRLF => "\x0d\x0a",
32 5     5   22 };
  5         9  
33              
34             #----------------------------------------------------------------------
35              
36             =head1 METHODS
37              
38             =head2 I->new( %OPTS )
39              
40             Returns an instance of the relevant subclass
41             (L or
42             L).
43             The following are common options for both:
44              
45             =over
46              
47             =item * C - A list of HTTP tokens (e.g., C)
48             that stand for subprotocols that this endpoint can use via the WebSocket
49             connection.
50              
51             =item * C - A list of extension objects that the Handshake
52             object will interact with to determine extension support.
53              
54             =head1 COMMON EXTENSION INTERFACE
55              
56             Each object in the C array must implement the following methods:
57              
58             =over
59              
60             =item * C The extension’s token. (e.g., C)
61              
62             =item * C Returns an instance of
63             L to represent the extension and
64             its parameters in the HTTP headers.
65              
66             =item * C Receives the extension parameters
67             (in the format that C
68             returns). This operation should configure the object to return the proper
69             value from its C method.
70              
71             =item * C A boolean that indicates whether the peer indicates
72             proper support for the extension. This should not be called until after
73             C
74              
75             =back
76              
77             =cut
78              
79             sub new {
80 15     15 1 15457 my ($class, %opts) = @_;
81              
82 15 100       47 if ($opts{'extensions'}) {
83 1         2 $opts{'_extension_tokens'} = { map { $_->token() => $_ } @{ $opts{'extensions'} } };
  2         5  
  1         4  
84             }
85              
86 15         56 return bless \%opts, $class;
87             }
88              
89             =head2 $sp_token = I->get_subprotocol()
90              
91             Returns the negotiated subprotocol’s token (e.g., C).
92              
93             =cut
94              
95             sub get_subprotocol {
96 2     2 1 11 my $self = shift;
97              
98 2 50       5 if (!$self->{'_no_use_legacy'}) {
99 0         0 die 'Must call consume_headers() first!';
100             }
101              
102 2         8 return $self->{'_subprotocol'};
103             }
104              
105             =head2 I->consume_headers( HDR1 => VAL1, HDR2 => VAL2, .. )
106              
107             The “workhorse” method of this base class. Takes in the HTTP headers
108             and verifies that the look as they should, setting this object’s own
109             internals as appropriate.
110              
111             This will throw an appropriate exception if any header is missing
112             or otherwise invalid.
113              
114             =cut
115              
116             sub consume_headers {
117 11     11 1 1944 my ($self, @kv_pairs) = @_;
118              
119 11         26 $self->{'_no_use_legacy'} = 1;
120              
121 11         40 while ( my ($k => $v) = splice( @kv_pairs, 0, 2 ) ) {
122 31 50       55 next if !defined $v;
123 31         71 $self->_consume_peer_header($k => $v);
124             }
125              
126 6         23 $self->_die_if_missing_headers();
127              
128 5         20 return;
129             }
130              
131             =head2 my $hdrs_txt = I->to_string()
132              
133             The text of the HTTP headers to send, with the 2nd trailing CR/LF
134             that ends the headers portion of an HTTP message.
135              
136             If you use this object
137             to negotiate a subprotocol and/or extensions, those will be included
138             in the output from this method.
139              
140             To append custom headers, do the following with the result of this method:
141              
142             substr($hdrs_txt, -2, 0) = '..';
143              
144             =cut
145              
146             sub to_string {
147 5     5 1 355 my $self = shift;
148              
149 5         17 return join( CRLF(), $self->_create_header_lines(), q<>, q<> );
150             }
151              
152             =head1 LEGACY INTERFACE
153              
154             Prior to version 0.5 this module was a great deal less “helpful”:
155             it required callers to parse out and write WebSocket headers,
156             doing most of the validation manually. Version 0.5 added a generic
157             interface for entering in HTTP headers, which allows Net::WebSocket to
158             handle the parsing and creation of HTTP headers as well as subprotocol
159             and extension negotiation.
160              
161             For now the legacy functionality is being left in; however,
162             it is considered DEPRECATED and will be removed eventually.
163              
164             =head2 my $hdrs_txt = I->create_header_text()
165              
166             The same output as C but minus the 2nd trailing
167             CR/LF. (This was intended to facilitate adding other headers; however,
168             that’s done easily enough with the newer C.)
169              
170             =cut
171              
172             sub create_header_text {
173 0     0 1 0 my $self = shift;
174              
175 0         0 return join( CRLF(), $self->_create_header_lines(), q<> );
176             }
177              
178             =head1 SEE ALSO
179              
180             =over
181              
182             =item * L
183              
184             =item * L
185              
186             =back
187              
188             =cut
189              
190             #----------------------------------------------------------------------
191              
192             sub _get_accept {
193 5     5   11 my ($self) = @_;
194              
195 5 50       16 my $key_b64 = $self->{'key'} or do {
196 0         0 die Net::WebSocket::X->create('BadArg', key => $self->{'key'});
197             };
198              
199 5         26 $key_b64 =~ s<\A\s+|\s+\z><>g;
200              
201 5         39 my $accept = Digest::SHA1::sha1_base64( $key_b64 . _WS_MAGIC_CONSTANT() );
202              
203             #pad base64
204 5         19 $accept .= '=' x (4 - (length($accept) % 4));
205              
206 5         19 return $accept;
207             }
208              
209             #Post-legacy, move this to Client and have the Server use logic
210             #that allows only one.
211             sub _encode_subprotocols {
212 5     5   76 my ($self) = @_;
213              
214             return ( $self->{'subprotocols'} && @{ $self->{'subprotocols'} }
215 5 100 66     29 ? ( 'Sec-WebSocket-Protocol: ' . join(', ', @{ $self->{'subprotocols'} } ) )
  3         32  
216             : ()
217             );
218             }
219              
220             sub _encode_extensions {
221 5     5   11 my ($self) = @_;
222              
223 5 100       34 return if !$self->{'extensions'};
224              
225 1         3 my @handshake_xtns;
226 1         2 for my $xtn ( @{ $self->{'extensions'} } ) {
  1         3  
227 2 50       11 if ( $xtn->isa('Net::WebSocket::Handshake::Extension') ) {
    0          
228 2         160 $self->_warn_legacy();
229 2         5 push @handshake_xtns, $xtn;
230             }
231             elsif ( $self->_should_include_extension_in_headers($xtn) ) {
232 0         0 push @handshake_xtns, $xtn->get_handshake_object();
233             }
234             }
235              
236 1 50       3 return if !@handshake_xtns;
237              
238 1         3 my ($first, @others) = @handshake_xtns;
239              
240 1         4 return 'Sec-WebSocket-Extensions: ' . $first->to_string(@others);
241             }
242              
243             sub _warn_legacy {
244 2     2   4 my ($self) = @_;
245              
246 2 100       6 if (!$self->{'_warned_legacy'}) {
247 1         3 my $ref = ref $self;
248 1         139 warn "You are using $ref’s legacy interface. This interface will eventually be removed from $ref entirely, so please update your application to the newer interface. (The update should simplify your code.)";
249              
250 1         6 $self->{'_warned_legacy'}++;
251             }
252              
253 2         3 return;
254             }
255              
256             sub _missing_generic_headers {
257 6     6   11 my ($self) = @_;
258              
259 6         7 my @missing;
260 6 100       16 push @missing, 'Connection' if !$self->{'_connection_header_ok'};
261 6 100       15 push @missing, 'Upgrade' if !$self->{'_upgrade_header_ok'};
262              
263 6         12 return @missing;
264             }
265              
266             sub _consume_sec_websocket_extensions_header {
267 0     0   0 my ($self, $value) = @_;
268              
269 0         0 require Net::WebSocket::Handshake::Extension;
270              
271 0         0 for my $xtn ( Net::WebSocket::Handshake::Extension->parse_string($value) ) {
272 0         0 my $xtn_token = $xtn->token();
273 0         0 my $xtn_handler = $self->{'_extension_tokens'}{ $xtn_token };
274 0 0       0 if ($xtn_handler) {
275 0         0 $xtn_handler->consume_parameters($xtn->parameters());
276              
277 0 0       0 if ($xtn_handler->ok_to_use()) {
278 0         0 $self->{'_match_extensions'}{ $xtn_token } = $xtn_handler;
279             }
280             }
281             else {
282 0         0 $self->_handle_unrecognized_extension($xtn);
283             }
284             }
285              
286 0         0 return;
287             }
288              
289             sub _consume_generic_header {
290 17     17   34 my ($self, $hname, $value) = @_;
291              
292 17         61 tr for ($hname);
293              
294 17 100       44 if ($hname eq 'connection') {
    100          
    50          
    50          
295 8         13 $value =~ tr;
296 8         22 for my $t ( Net::WebSocket::HTTP::split_tokens($value) ) {
297 8 100       18 if ($t eq 'upgrade') {
298 7         14 $self->{'_connection_header_ok'} = 1;
299             }
300             }
301              
302 8 100       22 if (!$self->{'_connection_header_ok'}) {
303 1         4 die Net::WebSocket::X->create('BadHeader', 'Connection' => $value, 'Must contain “upgrade”');
304             }
305             }
306             elsif ($hname eq 'upgrade') {
307 8         14 $value =~ tr;
308 8         18 for my $t ( Net::WebSocket::HTTP::split_tokens($value) ) {
309 8 100       19 if ($t eq 'websocket') {
310 7         17 $self->{'_upgrade_header_ok'} = 1;
311             }
312             }
313              
314 8 100       35 if (!$self->{'_upgrade_header_ok'}) {
315 1         4 die Net::WebSocket::X->create('BadHeader', 'Upgrade' => $value, 'Must contain “websocket”');
316             }
317             }
318             elsif ($hname eq 'sec-websocket-protocol') {
319 0         0 for my $token ( Net::WebSocket::HTTP::split_tokens($value) ) {
320 0 0       0 if (!defined $self->{'_match_protocol'}) {
321 0         0 ($self->{'_match_protocol'}) = grep { $_ eq $token } @{ $self->{'subprotocols'} };
  0         0  
  0         0  
322             }
323             }
324             }
325             elsif ($hname eq 'sec-websocket-extensions') {
326 0         0 $self->_consume_sec_websocket_extensions_header($value);
327             }
328              
329 15         27 return;
330             }
331              
332             1;