websocket-rack 0.1.4 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.md +11 -0
- data/README.md +42 -24
- data/Rakefile +1 -1
- data/example/example.ru +5 -5
- data/lib/rack/websocket/application.rb +31 -60
- data/lib/rack/websocket/extensions/common.rb +61 -0
- data/lib/rack/websocket/extensions/thin/connection.rb +2 -50
- data/lib/rack/websocket/extensions/thin.rb +3 -3
- data/lib/rack/websocket/extensions.rb +14 -0
- data/lib/rack/websocket/handler/base.rb +41 -0
- data/lib/rack/websocket/handler/stub.rb +14 -0
- data/lib/rack/websocket/handler/thin/connection.rb +89 -0
- data/lib/rack/websocket/handler/thin/handler_factory.rb +56 -0
- data/lib/rack/websocket/handler/thin.rb +61 -0
- data/lib/rack/websocket/handler.rb +15 -38
- data/lib/rack/websocket/version.rb +5 -0
- data/lib/rack/websocket.rb +5 -31
- data/spec/spec_helper.rb +18 -0
- data/spec/support/all_drafts.rb +43 -0
- data/spec/support/all_handlers.rb +31 -0
- data/spec/support/requests.rb +100 -0
- data/spec/thin_spec.rb +46 -0
- data/websocket-rack.gemspec +4 -4
- metadata +41 -47
- data/lib/rack/websocket/connection.rb +0 -112
- data/lib/rack/websocket/debugger.rb +0 -17
- data/lib/rack/websocket/framing03.rb +0 -178
- data/lib/rack/websocket/framing76.rb +0 -115
- data/lib/rack/websocket/handler03.rb +0 -14
- data/lib/rack/websocket/handler75.rb +0 -8
- data/lib/rack/websocket/handler76.rb +0 -11
- data/lib/rack/websocket/handler_factory.rb +0 -61
- data/lib/rack/websocket/handshake75.rb +0 -21
- data/lib/rack/websocket/handshake76.rb +0 -71
- data/spec/helper.rb +0 -44
- data/spec/integration/draft03_spec.rb +0 -252
- data/spec/integration/draft76_spec.rb +0 -212
- data/spec/unit/framing_spec.rb +0 -108
- data/spec/unit/handler_spec.rb +0 -136
- data/spec/websocket_spec.rb +0 -210
@@ -1,112 +0,0 @@
|
|
1
|
-
require 'addressable/uri'
|
2
|
-
|
3
|
-
module Rack
|
4
|
-
module WebSocket
|
5
|
-
class Connection
|
6
|
-
include Debugger
|
7
|
-
|
8
|
-
def initialize(app, socket, options = {})
|
9
|
-
@app = app
|
10
|
-
@socket = socket
|
11
|
-
@options = options
|
12
|
-
@debug = options[:debug] || false
|
13
|
-
|
14
|
-
socket.websocket = self
|
15
|
-
socket.comm_inactivity_timeout = 0
|
16
|
-
|
17
|
-
if socket.comm_inactivity_timeout != 0
|
18
|
-
puts "WARNING: You are using old EventMachine version. " +
|
19
|
-
"Please consider updating to EM version >= 1.0.0 " +
|
20
|
-
"or running Thin using thin-websocket."
|
21
|
-
end
|
22
|
-
|
23
|
-
debug [:initialize]
|
24
|
-
end
|
25
|
-
|
26
|
-
def trigger_on_message(msg)
|
27
|
-
@app.on_message(msg)
|
28
|
-
end
|
29
|
-
def trigger_on_open
|
30
|
-
@app.on_open
|
31
|
-
end
|
32
|
-
def trigger_on_close
|
33
|
-
@app.on_close
|
34
|
-
end
|
35
|
-
def trigger_on_error(error)
|
36
|
-
@app.on_error(error)
|
37
|
-
end
|
38
|
-
|
39
|
-
def method_missing(sym, *args, &block)
|
40
|
-
@socket.send sym, *args, &block
|
41
|
-
end
|
42
|
-
|
43
|
-
# Use this method to close the websocket connection cleanly
|
44
|
-
# This sends a close frame and waits for acknowlegement before closing
|
45
|
-
# the connection
|
46
|
-
def close_websocket
|
47
|
-
if @handler
|
48
|
-
@handler.close_websocket
|
49
|
-
else
|
50
|
-
# The handshake hasn't completed - should be safe to terminate
|
51
|
-
close_connection
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
def receive_data(data)
|
56
|
-
debug [:receive_data, data]
|
57
|
-
|
58
|
-
@handler.receive_data(data)
|
59
|
-
end
|
60
|
-
|
61
|
-
def unbind
|
62
|
-
debug [:unbind, :connection]
|
63
|
-
|
64
|
-
@handler.unbind if @handler
|
65
|
-
end
|
66
|
-
|
67
|
-
def dispatch(data)
|
68
|
-
debug [:inbound_headers, data.inspect] if @debug
|
69
|
-
@handler = HandlerFactory.build(self, data, @debug)
|
70
|
-
unless @handler
|
71
|
-
# The whole header has not been received yet.
|
72
|
-
return false
|
73
|
-
end
|
74
|
-
@handler.run
|
75
|
-
return true
|
76
|
-
rescue => e
|
77
|
-
debug [:error, e]
|
78
|
-
process_bad_request(e)
|
79
|
-
return false
|
80
|
-
end
|
81
|
-
|
82
|
-
def process_bad_request(reason)
|
83
|
-
trigger_on_error(reason)
|
84
|
-
send_data "HTTP/1.1 400 Bad request\r\n\r\n"
|
85
|
-
close_connection_after_writing
|
86
|
-
end
|
87
|
-
|
88
|
-
def send(data)
|
89
|
-
debug [:send, data]
|
90
|
-
|
91
|
-
if @handler
|
92
|
-
@handler.send_text_frame(data)
|
93
|
-
else
|
94
|
-
raise WebSocketError, "Cannot send data before onopen callback"
|
95
|
-
end
|
96
|
-
end
|
97
|
-
|
98
|
-
def close_with_error(message)
|
99
|
-
trigger_on_error(message)
|
100
|
-
close_connection_after_writing
|
101
|
-
end
|
102
|
-
|
103
|
-
def request
|
104
|
-
@handler ? @handler.request : {}
|
105
|
-
end
|
106
|
-
|
107
|
-
def state
|
108
|
-
@handler ? @handler.state : :handshake
|
109
|
-
end
|
110
|
-
end
|
111
|
-
end
|
112
|
-
end
|
@@ -1,178 +0,0 @@
|
|
1
|
-
# encoding: BINARY
|
2
|
-
|
3
|
-
module Rack
|
4
|
-
module WebSocket
|
5
|
-
module Framing03
|
6
|
-
|
7
|
-
def initialize_framing
|
8
|
-
@data = ''
|
9
|
-
@application_data_buffer = '' # Used for MORE frames
|
10
|
-
end
|
11
|
-
|
12
|
-
def process_data(newdata)
|
13
|
-
error = false
|
14
|
-
|
15
|
-
while !error && @data.size > 1
|
16
|
-
pointer = 0
|
17
|
-
|
18
|
-
more = (@data.getbyte(pointer) & 0b10000000) == 0b10000000
|
19
|
-
# Ignoring rsv1-3 for now
|
20
|
-
opcode = @data.getbyte(0) & 0b00001111
|
21
|
-
pointer += 1
|
22
|
-
|
23
|
-
# Ignoring rsv4
|
24
|
-
length = @data.getbyte(pointer) & 0b01111111
|
25
|
-
pointer += 1
|
26
|
-
|
27
|
-
payload_length = case length
|
28
|
-
when 127 # Length defined by 8 bytes
|
29
|
-
# Check buffer size
|
30
|
-
if @data.getbyte(pointer+8-1) == nil
|
31
|
-
debug [:buffer_incomplete, @data.inspect]
|
32
|
-
error = true
|
33
|
-
next
|
34
|
-
end
|
35
|
-
|
36
|
-
# Only using the last 4 bytes for now, till I work out how to
|
37
|
-
# unpack 8 bytes. I'm sure 4GB frames will do for now :)
|
38
|
-
l = @data[(pointer+4)..(pointer+7)].unpack('N').first
|
39
|
-
pointer += 8
|
40
|
-
l
|
41
|
-
when 126 # Length defined by 2 bytes
|
42
|
-
# Check buffer size
|
43
|
-
if @data.getbyte(pointer+2-1) == nil
|
44
|
-
debug [:buffer_incomplete, @data.inspect]
|
45
|
-
error = true
|
46
|
-
next
|
47
|
-
end
|
48
|
-
|
49
|
-
l = @data[pointer..(pointer+1)].unpack('n').first
|
50
|
-
pointer += 2
|
51
|
-
l
|
52
|
-
else
|
53
|
-
length
|
54
|
-
end
|
55
|
-
|
56
|
-
# Check buffer size
|
57
|
-
if @data.getbyte(pointer+payload_length-1) == nil
|
58
|
-
debug [:buffer_incomplete, @data.inspect]
|
59
|
-
error = true
|
60
|
-
next
|
61
|
-
end
|
62
|
-
|
63
|
-
# Throw away data up to pointer
|
64
|
-
@data.slice!(0...pointer)
|
65
|
-
|
66
|
-
# Read application data
|
67
|
-
application_data = @data.slice!(0...payload_length)
|
68
|
-
|
69
|
-
frame_type = opcode_to_type(opcode)
|
70
|
-
|
71
|
-
if frame_type == :continuation && !@frame_type
|
72
|
-
raise WebSocketError, 'Continuation frame not expected'
|
73
|
-
end
|
74
|
-
|
75
|
-
if more
|
76
|
-
debug [:moreframe, frame_type, application_data]
|
77
|
-
@application_data_buffer << application_data
|
78
|
-
@frame_type = frame_type
|
79
|
-
else
|
80
|
-
# Message is complete
|
81
|
-
if frame_type == :continuation
|
82
|
-
@application_data_buffer << application_data
|
83
|
-
message(@frame_type, '', @application_data_buffer)
|
84
|
-
@application_data_buffer = ''
|
85
|
-
@frame_type = nil
|
86
|
-
else
|
87
|
-
message(frame_type, '', application_data)
|
88
|
-
end
|
89
|
-
end
|
90
|
-
end # end while
|
91
|
-
end
|
92
|
-
|
93
|
-
def send_frame(frame_type, application_data)
|
94
|
-
if @state == :closing && data_frame?(frame_type)
|
95
|
-
raise WebSocketError, "Cannot send data frame since connection is closing"
|
96
|
-
end
|
97
|
-
|
98
|
-
frame = ''
|
99
|
-
|
100
|
-
opcode = type_to_opcode(frame_type)
|
101
|
-
byte1 = opcode # since more, rsv1-3 are 0
|
102
|
-
frame << byte1
|
103
|
-
|
104
|
-
length = application_data.size
|
105
|
-
if length <= 125
|
106
|
-
byte2 = length # since rsv4 is 0
|
107
|
-
frame << byte2
|
108
|
-
elsif length < 65536 # write 2 byte length
|
109
|
-
frame << 126
|
110
|
-
frame << [length].pack('n')
|
111
|
-
else # write 8 byte length
|
112
|
-
frame << 127
|
113
|
-
frame << [length >> 32, length & 0xFFFFFFFF].pack("NN")
|
114
|
-
end
|
115
|
-
|
116
|
-
frame << application_data
|
117
|
-
|
118
|
-
@connection.send_data(frame)
|
119
|
-
end
|
120
|
-
|
121
|
-
def send_text_frame(data)
|
122
|
-
send_frame(:text, data)
|
123
|
-
end
|
124
|
-
|
125
|
-
private
|
126
|
-
|
127
|
-
def message(message_type, extension_data, application_data)
|
128
|
-
case message_type
|
129
|
-
when :close
|
130
|
-
if @state == :closing
|
131
|
-
# TODO: Check that message body matches sent data
|
132
|
-
# We can close connection immediately since there is no more data
|
133
|
-
# is allowed to be sent or received on this connection
|
134
|
-
@connection.close_connection
|
135
|
-
@state = :closed
|
136
|
-
else
|
137
|
-
# Acknowlege close
|
138
|
-
# The connection is considered closed
|
139
|
-
send_frame(:close, application_data)
|
140
|
-
@state = :closed
|
141
|
-
@connection.close_connection_after_writing
|
142
|
-
end
|
143
|
-
when :ping
|
144
|
-
# Pong back the same data
|
145
|
-
send_frame(:pong, application_data)
|
146
|
-
when :pong
|
147
|
-
# TODO: Do something. Complete a deferrable established by a ping?
|
148
|
-
when :text, :binary
|
149
|
-
@connection.trigger_on_message(application_data)
|
150
|
-
end
|
151
|
-
end
|
152
|
-
|
153
|
-
FRAME_TYPES = {
|
154
|
-
:continuation => 0,
|
155
|
-
:close => 1,
|
156
|
-
:ping => 2,
|
157
|
-
:pong => 3,
|
158
|
-
:text => 4,
|
159
|
-
:binary => 5
|
160
|
-
}
|
161
|
-
FRAME_TYPES_INVERSE = FRAME_TYPES.invert
|
162
|
-
# Frames are either data frames or control frames
|
163
|
-
DATA_FRAMES = [:text, :binary, :continuation]
|
164
|
-
|
165
|
-
def type_to_opcode(frame_type)
|
166
|
-
FRAME_TYPES[frame_type] || raise("Unknown frame type")
|
167
|
-
end
|
168
|
-
|
169
|
-
def opcode_to_type(opcode)
|
170
|
-
FRAME_TYPES_INVERSE[opcode] || raise("Unknown opcode")
|
171
|
-
end
|
172
|
-
|
173
|
-
def data_frame?(type)
|
174
|
-
DATA_FRAMES.include?(type)
|
175
|
-
end
|
176
|
-
end
|
177
|
-
end
|
178
|
-
end
|
@@ -1,115 +0,0 @@
|
|
1
|
-
# encoding: BINARY
|
2
|
-
|
3
|
-
module Rack
|
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
|
-
@connection.close_with_error(DataError.new("Frame length too long (#{length} bytes)"))
|
46
|
-
return false
|
47
|
-
end
|
48
|
-
|
49
|
-
if @data.getbyte(pointer+length-1) == nil
|
50
|
-
debug [:buffer_incomplete, @data.inspect]
|
51
|
-
# Incomplete data - leave @data to accumulate
|
52
|
-
error = true
|
53
|
-
else
|
54
|
-
# Straight from spec - I'm sure this isn't crazy...
|
55
|
-
# 6. Read /length/ bytes.
|
56
|
-
# 7. Discard the read bytes.
|
57
|
-
@data = @data[(pointer+length)..-1]
|
58
|
-
|
59
|
-
# If the /frame type/ is 0xFF and the /length/ was 0, then close
|
60
|
-
if length == 0
|
61
|
-
@connection.send_data("\xff\x00")
|
62
|
-
@state = :closing
|
63
|
-
@connection.close_connection_after_writing
|
64
|
-
else
|
65
|
-
error = true
|
66
|
-
end
|
67
|
-
end
|
68
|
-
else
|
69
|
-
# If the high-order bit of the /frame type/ byte is _not_ set
|
70
|
-
|
71
|
-
if @data.getbyte(0) != 0x00
|
72
|
-
# Close the connection since this buffer can never match
|
73
|
-
@connection.close_with_error(DataError.new("Invalid frame received"))
|
74
|
-
end
|
75
|
-
|
76
|
-
# Addition to the spec to protect against malicious requests
|
77
|
-
if @data.size > MAXIMUM_FRAME_LENGTH
|
78
|
-
@connection.close_with_error(DataError.new("Frame length too long (#{@data.size} bytes)"))
|
79
|
-
return false
|
80
|
-
end
|
81
|
-
|
82
|
-
# Optimization to avoid calling slice! unnecessarily
|
83
|
-
error = true and next unless newdata =~ /\xff/
|
84
|
-
|
85
|
-
msg = @data.slice!(/\A\x00[^\xff]*\xff/)
|
86
|
-
if msg
|
87
|
-
msg.gsub!(/\A\x00|\xff\z/, '')
|
88
|
-
if @state == :closing
|
89
|
-
debug [:ignored_message, msg]
|
90
|
-
else
|
91
|
-
msg.force_encoding('UTF-8') if msg.respond_to?(:force_encoding)
|
92
|
-
@connection.trigger_on_message(msg)
|
93
|
-
end
|
94
|
-
else
|
95
|
-
error = true
|
96
|
-
end
|
97
|
-
end
|
98
|
-
end
|
99
|
-
|
100
|
-
false
|
101
|
-
end
|
102
|
-
|
103
|
-
# frames need to start with 0x00-0x7f byte and end with
|
104
|
-
# an 0xFF byte. Per spec, we can also set the first
|
105
|
-
# byte to a value betweent 0x80 and 0xFF, followed by
|
106
|
-
# a leading length indicator
|
107
|
-
def send_text_frame(data)
|
108
|
-
ary = ["\x00", data, "\xff"]
|
109
|
-
ary.collect{ |s| s.force_encoding('UTF-8') if s.respond_to?(:force_encoding) }
|
110
|
-
@connection.send_data(ary.join)
|
111
|
-
end
|
112
|
-
|
113
|
-
end
|
114
|
-
end
|
115
|
-
end
|
@@ -1,14 +0,0 @@
|
|
1
|
-
module Rack
|
2
|
-
module WebSocket
|
3
|
-
class Handler03 < Handler
|
4
|
-
include Handshake76
|
5
|
-
include Framing03
|
6
|
-
|
7
|
-
def close_websocket
|
8
|
-
# TODO: Should we send data and check the response matches?
|
9
|
-
send_frame(:close, '')
|
10
|
-
@state = :closing
|
11
|
-
end
|
12
|
-
end
|
13
|
-
end
|
14
|
-
end
|
@@ -1,61 +0,0 @@
|
|
1
|
-
module Rack
|
2
|
-
module WebSocket
|
3
|
-
class HandlerFactory
|
4
|
-
|
5
|
-
def self.build(connection, data, debug = false)
|
6
|
-
request = Rack::Request.new(data)
|
7
|
-
|
8
|
-
unless request.env['rack.input'].nil?
|
9
|
-
request.env['rack.input'].rewind
|
10
|
-
remains = request.env['rack.input'].read
|
11
|
-
else
|
12
|
-
# The whole header has not been received yet.
|
13
|
-
return nil
|
14
|
-
end
|
15
|
-
|
16
|
-
unless request.get?
|
17
|
-
raise HandshakeError, "Must be GET request"
|
18
|
-
end
|
19
|
-
|
20
|
-
version = request.env['HTTP_SEC_WEBSOCKET_KEY1'] ? 76 : 75
|
21
|
-
case version
|
22
|
-
when 75
|
23
|
-
if !remains.empty?
|
24
|
-
raise HandshakeError, "Extra bytes after header"
|
25
|
-
end
|
26
|
-
when 76
|
27
|
-
if remains.length < 8
|
28
|
-
# The whole third-key has not been received yet.
|
29
|
-
return nil
|
30
|
-
elsif remains.length > 8
|
31
|
-
raise HandshakeError, "Extra bytes after third key"
|
32
|
-
end
|
33
|
-
request.env['HTTP_THIRD_KEY'] = remains
|
34
|
-
else
|
35
|
-
raise WebSocketError, "Must not happen"
|
36
|
-
end
|
37
|
-
|
38
|
-
unless request.env['HTTP_CONNECTION'] == 'Upgrade' and request.env['HTTP_UPGRADE'] == 'WebSocket'
|
39
|
-
raise HandshakeError, "Connection and Upgrade headers required"
|
40
|
-
end
|
41
|
-
|
42
|
-
# transform headers
|
43
|
-
request.env['rack.url_scheme'] = (request.scheme == 'https' ? "wss" : "ws")
|
44
|
-
|
45
|
-
if version = request.env['HTTP_SEC_WEBSOCKET_DRAFT']
|
46
|
-
if version == '1' || version == '2' || version == '3'
|
47
|
-
# We'll use handler03 - I believe they're all compatible
|
48
|
-
Handler03.new(connection, request, debug)
|
49
|
-
else
|
50
|
-
# According to spec should abort the connection
|
51
|
-
raise WebSocketError, "Unknown draft version: #{version}"
|
52
|
-
end
|
53
|
-
elsif request.env['HTTP_SEC_WEBSOCKET_KEY1']
|
54
|
-
Handler76.new(connection, request, debug)
|
55
|
-
else
|
56
|
-
Handler75.new(connection, request, debug)
|
57
|
-
end
|
58
|
-
end
|
59
|
-
end
|
60
|
-
end
|
61
|
-
end
|
@@ -1,21 +0,0 @@
|
|
1
|
-
module Rack
|
2
|
-
module WebSocket
|
3
|
-
module Handshake75
|
4
|
-
def handshake
|
5
|
-
location = "#{request.env['rack.url_scheme']}://#{request.host}"
|
6
|
-
location << ":#{request.port}" if request.port > 0
|
7
|
-
location << request.path
|
8
|
-
|
9
|
-
upgrade = "HTTP/1.1 101 Web Socket Protocol Handshake\r\n"
|
10
|
-
upgrade << "Upgrade: WebSocket\r\n"
|
11
|
-
upgrade << "Connection: Upgrade\r\n"
|
12
|
-
upgrade << "WebSocket-Origin: #{request.env['HTTP_ORIGIN']}\r\n"
|
13
|
-
upgrade << "WebSocket-Location: #{location}\r\n\r\n"
|
14
|
-
|
15
|
-
debug [:upgrade_headers, upgrade]
|
16
|
-
|
17
|
-
return upgrade
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|
@@ -1,71 +0,0 @@
|
|
1
|
-
require 'digest/md5'
|
2
|
-
|
3
|
-
module Rack
|
4
|
-
module WebSocket
|
5
|
-
module Handshake76
|
6
|
-
def handshake
|
7
|
-
challenge_response = solve_challenge(
|
8
|
-
request.env['HTTP_SEC_WEBSOCKET_KEY1'],
|
9
|
-
request.env['HTTP_SEC_WEBSOCKET_KEY2'],
|
10
|
-
request.env['HTTP_THIRD_KEY']
|
11
|
-
)
|
12
|
-
|
13
|
-
location = "#{request.env['rack.url_scheme']}://#{request.host}"
|
14
|
-
location << ":#{request.port}" if request.port > 0
|
15
|
-
location << request.path
|
16
|
-
|
17
|
-
upgrade = "HTTP/1.1 101 WebSocket Protocol Handshake\r\n"
|
18
|
-
upgrade << "Upgrade: WebSocket\r\n"
|
19
|
-
upgrade << "Connection: Upgrade\r\n"
|
20
|
-
upgrade << "Sec-WebSocket-Location: #{location}\r\n"
|
21
|
-
upgrade << "Sec-WebSocket-Origin: #{request.env['HTTP_ORIGIN']}\r\n"
|
22
|
-
if protocol = request.env['HTTP_SEC_WEBSOCKET_PROTOCOL']
|
23
|
-
validate_protocol!(protocol)
|
24
|
-
upgrade << "Sec-WebSocket-Protocol: #{protocol}\r\n"
|
25
|
-
end
|
26
|
-
upgrade << "\r\n"
|
27
|
-
upgrade << challenge_response
|
28
|
-
|
29
|
-
debug [:upgrade_headers, upgrade]
|
30
|
-
|
31
|
-
return upgrade
|
32
|
-
end
|
33
|
-
|
34
|
-
private
|
35
|
-
|
36
|
-
def solve_challenge(first, second, third)
|
37
|
-
# Refer to 5.2 4-9 of the draft 76
|
38
|
-
sum = [numbers_over_spaces(first)].pack("N*") +
|
39
|
-
[numbers_over_spaces(second)].pack("N*") +
|
40
|
-
third
|
41
|
-
Digest::MD5.digest(sum)
|
42
|
-
end
|
43
|
-
|
44
|
-
def numbers_over_spaces(string)
|
45
|
-
numbers = string.scan(/[0-9]/).join.to_i
|
46
|
-
|
47
|
-
spaces = string.scan(/ /).size
|
48
|
-
# As per 5.2.5, abort the connection if spaces are zero.
|
49
|
-
raise HandshakeError, "Websocket Key1 or Key2 does not contain spaces - this is a symptom of a cross-protocol attack" if spaces == 0
|
50
|
-
|
51
|
-
# As per 5.2.6, abort if numbers is not an integral multiple of spaces
|
52
|
-
if numbers % spaces != 0
|
53
|
-
raise HandshakeError, "Invalid Key #{string.inspect}"
|
54
|
-
end
|
55
|
-
|
56
|
-
quotient = numbers / spaces
|
57
|
-
|
58
|
-
if quotient > 2**32-1
|
59
|
-
raise HandshakeError, "Challenge computation out of range for key #{string.inspect}"
|
60
|
-
end
|
61
|
-
|
62
|
-
return quotient
|
63
|
-
end
|
64
|
-
|
65
|
-
def validate_protocol!(protocol)
|
66
|
-
raise HandshakeError, "Invalid WebSocket-Protocol: empty" if protocol.empty?
|
67
|
-
# TODO: Validate characters
|
68
|
-
end
|
69
|
-
end
|
70
|
-
end
|
71
|
-
end
|
data/spec/helper.rb
DELETED
@@ -1,44 +0,0 @@
|
|
1
|
-
require 'rubygems'
|
2
|
-
require 'rspec'
|
3
|
-
require 'pp'
|
4
|
-
require 'stringio'
|
5
|
-
|
6
|
-
require 'rack/websocket'
|
7
|
-
|
8
|
-
Rspec.configure do |c|
|
9
|
-
c.mock_with :rspec
|
10
|
-
end
|
11
|
-
|
12
|
-
def format_request(r)
|
13
|
-
data = {}
|
14
|
-
data['REQUEST_METHOD'] = r[:method] if r[:method]
|
15
|
-
data['PATH_INFO'] = r[:path] if r[:path]
|
16
|
-
data['SERVER_PORT'] = r[:port] if r[:port] && r[:port] != 80
|
17
|
-
r[:headers].each do |key, value|
|
18
|
-
data['HTTP_' + key.upcase.gsub('-','_')] = value
|
19
|
-
end
|
20
|
-
data['rack.input'] = StringIO.new(r[:body]) if r[:body]
|
21
|
-
# data = "#{r[:method]} #{r[:path]} HTTP/1.1\r\n"
|
22
|
-
# header_lines = r[:headers].map { |k,v| "#{k}: #{v}" }
|
23
|
-
# data << [header_lines, '', r[:body]].join("\r\n")
|
24
|
-
data
|
25
|
-
end
|
26
|
-
|
27
|
-
def format_response(r)
|
28
|
-
data = "HTTP/1.1 101 WebSocket Protocol Handshake\r\n"
|
29
|
-
header_lines = r[:headers].map { |k,v| "#{k}: #{v}" }
|
30
|
-
data << [header_lines, '', r[:body]].join("\r\n")
|
31
|
-
data
|
32
|
-
end
|
33
|
-
|
34
|
-
def handler(request, secure = false)
|
35
|
-
connection = Object.new
|
36
|
-
secure_hash = secure ? {'rack.url_scheme' => 'https'} : {}
|
37
|
-
Rack::WebSocket::HandlerFactory.build(connection, format_request(request).merge(secure_hash))
|
38
|
-
end
|
39
|
-
|
40
|
-
RSpec::Matchers.define :send_handshake do |response|
|
41
|
-
match do |actual|
|
42
|
-
actual.handshake.lines.sort == format_response(response).lines.sort
|
43
|
-
end
|
44
|
-
end
|