em-websocket 0.1.3 → 0.1.4

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.rdoc ADDED
@@ -0,0 +1,37 @@
1
+ = Changelog
2
+
3
+ == 0.1.4 / 2010-08-23
4
+
5
+ - new features:
6
+ - Allow custom ssl certificate to be used by passing :tls_options
7
+ - Protect against errors caused by non limited frame lengths
8
+ - Use custom exceptions rather than RuntimeError
9
+ - bugfixes:
10
+ - Handle invalid HTTP request with HandshakeError
11
+
12
+ == 0.1.3 / 2010-07-18
13
+
14
+ - new features:
15
+ - onerror callback
16
+ - bugfixes:
17
+ - proper handling of zero spaces in key1 or key2(prevent ZeroDivisionError)
18
+ - convert received data to utf-8 to prevent ruby 1.9 errors
19
+ - fix handling of null bytes within a frame
20
+
21
+ == 0.1.2 / 2010-06-16
22
+
23
+ - new features:
24
+ - none
25
+ - bugfixes:
26
+ - allow $ character inside header key
27
+
28
+ == 0.1.1 / 2010-06-13
29
+
30
+ - new features:
31
+ - wss/ssl support
32
+ - bugfixes:
33
+ - can't & strings
34
+
35
+ == 0.1.0 / 2010-06-12
36
+
37
+ - initial release
data/README.rdoc CHANGED
@@ -24,6 +24,24 @@ check out the blog post below:
24
24
  end
25
25
  }
26
26
 
27
+ == Secure server
28
+
29
+ It is possible to accept secure wss:// connections by passing :secure => true when opening the connection. Safari 5 does not currently support prompting on untrusted SSL certificates therefore using signed certificates is highly recommended. Pass a :tls_options hash containing keys as described in http://eventmachine.rubyforge.org/EventMachine/Connection.html#M000296
30
+
31
+ For example,
32
+
33
+ EventMachine::WebSocket.start({
34
+ :host => "0.0.0.0",
35
+ :port => 443
36
+ :secure => true,
37
+ :tls_options => {
38
+ :private_key_file => "/private/key",
39
+ :cert_chain_file => "/ssl/certificate"
40
+ }
41
+ }) do |ws|
42
+ ...
43
+ end
44
+
27
45
  == Examples
28
46
  - examples/multicast.rb - broadcast all ruby tweets to all subscribers
29
47
  - examples/echo.rb - server <> client exchange via a websocket
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.3
1
+ 0.1.4
@@ -7,6 +7,10 @@ module EventMachine
7
7
 
8
8
  attr_reader :state, :request
9
9
 
10
+ # Set the max frame lenth to very high value (10MB) until there is a
11
+ # limit specified in the spec to protect against malicious attacks
12
+ MAXIMUM_FRAME_LENGTH = 10 * 1024 * 1024
13
+
10
14
  # define WebSocket callbacks
11
15
  def onopen(&blk); @onopen = blk; end
12
16
  def onclose(&blk); @onclose = blk; end
@@ -17,6 +21,7 @@ module EventMachine
17
21
  @options = options
18
22
  @debug = options[:debug] || false
19
23
  @secure = options[:secure] || false
24
+ @tls_options = options[:tls_options] || {}
20
25
  @state = :handshake
21
26
  @request = {}
22
27
  @data = ''
@@ -25,7 +30,7 @@ module EventMachine
25
30
  end
26
31
 
27
32
  def post_init
28
- start_tls if @secure
33
+ start_tls(@tls_options) if @secure
29
34
  end
30
35
 
31
36
  def receive_data(data)
@@ -48,7 +53,7 @@ module EventMachine
48
53
  handshake
49
54
  when :connected
50
55
  process_message
51
- else raise RuntimeError, "invalid state: #{@state}"
56
+ else raise WebSocketError, "invalid state: #{@state}"
52
57
  end
53
58
  end
54
59
 
@@ -118,6 +123,12 @@ module EventMachine
118
123
  break unless (b & 0x80) == 0x80
119
124
  end
120
125
 
126
+ # Addition to the spec to protect against malicious requests
127
+ if length > MAXIMUM_FRAME_LENGTH
128
+ close_with_error(DataError.new("Frame length too long (#{length} bytes)"))
129
+ return false
130
+ end
131
+
121
132
  if @data[pointer+length-1] == nil
122
133
  debug [:buffer_incomplete, @data.inspect]
123
134
  # Incomplete data - leave @data to accumulate
@@ -170,6 +181,11 @@ module EventMachine
170
181
  ary.collect{ |s| s.force_encoding('UTF-8') if s.respond_to?(:force_encoding) }
171
182
  send_data(ary.join)
172
183
  end
