websocket-rack 0.1.4 → 0.2.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.
- 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
|