libwebsocket 0.1.5 → 0.1.6
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 +6 -0
- data/README.md +2 -0
- data/examples/plain_client.rb +1 -1
- data/lib/libwebsocket.rb +1 -6
- data/lib/libwebsocket/opening_handshake.rb +11 -13
- data/lib/libwebsocket/opening_handshake/client.rb +2 -86
- data/lib/libwebsocket/opening_handshake/server.rb +2 -73
- data/lib/libwebsocket/version.rb +1 -1
- data/libwebsocket.gemspec +1 -1
- data/test/libwebsocket/opening_handshake/test_client.rb +10 -25
- data/test/libwebsocket/opening_handshake/test_server.rb +2 -2
- metadata +51 -91
- data/examples/thin_server.rb +0 -69
- data/lib/libwebsocket/cookie.rb +0 -60
- data/lib/libwebsocket/cookie/request.rb +0 -48
- data/lib/libwebsocket/cookie/response.rb +0 -44
- data/lib/libwebsocket/message.rb +0 -167
- data/lib/libwebsocket/request.rb +0 -288
- data/lib/libwebsocket/response.rb +0 -215
- data/lib/libwebsocket/stateful.rb +0 -24
- data/lib/libwebsocket/url.rb +0 -68
- data/test/libwebsocket/cookie/request.rb +0 -37
- data/test/libwebsocket/cookie/response.rb +0 -32
- data/test/libwebsocket/test_cookie.rb +0 -22
- data/test/libwebsocket/test_message.rb +0 -16
- data/test/libwebsocket/test_request_75.rb +0 -145
- data/test/libwebsocket/test_request_76.rb +0 -122
- data/test/libwebsocket/test_request_common.rb +0 -26
- data/test/libwebsocket/test_response_75.rb +0 -80
- data/test/libwebsocket/test_response_76.rb +0 -115
- data/test/libwebsocket/test_response_common.rb +0 -17
- data/test/libwebsocket/test_url.rb +0 -55
data/examples/thin_server.rb
DELETED
@@ -1,69 +0,0 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
|
-
|
3
|
-
require 'rubygems'
|
4
|
-
require 'thin'
|
5
|
-
require File.expand_path(File.dirname(__FILE__) + '/../lib/libwebsocket')
|
6
|
-
|
7
|
-
# This is required due to thin incompatibility with streamming of data
|
8
|
-
module ThinExtension
|
9
|
-
def self.included(thin_conn)
|
10
|
-
thin_conn.class_eval do
|
11
|
-
alias :pre_process_without_websocket :pre_process
|
12
|
-
alias :pre_process :pre_process_with_websocket
|
13
|
-
|
14
|
-
alias :receive_data_without_websocket :receive_data
|
15
|
-
alias :receive_data :receive_data_with_websocket
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
attr_accessor :websocket_client
|
20
|
-
|
21
|
-
def pre_process_with_websocket
|
22
|
-
@request.env['async.connection'] = self
|
23
|
-
pre_process_without_websocket
|
24
|
-
end
|
25
|
-
def receive_data_with_websocket(data)
|
26
|
-
if self.websocket_client
|
27
|
-
self.websocket_client.receive_data(data)
|
28
|
-
else
|
29
|
-
receive_data_without_websocket(data)
|
30
|
-
end
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
|
-
::Thin::Connection.send(:include, ThinExtension)
|
35
|
-
|
36
|
-
class EchoServer
|
37
|
-
def call(env)
|
38
|
-
@hs ||= LibWebSocket::OpeningHandshake::Server.new
|
39
|
-
@connection = env['async.connection']
|
40
|
-
|
41
|
-
if !@hs.done?
|
42
|
-
@hs.parse(env)
|
43
|
-
|
44
|
-
if @hs.done?
|
45
|
-
@connection.websocket_client = self
|
46
|
-
resp = @hs.to_rack
|
47
|
-
return resp
|
48
|
-
end
|
49
|
-
|
50
|
-
return
|
51
|
-
end
|
52
|
-
end
|
53
|
-
|
54
|
-
def receive_data(data)
|
55
|
-
@frame ||= LibWebSocket::Frame.new
|
56
|
-
|
57
|
-
@frame.append(data)
|
58
|
-
|
59
|
-
while message = @frame.next
|
60
|
-
@connection.send_data @frame.new(message).to_s
|
61
|
-
end
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
65
|
-
Thin::Server.start('127.0.0.1', 8080) do
|
66
|
-
map '/' do
|
67
|
-
run proc{ |env| EchoServer.new.call(env) }
|
68
|
-
end
|
69
|
-
end
|
data/lib/libwebsocket/cookie.rb
DELETED
@@ -1,60 +0,0 @@
|
|
1
|
-
module LibWebSocket
|
2
|
-
#A base class for LibWebSocket::Cookie::Request and LibWebSocket::Cookie::Response.
|
3
|
-
class Cookie
|
4
|
-
|
5
|
-
autoload :Request, "#{File.dirname(__FILE__)}/cookie/request"
|
6
|
-
autoload :Response, "#{File.dirname(__FILE__)}/cookie/response"
|
7
|
-
|
8
|
-
attr_accessor :pairs
|
9
|
-
|
10
|
-
TOKEN = /[^;,\s"]+/ # Cookie token
|
11
|
-
NAME = /[^;,\s"=]+/ # Cookie name
|
12
|
-
QUOTED_STRING = /"(?:\\"|[^"])+"/ # Cookie quoted value
|
13
|
-
VALUE = /(?:#{TOKEN}|#{QUOTED_STRING})/ # Cookie unquoted value
|
14
|
-
|
15
|
-
def initialize(hash = {})
|
16
|
-
hash.each do |k,v|
|
17
|
-
instance_variable_set("@#{k}",v)
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
21
|
-
# Parse cookie string to array
|
22
|
-
def parse(string = nil)
|
23
|
-
self.pairs = []
|
24
|
-
|
25
|
-
return if string.nil? || string == ''
|
26
|
-
|
27
|
-
while string.slice!(/\s*(#{NAME})\s*(?:=\s*(#{VALUE}))?;?/)
|
28
|
-
attr, value = $1, $2
|
29
|
-
if !value.nil?
|
30
|
-
value.gsub!(/^"/, '')
|
31
|
-
value.gsub!(/"$/, '')
|
32
|
-
value.gsub!(/\\"/, '"')
|
33
|
-
end
|
34
|
-
self.pairs.push([attr, value])
|
35
|
-
end
|
36
|
-
|
37
|
-
return self
|
38
|
-
end
|
39
|
-
|
40
|
-
# Convert cookie array to string
|
41
|
-
def to_s
|
42
|
-
pairs = []
|
43
|
-
|
44
|
-
self.pairs.each do |pair|
|
45
|
-
string = ''
|
46
|
-
string += pair[0]
|
47
|
-
|
48
|
-
unless pair[1].nil?
|
49
|
-
string += '='
|
50
|
-
string += (!pair[1].match(/^#{VALUE}$/) ? "\"#{pair[1]}\"" : pair[1])
|
51
|
-
end
|
52
|
-
|
53
|
-
pairs.push(string)
|
54
|
-
end
|
55
|
-
|
56
|
-
return pairs.join("; ")
|
57
|
-
end
|
58
|
-
|
59
|
-
end
|
60
|
-
end
|
@@ -1,48 +0,0 @@
|
|
1
|
-
module LibWebSocket
|
2
|
-
class Cookie
|
3
|
-
# Construct or parse a WebSocket request cookie.
|
4
|
-
class Request < Cookie
|
5
|
-
|
6
|
-
attr_accessor :name, :value, :version, :path, :domain
|
7
|
-
|
8
|
-
# Parse a WebSocket request cookie.
|
9
|
-
# @example
|
10
|
-
# cookie = LibWebSocket::Cookie::Request.new
|
11
|
-
# cookies = cookie.parse('$Version=1; foo="bar"; $Path=/; bar=baz; $Domain=.example.com')
|
12
|
-
def parse(string)
|
13
|
-
result = super
|
14
|
-
return unless result
|
15
|
-
|
16
|
-
cookies = []
|
17
|
-
|
18
|
-
pair = self.pairs.shift
|
19
|
-
version = pair[1]
|
20
|
-
|
21
|
-
cookie = nil
|
22
|
-
self.pairs.each do |pair|
|
23
|
-
next unless pair[0]
|
24
|
-
|
25
|
-
if pair[0].match(/^[^\$]/)
|
26
|
-
cookies.push(cookie) if cookie
|
27
|
-
|
28
|
-
cookie = self.build_cookie(:name => pair[0], :value => pair[1], :version => version)
|
29
|
-
elsif pair[0] == '$Path'
|
30
|
-
cookie.path = pair[1]
|
31
|
-
elsif pair[0] == '$Domain'
|
32
|
-
cookie.domain = pair[1]
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
|
-
cookies.push(cookie) if cookie
|
37
|
-
|
38
|
-
return cookies
|
39
|
-
end
|
40
|
-
|
41
|
-
protected
|
42
|
-
|
43
|
-
def build_cookie(hash)
|
44
|
-
self.class.new(hash)
|
45
|
-
end
|
46
|
-
end
|
47
|
-
end
|
48
|
-
end
|
@@ -1,44 +0,0 @@
|
|
1
|
-
module LibWebSocket
|
2
|
-
class Cookie
|
3
|
-
# Construct or parse a WebSocket response cookie.
|
4
|
-
class Response < Cookie
|
5
|
-
|
6
|
-
attr_accessor :name, :value, :comment, :comment_url, :discard, :max_age, :path, :portlist, :secure
|
7
|
-
|
8
|
-
# Construct a WebSocket response cookie.
|
9
|
-
# @example
|
10
|
-
# cookie = LibWebSocket::Cookie::Response.new(
|
11
|
-
# :name => 'foo',
|
12
|
-
# :value => 'bar',
|
13
|
-
# :discard => 1,
|
14
|
-
# :max_age => 0
|
15
|
-
# )
|
16
|
-
# cookie.to_s # foo=bar; Discard; Max-Age=0; Version=1
|
17
|
-
def to_s
|
18
|
-
pairs = []
|
19
|
-
|
20
|
-
pairs.push([self.name, self.value])
|
21
|
-
|
22
|
-
pairs.push ['Comment', self.comment] if self.comment
|
23
|
-
pairs.push ['CommentURL', self.comment_url] if self.comment_url
|
24
|
-
pairs.push ['Discard'] if self.discard
|
25
|
-
pairs.push ['Max-Age', self.max_age] if self.max_age
|
26
|
-
pairs.push ['Path', self.path] if self.path
|
27
|
-
|
28
|
-
if self.portlist
|
29
|
-
self.portlist = Array(self.portlist)
|
30
|
-
list = self.portlist.join(' ')
|
31
|
-
pairs.push ['Port', "\"#{list}\""]
|
32
|
-
end
|
33
|
-
|
34
|
-
pairs.push ['Secure'] if self.secure
|
35
|
-
pairs.push ['Version', '1']
|
36
|
-
|
37
|
-
self.pairs = pairs
|
38
|
-
|
39
|
-
super
|
40
|
-
end
|
41
|
-
|
42
|
-
end
|
43
|
-
end
|
44
|
-
end
|
data/lib/libwebsocket/message.rb
DELETED
@@ -1,167 +0,0 @@
|
|
1
|
-
require 'digest/md5'
|
2
|
-
|
3
|
-
module LibWebSocket
|
4
|
-
# A base class for LibWebSocket::Request and LibWebSocket::Response.
|
5
|
-
class Message
|
6
|
-
include Stateful
|
7
|
-
|
8
|
-
attr_accessor :fields, :error, :subprotocol, :host, :origin, :version, :number1, :number2, :challenge, :checksum
|
9
|
-
|
10
|
-
# A new instance of Message.
|
11
|
-
# Instance variables will be set from hash.
|
12
|
-
# @example
|
13
|
-
# msg = LibMessage.new( :host => 'example.org' )
|
14
|
-
# msg.host # => 'example.org'
|
15
|
-
def initialize(hash = {})
|
16
|
-
hash.each do |k,v|
|
17
|
-
instance_variable_set("@#{k}",v)
|
18
|
-
end
|
19
|
-
|
20
|
-
@version ||= 76
|
21
|
-
@buffer = ''
|
22
|
-
@fields ||= {}
|
23
|
-
@max_message_size ||= 2048
|
24
|
-
@cookies ||= []
|
25
|
-
@state = 'first_line'
|
26
|
-
end
|
27
|
-
|
28
|
-
# Alias for fields.
|
29
|
-
# Use this instead of fields directly to preserve normalization(lowercase etc.)
|
30
|
-
def field(f, val = nil)
|
31
|
-
name = f.downcase
|
32
|
-
|
33
|
-
return self.fields[name] unless val
|
34
|
-
|
35
|
-
self.fields[name] = val
|
36
|
-
|
37
|
-
return self
|
38
|
-
end
|
39
|
-
|
40
|
-
# Set state to 'error' and write error message
|
41
|
-
def error=(val)
|
42
|
-
@error = val
|
43
|
-
self.state = 'error'
|
44
|
-
end
|
45
|
-
|
46
|
-
# Calculate Draft 76 checksum
|
47
|
-
def checksum
|
48
|
-
return @checksum if @checksum
|
49
|
-
|
50
|
-
raise 'number1 is required' unless self.number1
|
51
|
-
raise 'number2 is required' unless self.number2
|
52
|
-
raise 'challenge is required' unless self.challenge
|
53
|
-
|
54
|
-
checksum = ''
|
55
|
-
checksum += [self.number1].pack('N')
|
56
|
-
checksum += [self.number2].pack('N')
|
57
|
-
checksum += self.challenge
|
58
|
-
checksum = Digest::MD5.digest(checksum)
|
59
|
-
|
60
|
-
return @checksum ||= checksum
|
61
|
-
end
|
62
|
-
|
63
|
-
# Parse string
|
64
|
-
# @see LibWebSocket::Request#parse
|
65
|
-
# @see LibWebSocket::Response#parse
|
66
|
-
def parse(string)
|
67
|
-
return unless string.is_a?(String)
|
68
|
-
|
69
|
-
return if self.error
|
70
|
-
|
71
|
-
return unless self.append(string)
|
72
|
-
|
73
|
-
while(!self.state?('body') && line = self.get_line)
|
74
|
-
if self.state?('first_line')
|
75
|
-
return unless self.parse_first_line(line)
|
76
|
-
|
77
|
-
self.state = 'fields'
|
78
|
-
elsif line != ''
|
79
|
-
return unless self.parse_field(line)
|
80
|
-
else
|
81
|
-
self.state = 'body'
|
82
|
-
break
|
83
|
-
end
|
84
|
-
end
|
85
|
-
|
86
|
-
return true unless self.state?('body')
|
87
|
-
|
88
|
-
rv = self.parse_body
|
89
|
-
return unless rv
|
90
|
-
|
91
|
-
# Need more data
|
92
|
-
return rv unless rv != true
|
93
|
-
|
94
|
-
return self.done
|
95
|
-
end
|
96
|
-
|
97
|
-
protected
|
98
|
-
|
99
|
-
def number(name, key, value = nil)
|
100
|
-
if value
|
101
|
-
return self.instance_variable_set("@#{name}", value)
|
102
|
-
end
|
103
|
-
|
104
|
-
return self.instance_variable_get("@#{name}") if self.instance_variable_get("@#{name}")
|
105
|
-
|
106
|
-
return self.instance_variable_set("@#{name}", self.extract_number(self.send(key)))
|
107
|
-
end
|
108
|
-
|
109
|
-
def extract_number(key)
|
110
|
-
number = ''
|
111
|
-
while key.slice!(/(\d)/)
|
112
|
-
number += $1
|
113
|
-
end
|
114
|
-
number = number.to_i
|
115
|
-
|
116
|
-
spaces = 0
|
117
|
-
while key.slice!(/ /)
|
118
|
-
spaces += 1
|
119
|
-
end
|
120
|
-
|
121
|
-
return if spaces == 0
|
122
|
-
|
123
|
-
return (number / spaces).to_i
|
124
|
-
end
|
125
|
-
|
126
|
-
def append(data)
|
127
|
-
return if self.error
|
128
|
-
|
129
|
-
@buffer += data
|
130
|
-
|
131
|
-
if @buffer.length > @max_message_size
|
132
|
-
self.error = 'Message is too long'
|
133
|
-
return
|
134
|
-
end
|
135
|
-
|
136
|
-
return self
|
137
|
-
end
|
138
|
-
|
139
|
-
def get_line
|
140
|
-
if @buffer.slice!(/\A(.*?)\x0d?\x0a/)
|
141
|
-
return $1
|
142
|
-
end
|
143
|
-
return
|
144
|
-
end
|
145
|
-
|
146
|
-
def parse_first_line(line)
|
147
|
-
self
|
148
|
-
end
|
149
|
-
|
150
|
-
def parse_field(line)
|
151
|
-
name, value = line.split(': ', 2)
|
152
|
-
unless name && value
|
153
|
-
self.error = 'Invalid field'
|
154
|
-
return
|
155
|
-
end
|
156
|
-
|
157
|
-
self.field(name, value)
|
158
|
-
|
159
|
-
return self
|
160
|
-
end
|
161
|
-
|
162
|
-
def parse_body
|
163
|
-
self
|
164
|
-
end
|
165
|
-
|
166
|
-
end
|
167
|
-
end
|
data/lib/libwebsocket/request.rb
DELETED
@@ -1,288 +0,0 @@
|
|
1
|
-
module LibWebSocket
|
2
|
-
# Construct or parse a WebSocket request.
|
3
|
-
class Request < Message
|
4
|
-
|
5
|
-
attr_accessor :cookies, :resource_name
|
6
|
-
|
7
|
-
# Parse a WebSocket request.
|
8
|
-
# @param [String] opts parse string
|
9
|
-
# @param [Hash] opts parse rack env hash
|
10
|
-
# @see Request#parse_rack_env
|
11
|
-
# @example Parser
|
12
|
-
# req = LibWebSocket::Request.new
|
13
|
-
# req.parse("GET /demo HTTP/1.1\x0d\x0a")
|
14
|
-
# req.parse("Upgrade: WebSocket\x0d\x0a")
|
15
|
-
# req.parse("Connection: Upgrade\x0d\x0a")
|
16
|
-
# req.parse("Host: example.com\x0d\x0a")
|
17
|
-
# req.parse("Origin: http://example.com\x0d\x0a")
|
18
|
-
# req.parse("Sec-WebSocket-Key1: 18x 6]8vM;54 *(5: { U1]8 z [ 8\x0d\x0a")
|
19
|
-
# req.parse("Sec-WebSocket-Key2: 1_ tx7X d < nw 334J702) 7]o}` 0\x0d\x0a")
|
20
|
-
# req.parse("\x0d\x0aTm[K T2u")
|
21
|
-
def parse(opts)
|
22
|
-
case opts
|
23
|
-
when String then super
|
24
|
-
when Hash then parse_rack_env(opts)
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
# Parse a WebSocket request.
|
29
|
-
# @param [Hash] env parse rack env hash
|
30
|
-
# @example Parser
|
31
|
-
# req = LibWebSocket::Request.new
|
32
|
-
# req.parse(env)
|
33
|
-
def parse_rack_env(env)
|
34
|
-
method = env['REQUEST_METHOD']
|
35
|
-
unless method == 'GET'
|
36
|
-
self.error = 'Wrong request method'
|
37
|
-
return false
|
38
|
-
end
|
39
|
-
|
40
|
-
self.resource_name = env['REQUEST_URI']
|
41
|
-
|
42
|
-
env.keys.select { |v| v =~ /\AHTTP\_/ }.each do |key|
|
43
|
-
self.field(key.gsub(/\AHTTP\_/, "").gsub('_','-'), env[key])
|
44
|
-
end
|
45
|
-
|
46
|
-
body = env['rack.input']
|
47
|
-
# Is compatible with rack.input spec?
|
48
|
-
unless body.respond_to?(:rewind) and body.respond_to?(:read)
|
49
|
-
self.error = "Invalid rack input"
|
50
|
-
return false
|
51
|
-
end
|
52
|
-
|
53
|
-
body.rewind
|
54
|
-
@buffer = body.read
|
55
|
-
|
56
|
-
rv = self.parse_body
|
57
|
-
return unless rv
|
58
|
-
|
59
|
-
# Need more data
|
60
|
-
return rv unless rv != true
|
61
|
-
|
62
|
-
return self.done
|
63
|
-
end
|
64
|
-
|
65
|
-
# A shortcut for self.field('Upgrade')
|
66
|
-
def upgrade
|
67
|
-
self.field('Upgrade')
|
68
|
-
end
|
69
|
-
# A shortcut for self.field('Connection')
|
70
|
-
def connection
|
71
|
-
self.field('Connection')
|
72
|
-
end
|
73
|
-
|
74
|
-
# Draft 76 number 1 reader
|
75
|
-
def number1
|
76
|
-
self.number('number1','key1')
|
77
|
-
end
|
78
|
-
# Draft 76 number 1 writter
|
79
|
-
def number1=(val)
|
80
|
-
self.number('number1','key1',val)
|
81
|
-
end
|
82
|
-
# Draft 76 number 2 reader
|
83
|
-
def number2
|
84
|
-
self.number('number2','key2')
|
85
|
-
end
|
86
|
-
# Draft 76 number 2 writter
|
87
|
-
def number2=(val)
|
88
|
-
self.number('number2','key2',val)
|
89
|
-
end
|
90
|
-
# Draft 76 Sec-WebSocket-Key1 reader
|
91
|
-
def key1
|
92
|
-
self.key('key1')
|
93
|
-
end
|
94
|
-
# Draft 76 Sec-WebSocket-Key1 writter
|
95
|
-
def key1=(val)
|
96
|
-
self.key('key1',val)
|
97
|
-
end
|
98
|
-
# Draft 76 Sec-WebSocket-Key2 reader
|
99
|
-
def key2
|
100
|
-
self.key('key2')
|
101
|
-
end
|
102
|
-
# Draft 76 Sec-WebSocket-Key2 writter
|
103
|
-
def key2=(val)
|
104
|
-
self.key('key2',val)
|
105
|
-
end
|
106
|
-
|
107
|
-
# Construct a WebSocket request.
|
108
|
-
# # Constructor
|
109
|
-
# my $req = Protocol::WebSocket::Request.new(
|
110
|
-
# host => 'example.com',
|
111
|
-
# resource_name => '/demo'
|
112
|
-
# );
|
113
|
-
# $req.to_s # GET /demo HTTP/1.1
|
114
|
-
# # Upgrade: WebSocket
|
115
|
-
# # Connection: Upgrade
|
116
|
-
# # Host: example.com
|
117
|
-
# # Origin: http://example.com
|
118
|
-
# # Sec-WebSocket-Key1: 32 0 3lD& 24+< i u4 8! -6/4
|
119
|
-
# # Sec-WebSocket-Key2: 2q 4 2 54 09064
|
120
|
-
# #
|
121
|
-
# # x#####
|
122
|
-
def to_s
|
123
|
-
string = ''
|
124
|
-
|
125
|
-
raise 'resource_name is required' unless self.resource_name
|
126
|
-
string += "GET " + self.resource_name + " HTTP/1.1\x0d\x0a"
|
127
|
-
|
128
|
-
string += "Upgrade: WebSocket\x0d\x0a"
|
129
|
-
string += "Connection: Upgrade\x0d\x0a"
|
130
|
-
|
131
|
-
raise 'Host is required' unless self.host
|
132
|
-
string += "Host: " + self.host + "\x0d\x0a"
|
133
|
-
|
134
|
-
origin = self.origin || 'http://' + self.host
|
135
|
-
string += "Origin: " + origin + "\x0d\x0a"
|
136
|
-
|
137
|
-
if self.version > 75
|
138
|
-
self.generate_keys
|
139
|
-
|
140
|
-
string += 'Sec-WebSocket-Protocol: ' + self.subprotocol + "\x0d\x0a" if self.subprotocol
|
141
|
-
|
142
|
-
string += 'Sec-WebSocket-Key1: ' + self.key1 + "\x0d\x0a"
|
143
|
-
string += 'Sec-WebSocket-Key2: ' + self.key2 + "\x0d\x0a"
|
144
|
-
|
145
|
-
string += 'Content-Length: ' + self.challenge.length.to_s + "\x0d\x0a"
|
146
|
-
else
|
147
|
-
string += 'WebSocket-Protocol: ' + self.subprotocol + "\x0d\x0a" if self.subprotocol
|
148
|
-
end
|
149
|
-
|
150
|
-
# TODO cookies
|
151
|
-
|
152
|
-
string += "\x0d\x0a"
|
153
|
-
|
154
|
-
string += self.challenge if self.version > 75
|
155
|
-
|
156
|
-
return string
|
157
|
-
end
|
158
|
-
|
159
|
-
protected
|
160
|
-
|
161
|
-
def parse_first_line(line)
|
162
|
-
req, resource_name, http = line.split(' ')
|
163
|
-
|
164
|
-
unless req && resource_name && http
|
165
|
-
self.error = 'Wrong request line'
|
166
|
-
return
|
167
|
-
end
|
168
|
-
|
169
|
-
unless req == 'GET' && http == 'HTTP/1.1'
|
170
|
-
self.error = 'Wrong method or http version'
|
171
|
-
return
|
172
|
-
end
|
173
|
-
|
174
|
-
self.resource_name = resource_name
|
175
|
-
|
176
|
-
return self
|
177
|
-
end
|
178
|
-
|
179
|
-
def parse_body
|
180
|
-
if self.key1 && self.key2
|
181
|
-
return true if @buffer.length < 8
|
182
|
-
|
183
|
-
challenge = @buffer.slice!(0..7)
|
184
|
-
self.challenge = challenge
|
185
|
-
else
|
186
|
-
self.version = 75
|
187
|
-
end
|
188
|
-
|
189
|
-
if @buffer.length > 0
|
190
|
-
self.error = 'Leftovers'
|
191
|
-
return
|
192
|
-
end
|
193
|
-
|
194
|
-
return self if self.finalize
|
195
|
-
|
196
|
-
self.error = 'Not a valid request'
|
197
|
-
return
|
198
|
-
end
|
199
|
-
|
200
|
-
def key(name, value = nil)
|
201
|
-
unless value
|
202
|
-
if value = self.instance_variable_get("@#{name}")
|
203
|
-
self.field("Sec-WebSocket-" + name.capitalize, value)
|
204
|
-
end
|
205
|
-
|
206
|
-
return self.field("Sec-WebSocket-" + name.capitalize)
|
207
|
-
end
|
208
|
-
|
209
|
-
return self.field("Sec-WebSocket-" + name.capitalize, value)
|
210
|
-
|
211
|
-
return self
|
212
|
-
end
|
213
|
-
|
214
|
-
def generate_keys
|
215
|
-
unless self.key1
|
216
|
-
number, key = self.generate_key
|
217
|
-
self.number1 = number
|
218
|
-
self.key1 = key
|
219
|
-
end
|
220
|
-
|
221
|
-
unless self.key2
|
222
|
-
number, key = self.generate_key
|
223
|
-
self.number2 = number
|
224
|
-
self.key2 = key
|
225
|
-
end
|
226
|
-
|
227
|
-
self.challenge ||= self.generate_challenge
|
228
|
-
|
229
|
-
return self
|
230
|
-
end
|
231
|
-
|
232
|
-
NOISE_CHARS = ("\x21".."\x2f").to_a + ("\x3a".."\x7e").to_a # From spec
|
233
|
-
|
234
|
-
def generate_key
|
235
|
-
spaces = 1 + rand(12)
|
236
|
-
max = 4_294_967_295 / spaces
|
237
|
-
number = rand(max + 1)
|
238
|
-
key = (number * spaces).to_s
|
239
|
-
(1 + rand(12)).times do
|
240
|
-
char = NOISE_CHARS[rand(NOISE_CHARS.size)]
|
241
|
-
pos = rand(key.size + 1)
|
242
|
-
key[pos...pos] = char
|
243
|
-
end
|
244
|
-
spaces.times do
|
245
|
-
pos = 1 + rand(key.size - 1)
|
246
|
-
key[pos...pos] = " "
|
247
|
-
end
|
248
|
-
return [number, key]
|
249
|
-
end
|
250
|
-
|
251
|
-
def generate_challenge
|
252
|
-
challenge = ''
|
253
|
-
8.times do
|
254
|
-
challenge += rand(256).chr
|
255
|
-
end
|
256
|
-
|
257
|
-
return challenge
|
258
|
-
end
|
259
|
-
|
260
|
-
def finalize
|
261
|
-
return unless self.upgrade && self.upgrade == 'WebSocket'
|
262
|
-
return unless self.connection && self.connection == 'Upgrade'
|
263
|
-
|
264
|
-
origin = self.field('Origin')
|
265
|
-
return unless origin
|
266
|
-
self.origin = origin
|
267
|
-
|
268
|
-
host = self.field('Host')
|
269
|
-
return unless host
|
270
|
-
self.host = host
|
271
|
-
|
272
|
-
subprotocol = self.field('Sec-WebSocket-Protocol') || self.field('WebSocket-Protocol')
|
273
|
-
self.subprotocol = subprotocol if subprotocol
|
274
|
-
|
275
|
-
cookie = self.build_cookie
|
276
|
-
if cookies = cookie.parse(self.fields['cookie'])
|
277
|
-
self.cookies = cookies
|
278
|
-
end
|
279
|
-
|
280
|
-
return self
|
281
|
-
end
|
282
|
-
|
283
|
-
def build_cookie
|
284
|
-
Cookie::Request.new
|
285
|
-
end
|
286
|
-
|
287
|
-
end
|
288
|
-
end
|