184
+
185
+ def close_with_error(message)
186
+ @onerror.call(message) if @onerror
187
+ close_connection_after_writing
188
+ end
173
189
  end
174
190
  end
175
191
  end
@@ -1,7 +1,5 @@
1
1
  module EventMachine
2
2
  module WebSocket
3
- class HandshakeError < RuntimeError; end
4
-
5
3
  class Handler
6
4
  include Debugger
7
5
 
@@ -7,7 +7,7 @@ module EventMachine
7
7
  TERMINATE_STRING = "\xff\x00"
8
8
 
9
9
  def handshake
10
- challenge_response = solve_challange(
10
+ challenge_response = solve_challenge(
11
11
  @request['Sec-WebSocket-Key1'],
12
12
  @request['Sec-WebSocket-Key2'],
13
13
  @request['Third-Key']
@@ -36,7 +36,7 @@ module EventMachine
36
36
 
37
37
  private
38
38
 
39
- def solve_challange(first, second, third)
39
+ def solve_challenge(first, second, third)
40
40
  # Refer to 5.2 4-9 of the draft 76
41
41
  sum = [(extract_nums(first) / count_spaces(first))].pack("N*") +
42
42
  [(extract_nums(second) / count_spaces(second))].pack("N*") +
@@ -12,6 +12,7 @@ module EventMachine
12
12
 
13
13
  # extract request path
14
14
  first_line = lines.shift.match(PATH)
15
+ raise HandshakeError, "Invalid HTTP header" unless first_line
15
16
  request['Method'] = first_line[1].strip
16
17
  request['Path'] = first_line[2].strip
17
18
 
@@ -44,7 +45,7 @@ module EventMachine
44
45
  when 76
45
46
  Handler76.new(request, response, debug)
46
47
  else
47
- raise "Must not happen"
48
+ raise WebSocketError, "Must not happen"
48
49
  end
49
50
  end
50
51
  end
@@ -1,5 +1,8 @@
1
1
  module EventMachine
2
2
  module WebSocket
3
+ class WebSocketError < RuntimeError; end
4
+ class HandshakeError < WebSocketError; end
5
+ class DataError < WebSocketError; end
3
6
 
4
7
  def self.start(options, &blk)
5
8
  EM.epoll
@@ -125,4 +125,43 @@ describe "WebSocket server draft76" do
125
125
  end
126
126
  end
127
127
  end
128
+
129
+ it "should handle unreasonable frame lengths by calling onerror callback" do
130
+ EM.run do
131
+ EventMachine::WebSocket.start(:host => "0.0.0.0", :port => 12345) { |server|
132
+ server.onerror { |error|
133
+ error.should be_an_instance_of EM::WebSocket::DataError
134
+ error.message.should == "Frame length too long (1180591620717411303296 bytes)"
135
+ EM.stop
136
+ }
137
+ }
138
+
139
+ # Create a fake client which sends draft 76 handshake
140
+ client = EM.connect('0.0.0.0', 12345, FakeWebSocketClient)
141
+ client.send_data(format_request(@request))
142
+
143
+ # This particular frame indicates a message length of
144
+ # 1180591620717411303296 bytes. Such a message would previously cause
145
+ # a "bignum too big to convert into `long'" error.
146
+ # However it is clearly unreasonable and should be rejected.
147
+ client.onopen = lambda {
148
+ client.send_data("\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00")
149
+ }
150
+ end
151
+ end
152
+
153
+ it "should handle invalid http requests by raising HandshakeError passed to onerror callback" do
154
+ EM.run {
155
+ EventMachine::WebSocket.start(:host => "0.0.0.0", :port => 12345) { |server|
156
+ server.onerror { |error|
157
+ error.should be_an_instance_of EM::WebSocket::HandshakeError
158
+ error.message.should == "Invalid HTTP header"
159
+ EM.stop
160
+ }
161
+ }
162
+
163
+ client = EM.connect('0.0.0.0', 12345, FakeWebSocketClient)
164
+ client.send_data("This is not a HTTP header")
165
+ }
166
+ end
128
167
  end
metadata CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
5
5
  segments:
6
6
  - 0
7
7
  - 1
8
- - 3
9
- version: 0.1.3
8
+ - 4
9
+ version: 0.1.4
10
10
  platform: ruby
11
11
  authors:
12
12
  - Ilya Grigorik
@@ -14,7 +14,7 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2010-07-18 00:00:00 -04:00
17
+ date: 2010-08-23 00:00:00 -04:00
18
18
  default_executable:
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
@@ -69,6 +69,7 @@ extra_rdoc_files:
69
69
  - README.rdoc
70
70
  files:
71
71
  - .gitignore
72
+ - CHANGELOG.rdoc
72
73
  - README.rdoc
73
74
  - Rakefile
74
75
  - VERSION