ed3-precompiled_websocket-driver 0.8.0-arm64-darwin
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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +169 -0
- data/LICENSE.md +12 -0
- data/README.md +394 -0
- data/ext/websocket-driver/WebsocketMaskService.java +57 -0
- data/ext/websocket-driver/extconf.rb +4 -0
- data/ext/websocket-driver/websocket_mask.c +32 -0
- data/lib/3.0/websocket_mask.bundle +0 -0
- data/lib/3.1/websocket_mask.bundle +0 -0
- data/lib/3.2/websocket_mask.bundle +0 -0
- data/lib/3.3/websocket_mask.bundle +0 -0
- data/lib/3.4/websocket_mask.bundle +0 -0
- data/lib/websocket/driver/client.rb +139 -0
- data/lib/websocket/driver/draft75.rb +102 -0
- data/lib/websocket/driver/draft76.rb +99 -0
- data/lib/websocket/driver/event_emitter.rb +54 -0
- data/lib/websocket/driver/headers.rb +45 -0
- data/lib/websocket/driver/hybi/frame.rb +20 -0
- data/lib/websocket/driver/hybi/message.rb +31 -0
- data/lib/websocket/driver/hybi.rb +426 -0
- data/lib/websocket/driver/proxy.rb +66 -0
- data/lib/websocket/driver/server.rb +80 -0
- data/lib/websocket/driver/stream_reader.rb +55 -0
- data/lib/websocket/driver.rb +248 -0
- data/lib/websocket/http/headers.rb +112 -0
- data/lib/websocket/http/request.rb +45 -0
- data/lib/websocket/http/response.rb +29 -0
- data/lib/websocket/http.rb +15 -0
- data/lib/websocket/mask.rb +14 -0
- data/lib/websocket/websocket_mask.rb +9 -0
- metadata +160 -0
@@ -0,0 +1,139 @@
|
|
1
|
+
module WebSocket
|
2
|
+
class Driver
|
3
|
+
|
4
|
+
class Client < Hybi
|
5
|
+
VALID_SCHEMES = %w[ws wss]
|
6
|
+
|
7
|
+
def self.generate_key
|
8
|
+
Base64.strict_encode64(SecureRandom.random_bytes(16))
|
9
|
+
end
|
10
|
+
|
11
|
+
attr_reader :status, :headers
|
12
|
+
|
13
|
+
def initialize(socket, options = {})
|
14
|
+
super
|
15
|
+
|
16
|
+
@ready_state = -1
|
17
|
+
@key = Client.generate_key
|
18
|
+
@accept = Hybi.generate_accept(@key)
|
19
|
+
@http = HTTP::Response.new
|
20
|
+
|
21
|
+
uri = URI.parse(@socket.url)
|
22
|
+
unless VALID_SCHEMES.include?(uri.scheme)
|
23
|
+
raise URIError, "#{ socket.url } is not a valid WebSocket URL"
|
24
|
+
end
|
25
|
+
|
26
|
+
path = (uri.path == '') ? '/' : uri.path
|
27
|
+
@pathname = path + (uri.query ? '?' + uri.query : '')
|
28
|
+
|
29
|
+
@headers['Host'] = Driver.host_header(uri)
|
30
|
+
@headers['Upgrade'] = 'websocket'
|
31
|
+
@headers['Connection'] = 'Upgrade'
|
32
|
+
@headers['Sec-WebSocket-Key'] = @key
|
33
|
+
@headers['Sec-WebSocket-Version'] = VERSION
|
34
|
+
|
35
|
+
if @protocols.size > 0
|
36
|
+
@headers['Sec-WebSocket-Protocol'] = @protocols * ', '
|
37
|
+
end
|
38
|
+
|
39
|
+
if uri.user
|
40
|
+
auth = Base64.strict_encode64([uri.user, uri.password] * ':')
|
41
|
+
@headers['Authorization'] = 'Basic ' + auth
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def version
|
46
|
+
"hybi-#{ VERSION }"
|
47
|
+
end
|
48
|
+
|
49
|
+
def proxy(origin, options = {})
|
50
|
+
Proxy.new(self, origin, options)
|
51
|
+
end
|
52
|
+
|
53
|
+
def start
|
54
|
+
return false unless @ready_state == -1
|
55
|
+
@socket.write(handshake_request)
|
56
|
+
@ready_state = 0
|
57
|
+
true
|
58
|
+
end
|
59
|
+
|
60
|
+
def parse(chunk)
|
61
|
+
return if @ready_state == 3
|
62
|
+
return super if @ready_state > 0
|
63
|
+
|
64
|
+
@http.parse(chunk)
|
65
|
+
return fail_handshake('Invalid HTTP response') if @http.error?
|
66
|
+
return unless @http.complete?
|
67
|
+
|
68
|
+
validate_handshake
|
69
|
+
return if @ready_state == 3
|
70
|
+
|
71
|
+
open
|
72
|
+
parse(@http.body)
|
73
|
+
end
|
74
|
+
|
75
|
+
private
|
76
|
+
|
77
|
+
def handshake_request
|
78
|
+
extensions = @extensions.generate_offer
|
79
|
+
@headers['Sec-WebSocket-Extensions'] = extensions if extensions
|
80
|
+
|
81
|
+
start = "GET #{ @pathname } HTTP/1.1"
|
82
|
+
headers = [start, @headers.to_s, '']
|
83
|
+
headers.join("\r\n")
|
84
|
+
end
|
85
|
+
|
86
|
+
def fail_handshake(message)
|
87
|
+
message = "Error during WebSocket handshake: #{ message }"
|
88
|
+
@ready_state = 3
|
89
|
+
emit(:error, ProtocolError.new(message))
|
90
|
+
emit(:close, CloseEvent.new(ERRORS[:protocol_error], message))
|
91
|
+
end
|
92
|
+
|
93
|
+
def validate_handshake
|
94
|
+
@status = @http.code
|
95
|
+
@headers = Headers.new(@http.headers)
|
96
|
+
|
97
|
+
unless @http.code == 101
|
98
|
+
return fail_handshake("Unexpected response code: #{ @http.code }")
|
99
|
+
end
|
100
|
+
|
101
|
+
upgrade = @http['Upgrade'] || ''
|
102
|
+
connection = @http['Connection'] || ''
|
103
|
+
accept = @http['Sec-WebSocket-Accept'] || ''
|
104
|
+
protocol = @http['Sec-WebSocket-Protocol'] || ''
|
105
|
+
|
106
|
+
if upgrade == ''
|
107
|
+
return fail_handshake("'Upgrade' header is missing")
|
108
|
+
elsif upgrade.downcase != 'websocket'
|
109
|
+
return fail_handshake("'Upgrade' header value is not 'WebSocket'")
|
110
|
+
end
|
111
|
+
|
112
|
+
if connection == ''
|
113
|
+
return fail_handshake("'Connection' header is missing")
|
114
|
+
elsif connection.downcase != 'upgrade'
|
115
|
+
return fail_handshake("'Connection' header value is not 'Upgrade'")
|
116
|
+
end
|
117
|
+
|
118
|
+
unless accept == @accept
|
119
|
+
return fail_handshake('Sec-WebSocket-Accept mismatch')
|
120
|
+
end
|
121
|
+
|
122
|
+
unless protocol == ''
|
123
|
+
if @protocols.include?(protocol)
|
124
|
+
@protocol = protocol
|
125
|
+
else
|
126
|
+
return fail_handshake('Sec-WebSocket-Protocol mismatch')
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
begin
|
131
|
+
@extensions.activate(@headers['Sec-WebSocket-Extensions'])
|
132
|
+
rescue ::WebSocket::Extensions::ExtensionError => error
|
133
|
+
return fail_handshake(error.message)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
end
|
139
|
+
end
|
@@ -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, Encoding::UTF_8)))
|
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,99 @@
|
|
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'] || StringIO.new('')).read
|
10
|
+
input = input.dup if input.frozen?
|
11
|
+
@stage = -1
|
12
|
+
@body = input.force_encoding(Encoding::BINARY)
|
13
|
+
|
14
|
+
@headers.clear
|
15
|
+
@headers['Upgrade'] = 'WebSocket'
|
16
|
+
@headers['Connection'] = 'Upgrade'
|
17
|
+
@headers['Sec-WebSocket-Origin'] = @socket.env['HTTP_ORIGIN']
|
18
|
+
@headers['Sec-WebSocket-Location'] = @socket.url
|
19
|
+
end
|
20
|
+
|
21
|
+
def version
|
22
|
+
'hixie-76'
|
23
|
+
end
|
24
|
+
|
25
|
+
def start
|
26
|
+
return false unless super
|
27
|
+
send_handshake_body
|
28
|
+
true
|
29
|
+
end
|
30
|
+
|
31
|
+
def close(reason = nil, code = nil)
|
32
|
+
return false if @ready_state == 3
|
33
|
+
@socket.write([0xFF, 0x00].pack('C*')) if @ready_state == 1
|
34
|
+
@ready_state = 3
|
35
|
+
emit(:close, CloseEvent.new(nil, nil))
|
36
|
+
true
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def handshake_response
|
42
|
+
env = @socket.env
|
43
|
+
key1 = env['HTTP_SEC_WEBSOCKET_KEY1']
|
44
|
+
key2 = env['HTTP_SEC_WEBSOCKET_KEY2']
|
45
|
+
|
46
|
+
raise ProtocolError.new('Missing required header: Sec-WebSocket-Key1') unless key1
|
47
|
+
raise ProtocolError.new('Missing required header: Sec-WebSocket-Key2') unless key2
|
48
|
+
|
49
|
+
number1 = number_from_key(key1)
|
50
|
+
spaces1 = spaces_in_key(key1)
|
51
|
+
|
52
|
+
number2 = number_from_key(key2)
|
53
|
+
spaces2 = spaces_in_key(key2)
|
54
|
+
|
55
|
+
if number1 % spaces1 != 0 or number2 % spaces2 != 0
|
56
|
+
raise ProtocolError.new('Client sent invalid Sec-WebSocket-Key headers')
|
57
|
+
end
|
58
|
+
|
59
|
+
@key_values = [number1 / spaces1, number2 / spaces2]
|
60
|
+
|
61
|
+
start = 'HTTP/1.1 101 WebSocket Protocol Handshake'
|
62
|
+
headers = [start, @headers.to_s, '']
|
63
|
+
headers.join("\r\n")
|
64
|
+
end
|
65
|
+
|
66
|
+
def handshake_signature
|
67
|
+
return nil unless @body.bytesize >= BODY_SIZE
|
68
|
+
|
69
|
+
head = @body[0...BODY_SIZE]
|
70
|
+
Digest::MD5.digest((@key_values + [head]).pack('N2A*'))
|
71
|
+
end
|
72
|
+
|
73
|
+
def send_handshake_body
|
74
|
+
return unless signature = handshake_signature
|
75
|
+
@socket.write(signature)
|
76
|
+
@stage = 0
|
77
|
+
open
|
78
|
+
parse(@body[BODY_SIZE..-1]) if @body.bytesize > BODY_SIZE
|
79
|
+
end
|
80
|
+
|
81
|
+
def parse_leading_byte(octet)
|
82
|
+
return super unless octet == 0xFF
|
83
|
+
@closing = true
|
84
|
+
@length = 0
|
85
|
+
@stage = 1
|
86
|
+
end
|
87
|
+
|
88
|
+
def number_from_key(key)
|
89
|
+
number = key.scan(/[0-9]/).join('')
|
90
|
+
number == '' ? Float::NAN : number.to_i(10)
|
91
|
+
end
|
92
|
+
|
93
|
+
def spaces_in_key(key)
|
94
|
+
key.scan(/ /).size
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
99
|
+
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,31 @@
|
|
1
|
+
module WebSocket
|
2
|
+
class Driver
|
3
|
+
class Hybi
|
4
|
+
|
5
|
+
class Message
|
6
|
+
attr_accessor :rsv1,
|
7
|
+
:rsv2,
|
8
|
+
:rsv3,
|
9
|
+
:opcode,
|
10
|
+
:data
|
11
|
+
|
12
|
+
def initialize
|
13
|
+
@rsv1 = false
|
14
|
+
@rsv2 = false
|
15
|
+
@rsv3 = false
|
16
|
+
@opcode = nil
|
17
|
+
@data = String.new('').force_encoding(Encoding::BINARY)
|
18
|
+
end
|
19
|
+
|
20
|
+
def <<(frame)
|
21
|
+
@rsv1 ||= frame.rsv1
|
22
|
+
@rsv2 ||= frame.rsv2
|
23
|
+
@rsv3 ||= frame.rsv3
|
24
|
+
@opcode ||= frame.opcode
|
25
|
+
@data << frame.payload
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|