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 +1 -1
- data/CHANGELOG.rdoc +16 -0
- data/Rakefile +2 -0
- data/examples/echo.rb +1 -0
- data/lib/em-websocket.rb +8 -0
- data/lib/em-websocket/connection.rb +0 -1
- data/lib/em-websocket/framing03.rb +9 -7
- data/lib/em-websocket/framing76.rb +26 -7
- data/lib/em-websocket/handler.rb +1 -1
- data/lib/em-websocket/handshake76.rb +17 -7
- data/lib/em-websocket/version.rb +1 -1
- data/spec/integration/draft03_spec.rb +2 -2
- data/spec/integration/draft76_spec.rb +21 -1
- data/spec/unit/framing_spec.rb +2 -2
- data/spec/unit/handler_spec.rb +9 -1
- data/spec/websocket_spec.rb +4 -2
- metadata +11 -4
data/.gitignore
CHANGED
data/CHANGELOG.rdoc
CHANGED
@@ -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
data/examples/echo.rb
CHANGED
data/lib/em-websocket.rb
CHANGED
@@ -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
|
18
|
+
more = (@data.getbyte(pointer) & 0b10000000) == 0b10000000
|
17
19
|
# Ignoring rsv1-3 for now
|
18
|
-
opcode = @data
|
20
|
+
opcode = @data.getbyte(0) & 0b00001111
|
19
21
|
pointer += 1
|
20
22
|
|
21
23
|
# Ignoring rsv4
|
22
|
-
length = @data
|
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
|
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
|
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
|
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-
|
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
|
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
|
32
|
-
b = @data
|
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
|
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
|
-
|
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
|
data/lib/em-websocket/handler.rb
CHANGED
@@ -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 = [(
|
39
|
-
[(
|
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
|
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
|
-
|
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)
|
data/lib/em-websocket/version.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require '
|
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
|
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 '
|
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|
|
data/spec/unit/framing_spec.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require '
|
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
|
data/spec/unit/handler_spec.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require '
|
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
|
data/spec/websocket_spec.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require '
|
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"].
|
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
|
-
-
|
9
|
-
version: 0.2.
|
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:
|
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.
|
164
|
+
rubygems_version: 1.3.7
|
158
165
|
signing_key:
|
159
166
|
specification_version: 3
|
160
167
|
summary: EventMachine based WebSocket server
|