File Coverage

blib/lib/Protocol/WebSocket/Response.pm
Criterion Covered Total %
statement 130 133 97.7
branch 52 64 81.2
condition 10 11 90.9
subroutine 26 26 100.0
pod 13 13 100.0
total 231 247 93.5


line stmt bran cond sub pod time code
1             package Protocol::WebSocket::Response;
2              
3 14     14   3214 use strict;
  14         29  
  14         403  
4 14     14   74 use warnings;
  14         28  
  14         405  
5              
6 14     14   75 use base 'Protocol::WebSocket::Message';
  14         26  
  14         2462  
7              
8             require Carp;
9 14     14   1576 use MIME::Base64 ();
  14         2554  
  14         255  
10 14     14   4359 use Digest::SHA ();
  14         34549  
  14         413  
11              
12 14     14   4787 use Protocol::WebSocket::URL;
  14         38  
  14         456  
13 14     14   4402 use Protocol::WebSocket::Cookie::Response;
  14         56  
  14         17215  
14              
15 14 100   14 1 54 sub location { @_ > 1 ? $_[0]->{location} = $_[1] : $_[0]->{location} }
16              
17             sub resource_name {
18 195 100   195 1 442 @_ > 1 ? $_[0]->{resource_name} = $_[1] : $_[0]->{resource_name};
19             }
20              
21 260 50   260 1 619 sub cookies { @_ > 1 ? $_[0]->{cookies} = $_[1] : $_[0]->{cookies} }
22              
23             sub cookie {
24 1     1 1 4 my $self = shift;
25              
26 1         2 push @{$self->{cookies}}, $self->_build_cookie(@_);
  1         6  
27             }
28              
29 136 100   136 1 317 sub key { @_ > 1 ? $_[0]->{key} = $_[1] : $_[0]->{key} }
30              
31 19     19 1 46 sub number1 { shift->_number('number1', 'key1', @_) }
32 19     19 1 35 sub number2 { shift->_number('number2', 'key2', @_) }
33              
34             sub _number {
35 38     38   52 my $self = shift;
36 38         71 my ($name, $key, $value) = @_;
37              
38 38         59 my $method = "SUPER::$name";
39 38 100       87 return $self->$method($value) if defined $value;
40              
41 32         88 $value = $self->$method();
42 32 100       63 $value = $self->_extract_number($self->$key) if not defined $value;
43              
44 32         107 return $value;
45             }
46              
47 2 50   2 1 11 sub key1 { @_ > 1 ? $_[0]->{key1} = $_[1] : $_[0]->{key1} }
48 2 50   2 1 6 sub key2 { @_ > 1 ? $_[0]->{key2} = $_[1] : $_[0]->{key2} }
49              
50             sub status {
51 37     37 1 81 return '101';
52             }
53              
54             sub headers {
55 244     244 1 317 my $self = shift;
56              
57 244   100     451 my $version = $self->version || 'draft-ietf-hybi-10';
58              
59 244         405 my $headers = [];
60              
61 244         411 push @$headers, Upgrade => 'WebSocket';
62 244         350 push @$headers, Connection => 'Upgrade';
63              
64 244 100 100     666 if ($version eq 'draft-hixie-75' || $version eq 'draft-ietf-hybi-00') {
    50 66        
65 178 50       327 Carp::croak(qq/host is required/) unless defined $self->host;
66              
67 178         319 my $location = $self->_build_url(
68             host => $self->host,
69             secure => $self->secure,
70             resource_name => $self->resource_name,
71             );
72 178 100       358 my $origin =
73             $self->origin ? $self->origin : 'http://' . $location->host;
74 178 100 100     319 $origin =~ s{^http:}{https:} if !$self->origin && $self->secure;
75              
76 178 100       348 if ($version eq 'draft-hixie-75') {
    50          
77 58 100       106 push @$headers, 'WebSocket-Protocol' => $self->subprotocol
78             if defined $self->subprotocol;
79 58         115 push @$headers, 'WebSocket-Origin' => $origin;
80 58         111 push @$headers, 'WebSocket-Location' => $location->to_string;
81             }
82             elsif ($version eq 'draft-ietf-hybi-00') {
83 120 100       222 push @$headers, 'Sec-WebSocket-Protocol' => $self->subprotocol
84             if defined $self->subprotocol;
85 120         221 push @$headers, 'Sec-WebSocket-Origin' => $origin;
86 120         233 push @$headers, 'Sec-WebSocket-Location' => $location->to_string;
87             }
88             }
89             elsif ($version eq 'draft-ietf-hybi-10' || $version eq 'draft-ietf-hybi-17') {
90 66 50       113 Carp::croak(qq/key is required/) unless defined $self->key;
91              
92 66         99 my $key = $self->key;
93 66         126 $key .= '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'; # WTF
94 66         362 $key = Digest::SHA::sha1($key);
95 66         163 $key = MIME::Base64::encode_base64($key);
96 66         211 $key =~ s{\s+}{}g;
97              
98 66         140 push @$headers, 'Sec-WebSocket-Accept' => $key;
99              
100 66 100       148 push @$headers, 'Sec-WebSocket-Protocol' => $self->subprotocol
101             if defined $self->subprotocol;
102             }
103             else {
104 0         0 Carp::croak('Version ' . $version . ' is not supported');
105             }
106              
107 244 100       352 if (@{$self->cookies}) {
  244         423  
108 16         22 my $cookie = join ',' => map { $_->to_string } @{$self->cookies};
  16         33  
  16         20  
109 16         38 push @$headers, 'Set-Cookie' => $cookie;
110             }
111              
112 244         533 return $headers;
113             }
114              
115             sub body {
116 19     19 1 30 my $self = shift;
117              
118 19 100       47 return $self->checksum if $self->version eq 'draft-ietf-hybi-00';
119              
120 10         23 return '';
121             }
122              
123             sub to_string {
124 19     19 1 59 my $self = shift;
125              
126 19         56 my $status = $self->status;
127              
128 19         34 my $string = '';
129 19         64 $string .= "HTTP/1.1 $status WebSocket Protocol Handshake\x0d\x0a";
130              
131 19         38 for (my $i = 0; $i < @{$self->headers}; $i += 2) {
  94         215  
132 75         131 my $key = $self->headers->[$i];
133 75         162 my $value = $self->headers->[$i + 1];
134              
135 75         256 $string .= "$key: $value\x0d\x0a";
136             }
137              
138 19         36 $string .= "\x0d\x0a";
139              
140 19         46 $string .= $self->body;
141              
142 19         149 return $string;
143             }
144              
145             sub _parse_first_line {
146 18     18   51 my ($self, $line) = @_;
147              
148 18         53 my $status = $self->status;
149 18 100       217 unless ($line =~ m{^HTTP/1\.1 $status }) {
150 3         11 $self->error('Wrong response line');
151 3         56 return;
152             }
153              
154 15         60 return $self;
155             }
156              
157             sub _parse_body {
158 15     15   32 my $self = shift;
159              
160 15 100       46 if ($self->field('Sec-WebSocket-Accept')) {
    100          
161 6         32 $self->version('draft-ietf-hybi-10');
162             }
163             elsif ($self->field('Sec-WebSocket-Origin')) {
164 7         29 $self->version('draft-ietf-hybi-00');
165              
166 7 100       25 return 1 if length $self->{buffer} < 16;
167              
168 5         21 my $checksum = substr $self->{buffer}, 0, 16, '';
169 5         51 $self->checksum($checksum);
170             }
171             else {
172 2         4 $self->version('draft-hixie-75');
173             }
174              
175 13 50       49 return $self if $self->_finalize;
176              
177 0         0 $self->error('Not a valid response');
178 0         0 return;
179             }
180              
181             sub _finalize {
182 13     13   24 my $self = shift;
183              
184 13 100       67 if ($self->version eq 'draft-hixie-75') {
    100          
185 2         4 my $location = $self->field('WebSocket-Location');
186 2 50       4 return unless defined $location;
187 2         6 $self->location($location);
188              
189 2         4 my $url = $self->_build_url;
190 2 50       5 return unless $url->parse($self->location);
191              
192 2         6 $self->secure($url->secure);
193 2         7 $self->host($url->host);
194 2         5 $self->resource_name($url->resource_name);
195              
196 2         5 $self->origin($self->field('WebSocket-Origin'));
197              
198 2         4 $self->subprotocol($self->field('WebSocket-Protocol'));
199             }
200             elsif ($self->version eq 'draft-ietf-hybi-00') {
201 5         19 my $location = $self->field('Sec-WebSocket-Location');
202 5 50       13 return unless defined $location;
203 5         19 $self->location($location);
204              
205 5         14 my $url = $self->_build_url;
206 5 50       12 return unless $url->parse($self->location);
207              
208 5         14 $self->secure($url->secure);
209 5         13 $self->host($url->host);
210 5         16 $self->resource_name($url->resource_name);
211              
212 5         37 $self->origin($self->field('Sec-WebSocket-Origin'));
213 5         11 $self->subprotocol($self->field('Sec-WebSocket-Protocol'));
214             }
215             else {
216 6         17 $self->subprotocol($self->field('Sec-WebSocket-Protocol'));
217             }
218              
219 13         53 return 1;
220             }
221              
222 185     185   221 sub _build_url { shift; Protocol::WebSocket::URL->new(@_) }
  185         401  
223 1     1   2 sub _build_cookie { shift; Protocol::WebSocket::Cookie::Response->new(@_) }
  1         5  
224              
225             1;
226             __END__