libwebsocket 0.1.5 → 0.1.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -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
@@ -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
@@ -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