sonixlabs-em-websocket 0.3.7
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/CHANGELOG.rdoc +80 -0
- data/Gemfile +3 -0
- data/README.md +98 -0
- data/Rakefile +11 -0
- data/em-websocket.gemspec +27 -0
- data/examples/echo.rb +8 -0
- data/examples/flash_policy_file_server.rb +21 -0
- data/examples/js/FABridge.js +604 -0
- data/examples/js/WebSocketMain.swf +0 -0
- data/examples/js/swfobject.js +4 -0
- data/examples/js/web_socket.js +312 -0
- data/examples/multicast.rb +47 -0
- data/examples/test.html +30 -0
- data/lib/em-websocket/client_connection.rb +19 -0
- data/lib/em-websocket/close03.rb +11 -0
- data/lib/em-websocket/close05.rb +11 -0
- data/lib/em-websocket/close06.rb +16 -0
- data/lib/em-websocket/close75.rb +10 -0
- data/lib/em-websocket/connection.rb +184 -0
- data/lib/em-websocket/debugger.rb +17 -0
- data/lib/em-websocket/framing03.rb +167 -0
- data/lib/em-websocket/framing04.rb +15 -0
- data/lib/em-websocket/framing05.rb +168 -0
- data/lib/em-websocket/framing07.rb +180 -0
- data/lib/em-websocket/framing76.rb +114 -0
- data/lib/em-websocket/handler.rb +56 -0
- data/lib/em-websocket/handler03.rb +10 -0
- data/lib/em-websocket/handler05.rb +10 -0
- data/lib/em-websocket/handler06.rb +10 -0
- data/lib/em-websocket/handler07.rb +10 -0
- data/lib/em-websocket/handler08.rb +10 -0
- data/lib/em-websocket/handler13.rb +10 -0
- data/lib/em-websocket/handler75.rb +9 -0
- data/lib/em-websocket/handler76.rb +12 -0
- data/lib/em-websocket/handler_factory.rb +107 -0
- data/lib/em-websocket/handshake04.rb +75 -0
- data/lib/em-websocket/handshake75.rb +21 -0
- data/lib/em-websocket/handshake76.rb +71 -0
- data/lib/em-websocket/masking04.rb +63 -0
- data/lib/em-websocket/message_processor_03.rb +38 -0
- data/lib/em-websocket/message_processor_06.rb +52 -0
- data/lib/em-websocket/version.rb +5 -0
- data/lib/em-websocket/websocket.rb +45 -0
- data/lib/em-websocket.rb +23 -0
- data/lib/sonixlabs-em-websocket.rb +1 -0
- data/spec/helper.rb +146 -0
- data/spec/integration/client_examples.rb +48 -0
- data/spec/integration/common_spec.rb +118 -0
- data/spec/integration/draft03_spec.rb +270 -0
- data/spec/integration/draft05_spec.rb +48 -0
- data/spec/integration/draft06_spec.rb +88 -0
- data/spec/integration/draft13_spec.rb +75 -0
- data/spec/integration/draft75_spec.rb +117 -0
- data/spec/integration/draft76_spec.rb +230 -0
- data/spec/integration/shared_examples.rb +91 -0
- data/spec/unit/framing_spec.rb +325 -0
- data/spec/unit/handler_spec.rb +147 -0
- data/spec/unit/masking_spec.rb +27 -0
- data/spec/unit/message_processor_spec.rb +36 -0
- metadata +198 -0
@@ -0,0 +1,167 @@
|
|
1
|
+
# encoding: BINARY
|
2
|
+
|
3
|
+
module EventMachine
|
4
|
+
module WebSocket
|
5
|
+
module Framing03
|
6
|
+
|
7
|
+
# Set the max frame lenth to very high value (10MB) until there is a
|
8
|
+
# limit specified in the spec to protect against malicious attacks
|
9
|
+
MAXIMUM_FRAME_LENGTH = 10 * 1024 * 1024
|
10
|
+
|
11
|
+
def initialize_framing
|
12
|
+
@data = ''
|
13
|
+
@application_data_buffer = '' # Used for MORE frames
|
14
|
+
end
|
15
|
+
|
16
|
+
def process_data(newdata)
|
17
|
+
error = false
|
18
|
+
|
19
|
+
while !error && @data.size > 1
|
20
|
+
pointer = 0
|
21
|
+
|
22
|
+
more = ((@data.getbyte(pointer) & 0b10000000) == 0b10000000) ^ fin
|
23
|
+
# Ignoring rsv1-3 for now
|
24
|
+
opcode = @data.getbyte(0) & 0b00001111
|
25
|
+
pointer += 1
|
26
|
+
|
27
|
+
# Ignoring rsv4
|
28
|
+
length = @data.getbyte(pointer) & 0b01111111
|
29
|
+
pointer += 1
|
30
|
+
|
31
|
+
payload_length = case length
|
32
|
+
when 127 # Length defined by 8 bytes
|
33
|
+
# Check buffer size
|
34
|
+
if @data.getbyte(pointer+8-1) == nil
|
35
|
+
debug [:buffer_incomplete, @data]
|
36
|
+
error = true
|
37
|
+
next
|
38
|
+
end
|
39
|
+
|
40
|
+
# Only using the last 4 bytes for now, till I work out how to
|
41
|
+
# unpack 8 bytes. I'm sure 4GB frames will do for now :)
|
42
|
+
l = @data[(pointer+4)..(pointer+7)].unpack('N').first
|
43
|
+
pointer += 8
|
44
|
+
l
|
45
|
+
when 126 # Length defined by 2 bytes
|
46
|
+
# Check buffer size
|
47
|
+
if @data.getbyte(pointer+2-1) == nil
|
48
|
+
debug [:buffer_incomplete, @data]
|
49
|
+
error = true
|
50
|
+
next
|
51
|
+
end
|
52
|
+
|
53
|
+
l = @data[pointer..(pointer+1)].unpack('n').first
|
54
|
+
pointer += 2
|
55
|
+
l
|
56
|
+
else
|
57
|
+
length
|
58
|
+
end
|
59
|
+
|
60
|
+
# Addition to the spec to protect against malicious requests
|
61
|
+
if payload_length > MAXIMUM_FRAME_LENGTH
|
62
|
+
raise DataError, "Frame length too long (#{payload_length} bytes)"
|
63
|
+
end
|
64
|
+
|
65
|
+
# Check buffer size
|
66
|
+
if @data.getbyte(pointer+payload_length-1) == nil
|
67
|
+
debug [:buffer_incomplete, @data]
|
68
|
+
error = true
|
69
|
+
next
|
70
|
+
end
|
71
|
+
|
72
|
+
# Throw away data up to pointer
|
73
|
+
@data.slice!(0...pointer)
|
74
|
+
|
75
|
+
# Read application data
|
76
|
+
application_data = @data.slice!(0...payload_length)
|
77
|
+
|
78
|
+
frame_type = opcode_to_type(opcode)
|
79
|
+
|
80
|
+
if frame_type == :continuation && !@frame_type
|
81
|
+
raise WebSocketError, 'Continuation frame not expected'
|
82
|
+
end
|
83
|
+
|
84
|
+
if more
|
85
|
+
debug [:moreframe, frame_type, application_data]
|
86
|
+
@application_data_buffer << application_data
|
87
|
+
# The message type is passed in the first frame
|
88
|
+
@frame_type ||= frame_type
|
89
|
+
else
|
90
|
+
# Message is complete
|
91
|
+
if frame_type == :continuation
|
92
|
+
@application_data_buffer << application_data
|
93
|
+
message(@frame_type, '', @application_data_buffer)
|
94
|
+
@application_data_buffer = ''
|
95
|
+
@frame_type = nil
|
96
|
+
else
|
97
|
+
message(frame_type, '', application_data)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end # end while
|
101
|
+
end
|
102
|
+
|
103
|
+
def send_frame(frame_type, application_data)
|
104
|
+
debug [:sending_frame, frame_type, application_data]
|
105
|
+
|
106
|
+
if @state == :closing && data_frame?(frame_type)
|
107
|
+
raise WebSocketError, "Cannot send data frame since connection is closing"
|
108
|
+
end
|
109
|
+
|
110
|
+
frame = ''
|
111
|
+
|
112
|
+
opcode = type_to_opcode(frame_type)
|
113
|
+
byte1 = opcode # since more, rsv1-3 are 0
|
114
|
+
frame << byte1
|
115
|
+
|
116
|
+
length = application_data.size
|
117
|
+
if length <= 125
|
118
|
+
byte2 = length # since rsv4 is 0
|
119
|
+
frame << byte2
|
120
|
+
elsif length < 65536 # write 2 byte length
|
121
|
+
frame << 126
|
122
|
+
frame << [length].pack('n')
|
123
|
+
else # write 8 byte length
|
124
|
+
frame << 127
|
125
|
+
frame << [length >> 32, length & 0xFFFFFFFF].pack("NN")
|
126
|
+
end
|
127
|
+
|
128
|
+
frame << application_data
|
129
|
+
|
130
|
+
@connection.send_data(frame)
|
131
|
+
end
|
132
|
+
|
133
|
+
def send_text_frame(data)
|
134
|
+
send_frame(:text, data)
|
135
|
+
end
|
136
|
+
|
137
|
+
private
|
138
|
+
|
139
|
+
# This allows flipping the more bit to fin for draft 04
|
140
|
+
def fin; false; end
|
141
|
+
|
142
|
+
FRAME_TYPES = {
|
143
|
+
:continuation => 0,
|
144
|
+
:close => 1,
|
145
|
+
:ping => 2,
|
146
|
+
:pong => 3,
|
147
|
+
:text => 4,
|
148
|
+
:binary => 5
|
149
|
+
}
|
150
|
+
FRAME_TYPES_INVERSE = FRAME_TYPES.invert
|
151
|
+
# Frames are either data frames or control frames
|
152
|
+
DATA_FRAMES = [:text, :binary, :continuation]
|
153
|
+
|
154
|
+
def type_to_opcode(frame_type)
|
155
|
+
FRAME_TYPES[frame_type] || raise("Unknown frame type")
|
156
|
+
end
|
157
|
+
|
158
|
+
def opcode_to_type(opcode)
|
159
|
+
FRAME_TYPES_INVERSE[opcode] || raise(DataError, "Unknown opcode")
|
160
|
+
end
|
161
|
+
|
162
|
+
def data_frame?(type)
|
163
|
+
DATA_FRAMES.include?(type)
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# encoding: BINARY
|
2
|
+
|
3
|
+
module EventMachine
|
4
|
+
module WebSocket
|
5
|
+
# The only difference between draft 03 framing and draft 04 framing is
|
6
|
+
# that the MORE bit has been changed to a FIN bit
|
7
|
+
module Framing04
|
8
|
+
include Framing03
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def fin; true; end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,168 @@
|
|
1
|
+
# encoding: BINARY
|
2
|
+
|
3
|
+
module EventMachine
|
4
|
+
module WebSocket
|
5
|
+
module Framing05
|
6
|
+
|
7
|
+
# Set the max frame lenth to very high value (10MB) until there is a
|
8
|
+
# limit specified in the spec to protect against malicious attacks
|
9
|
+
MAXIMUM_FRAME_LENGTH = 10 * 1024 * 1024
|
10
|
+
|
11
|
+
def initialize_framing
|
12
|
+
@data = MaskedString.new
|
13
|
+
@application_data_buffer = '' # Used for MORE frames
|
14
|
+
end
|
15
|
+
|
16
|
+
def process_data(newdata)
|
17
|
+
error = false
|
18
|
+
|
19
|
+
while !error && @data.size > 5 # mask plus first byte present
|
20
|
+
pointer = 0
|
21
|
+
|
22
|
+
@data.read_mask
|
23
|
+
pointer += 4
|
24
|
+
|
25
|
+
fin = (@data.getbyte(pointer) & 0b10000000) == 0b10000000
|
26
|
+
# Ignoring rsv1-3 for now
|
27
|
+
opcode = @data.getbyte(pointer) & 0b00001111
|
28
|
+
pointer += 1
|
29
|
+
|
30
|
+
# Ignoring rsv4
|
31
|
+
length = @data.getbyte(pointer) & 0b01111111
|
32
|
+
pointer += 1
|
33
|
+
|
34
|
+
payload_length = case length
|
35
|
+
when 127 # Length defined by 8 bytes
|
36
|
+
# Check buffer size
|
37
|
+
if @data.getbyte(pointer+8-1) == nil
|
38
|
+
debug [:buffer_incomplete, @data]
|
39
|
+
error = true
|
40
|
+
next
|
41
|
+
end
|
42
|
+
|
43
|
+
# Only using the last 4 bytes for now, till I work out how to
|
44
|
+
# unpack 8 bytes. I'm sure 4GB frames will do for now :)
|
45
|
+
l = @data.getbytes(pointer+4, 4).unpack('N').first
|
46
|
+
pointer += 8
|
47
|
+
l
|
48
|
+
when 126 # Length defined by 2 bytes
|
49
|
+
# Check buffer size
|
50
|
+
if @data.getbyte(pointer+2-1) == nil
|
51
|
+
debug [:buffer_incomplete, @data]
|
52
|
+
error = true
|
53
|
+
next
|
54
|
+
end
|
55
|
+
|
56
|
+
l = @data.getbytes(pointer, 2).unpack('n').first
|
57
|
+
pointer += 2
|
58
|
+
l
|
59
|
+
else
|
60
|
+
length
|
61
|
+
end
|
62
|
+
|
63
|
+
# Addition to the spec to protect against malicious requests
|
64
|
+
if payload_length > MAXIMUM_FRAME_LENGTH
|
65
|
+
raise DataError, "Frame length too long (#{payload_length} bytes)"
|
66
|
+
end
|
67
|
+
|
68
|
+
# Check buffer size
|
69
|
+
if @data.getbyte(pointer+payload_length-1) == nil
|
70
|
+
debug [:buffer_incomplete, @data]
|
71
|
+
error = true
|
72
|
+
next
|
73
|
+
end
|
74
|
+
|
75
|
+
# Read application data
|
76
|
+
application_data = @data.getbytes(pointer, payload_length)
|
77
|
+
pointer += payload_length
|
78
|
+
|
79
|
+
# Throw away data up to pointer
|
80
|
+
@data.unset_mask
|
81
|
+
@data.slice!(0...pointer)
|
82
|
+
|
83
|
+
frame_type = opcode_to_type(opcode)
|
84
|
+
|
85
|
+
if frame_type == :continuation && !@frame_type
|
86
|
+
raise WebSocketError, 'Continuation frame not expected'
|
87
|
+
end
|
88
|
+
|
89
|
+
if !fin
|
90
|
+
debug [:moreframe, frame_type, application_data]
|
91
|
+
@application_data_buffer << application_data
|
92
|
+
@frame_type = frame_type
|
93
|
+
else
|
94
|
+
# Message is complete
|
95
|
+
if frame_type == :continuation
|
96
|
+
@application_data_buffer << application_data
|
97
|
+
message(@frame_type, '', @application_data_buffer)
|
98
|
+
@application_data_buffer = ''
|
99
|
+
@frame_type = nil
|
100
|
+
else
|
101
|
+
message(frame_type, '', application_data)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end # end while
|
105
|
+
end
|
106
|
+
|
107
|
+
def send_frame(frame_type, application_data)
|
108
|
+
debug [:sending_frame, frame_type, application_data]
|
109
|
+
|
110
|
+
if @state == :closing && data_frame?(frame_type)
|
111
|
+
raise WebSocketError, "Cannot send data frame since connection is closing"
|
112
|
+
end
|
113
|
+
|
114
|
+
frame = ''
|
115
|
+
|
116
|
+
opcode = type_to_opcode(frame_type)
|
117
|
+
byte1 = opcode | 0b10000000 # fin bit set, rsv1-3 are 0
|
118
|
+
frame << byte1
|
119
|
+
|
120
|
+
length = application_data.size
|
121
|
+
if length <= 125
|
122
|
+
byte2 = length # since rsv4 is 0
|
123
|
+
frame << byte2
|
124
|
+
elsif length < 65536 # write 2 byte length
|
125
|
+
frame << 126
|
126
|
+
frame << [length].pack('n')
|
127
|
+
else # write 8 byte length
|
128
|
+
frame << 127
|
129
|
+
frame << [length >> 32, length & 0xFFFFFFFF].pack("NN")
|
130
|
+
end
|
131
|
+
|
132
|
+
frame << application_data
|
133
|
+
|
134
|
+
@connection.send_data(frame)
|
135
|
+
end
|
136
|
+
|
137
|
+
def send_text_frame(data)
|
138
|
+
send_frame(:text, data)
|
139
|
+
end
|
140
|
+
|
141
|
+
private
|
142
|
+
|
143
|
+
FRAME_TYPES = {
|
144
|
+
:continuation => 0,
|
145
|
+
:close => 1,
|
146
|
+
:ping => 2,
|
147
|
+
:pong => 3,
|
148
|
+
:text => 4,
|
149
|
+
:binary => 5
|
150
|
+
}
|
151
|
+
FRAME_TYPES_INVERSE = FRAME_TYPES.invert
|
152
|
+
# Frames are either data frames or control frames
|
153
|
+
DATA_FRAMES = [:text, :binary, :continuation]
|
154
|
+
|
155
|
+
def type_to_opcode(frame_type)
|
156
|
+
FRAME_TYPES[frame_type] || raise("Unknown frame type")
|
157
|
+
end
|
158
|
+
|
159
|
+
def opcode_to_type(opcode)
|
160
|
+
FRAME_TYPES_INVERSE[opcode] || raise(DataError, "Unknown opcode")
|
161
|
+
end
|
162
|
+
|
163
|
+
def data_frame?(type)
|
164
|
+
DATA_FRAMES.include?(type)
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
@@ -0,0 +1,180 @@
|
|
1
|
+
# encoding: BINARY
|
2
|
+
|
3
|
+
module EventMachine
|
4
|
+
module WebSocket
|
5
|
+
module Framing07
|
6
|
+
|
7
|
+
attr_accessor :mask_outbound_messages, :require_masked_inbound_messages
|
8
|
+
|
9
|
+
def initialize_framing
|
10
|
+
@data = MaskedString.new
|
11
|
+
@application_data_buffer = '' # Used for MORE frames
|
12
|
+
@mask_outbound_messages = false
|
13
|
+
@require_masked_inbound_messages = true
|
14
|
+
end
|
15
|
+
|
16
|
+
def process_data(newdata)
|
17
|
+
error = false
|
18
|
+
|
19
|
+
while !error && @data.size >= 2
|
20
|
+
pointer = 0
|
21
|
+
|
22
|
+
fin = (@data.getbyte(pointer) & 0b10000000) == 0b10000000
|
23
|
+
# Ignoring rsv1-3 for now
|
24
|
+
opcode = @data.getbyte(pointer) & 0b00001111
|
25
|
+
pointer += 1
|
26
|
+
|
27
|
+
mask = (@data.getbyte(pointer) & 0b10000000) == 0b10000000
|
28
|
+
length = @data.getbyte(pointer) & 0b01111111
|
29
|
+
pointer += 1
|
30
|
+
|
31
|
+
if require_masked_inbound_messages
|
32
|
+
raise WebSocketError, 'Data from client must be masked' unless mask
|
33
|
+
end
|
34
|
+
|
35
|
+
payload_length = case length
|
36
|
+
when 127 # Length defined by 8 bytes
|
37
|
+
# Check buffer size
|
38
|
+
if @data.getbyte(pointer+8-1) == nil
|
39
|
+
debug [:buffer_incomplete, @data]
|
40
|
+
error = true
|
41
|
+
next
|
42
|
+
end
|
43
|
+
|
44
|
+
# Only using the last 4 bytes for now, till I work out how to
|
45
|
+
# unpack 8 bytes. I'm sure 4GB frames will do for now :)
|
46
|
+
l = @data.getbytes(pointer+4, 4).unpack('N').first
|
47
|
+
pointer += 8
|
48
|
+
l
|
49
|
+
when 126 # Length defined by 2 bytes
|
50
|
+
# Check buffer size
|
51
|
+
if @data.getbyte(pointer+2-1) == nil
|
52
|
+
debug [:buffer_incomplete, @data]
|
53
|
+
error = true
|
54
|
+
next
|
55
|
+
end
|
56
|
+
|
57
|
+
l = @data.getbytes(pointer, 2).unpack('n').first
|
58
|
+
pointer += 2
|
59
|
+
l
|
60
|
+
else
|
61
|
+
length
|
62
|
+
end
|
63
|
+
|
64
|
+
# Compute the expected frame length
|
65
|
+
frame_length = pointer + payload_length
|
66
|
+
frame_length += 4 if mask
|
67
|
+
|
68
|
+
# Check buffer size
|
69
|
+
if @data.getbyte(frame_length - 1) == nil
|
70
|
+
debug [:buffer_incomplete, @data]
|
71
|
+
error = true
|
72
|
+
next
|
73
|
+
end
|
74
|
+
|
75
|
+
# Remove frame header
|
76
|
+
@data.slice!(0...pointer)
|
77
|
+
pointer = 0
|
78
|
+
|
79
|
+
# Read application data (unmasked if required)
|
80
|
+
@data.read_mask if mask
|
81
|
+
pointer += 4 if mask
|
82
|
+
application_data = @data.getbytes(pointer, payload_length)
|
83
|
+
pointer += payload_length
|
84
|
+
@data.unset_mask if mask
|
85
|
+
|
86
|
+
# Throw away data up to pointer
|
87
|
+
@data.slice!(0...pointer)
|
88
|
+
|
89
|
+
frame_type = opcode_to_type(opcode)
|
90
|
+
|
91
|
+
if frame_type == :continuation && !@frame_type
|
92
|
+
raise WebSocketError, 'Continuation frame not expected'
|
93
|
+
end
|
94
|
+
|
95
|
+
if !fin
|
96
|
+
debug [:moreframe, frame_type, application_data]
|
97
|
+
@application_data_buffer << application_data
|
98
|
+
# The message type is passed in the first frame
|
99
|
+
@frame_type ||= frame_type
|
100
|
+
else
|
101
|
+
# Message is complete
|
102
|
+
if frame_type == :continuation
|
103
|
+
@application_data_buffer << application_data
|
104
|
+
message(@frame_type, '', @application_data_buffer)
|
105
|
+
@application_data_buffer = ''
|
106
|
+
@frame_type = nil
|
107
|
+
else
|
108
|
+
message(frame_type, '', application_data)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end # end while
|
112
|
+
end
|
113
|
+
|
114
|
+
def send_frame(frame_type, application_data)
|
115
|
+
debug [:sending_frame, frame_type, application_data]
|
116
|
+
|
117
|
+
if @state == :closing && data_frame?(frame_type)
|
118
|
+
raise WebSocketError, "Cannot send data frame since connection is closing"
|
119
|
+
end
|
120
|
+
|
121
|
+
frame = ''
|
122
|
+
|
123
|
+
opcode = type_to_opcode(frame_type)
|
124
|
+
byte1 = opcode | 0b10000000 # fin bit set, rsv1-3 are 0
|
125
|
+
frame << byte1
|
126
|
+
|
127
|
+
mask = mask_outbound_messages ? 0b10000000 : 0b00000000 # must be masked if from client
|
128
|
+
length = application_data.size
|
129
|
+
if length <= 125
|
130
|
+
byte2 = length # since rsv4 is 0
|
131
|
+
frame << (mask | byte2)
|
132
|
+
elsif length < 65536 # write 2 byte length
|
133
|
+
frame << (mask | 126)
|
134
|
+
frame << [length].pack('n')
|
135
|
+
else # write 8 byte length
|
136
|
+
frame << (mask | 127)
|
137
|
+
frame << [length >> 32, length & 0xFFFFFFFF].pack("NN")
|
138
|
+
end
|
139
|
+
|
140
|
+
if mask_outbound_messages
|
141
|
+
frame << MaskedString.create_masked_string(application_data)
|
142
|
+
else
|
143
|
+
frame << application_data
|
144
|
+
end
|
145
|
+
|
146
|
+
@connection.send_data(frame)
|
147
|
+
end
|
148
|
+
|
149
|
+
def send_text_frame(data)
|
150
|
+
send_frame(:text, data)
|
151
|
+
end
|
152
|
+
|
153
|
+
private
|
154
|
+
|
155
|
+
FRAME_TYPES = {
|
156
|
+
:continuation => 0,
|
157
|
+
:text => 1,
|
158
|
+
:binary => 2,
|
159
|
+
:close => 8,
|
160
|
+
:ping => 9,
|
161
|
+
:pong => 10,
|
162
|
+
}
|
163
|
+
FRAME_TYPES_INVERSE = FRAME_TYPES.invert
|
164
|
+
# Frames are either data frames or control frames
|
165
|
+
DATA_FRAMES = [:text, :binary, :continuation]
|
166
|
+
|
167
|
+
def type_to_opcode(frame_type)
|
168
|
+
FRAME_TYPES[frame_type] || raise("Unknown frame type")
|
169
|
+
end
|
170
|
+
|
171
|
+
def opcode_to_type(opcode)
|
172
|
+
FRAME_TYPES_INVERSE[opcode] || raise(DataError, "Unknown opcode")
|
173
|
+
end
|
174
|
+
|
175
|
+
def data_frame?(type)
|
176
|
+
DATA_FRAMES.include?(type)
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
# encoding: BINARY
|
2
|
+
|
3
|
+
module EventMachine
|
4
|
+
module WebSocket
|
5
|
+
module Framing76
|
6
|
+
|
7
|
+
# Set the max frame lenth to very high value (10MB) until there is a
|
8
|
+
# limit specified in the spec to protect against malicious attacks
|
9
|
+
MAXIMUM_FRAME_LENGTH = 10 * 1024 * 1024
|
10
|
+
|
11
|
+
def initialize_framing
|
12
|
+
@data = ''
|
13
|
+
end
|
14
|
+
|
15
|
+
def process_data(newdata)
|
16
|
+
debug [:message, @data]
|
17
|
+
|
18
|
+
# This algorithm comes straight from the spec
|
19
|
+
# http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76#section-5.3
|
20
|
+
|
21
|
+
error = false
|
22
|
+
|
23
|
+
while !error
|
24
|
+
return if @data.size == 0
|
25
|
+
|
26
|
+
pointer = 0
|
27
|
+
frame_type = @data.getbyte(pointer)
|
28
|
+
pointer += 1
|
29
|
+
|
30
|
+
if (frame_type & 0x80) == 0x80
|
31
|
+
# If the high-order bit of the /frame type/ byte is set
|
32
|
+
length = 0
|
33
|
+
|
34
|
+
loop do
|
35
|
+
return false if !@data.getbyte(pointer)
|
36
|
+
b = @data.getbyte(pointer)
|
37
|
+
pointer += 1
|
38
|
+
b_v = b & 0x7F
|
39
|
+
length = length * 128 + b_v
|
40
|
+
break unless (b & 0x80) == 0x80
|
41
|
+
end
|
42
|
+
|
43
|
+
# Addition to the spec to protect against malicious requests
|
44
|
+
if length > MAXIMUM_FRAME_LENGTH
|
45
|
+
raise DataError, "Frame length too long (#{length} bytes)"
|
46
|
+
end
|
47
|
+
|
48
|
+
if @data.getbyte(pointer+length-1) == nil
|
49
|
+
debug [:buffer_incomplete, @data]
|
50
|
+
# Incomplete data - leave @data to accumulate
|
51
|
+
error = true
|
52
|
+
else
|
53
|
+
# Straight from spec - I'm sure this isn't crazy...
|
54
|
+
# 6. Read /length/ bytes.
|
55
|
+
# 7. Discard the read bytes.
|
56
|
+
@data = @data[(pointer+length)..-1]
|
57
|
+
|
58
|
+
# If the /frame type/ is 0xFF and the /length/ was 0, then close
|
59
|
+
if length == 0
|
60
|
+
@connection.send_data("\xff\x00")
|
61
|
+
@state = :closing
|
62
|
+
@connection.close_connection_after_writing
|
63
|
+
else
|
64
|
+
error = true
|
65
|
+
end
|
66
|
+
end
|
67
|
+
else
|
68
|
+
# If the high-order bit of the /frame type/ byte is _not_ set
|
69
|
+
|
70
|
+
if @data.getbyte(0) != 0x00
|
71
|
+
# Close the connection since this buffer can never match
|
72
|
+
raise DataError, "Invalid frame received"
|
73
|
+
end
|
74
|
+
|
75
|
+
# Addition to the spec to protect against malicious requests
|
76
|
+
if @data.size > MAXIMUM_FRAME_LENGTH
|
77
|
+
raise DataError, "Frame length too long (#{@data.size} bytes)"
|
78
|
+
end
|
79
|
+
|
80
|
+
# Optimization to avoid calling slice! unnecessarily
|
81
|
+
error = true and next unless newdata =~ /\xff/
|
82
|
+
|
83
|
+
msg = @data.slice!(/\A\x00[^\xff]*\xff/)
|
84
|
+
if msg
|
85
|
+
msg.gsub!(/\A\x00|\xff\z/, '')
|
86
|
+
if @state == :closing
|
87
|
+
debug [:ignored_message, msg]
|
88
|
+
else
|
89
|
+
msg.force_encoding('UTF-8') if msg.respond_to?(:force_encoding)
|
90
|
+
@connection.trigger_on_message(msg)
|
91
|
+
end
|
92
|
+
else
|
93
|
+
error = true
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
false
|
99
|
+
end
|
100
|
+
|
101
|
+
# frames need to start with 0x00-0x7f byte and end with
|
102
|
+
# an 0xFF byte. Per spec, we can also set the first
|
103
|
+
# byte to a value betweent 0x80 and 0xFF, followed by
|
104
|
+
# a leading length indicator
|
105
|
+
def send_text_frame(data)
|
106
|
+
debug [:sending_text_frame, data]
|
107
|
+
ary = ["\x00", data, "\xff"]
|
108
|
+
ary.collect{ |s| s.force_encoding('UTF-8') if s.respond_to?(:force_encoding) }
|
109
|
+
@connection.send_data(ary.join)
|
110
|
+
end
|
111
|
+
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module EventMachine
|
2
|
+
module WebSocket
|
3
|
+
class Handler
|
4
|
+
include Debugger
|
5
|
+
|
6
|
+
attr_reader :request, :state
|
7
|
+
|
8
|
+
def initialize(connection, request, debug = false)
|
9
|
+
@connection, @request = connection, request
|
10
|
+
@debug = debug
|
11
|
+
@state = :handshake
|
12
|
+
initialize_framing
|
13
|
+
end
|
14
|
+
|
15
|
+
def run_server
|
16
|
+
@connection.send_data handshake_server
|
17
|
+
@state = :connected
|
18
|
+
@connection.trigger_on_open
|
19
|
+
end
|
20
|
+
|
21
|
+
def run_client
|
22
|
+
self.mask_outbound_messages = true
|
23
|
+
self.require_masked_inbound_messages = false
|
24
|
+
@connection.send_data handshake_client
|
25
|
+
end
|
26
|
+
|
27
|
+
# Handshake response
|
28
|
+
def handshake
|
29
|
+
# Implemented in subclass
|
30
|
+
end
|
31
|
+
|
32
|
+
def handshake_server
|
33
|
+
handshake #backwards compatibility
|
34
|
+
end
|
35
|
+
|
36
|
+
# Handshake initiation
|
37
|
+
def handshake_client
|
38
|
+
# Implemented in subclass
|
39
|
+
end
|
40
|
+
|
41
|
+
def receive_data(data)
|
42
|
+
@data << data
|
43
|
+
process_data(data)
|
44
|
+
end
|
45
|
+
|
46
|
+
def close_websocket(code, body)
|
47
|
+
# Implemented in subclass
|
48
|
+
end
|
49
|
+
|
50
|
+
def unbind
|
51
|
+
@state = :closed
|
52
|
+
@connection.trigger_on_close
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|