rjr 0.9.0 → 0.11.7

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,589 +0,0 @@
1
- # Origincally copied from here:
2
- # https://raw.github.com/gimite/web-socket-ruby/master/lib/web_socket.rb
3
- # @ git commit 30977897642e017532dd0fe11d1e9ebe96c870f1
4
- # with original header text:
5
- #
6
- # Copyright: Hiroshi Ichikawa <http://gimite.net/en/>
7
- # Lincense: New BSD Lincense
8
- # Reference: http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-75
9
- # Reference: http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76
10
- # Reference: http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-07
11
- # Reference: http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-10
12
-
13
- require "base64"
14
- require "socket"
15
- require "uri"
16
- require "digest/md5"
17
- require "digest/sha1"
18
- require "openssl"
19
- require "stringio"
20
-
21
-
22
- class WebSocket
23
-
24
- class << self
25
-
26
- attr_accessor(:debug)
27
-
28
- end
29
-
30
- class Error < RuntimeError
31
-
32
- end
33
-
34
- WEB_SOCKET_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
35
- OPCODE_CONTINUATION = 0x00
36
- OPCODE_TEXT = 0x01
37
- OPCODE_BINARY = 0x02
38
- OPCODE_CLOSE = 0x08
39
- OPCODE_PING = 0x09
40
- OPCODE_PONG = 0x0a
41
-
42
- def initialize(arg, params = {})
43
- if params[:server] # server
44
-
45
- @server = params[:server]
46
- @socket = arg
47
- line = gets().chomp()
48
- if !(line =~ /\AGET (\S+) HTTP\/1.1\z/n)
49
- raise(WebSocket::Error, "invalid request: #{line}")
50
- end
51
- @path = $1
52
- read_header()
53
- if @header["sec-websocket-version"]
54
- @web_socket_version = @header["sec-websocket-version"]
55
- @key3 = nil
56
- elsif @header["sec-websocket-key1"] && @header["sec-websocket-key2"]
57
- @web_socket_version = "hixie-76"
58
- @key3 = read(8)
59
- else
60
- @web_socket_version = "hixie-75"
61
- @key3 = nil
62
- end
63
- if !@server.accepted_origin?(self.origin)
64
- raise(WebSocket::Error,
65
- ("Unaccepted origin: %s (server.accepted_domains = %p)\n\n" +
66
- "To accept this origin, write e.g. \n" +
67
- " WebSocketServer.new(..., :accepted_domains => [%p]), or\n" +
68
- " WebSocketServer.new(..., :accepted_domains => [\"*\"])\n") %
69
- [self.origin, @server.accepted_domains, @server.origin_to_domain(self.origin)])
70
- end
71
- @handshaked = false
72
-
73
- else # client
74
-
75
- @web_socket_version = "hixie-76"
76
- uri = arg.is_a?(String) ? URI.parse(arg) : arg
77
-
78
- if uri.scheme == "ws"
79
- default_port = 80
80
- elsif uri.scheme = "wss"
81
- default_port = 443
82
- else
83
- raise(WebSocket::Error, "unsupported scheme: #{uri.scheme}")
84
- end
85
-
86
- @path = (uri.path.empty? ? "/" : uri.path) + (uri.query ? "?" + uri.query : "")
87
- host = uri.host + ((!uri.port || uri.port == default_port) ? "" : ":#{uri.port}")
88
- origin = params[:origin] || "http://#{uri.host}"
89
- key1 = generate_key()
90
- key2 = generate_key()
91
- key3 = generate_key3()
92
-
93
- socket = TCPSocket.new(uri.host, uri.port || default_port)
94
-
95
- if uri.scheme == "ws"
96
- @socket = socket
97
- else
98
- @socket = ssl_handshake(socket)
99
- end
100
-
101
- write(
102
- "GET #{@path} HTTP/1.1\r\n" +
103
- "Upgrade: WebSocket\r\n" +
104
- "Connection: Upgrade\r\n" +
105
- "Host: #{host}\r\n" +
106
- "Origin: #{origin}\r\n" +
107
- "Sec-WebSocket-Key1: #{key1}\r\n" +
108
- "Sec-WebSocket-Key2: #{key2}\r\n" +
109
- "\r\n" +
110
- "#{key3}")
111
- flush()
112
-
113
- line = gets().chomp()
114
- raise(WebSocket::Error, "bad response: #{line}") if !(line =~ /\AHTTP\/1.1 101 /n)
115
- read_header()
116
- if (@header["sec-websocket-origin"] || "").downcase() != origin.downcase()
117
- raise(WebSocket::Error,
118
- "origin doesn't match: '#{@header["sec-websocket-origin"]}' != '#{origin}'")
119
- end
120
- reply_digest = read(16)
121
- expected_digest = hixie_76_security_digest(key1, key2, key3)
122
- if reply_digest != expected_digest
123
- raise(WebSocket::Error,
124
- "security digest doesn't match: %p != %p" % [reply_digest, expected_digest])
125
- end
126
- @handshaked = true
127
-
128
- end
129
- @received = []
130
- @buffer = ""
131
- @closing_started = false
132
- end
133
-
134
- attr_reader(:server, :header, :path)
135
-
136
- def handshake(status = nil, header = {})
137
- if @handshaked
138
- raise(WebSocket::Error, "handshake has already been done")
139
- end
140
- status ||= "101 Switching Protocols"
141
- def_header = {}
142
- case @web_socket_version
143
- when "hixie-75"
144
- def_header["WebSocket-Origin"] = self.origin
145
- def_header["WebSocket-Location"] = self.location
146
- extra_bytes = ""
147
- when "hixie-76"
148
- def_header["Sec-WebSocket-Origin"] = self.origin
149
- def_header["Sec-WebSocket-Location"] = self.location
150
- extra_bytes = hixie_76_security_digest(
151
- @header["Sec-WebSocket-Key1"], @header["Sec-WebSocket-Key2"], @key3)
152
- else
153
- def_header["Sec-WebSocket-Accept"] = security_digest(@header["sec-websocket-key"])
154
- extra_bytes = ""
155
- end
156
- header = def_header.merge(header)
157
- header_str = header.map(){ |k, v| "#{k}: #{v}\r\n" }.join("")
158
- # Note that Upgrade and Connection must appear in this order.
159
- write(
160
- "HTTP/1.1 #{status}\r\n" +
161
- "Upgrade: websocket\r\n" +
162
- "Connection: Upgrade\r\n" +
163
- "#{header_str}\r\n#{extra_bytes}")
164
- flush()
165
- @handshaked = true
166
- end
167
-
168
- def send(data)
169
- if !@handshaked
170
- raise(WebSocket::Error, "call WebSocket\#handshake first")
171
- end
172
- case @web_socket_version
173
- when "hixie-75", "hixie-76"
174
- data = force_encoding(data.dup(), "ASCII-8BIT")
175
- write("\x00#{data}\xff")
176
- flush()
177
- else
178
- send_frame(OPCODE_TEXT, data, !@server)
179
- end
180
- end
181
-
182
- def receive()
183
- if !@handshaked
184
- raise(WebSocket::Error, "call WebSocket\#handshake first")
185
- end
186
- case @web_socket_version
187
-
188
- when "hixie-75", "hixie-76"
189
- packet = gets("\xff")
190
- return nil if !packet
191
- if packet =~ /\A\x00(.*)\xff\z/nm
192
- return force_encoding($1, "UTF-8")
193
- elsif packet == "\xff" && read(1) == "\x00" # closing
194
- close(1005, "", :peer)
195
- return nil
196
- else
197
- raise(WebSocket::Error, "input must be either '\\x00...\\xff' or '\\xff\\x00'")
198
- end
199
-
200
- else
201
- begin
202
- bytes = read(2).unpack("C*")
203
- fin = (bytes[0] & 0x80) != 0
204
- opcode = bytes[0] & 0x0f
205
- mask = (bytes[1] & 0x80) != 0
206
- plength = bytes[1] & 0x7f
207
- if plength == 126
208
- bytes = read(2)
209
- plength = bytes.unpack("n")[0]
210
- elsif plength == 127
211
- bytes = read(8)
212
- (high, low) = bytes.unpack("NN")
213
- plength = high * (2 ** 32) + low
214
- end
215
- if @server && !mask
216
- # Masking is required.
217
- @socket.close()
218
- raise(WebSocket::Error, "received unmasked data")
219
- end
220
- mask_key = mask ? read(4).unpack("C*") : nil
221
- payload = read(plength)
222
- payload = apply_mask(payload, mask_key) if mask
223
- case opcode
224
- when OPCODE_TEXT
225
- return force_encoding(payload, "UTF-8")
226
- when OPCODE_BINARY
227
- raise(WebSocket::Error, "received binary data, which is not supported")
228
- when OPCODE_CLOSE
229
- close(1005, "", :peer)
230
- return nil
231
- when OPCODE_PING
232
- raise(WebSocket::Error, "received ping, which is not supported")
233
- when OPCODE_PONG
234
- else
235
- raise(WebSocket::Error, "received unknown opcode: %d" % opcode)
236
- end
237
- rescue EOFError
238
- return nil
239
- end
240
-
241
- end
242
- end
243
-
244
- def tcp_socket
245
- return @socket
246
- end
247
-
248
- def host
249
- return @header["host"]
250
- end
251
-
252
- def origin
253
- case @web_socket_version
254
- when "7", "8"
255
- name = "sec-websocket-origin"
256
- else
257
- name = "origin"
258
- end
259
- if @header[name]
260
- return @header[name]
261
- else
262
- raise(WebSocket::Error, "%s header is missing" % name)
263
- end
264
- end
265
-
266
- def location
267
- return "ws://#{self.host}#{@path}"
268
- end
269
-
270
- # Does closing handshake.
271
- def close(code = 1005, reason = "", origin = :self)
272
- if !@closing_started
273
- case @web_socket_version
274
- when "hixie-75", "hixie-76"
275
- write("\xff\x00")
276
- else
277
- if code == 1005
278
- payload = ""
279
- else
280
- payload = [code].pack("n") + force_encoding(reason.dup(), "ASCII-8BIT")
281
- end
282
- send_frame(OPCODE_CLOSE, payload, false)
283
- end
284
- end
285
- @socket.close() if origin == :peer
286
- @closing_started = true
287
- end
288
-
289
- def close_socket()
290
- @socket.close()
291
- end
292
-
293
- private
294
-
295
- NOISE_CHARS = ("\x21".."\x2f").to_a() + ("\x3a".."\x7e").to_a()
296
-
297
- def read_header()
298
- @header = {}
299
- while line = gets()
300
- line = line.chomp()
301
- break if line.empty?
302
- if !(line =~ /\A(\S+): (.*)\z/n)
303
- raise(WebSocket::Error, "invalid request: #{line}")
304
- end
305
- @header[$1] = $2
306
- @header[$1.downcase()] = $2
307
- end
308
- if !@header["upgrade"]
309
- raise(WebSocket::Error, "Upgrade header is missing")
310
- end
311
- if !(@header["upgrade"] =~ /\AWebSocket\z/i)
312
- raise(WebSocket::Error, "invalid Upgrade: " + @header["upgrade"])
313
- end
314
- if !@header["connection"]
315
- raise(WebSocket::Error, "Connection header is missing")
316
- end
317
- if @header["connection"].split(/,/).grep(/\A\s*Upgrade\s*\z/i).empty?
318
- raise(WebSocket::Error, "invalid Connection: " + @header["connection"])
319
- end
320
- end
321
-
322
- def send_frame(opcode, payload, mask)
323
- payload = force_encoding(payload.dup(), "ASCII-8BIT")
324
- # Setting StringIO's encoding to ASCII-8BIT.
325
- buffer = StringIO.new(force_encoding("", "ASCII-8BIT"))
326
- write_byte(buffer, 0x80 | opcode)
327
- masked_byte = mask ? 0x80 : 0x00
328
- if payload.bytesize <= 125
329
- write_byte(buffer, masked_byte | payload.bytesize)
330
- elsif payload.bytesize < 2 ** 16
331
- write_byte(buffer, masked_byte | 126)
332
- buffer.write([payload.bytesize].pack("n"))
333
- else
334
- write_byte(buffer, masked_byte | 127)
335
- buffer.write([payload.bytesize / (2 ** 32), payload.bytesize % (2 ** 32)].pack("NN"))
336
- end
337
- if mask
338
- mask_key = Array.new(4){ rand(256) }
339
- buffer.write(mask_key.pack("C*"))
340
- payload = apply_mask(payload, mask_key)
341
- end
342
- buffer.write(payload)
343
- write(buffer.string)
344
- end
345
-
346
- def gets(rs = $/)
347
- line = @socket.gets(rs)
348
- $stderr.printf("recv> %p\n", line) if WebSocket.debug
349
- return line
350
- end
351
-
352
- def read(num_bytes)
353
- str = @socket.read(num_bytes)
354
- $stderr.printf("recv> %p\n", str) if WebSocket.debug
355
- if str && str.bytesize == num_bytes
356
- return str
357
- else
358
- raise(EOFError)
359
- end
360
- end
361
-
362
- def write(data)
363
- if WebSocket.debug
364
- data.scan(/\G(.*?(\n|\z))/n) do
365
- $stderr.printf("send> %p\n", $&) if !$&.empty?
366
- end
367
- end
368
- @socket.write(data)
369
- end
370
-
371
- def flush()
372
- @socket.flush()
373
- end
374
-
375
- def write_byte(buffer, byte)
376
- buffer.write([byte].pack("C"))
377
- end
378
-
379
- def security_digest(key)
380
- return Base64.encode64(Digest::SHA1.digest(key + WEB_SOCKET_GUID)).gsub(/\n/, "")
381
- end
382
-
383
- def hixie_76_security_digest(key1, key2, key3)
384
- bytes1 = websocket_key_to_bytes(key1)
385
- bytes2 = websocket_key_to_bytes(key2)
386
- return Digest::MD5.digest(bytes1 + bytes2 + key3)
387
- end
388
-
389
- def apply_mask(payload, mask_key)
390
- orig_bytes = payload.unpack("C*")
391
- new_bytes = []
392
- orig_bytes.each_with_index() do |b, i|
393
- new_bytes.push(b ^ mask_key[i % 4])
394
- end
395
- return new_bytes.pack("C*")
396
- end
397
-
398
- def generate_key()
399
- spaces = 1 + rand(12)
400
- max = 0xffffffff / spaces
401
- number = rand(max + 1)
402
- key = (number * spaces).to_s()
403
- (1 + rand(12)).times() do
404
- char = NOISE_CHARS[rand(NOISE_CHARS.size)]
405
- pos = rand(key.size + 1)
406
- key[pos...pos] = char
407
- end
408
- spaces.times() do
409
- pos = 1 + rand(key.size - 1)
410
- key[pos...pos] = " "
411
- end
412
- return key
413
- end
414
-
415
- def generate_key3()
416
- return [rand(0x100000000)].pack("N") + [rand(0x100000000)].pack("N")
417
- end
418
-
419
- def websocket_key_to_bytes(key)
420
- num = key.gsub(/[^\d]/n, "").to_i() / key.scan(/ /).size
421
- return [num].pack("N")
422
- end
423
-
424
- def force_encoding(str, encoding)
425
- if str.respond_to?(:force_encoding)
426
- return str.force_encoding(encoding)
427
- else
428
- return str
429
- end
430
- end
431
-
432
- def ssl_handshake(socket)
433
- ssl_context = OpenSSL::SSL::SSLContext.new()
434
- ssl_socket = OpenSSL::SSL::SSLSocket.new(socket, ssl_context)
435
- ssl_socket.sync_close = true
436
- ssl_socket.connect()
437
- return ssl_socket
438
- end
439
-
440
- end
441
-
442
-
443
- class WebSocketServer
444
-
445
- def initialize(params_or_uri, params = nil)
446
- if params
447
- uri = params_or_uri.is_a?(String) ? URI.parse(params_or_uri) : params_or_uri
448
- params[:port] ||= uri.port
449
- params[:accepted_domains] ||= [uri.host]
450
- else
451
- params = params_or_uri
452
- end
453
- @port = params[:port] || 80
454
- @accepted_domains = params[:accepted_domains]
455
- if !@accepted_domains
456
- raise(ArgumentError, "params[:accepted_domains] is required")
457
- end
458
- if params[:host]
459
- @tcp_server = TCPServer.open(params[:host], @port)
460
- else
461
- @tcp_server = TCPServer.open(@port)
462
- end
463
- end
464
-
465
- attr_reader(:tcp_server, :port, :accepted_domains)
466
-
467
- def run(&block)
468
- while true
469
- Thread.start(accept()) do |s|
470
- begin
471
- ws = create_web_socket(s)
472
- yield(ws) if ws
473
- rescue => ex
474
- print_backtrace(ex)
475
- ensure
476
- begin
477
- ws.close_socket() if ws
478
- rescue
479
- end
480
- end
481
- end
482
- end
483
- end
484
-
485
- def accept()
486
- return @tcp_server.accept()
487
- end
488
-
489
- def accepted_origin?(origin)
490
- domain = origin_to_domain(origin)
491
- return @accepted_domains.any?(){ |d| File.fnmatch(d, domain) }
492
- end
493
-
494
- def origin_to_domain(origin)
495
- if origin == "null" || origin == "file://" # local file
496
- return "null"
497
- else
498
- return URI.parse(origin).host
499
- end
500
- end
501
-
502
- def create_web_socket(socket)
503
- ch = socket.getc()
504
- if ch == ?<
505
- # This is Flash socket policy file request, not an actual Web Socket connection.
506
- send_flash_socket_policy_file(socket)
507
- return nil
508
- else
509
- socket.ungetc(ch)
510
- return WebSocket.new(socket, :server => self)
511
- end
512
- end
513
-
514
- private
515
-
516
- def print_backtrace(ex)
517
- $stderr.printf("%s: %s (%p)\n", ex.backtrace[0], ex.message, ex.class)
518
- for s in ex.backtrace[1..-1]
519
- $stderr.printf(" %s\n", s)
520
- end
521
- end
522
-
523
- # Handles Flash socket policy file request sent when web-socket-js is used:
524
- # http://github.com/gimite/web-socket-js/tree/master
525
- def send_flash_socket_policy_file(socket)
526
- socket.puts('<?xml version="1.0"?>')
527
- socket.puts('<!DOCTYPE cross-domain-policy SYSTEM ' +
528
- '"http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd">')
529
- socket.puts('<cross-domain-policy>')
530
- for domain in @accepted_domains
531
- next if domain == "file://"
532
- socket.puts("<allow-access-from domain=\"#{domain}\" to-ports=\"#{@port}\"/>")
533
- end
534
- socket.puts('</cross-domain-policy>')
535
- socket.close()
536
- end
537
-
538
- end
539
-
540
-
541
- if __FILE__ == $0
542
- Thread.abort_on_exception = true
543
-
544
- if ARGV[0] == "server" && ARGV.size == 3
545
-
546
- server = WebSocketServer.new(
547
- :accepted_domains => [ARGV[1]],
548
- :port => ARGV[2].to_i())
549
- puts("Server is running at port %d" % server.port)
550
- server.run() do |ws|
551
- puts("Connection accepted")
552
- puts("Path: #{ws.path}, Origin: #{ws.origin}")
553
- if ws.path == "/"
554
- ws.handshake()
555
- while data = ws.receive()
556
- printf("Received: %p\n", data)
557
- ws.send(data)
558
- printf("Sent: %p\n", data)
559
- end
560
- else
561
- ws.handshake("404 Not Found")
562
- end
563
- puts("Connection closed")
564
- end
565
-
566
- elsif ARGV[0] == "client" && ARGV.size == 2
567
-
568
- client = WebSocket.new(ARGV[1])
569
- puts("Connected")
570
- Thread.new() do
571
- while data = client.receive()
572
- printf("Received: %p\n", data)
573
- end
574
- end
575
- $stdin.each_line() do |line|
576
- data = line.chomp()
577
- client.send(data)
578
- printf("Sent: %p\n", data)
579
- end
580
-
581
- else
582
-
583
- $stderr.puts("Usage:")
584
- $stderr.puts(" ruby web_socket.rb server ACCEPTED_DOMAIN PORT")
585
- $stderr.puts(" ruby web_socket.rb client ws://HOST:PORT/")
586
- exit(1)
587
-
588
- end
589
- end