File Coverage

blib/lib/Protocol/WebSocket/Response.pm
Criterion Covered Total %
statement 133 136 97.7
branch 54 66 81.8
condition 10 11 90.9
subroutine 26 26 100.0
pod 13 13 100.0
total 236 252 93.6


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