em-websocket 0.2.0 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
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