File: handshake_spec.rb

package info (click to toggle)
ruby-em-websocket 0.5.3-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 416 kB
  • sloc: ruby: 3,137; makefile: 5
file content (216 lines) | stat: -rw-r--r-- 7,623 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
require 'helper'

describe EM::WebSocket::Handshake do
  def handshake(request, secure = false)
    handshake = EM::WebSocket::Handshake.new(secure)
    handshake.receive_data(format_request(request))
    handshake
  end
  
  before :each do
    @request = {
      :port => 80,
      :method => "GET",
      :path => "/demo",
      :headers => {
        'Host' => 'example.com',
        'Connection' => 'Upgrade',
        'Sec-WebSocket-Key2' => '12998 5 Y3 1  .P00',
        'Sec-WebSocket-Protocol' => 'sample',
        'Upgrade' => 'WebSocket',
        'Sec-WebSocket-Key1' => '4 @1  46546xW%0l 1 5',
        'Origin' => 'http://example.com'
      },
      :body => '^n:ds[4U'
    }
    @secure_request = @request.merge(:port => 443)

    @response = {
      :headers => {
        "Upgrade" => "WebSocket",
        "Connection" => "Upgrade",
        "Sec-WebSocket-Location" => "ws://example.com/demo",
        "Sec-WebSocket-Origin" => "http://example.com",
        "Sec-WebSocket-Protocol" => "sample"
      },
      :body => "8jKS\'y:G*Co,Wxa-"
    }
    @secure_response = @response.merge(:headers => @response[:headers].merge('Sec-WebSocket-Location' => "wss://example.com/demo"))
  end
  
  it "should handle good request" do
    handshake(@request).should succeed_with_upgrade(@response)
  end
  
  it "should handle good request to secure default port if secure mode is enabled" do
    handshake(@secure_request, true).
      should succeed_with_upgrade(@secure_response)
  end
  
  it "should not handle good request to secure default port if secure mode is disabled" do
    handshake(@secure_request, false).
      should_not succeed_with_upgrade(@secure_response)
  end
  
  it "should handle good request on nondefault port" do
    @request[:port] = 8081
    @request[:headers]['Host'] = 'example.com:8081'
    @response[:headers]['Sec-WebSocket-Location'] =
      'ws://example.com:8081/demo'

    handshake(@request).should succeed_with_upgrade(@response)
  end
  
  it "should handle good request to secure nondefault port" do
    @secure_request[:port] = 8081
    @secure_request[:headers]['Host'] = 'example.com:8081'
    @secure_response[:headers]['Sec-WebSocket-Location'] = 'wss://example.com:8081/demo'
    
    handshake(@secure_request, true).
      should succeed_with_upgrade(@secure_response)
  end
  
  it "should handle good request with no protocol" do
    @request[:headers].delete('Sec-WebSocket-Protocol')
    @response[:headers].delete("Sec-WebSocket-Protocol")

    handshake(@request).should succeed_with_upgrade(@response)
  end
  
  it "should handle extra headers by simply ignoring them" do
    @request[:headers]['EmptyValue'] = ""
    @request[:headers]['AKey'] = "AValue"

    handshake(@request).should succeed_with_upgrade(@response)
  end
  
  it "should raise error on HTTP request" do
    @request[:headers] = {
      'Host' => 'www.google.com',
      'User-Agent' => 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1.3) Gecko/20090824 Firefox/3.5.3 GTB6 GTBA',
      'Accept' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
      'Accept-Language' => 'en-us,en;q=0.5',
      'Accept-Encoding' => 'gzip,deflate',
      'Accept-Charset' => 'ISO-8859-1,utf-8;q=0.7,*;q=0.7',
      'Keep-Alive' => '300',
      'Connection' => 'keep-alive',
    }
    
    handshake(@request).should fail_with_error(EM::WebSocket::HandshakeError)
  end
  
  it "should raise error on wrong method" do
    @request[:method] = 'POST'

    handshake(@request).should fail_with_error(EM::WebSocket::HandshakeError)
  end
  
  it "should raise error if upgrade header incorrect" do
    @request[:headers]['Upgrade'] = 'NonWebSocket'

    handshake(@request).should fail_with_error(EM::WebSocket::HandshakeError)
  end
  
  it "should raise error if Sec-WebSocket-Protocol is empty" do
    @request[:headers]['Sec-WebSocket-Protocol'] = ''

    handshake(@request).should fail_with_error(EM::WebSocket::HandshakeError)
  end
  
  %w[Sec-WebSocket-Key1 Sec-WebSocket-Key2].each do |header|
    it "should raise error if #{header} has zero spaces" do
      @request[:headers][header] = 'nospaces'

      handshake(@request).
        should fail_with_error(EM::WebSocket::HandshakeError, 'Websocket Key1 or Key2 does not contain spaces - this is a symptom of a cross-protocol attack')
    end
  end
  
  it "should raise error if Sec-WebSocket-Key1 is missing" do
    @request[:headers].delete("Sec-WebSocket-Key1")

    # The error message isn't correct since key1 is used to heuristically
    # determine the protocol version in use, however this test at least checks
    # that the handshake does correctly fail
    handshake(@request).
      should fail_with_error(EM::WebSocket::HandshakeError, 'Extra bytes after header')
  end

  it "should raise error if Sec-WebSocket-Key2 is missing" do
    @request[:headers].delete("Sec-WebSocket-Key2")

    handshake(@request).
      should fail_with_error(EM::WebSocket::HandshakeError, 'WebSocket key1 or key2 is missing')
  end

  it "should raise error if spaces do not divide numbers in Sec-WebSocket-Key* " do
    @request[:headers]['Sec-WebSocket-Key2'] = '12998 5 Y3 1.P00'

    handshake(@request).
      should fail_with_error(EM::WebSocket::HandshakeError, 'Invalid Key "12998 5 Y3 1.P00"')
  end
  
  it "should raise error if the HTTP header is empty" do
    handshake = EM::WebSocket::Handshake.new(false)
    handshake.receive_data("\r\n\r\nfoobar")
    
    handshake.
      should fail_with_error(EM::WebSocket::HandshakeError, 'Invalid HTTP header: Could not parse data entirely (4 != 10)')
  end

  # This might seems crazy, but very occasionally we saw multiple "Upgrade:
  # WebSocket" headers in the wild. RFC 4.2.1 isn't particularly clear on this
  # point, so for now I have decided not to accept --@mloughran
  it "should raise error on multiple upgrade headers" do
    handshake = EM::WebSocket::Handshake.new(false)

    # Add a duplicate upgrade header
    headers = format_request(@request)
    upgrade_header = "Upgrade: WebSocket\r\n"
    headers.gsub!(upgrade_header, "#{upgrade_header}#{upgrade_header}")

    handshake.receive_data(headers)

    handshake.errback { |e|
      e.class.should == EM::WebSocket::HandshakeError
      e.message.should == 'Invalid upgrade header: ["WebSocket", "WebSocket"]'
    }
  end
  
  it "should cope with requests where the header is split" do
    request = format_request(@request)
    incomplete_request = request[0...(request.length / 2)]
    rest = request[(request.length / 2)..-1]
    handshake = EM::WebSocket::Handshake.new(false)
    handshake.receive_data(incomplete_request)
    
    handshake.instance_variable_get(:@deferred_status).should == nil
    
    # Send the remaining header
    handshake.receive_data(rest)
    
    handshake(@request).should succeed_with_upgrade(@response)
  end
  
  it "should cope with requests where the third key is split" do
    request = format_request(@request)
    # Removes last two bytes of the third key
    incomplete_request = request[0..-3]
    rest = request[-2..-1]
    handshake = EM::WebSocket::Handshake.new(false)
    handshake.receive_data(incomplete_request)
    
    handshake.instance_variable_get(:@deferred_status).should == nil
    
    # Send the remaining third key
    handshake.receive_data(rest)
    
    handshake(@request).should succeed_with_upgrade(@response)
  end

  it "should fail if the request URI is invalid" do
    @request[:path] = "/%"
    handshake(@request).should \
      fail_with_error(EM::WebSocket::HandshakeError, 'Invalid request URI: /%')
  end
end