em-websocket 0.2.0 → 0.2.1

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/.gitignore CHANGED
@@ -1,3 +1,3 @@
1
1
  *.gemspec
2
2
  pkg
3
-
3
+ Gemfile.lock
@@ -1,5 +1,21 @@
1
1
  = Changelog
2
2
 
3
+ == 0.2.1 / 2011-03-01
4
+
5
+ - bugfixes:
6
+ - Performance improvements to draft 76 framing
7
+ - Limit frame lengths for draft 76
8
+ - Better error handling for draft 76 handshake
9
+ - Ruby 1.9 support
10
+
11
+ == 0.2.0 / 2010-11-23
12
+
13
+ - new features:
14
+ - Support for WebSocket draft 03
15
+ - bugfixes:
16
+ - Handle case when handshake split into two receive_data calls
17
+ - Stricter regexp matching of frames
18
+
3
19
  == 0.1.4 / 2010-08-23
4
20
 
5
21
  - new features:
data/Rakefile CHANGED
@@ -7,3 +7,5 @@ RSpec::Core::RakeTask.new do |t|
7
7
  t.rspec_opts = ["-c", "-f progress", "-r ./spec/helper.rb"]
8
8
  t.pattern = 'spec/**/*_spec.rb'
9
9
  end
10
+
11
+ task :default => :spec
@@ -4,4 +4,5 @@ EventMachine::WebSocket.start(:host => "0.0.0.0", :port => 8080, :debug => true)
4
4
  ws.onopen { ws.send "Hello Client!"}
5
5
  ws.onmessage { |msg| ws.send "Pong: #{msg}" }
6
6
  ws.onclose { puts "WebSocket closed" }
7
+ ws.onerror { |e| puts "Error: #{e.message}" }
7
8
  end
@@ -10,3 +10,11 @@ require "eventmachine"
10
10
  ].each do |file|
11
11
  require "em-websocket/#{file}"
12
12
  end
13
+
14
+ unless ''.respond_to?(:getbyte)
15
+ class String
16
+ def getbyte(i)
17
+ self[i]
18
+ end
19
+ end
20
+ end
@@ -26,7 +26,6 @@ module EventMachine
26
26
  @debug = options[:debug] || false
27
27
  @secure = options[:secure] || false
28
28
  @tls_options = options[:tls_options] || {}
29
- @request = {}
30
29
  @data = ''
31
30
 
32
31
  debug [:initialize]
@@ -1,3 +1,5 @@
1
+ # encoding: BINARY
2
+
1
3
  module EventMachine
2
4
  module WebSocket
3
5
  module Framing03
@@ -7,25 +9,25 @@ module EventMachine
7
9
  @application_data_buffer = '' # Used for MORE frames
8
10
  end
9
11
 
10
- def process_data
12
+ def process_data(newdata)
11
13
  error = false
12
14
 
13
15
  while !error && @data.size > 1
14
16
  pointer = 0
15
17
 
16
- more = (@data[pointer] & 0b10000000) == 0b10000000
18
+ more = (@data.getbyte(pointer) & 0b10000000) == 0b10000000
17
19
  # Ignoring rsv1-3 for now
18
- opcode = @data[0] & 0b00001111
20
+ opcode = @data.getbyte(0) & 0b00001111
19
21
  pointer += 1
20
22
 
21
23
  # Ignoring rsv4
22
- length = @data[pointer] & 0b01111111
24
+ length = @data.getbyte(pointer) & 0b01111111
23
25
  pointer += 1
24
26
 
25
27
  payload_length = case length
26
28
  when 127 # Length defined by 8 bytes
27
29
  # Check buffer size
28
- if @data[pointer+8-1] == nil
30
+ if @data.getbyte(pointer+8-1) == nil
29
31
  debug [:buffer_incomplete, @data.inspect]
30
32
  error = true
31
33
  next
@@ -38,7 +40,7 @@ module EventMachine
38
40
  l
39
41
  when 126 # Length defined by 2 bytes
40
42
  # Check buffer size
