em-websocket 0.1.3 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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