File Coverage

blib/lib/Net/WebSocket/Handshake.pm
Criterion Covered Total %
statement 84 106 79.2
branch 22 40 55.0
condition 2 3 66.6
subroutine 18 20 90.0
pod 5 5 100.0
total 131 174 75.2


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