41
- if @data[pointer+2-1] == nil
43
+ if @data.getbyte(pointer+2-1) == nil
42
44
  debug [:buffer_incomplete, @data.inspect]
43
45
  error = true
44
46
  next
@@ -52,7 +54,7 @@ module EventMachine
52
54
  end
53
55
 
54
56
  # Check buffer size
55
- if @data[pointer+payload_length-1] == nil
57
+ if @data.getbyte(pointer+payload_length-1) == nil
56
58
  debug [:buffer_incomplete, @data.inspect]
57
59
  error = true
58
60
  next
@@ -1,3 +1,5 @@
1
+ # encoding: BINARY
2
+
1
3
  module EventMachine
2
4
  module WebSocket
3
5
  module Framing76
@@ -10,17 +12,19 @@ module EventMachine
10
12
  @data = ''
11
13
  end
12
14
 
13
- def process_data
15
+ def process_data(newdata)
14
16
  debug [:message, @data]
15
17
 
16
18
  # This algorithm comes straight from the spec
17
- # http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76#section-4.2
19
+ # http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76#section-5.3
18
20
 
19
21
  error = false
20
22
 
21
23
  while !error
24
+ return if @data.size == 0
25
+
22
26
  pointer = 0
23
- frame_type = @data[pointer].to_i
27
+ frame_type = @data.getbyte(pointer)
24
28
  pointer += 1
25
29
 
26
30
  if (frame_type & 0x80) == 0x80
@@ -28,8 +32,8 @@ module EventMachine
28
32
  length = 0
29
33
 
30
34
  loop do
31
- return false if !@data[pointer]
32
- b = @data[pointer].to_i
35
+ return false if !@data.getbyte(pointer)
36
+ b = @data.getbyte(pointer)
33
37
  pointer += 1
34
38
  b_v = b & 0x7F
35
39
  length = length * 128 + b_v
@@ -42,7 +46,7 @@ module EventMachine
42
46
  return false
43
47
  end
44
48
 
45
- if @data[pointer+length-1] == nil
49
+ if @data.getbyte(pointer+length-1) == nil
46
50
  debug [:buffer_incomplete, @data.inspect]
47
51
  # Incomplete data - leave @data to accumulate
48
52
  error = true
@@ -63,7 +67,22 @@ module EventMachine
63
67
  end
64
68
  else
65
69
  # If the high-order bit of the /frame type/ byte is _not_ set
66
- msg = @data.slice!(/\A\x00([^\xff]*)\xff/)
70
+
71
+ if @data.getbyte(0) != 0x00
72
+ # Close the connection since this buffer can never match
73
+ @connection.close_with_error(DataError.new("Invalid frame received"))
74
+ end
75
+
76
+ # Addition to the spec to protect against malicious requests
77
+ if @data.size > MAXIMUM_FRAME_LENGTH
78
+ @connection.close_with_error(DataError.new("Frame length too long (#{@data.size} bytes)"))
79
+ return false
80
+ end
81
+
82
+ # Optimization to avoid calling slice! unnecessarily
83
+ error = true and next unless newdata =~ /\xff/
84
+
85
+ msg = @data.slice!(/\A\x00[^\xff]*\xff/)
67
86
  if msg
68
87
  msg.gsub!(/\A\x00|\xff\z/, '')
69
88
  if @state == :closing
@@ -25,7 +25,7 @@ module EventMachine
25
25
 
26
26
  def receive_data(data)
27
27
  @data << data
28
- process_data
28
+ process_data(data)
29
29
  end
30
30
 
31
31
  def close_websocket
@@ -35,21 +35,31 @@ module EventMachine
35
35
 
36
36
  def solve_challenge(first, second, third)
37
37
  # Refer to 5.2 4-9 of the draft 76
38
- sum = [(extract_nums(first) / count_spaces(first))].pack("N*") +
39
- [(extract_nums(second) / count_spaces(second))].pack("N*") +
38
+ sum = [numbers_over_spaces(first)].pack("N*") +
39
+ [numbers_over_spaces(second)].pack("N*") +
40
40
  third
41
41
  Digest::MD5.digest(sum)
