websocket-driver 0.7.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.
@@ -0,0 +1,102 @@
1
+ module WebSocket
2
+ class Driver
3
+
4
+ class Draft75 < Driver
5
+ def initialize(socket, options = {})
6
+ super
7
+
8
+ @stage = 0
9
+ @closing = false
10
+
11
+ @headers['Upgrade'] = 'WebSocket'
12
+ @headers['Connection'] = 'Upgrade'
13
+ @headers['WebSocket-Origin'] = @socket.env['HTTP_ORIGIN']
14
+ @headers['WebSocket-Location'] = @socket.url
15
+ end
16
+
17
+ def version
18
+ 'hixie-75'
19
+ end
20
+
21
+ def close(reason = nil, code = nil)
22
+ return false if @ready_state == 3
23
+ @ready_state = 3
24
+ emit(:close, CloseEvent.new(nil, nil))
25
+ true
26
+ end
27
+
28
+ def parse(chunk)
29
+ return if @ready_state > 1
30
+
31
+ @reader.put(chunk)
32
+
33
+ @reader.each_byte do |octet|
34
+ case @stage
35
+ when -1 then
36
+ @body << octet
37
+ send_handshake_body
38
+
39
+ when 0 then
40
+ parse_leading_byte(octet)
41
+
42
+ when 1 then
43
+ @length = (octet & 0x7F) + 128 * @length
44
+
45
+ if @closing and @length.zero?
46
+ return close
47
+ elsif (octet & 0x80) != 0x80
48
+ if @length.zero?
49
+ @stage = 0
50
+ else
51
+ @skipped = 0
52
+ @stage = 2
53
+ end
54
+ end
55
+
56
+ when 2 then
57
+ if octet == 0xFF
58
+ @stage = 0
59
+ emit(:message, MessageEvent.new(Driver.encode(@buffer, UNICODE)))
60
+ else
61
+ if @length
62
+ @skipped += 1
63
+ @stage = 0 if @skipped == @length
64
+ else
65
+ @buffer << octet
66
+ return close if @buffer.size > @max_length
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
72
+
73
+ def frame(buffer, type = nil, error_type = nil)
74
+ return queue([buffer, type, error_type]) if @ready_state == 0
75
+ frame = [0x00, buffer, 0xFF].pack('CA*C')
76
+ @socket.write(frame)
77
+ true
78
+ end
79
+
80
+ private
81
+
82
+ def handshake_response
83
+ start = 'HTTP/1.1 101 Web Socket Protocol Handshake'
84
+ headers = [start, @headers.to_s, '']
85
+ headers.join("\r\n")
86
+ end
87
+
88
+ def parse_leading_byte(octet)
89
+ if (octet & 0x80) == 0x80
90
+ @length = 0
91
+ @stage = 1
92
+ else
93
+ @length = nil
94
+ @skipped = nil
95
+ @buffer = []
96
+ @stage = 2
97
+ end
98
+ end
99
+ end
100
+
101
+ end
102
+ end
@@ -0,0 +1,98 @@
1
+ module WebSocket
2
+ class Driver
3
+
4
+ class Draft76 < Draft75
5
+ BODY_SIZE = 8
6
+
7
+ def initialize(socket, options = {})
8
+ super
9
+ input = @socket.env['rack.input']
10
+ @stage = -1
11
+ @body = (input ? input.read : String.new('')).force_encoding(BINARY)
12
+
13
+ @headers.clear
14
+ @headers['Upgrade'] = 'WebSocket'
15
+ @headers['Connection'] = 'Upgrade'
16
+ @headers['Sec-WebSocket-Origin'] = @socket.env['HTTP_ORIGIN']
17
+ @headers['Sec-WebSocket-Location'] = @socket.url
18
+ end
19
+
20
+ def version
21
+ 'hixie-76'
22
+ end
23
+
24
+ def start
25
+ return false unless super
26
+ send_handshake_body
27
+ true
28
+ end
29
+
30
+ def close(reason = nil, code = nil)
31
+ return false if @ready_state == 3
32
+ @socket.write([0xFF, 0x00].pack('C*')) if @ready_state == 1
33
+ @ready_state = 3
34
+ emit(:close, CloseEvent.new(nil, nil))
35
+ true
36
+ end
37
+
38
+ private
39
+
40
+ def handshake_response
41
+ env = @socket.env
42
+ key1 = env['HTTP_SEC_WEBSOCKET_KEY1']
43
+ key2 = env['HTTP_SEC_WEBSOCKET_KEY2']
44
+
45
+ raise ProtocolError.new('Missing required header: Sec-WebSocket-Key1') unless key1
46
+ raise ProtocolError.new('Missing required header: Sec-WebSocket-Key2') unless key2
47
+
48
+ number1 = number_from_key(key1)
49
+ spaces1 = spaces_in_key(key1)
50
+
51
+ number2 = number_from_key(key2)
52
+ spaces2 = spaces_in_key(key2)
53
+
54
+ if number1 % spaces1 != 0 or number2 % spaces2 != 0
55
+ raise ProtocolError.new('Client sent invalid Sec-WebSocket-Key headers')
56
+ end
57
+
58
+ @key_values = [number1 / spaces1, number2 / spaces2]
59
+
60
+ start = 'HTTP/1.1 101 WebSocket Protocol Handshake'
61
+ headers = [start, @headers.to_s, '']
62
+ headers.join("\r\n")
63
+ end
64
+
65
+ def handshake_signature
66
+ return nil unless @body.bytesize >= BODY_SIZE
67
+
68
+ head = @body[0...BODY_SIZE]
69
+ Digest::MD5.digest((@key_values + [head]).pack('N2A*'))
70
+ end
71
+
72
+ def send_handshake_body
73
+ return unless signature = handshake_signature
74
+ @socket.write(signature)
75
+ @stage = 0
76
+ open
77
+ parse(@body[BODY_SIZE..-1]) if @body.bytesize > BODY_SIZE
78
+ end
79
+
80
+ def parse_leading_byte(octet)
81
+ return super unless octet == 0xFF
82
+ @closing = true
83
+ @length = 0
84
+ @stage = 1
85
+ end
86
+
87
+ def number_from_key(key)
88
+ number = key.scan(/[0-9]/).join('')
89
+ number == '' ? Float::NAN : number.to_i(10)
90
+ end
91
+
92
+ def spaces_in_key(key)
93
+ key.scan(/ /).size
94
+ end
95
+ end
96
+
97
+ end
98
+ end
@@ -0,0 +1,54 @@
1
+ module WebSocket
2
+ class Driver
3
+
4
+ module EventEmitter
5
+ def initialize
6
+ @listeners = Hash.new { |h,k| h[k] = [] }
7
+ end
8
+
9
+ def add_listener(event, callable = nil, &block)
10
+ listener = callable || block
11
+ @listeners[event.to_s] << listener
12
+ listener
13
+ end
14
+
15
+ def on(event, callable = nil, &block)
16
+ if callable
17
+ add_listener(event, callable)
18
+ else
19
+ add_listener(event, &block)
20
+ end
21
+ end
22
+
23
+ def remove_listener(event, callable = nil, &block)
24
+ listener = callable || block
25
+ @listeners[event.to_s].delete(listener)
26
+ listener
27
+ end
28
+
29
+ def remove_all_listeners(event = nil)
30
+ if event
31
+ @listeners.delete(event.to_s)
32
+ else
33
+ @listeners.clear
34
+ end
35
+ end
36
+
37
+ def emit(event, *args)
38
+ @listeners[event.to_s].dup.each do |listener|
39
+ listener.call(*args)
40
+ end
41
+ end
42
+
43
+ def listener_count(event)
44
+ return 0 unless @listeners.has_key?(event.to_s)
45
+ @listeners[event.to_s].size
46
+ end
47
+
48
+ def listeners(event)
49
+ @listeners[event.to_s]
50
+ end
51
+ end
52
+
53
+ end
54
+ end
@@ -0,0 +1,45 @@
1
+ module WebSocket
2
+ class Driver
3
+
4
+ class Headers
5
+ ALLOWED_DUPLICATES = %w[set-cookie set-cookie2 warning www-authenticate]
6
+
7
+ def initialize(received = {})
8
+ @raw = received
9
+ clear
10
+
11
+ @received = {}
12
+ @raw.each { |k,v| @received[HTTP.normalize_header(k)] = v }
13
+ end
14
+
15
+ def clear
16
+ @sent = Set.new
17
+ @lines = []
18
+ end
19
+
20
+ def [](name)
21
+ @received[HTTP.normalize_header(name)]
22
+ end
23
+
24
+ def []=(name, value)
25
+ return if value.nil?
26
+ key = HTTP.normalize_header(name)
27
+ return unless @sent.add?(key) or ALLOWED_DUPLICATES.include?(key)
28
+ @lines << "#{name.strip}: #{value.to_s.strip}\r\n"
29
+ end
30
+
31
+ def inspect
32
+ @raw.inspect
33
+ end
34
+
35
+ def to_h
36
+ @raw.dup
37
+ end
38
+
39
+ def to_s
40
+ @lines.join('')
41
+ end
42
+ end
43
+
44
+ end
45
+ end
@@ -0,0 +1,414 @@
1
+ module WebSocket
2
+ class Driver
3
+
4
+ class Hybi < Driver
5
+ root = File.expand_path('../hybi', __FILE__)
6
+
7
+ autoload :Frame, root + '/frame'
8
+ autoload :Message, root + '/message'
9
+
10
+ def self.generate_accept(key)
11
+ Base64.strict_encode64(Digest::SHA1.digest(key + GUID))
12
+ end
13
+
14
+ VERSION = '13'
15
+ GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
16
+
17
+ BYTE = 0b11111111
18
+ FIN = MASK = 0b10000000
19
+ RSV1 = 0b01000000
20
+ RSV2 = 0b00100000
21
+ RSV3 = 0b00010000
22
+ OPCODE = 0b00001111
23
+ LENGTH = 0b01111111
24
+
25
+ OPCODES = {
26
+ :continuation => 0,
27
+ :text => 1,
28
+ :binary => 2,
29
+ :close => 8,
30
+ :ping => 9,
31
+ :pong => 10
32
+ }
33
+
34
+ OPCODE_CODES = OPCODES.values
35
+ MESSAGE_OPCODES = OPCODES.values_at(:continuation, :text, :binary)
36
+ OPENING_OPCODES = OPCODES.values_at(:text, :binary)
37
+
38
+ ERRORS = {
39
+ :normal_closure => 1000,
40
+ :going_away => 1001,
41
+ :protocol_error => 1002,
42
+ :unacceptable => 1003,
43
+ :encoding_error => 1007,
44
+ :policy_violation => 1008,
45
+ :too_large => 1009,
46
+ :extension_error => 1010,
47
+ :unexpected_condition => 1011
48
+ }
49
+
50
+ ERROR_CODES = ERRORS.values
51
+ DEFAULT_ERROR_CODE = 1000
52
+ MIN_RESERVED_ERROR = 3000
53
+ MAX_RESERVED_ERROR = 4999
54
+
55
+ PACK_FORMATS = {2 => 'n', 8 => 'Q>'}
56
+
57
+ def initialize(socket, options = {})
58
+ super
59
+
60
+ @extensions = ::WebSocket::Extensions.new
61
+ @stage = 0
62
+ @masking = options[:masking]
63
+ @protocols = options[:protocols] || []
64
+ @protocols = @protocols.strip.split(/ *, */) if String === @protocols
65
+ @require_masking = options[:require_masking]
66
+ @ping_callbacks = {}
67
+
68
+ @frame = @message = nil
69
+
70
+ return unless @socket.respond_to?(:env)
71
+
72
+ if protos = @socket.env['HTTP_SEC_WEBSOCKET_PROTOCOL']
73
+ protos = protos.split(/ *, */) if String === protos
74
+ @protocol = protos.find { |p| @protocols.include?(p) }
75
+ else
76
+ @protocol = nil
77
+ end
78
+ end
79
+
80
+ def version
81
+ "hybi-#{VERSION}"
82
+ end
83
+
84
+ def add_extension(extension)
85
+ @extensions.add(extension)
86
+ true
87
+ end
88
+
89
+ def parse(chunk)
90
+ @reader.put(chunk)
91
+ buffer = true
92
+ while buffer
93
+ case @stage
94
+ when 0 then
95
+ buffer = @reader.read(1)
96
+ parse_opcode(buffer.getbyte(0)) if buffer
97
+
98
+ when 1 then
99
+ buffer = @reader.read(1)
100
+ parse_length(buffer.getbyte(0)) if buffer
101
+
102
+ when 2 then
103
+ buffer = @reader.read(@frame.length_bytes)
104
+ parse_extended_length(buffer) if buffer
105
+
106
+ when 3 then
107
+ buffer = @reader.read(4)
108
+ if buffer
109
+ @stage = 4
110
+ @frame.masking_key = buffer
111
+ end
112
+
113
+ when 4 then
114
+ buffer = @reader.read(@frame.length)
115
+
116
+ if buffer
117
+ @stage = 0
118
+ emit_frame(buffer)
119
+ end
120
+
121
+ else
122
+ buffer = nil
123
+ end
124
+ end
125
+ end
126
+
127
+ def binary(message)
128
+ frame(message, :binary)
129
+ end
130
+
131
+ def ping(message = '', &callback)
132
+ @ping_callbacks[message] = callback if callback
133
+ frame(message, :ping)
134
+ end
135
+
136
+ def pong(message = '')
137
+ frame(message, :pong)
138
+ end
139
+
140
+ def close(reason = nil, code = nil)
141
+ reason ||= ''
142
+ code ||= ERRORS[:normal_closure]
143
+
144
+ if @ready_state <= 0
145
+ @ready_state = 3
146
+ emit(:close, CloseEvent.new(code, reason))
147
+ true
148
+ elsif @ready_state == 1
149
+ frame(reason, :close, code)
150
+ @ready_state = 2
151
+ true
152
+ else
153
+ false
154
+ end
155
+ end
156
+
157
+ def frame(buffer, type = nil, code = nil)
158
+ return queue([buffer, type, code]) if @ready_state <= 0
159
+ return false unless @ready_state == 1
160
+
161
+ message = Message.new
162
+ frame = Frame.new
163
+ is_text = String === buffer
164
+
165
+ message.rsv1 = message.rsv2 = message.rsv3 = false
166
+ message.opcode = OPCODES[type || (is_text ? :text : :binary)]
167
+
168
+ payload = is_text ? buffer.bytes.to_a : buffer
169
+ payload = [code].pack(PACK_FORMATS[2]).bytes.to_a + payload if code
170
+ message.data = payload.pack('C*')
171
+
172
+ if MESSAGE_OPCODES.include?(message.opcode)
173
+ message = @extensions.process_outgoing_message(message)
174
+ end
175
+
176
+ frame.final = true
177
+ frame.rsv1 = message.rsv1
178
+ frame.rsv2 = message.rsv2
179
+ frame.rsv3 = message.rsv3
180
+ frame.opcode = message.opcode
181
+ frame.masked = !!@masking
182
+ frame.masking_key = SecureRandom.random_bytes(4) if frame.masked
183
+ frame.length = message.data.bytesize
184
+ frame.payload = message.data
185
+
186
+ send_frame(frame)
187
+ true
188
+
189
+ rescue ::WebSocket::Extensions::ExtensionError => error
190
+ fail(:extension_error, error.message)
191
+ end
192
+
193
+ private
194
+
195
+ def send_frame(frame)
196
+ length = frame.length
197
+ buffer = []
198
+ masked = frame.masked ? MASK : 0
199
+
200
+ buffer[0] = (frame.final ? FIN : 0) |
201
+ (frame.rsv1 ? RSV1 : 0) |
202
+ (frame.rsv2 ? RSV2 : 0) |
203
+ (frame.rsv3 ? RSV3 : 0) |
204
+ frame.opcode
205
+
206
+ if length <= 125
207
+ buffer[1] = masked | length
208
+ elsif length <= 65535
209
+ buffer[1] = masked | 126
210
+ buffer[2..3] = [length].pack(PACK_FORMATS[2]).bytes.to_a
211
+ else
212
+ buffer[1] = masked | 127
213
+ buffer[2..9] = [length].pack(PACK_FORMATS[8]).bytes.to_a
214
+ end
215
+
216
+ if frame.masked
217
+ buffer.concat(frame.masking_key.bytes.to_a)
218
+ buffer.concat(Mask.mask(frame.payload, frame.masking_key).bytes.to_a)
219
+ else
220
+ buffer.concat(frame.payload.bytes.to_a)
221
+ end
222
+
223
+ @socket.write(buffer.pack('C*'))
224
+ end
225
+
226
+ def handshake_response
227
+ sec_key = @socket.env['HTTP_SEC_WEBSOCKET_KEY']
228
+ version = @socket.env['HTTP_SEC_WEBSOCKET_VERSION']
229
+
230
+ unless version == VERSION
231
+ raise ProtocolError.new("Unsupported WebSocket version: #{VERSION}")
232
+ end
233
+
234
+ unless sec_key
235
+ raise ProtocolError.new('Missing handshake request header: Sec-WebSocket-Key')
236
+ end
237
+
238
+ @headers['Upgrade'] = 'websocket'
239
+ @headers['Connection'] = 'Upgrade'
240
+ @headers['Sec-WebSocket-Accept'] = Hybi.generate_accept(sec_key)
241
+
242
+ @headers['Sec-WebSocket-Protocol'] = @protocol if @protocol
243
+
244
+ extensions = @extensions.generate_response(@socket.env['HTTP_SEC_WEBSOCKET_EXTENSIONS'])
245
+ @headers['Sec-WebSocket-Extensions'] = extensions if extensions
246
+
247
+ start = 'HTTP/1.1 101 Switching Protocols'
248
+ headers = [start, @headers.to_s, '']
249
+ headers.join("\r\n")
250
+ end
251
+
252
+ def shutdown(code, reason, error = false)
253
+ @frame = @message = nil
254
+ @stage = 5
255
+ @extensions.close
256
+
257
+ frame(reason, :close, code) if @ready_state < 2
258
+ @ready_state = 3
259
+
260
+ emit(:error, ProtocolError.new(reason)) if error
261
+ emit(:close, CloseEvent.new(code, reason))
262
+ end
263
+
264
+ def fail(type, message)
265
+ return if @ready_state > 1
266
+ shutdown(ERRORS[type], message, true)
267
+ end
268
+
269
+ def parse_opcode(octet)
270
+ rsvs = [RSV1, RSV2, RSV3].map { |rsv| (octet & rsv) == rsv }
271
+
272
+ @frame = Frame.new
273
+
274
+ @frame.final = (octet & FIN) == FIN
275
+ @frame.rsv1 = rsvs[0]
276
+ @frame.rsv2 = rsvs[1]
277
+ @frame.rsv3 = rsvs[2]
278
+ @frame.opcode = (octet & OPCODE)
279
+
280
+ @stage = 1
281
+
282
+ unless @extensions.valid_frame_rsv?(@frame)
283
+ return fail(:protocol_error,
284
+ "One or more reserved bits are on: reserved1 = #{@frame.rsv1 ? 1 : 0}" +
285
+ ", reserved2 = #{@frame.rsv2 ? 1 : 0 }" +
286
+ ", reserved3 = #{@frame.rsv3 ? 1 : 0 }")
287
+ end
288
+
289
+ unless OPCODES.values.include?(@frame.opcode)
290
+ return fail(:protocol_error, "Unrecognized frame opcode: #{@frame.opcode}")
291
+ end
292
+
293
+ unless MESSAGE_OPCODES.include?(@frame.opcode) or @frame.final
294
+ return fail(:protocol_error, "Received fragmented control frame: opcode = #{@frame.opcode}")
295
+ end
296
+
297
+ if @message and OPENING_OPCODES.include?(@frame.opcode)
298
+ return fail(:protocol_error, 'Received new data frame but previous continuous frame is unfinished')
299
+ end
300
+ end
301
+
302
+ def parse_length(octet)
303
+ @frame.masked = (octet & MASK) == MASK
304
+ @frame.length = (octet & LENGTH)
305
+
306
+ if @frame.length >= 0 and @frame.length <= 125
307
+ @stage = @frame.masked ? 3 : 4
308
+ return unless check_frame_length
309
+ else
310
+ @stage = 2
311
+ @frame.length_bytes = (@frame.length == 126) ? 2 : 8
312
+ end
313
+
314
+ if @require_masking and not @frame.masked
315
+ return fail(:unacceptable, 'Received unmasked frame but masking is required')
316
+ end
317
+ end
318
+
319
+ def parse_extended_length(buffer)
320
+ @frame.length = buffer.unpack(PACK_FORMATS[buffer.bytesize]).first
321
+ @stage = @frame.masked ? 3 : 4
322
+
323
+ unless MESSAGE_OPCODES.include?(@frame.opcode) or @frame.length <= 125
324
+ return fail(:protocol_error, "Received control frame having too long payload: #{@frame.length}")
325
+ end
326
+
327
+ return unless check_frame_length
328
+ end
329
+
330
+ def check_frame_length
331
+ length = @message ? @message.data.bytesize : 0
332
+
333
+ if length + @frame.length > @max_length
334
+ fail(:too_large, 'WebSocket frame length too large')
335
+ false
336
+ else
337
+ true
338
+ end
339
+ end
340
+
341
+ def emit_frame(buffer)
342
+ frame = @frame
343
+ opcode = frame.opcode
344
+ payload = frame.payload = Mask.mask(buffer, @frame.masking_key)
345
+ bytesize = payload.bytesize
346
+ bytes = payload.bytes.to_a
347
+
348
+ @frame = nil
349
+
350
+ case opcode
351
+ when OPCODES[:continuation] then
352
+ return fail(:protocol_error, 'Received unexpected continuation frame') unless @message
353
+ @message << frame
354
+
355
+ when OPCODES[:text], OPCODES[:binary] then
356
+ @message = Message.new
357
+ @message << frame
358
+
359
+ when OPCODES[:close] then
360
+ code = (bytesize >= 2) ? payload.unpack(PACK_FORMATS[2]).first : nil
361
+ reason = (bytesize > 2) ? Driver.encode(bytes[2..-1] || [], UNICODE) : nil
362
+
363
+ unless (bytesize == 0) or
364
+ (code && code >= MIN_RESERVED_ERROR && code <= MAX_RESERVED_ERROR) or
365
+ ERROR_CODES.include?(code)
366
+ code = ERRORS[:protocol_error]
367
+ end
368
+
369
+ if bytesize > 125 or (bytesize > 2 and reason.nil?)
370
+ code = ERRORS[:protocol_error]
371
+ end
372
+
373
+ shutdown(code || DEFAULT_ERROR_CODE, reason || '')
374
+
375
+ when OPCODES[:ping] then
376
+ frame(payload, :pong)
377
+ emit(:ping, PingEvent.new(payload))
378
+
379
+ when OPCODES[:pong] then
380
+ message = Driver.encode(payload, UNICODE)
381
+ callback = @ping_callbacks[message]
382
+ @ping_callbacks.delete(message)
383
+ callback.call if callback
384
+ emit(:pong, PongEvent.new(payload))
385
+ end
386
+
387
+ emit_message if frame.final and MESSAGE_OPCODES.include?(opcode)
388
+ end
389
+
390
+ def emit_message
391
+ message = @extensions.process_incoming_message(@message)
392
+ @message = nil
393
+
394
+ payload = message.data
395
+
396
+ case message.opcode
397
+ when OPCODES[:text] then
398
+ payload = Driver.encode(payload, UNICODE)
399
+ when OPCODES[:binary]
400
+ payload = payload.bytes.to_a
401
+ end
402
+
403
+ if payload
404
+ emit(:message, MessageEvent.new(payload))
405
+ else
406
+ fail(:encoding_error, 'Could not decode a text frame as UTF-8')
407
+ end
408
+ rescue ::WebSocket::Extensions::ExtensionError => error
409
+ fail(:extension_error, error.message)
410
+ end
411
+ end
412
+
413
+ end
414
+ end