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.
@@ -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