42
42
  end
43
43
 
44
- def extract_nums(string)
45
- string.scan(/[0-9]/).join.to_i
46
- end
44
+ def numbers_over_spaces(string)
45
+ numbers = string.scan(/[0-9]/).join.to_i
47
46
 
48
- def count_spaces(string)
49
47
  spaces = string.scan(/ /).size
50
48
  # As per 5.2.5, abort the connection if spaces are zero.
51
49
  raise HandshakeError, "Websocket Key1 or Key2 does not contain spaces - this is a symptom of a cross-protocol attack" if spaces == 0
52
- return spaces
50
+
51
+ # As per 5.2.6, abort if numbers is not an integral multiple of spaces
52
+ if numbers % spaces != 0
53
+ raise HandshakeError, "Invalid Key #{string.inspect}"
54
+ end
55
+
56
+ quotient = numbers / spaces
57
+
58
+ if quotient > 2**32-1
59
+ raise HandshakeError, "Challenge computation out of range for key #{string.inspect}"
60
+ end
61
+
62
+ return quotient
53
63
  end
54
64
 
55
65
  def validate_protocol!(protocol)
@@ -1,5 +1,5 @@
1
1
  module EventMachine
2
2
  module Websocket
3
- VERSION = "0.2.0"
3
+ VERSION = "0.2.1"
4
4
  end
5
5
  end
@@ -1,4 +1,4 @@
1
- require 'spec/helper'
1
+ require 'helper'
2
2
 
3
3
  describe "draft03" do
4
4
  before :each do
@@ -195,7 +195,7 @@ describe "draft03" do
195
195
  end
196
196
  end
197
197
 
