sonixlabs-em-websocket 0.3.7
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 +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
|