websocket-driver-kontena 0.6.5

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