198
- it "should not allow data fraome to be sent after close frame sent" do
198
+ it "should not allow data frame to be sent after close frame sent" do
199
199
  EM.run {
200
200
  EM::WebSocket.start(:host => "0.0.0.0", :port => 12345) { |ws|
201
201
  ws.onopen {
@@ -1,4 +1,4 @@
1
- require 'spec/helper'
1
+ require 'helper'
2
2
 
3
3
  describe "WebSocket server draft76" do
4
4
  before :each do
@@ -150,6 +150,26 @@ describe "WebSocket server draft76" do
150
150
  end
151
151
  end
152
152
 
153
+ it "should handle impossible frames by calling onerror callback" do
154
+ EM.run do
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::DataError
158
+ error.message.should == "Invalid frame received"
159
+ EM.stop
160
+ }
161
+ }
162
+
163
+ # Create a fake client which sends draft 76 handshake
164
+ client = EM.connect('0.0.0.0', 12345, FakeWebSocketClient)
165
+ client.send_data(format_request(@request))
166
+
167
+ client.onopen = lambda {
168
+ client.send_data("foobar") # Does not start with \x00 or \xff
169
+ }
170
+ end
171
+ end
172
+
153
173
  it "should handle invalid http requests by raising HandshakeError passed to onerror callback" do
154
174
  EM.run {
155
175
  EventMachine::WebSocket.start(:host => "0.0.0.0", :port => 12345) { |server|
@@ -1,4 +1,4 @@
1
- require 'spec/helper'
1
+ require 'helper'
2
2
 
3
3
  describe EM::WebSocket::Framing03 do
4
4
  class FramingContainer
@@ -6,7 +6,7 @@ describe EM::WebSocket::Framing03 do
6
6
 
7
7
  def <<(data)
8
8
  @data << data
9
- process_data
9
+ process_data(data)
10
10
  end
11
11
 
12
12
  def debug(*args); end
@@ -1,4 +1,4 @@
1
- require 'spec/helper'
1
+ require 'helper'
2
2
 
3
3
  describe "EventMachine::WebSocket::Handler" do
4
4
  before :each do
@@ -125,6 +125,14 @@ describe "EventMachine::WebSocket::Handler" do
125
125
  end
126
126
  end
127
127
 
128
+ it "should raise error if spaces do not divide numbers in Sec-WebSocket-Key* " do
129
+ @request[:headers]['Sec-WebSocket-Key2'] = '12998 5 Y3 1.P00'
130
+
131
+ lambda {
132
+ handler(@request).handshake
133
+ }.should raise_error(EM::WebSocket::HandshakeError, 'Invalid Key "12998 5 Y3 1.P00"')
134
+ end
135
+
128
136
  it "should leave request with incomplete header" do
129
137
  data = format_request(@request)
130
138
  # Sends only half of the request
@@ -1,4 +1,4 @@
1
- require 'spec/helper'
1
+ require 'helper'
2
2
 
3
3
  describe EventMachine::WebSocket do
4
4
 
@@ -153,7 +153,9 @@ describe EventMachine::WebSocket do
153
153
 
154
154
  EventMachine::WebSocket.start(:host => "0.0.0.0", :port => 12345) do |ws|
155
155
  ws.onopen {
156
- ws.request["Path"].should == "/?baz=qux&foo=bar"
156
+ path, query = ws.request["Path"].split('?')
157
+ path.should == '/'
158
+ Hash[*query.split(/&|=/)].should == {"foo"=>"bar", "baz"=>"qux"}
157
159
  ws.request["Query"]["foo"].should == "bar"
158
160
  ws.request["Query"]["baz"].should == "qux"
159
161
  }
metadata CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
5
5
  segments:
6
6
  - 0
7
7
  - 2
8
- - 0
9
- version: 0.2.0
8
+ - 1
9
+ version: 0.2.1
10
10
  platform: ruby
11
11
  authors:
12
12
  - Ilya Grigorik
@@ -14,13 +14,14 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2010-11-23 00:00:00 +00:00
17
+ date: 2011-03-01 00:00:00 +00:00
18
18
  default_executable:
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
21
21
  name: eventmachine
22
22
  prerelease: false
23
23
  requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
24
25
  requirements:
25
26
  - - ">="
26
27
  - !ruby/object:Gem::Version
@@ -35,6 +36,7 @@ dependencies:
35
36
  name: addressable
36
37
  prerelease: false
37
38
  requirement: &id002 !ruby/object:Gem::Requirement
39
+ none: false
38
40
  requirements:
39
41
  - - ">="
40
42
  - !ruby/object:Gem::Version
@@ -49,6 +51,7 @@ dependencies:
49
51
  name: em-http-request
50
52
  prerelease: false
51
53
  requirement: &id003 !ruby/object:Gem::Requirement
54
+ none: false
52
55
  requirements:
53
56
  - - ~>
54
57
  - !ruby/object:Gem::Version
@@ -63,6 +66,7 @@ dependencies:
63
66
  name: rspec
64
67
  prerelease: false
65
68
  requirement: &id004 !ruby/object:Gem::Requirement
69
+ none: false
66
70
  requirements:
67
71
  - - ~>
68
72
  - !ruby/object:Gem::Version
@@ -77,6 +81,7 @@ dependencies:
77
81
  name: rake
78
82
  prerelease: false
79
83
  requirement: &id005 !ruby/object:Gem::Requirement
84
+ none: false
80
85
  requirements:
81
86
  - - ">="
82
87
  - !ruby/object:Gem::Version
@@ -138,6 +143,7 @@ rdoc_options: []
138
143
  require_paths:
139
144
  - lib
140
145
  required_ruby_version: !ruby/object:Gem::Requirement
146
+ none: false
141
147
  requirements:
142
148
  - - ">="
143
149
  - !ruby/object:Gem::Version
@@ -145,6 +151,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
145
151
  - 0
146
152
  version: "0"
147
153
  required_rubygems_version: !ruby/object:Gem::Requirement
154
+ none: false
148
155
  requirements:
149
156
  - - ">="
150
157
  - !ruby/object:Gem::Version
@@ -154,7 +161,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
154
161
  requirements: []
155
162
 
156
163
  rubyforge_project: em-websocket
157
- rubygems_version: 1.3.6
164
+ rubygems_version: 1.3.7
158
165
  signing_key:
159
166
  specification_version: 3
160
167
  summary: EventMachine based WebSocket server