libwebsocket 0.0.4

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.
@@ -0,0 +1,67 @@
1
+ module LibWebSocket
2
+ # Construct or parse a WebSocket frame.
3
+ #
4
+ # SYNOPSIS
5
+ #
6
+ # # Create frame
7
+ # frame = LibWebSocket::Frame.new('123')
8
+ # frame.to_s # \x00123\xff
9
+ #
10
+ # # Parse frames
11
+ # frame = LibWebSocket::Frame.new
12
+ # frame.append("123\x00foo\xff56\x00bar\xff789")
13
+ # frame.next # => foo
14
+ # frame.next # => bar
15
+ class Frame
16
+
17
+ def initialize(buffer = nil)
18
+ @buffer = buffer || ''
19
+ end
20
+
21
+ # Create new frame without modification of current
22
+ def new(buffer = nil)
23
+ self.class.new(buffer)
24
+ end
25
+
26
+ # Append a frame chunk.
27
+ # @example
28
+ # frame.append("\x00foo")
29
+ # frame.append("bar\xff")
30
+ def append(string = nil)
31
+ return unless string.is_a?(String)
32
+
33
+ @buffer += string
34
+
35
+ return self
36
+ end
37
+
38
+ # Return the next frame.
39
+ # @example
40
+ # frame.append("\x00foo")
41
+ # frame.append("\xff\x00bar\xff")
42
+ #
43
+ # frame.next; # => foo
44
+ # frame.next; # => bar
45
+ def next
46
+ return unless @buffer.slice!(/^[^\x00]*\x00(.*?)\xff/m)
47
+
48
+ string = $1
49
+ string.force_encoding('UTF-8') if string.respond_to?(:force_encoding)
50
+
51
+ return string
52
+ end
53
+
54
+ # Construct a WebSocket frame.
55
+ # @example
56
+ # frame = LibWebSocket::Frame.new('foo')
57
+ # frame.to_s # => \x00foo\xff
58
+ def to_s
59
+ ary = ["\x00", @buffer.dup, "\xff"]
60
+
61
+ ary.collect{ |s| s.force_encoding('UTF-8') if s.respond_to?(:force_encoding) }
62
+
63
+ return ary.join
64
+ end
65
+
66
+ end
67
+ end
@@ -0,0 +1,28 @@
1
+ module LibWebSocket
2
+ # This is a base class for LibWebSocket::Handshake::Client and LibWebSocket::Handshake::Server.
3
+ class Handshake
4
+
5
+ autoload :Client, "#{File.dirname(__FILE__)}/handshake/client"
6
+ autoload :Server, "#{File.dirname(__FILE__)}/handshake/server"
7
+
8
+ attr_accessor :secure, :error
9
+
10
+ # Convert all hash keys to instance variables.
11
+ def initialize(hash = {})
12
+ hash.each do |k,v|
13
+ instance_variable_set("@#{k}",v)
14
+ end
15
+ end
16
+
17
+ # WebSocket request object.
18
+ def req
19
+ @req ||= Request.new
20
+ end
21
+
22
+ # WebSocket response object.
23
+ def res
24
+ @res ||= Response.new
25
+ end
26
+
27
+ end
28
+ end
@@ -0,0 +1,129 @@
1
+ module LibWebSocket
2
+ class Handshake
3
+ # Construct or parse a client WebSocket handshake. This module is written for
4
+ # convenience, since using request and response directly requires the same code
5
+ # again and again.
6
+ #
7
+ # SYNOPSIS
8
+ #
9
+ # h = LibWebSocket::Handshake::Client.new(:url => 'ws://example.com')
10
+ #
11
+ # # Create request
12
+ # h.to_s # GET /demo HTTP/1.1
13
+ # # Upgrade: WebSocket
14
+ # # Connection: Upgrade
15
+ # # Host: example.com
16
+ # # Origin: http://example.com
17
+ # # Sec-WebSocket-Key1: 18x 6]8vM;54 *(5: { U1]8 z [ 8
18
+ # # Sec-WebSocket-Key2: 1_ tx7X d < nw 334J702) 7]o}` 0
19
+ # #
20
+ # # Tm[K T2u
21
+ #
22
+ # # Parse server response
23
+ # h.parse \<<EOF
24
+ # HTTP/1.1 101 WebSocket Protocol Handshake
25
+ # Upgrade: WebSocket
26
+ # Connection: Upgrade
27
+ # Sec-WebSocket-Origin: http://example.com
28
+ # Sec-WebSocket-Location: ws://example.com/demo
29
+ #
30
+ # fQJ,fN/4F4!~K~MH
31
+ # EOF
32
+ #
33
+ # h.error # Check if there were any errors
34
+ # h.done? # Returns true
35
+ class Client < Handshake
36
+
37
+ attr_accessor :url
38
+
39
+ def initialize(hash = {})
40
+ super
41
+
42
+ self.set_url(self.url) if self.url
43
+ end
44
+
45
+ # Set or get WebSocket url.
46
+ # @see LibWebSocket::URL#initialize
47
+ # @example
48
+ # handshake.url = 'ws://example.com/demo'
49
+ def url=(val)
50
+ self.set_url(val)
51
+
52
+ return self
53
+ end
54
+
55
+ # Parse server response
56
+ # @example
57
+ # h.parse \<<EOF
58
+ # HTTP/1.1 101 WebSocket Protocol Handshake
59
+ # Upgrade: WebSocket
60
+ # Connection: Upgrade
61
+ # Sec-WebSocket-Origin: http://example.com
62
+ # Sec-WebSocket-Location: ws://example.com/demo
63
+ #
64
+ # fQJ,fN/4F4!~K~MH
65
+ # EOF
66
+ def parse(opts)
67
+ req = self.req
68
+ res = self.res
69
+
70
+ unless res.done?
71
+ unless res.parse(opts)
72
+ self.error = res.error
73
+ return
74
+ end
75
+
76
+ if res.done?
77
+ if req.version > 75 && req.checksum != res.checksum
78
+ self.error = 'Checksum is wrong.'
79
+ return
80
+ end
81
+ end
82
+ end
83
+
84
+ return true
85
+ end
86
+
87
+ # Check if response is correct
88
+ def done?
89
+ res.done?
90
+ end
91
+
92
+ # Create request
93
+ # @example
94
+ # h.to_s # GET /demo HTTP/1.1
95
+ # # Upgrade: WebSocket
96
+ # # Connection: Upgrade
97
+ # # Host: example.com
98
+ # # Origin: http://example.com
99
+ # # Sec-WebSocket-Key1: 18x 6]8vM;54 *(5: { U1]8 z [ 8
100
+ # # Sec-WebSocket-Key2: 1_ tx7X d < nw 334J702) 7]o}` 0
101
+ # #
102
+ # # Tm[K T2u
103
+ def to_s
104
+ req.to_s
105
+ end
106
+
107
+ protected
108
+
109
+ def build_url
110
+ LibWebSocket::URL.new
111
+ end
112
+
113
+ def set_url(url)
114
+ @url = self.build_url.parse(url) unless url.is_a?(LibWebSocket::URL)
115
+
116
+ req = self.req
117
+
118
+ host = @url.host
119
+ host += ':' + @url.port if @url.port
120
+ req.host = host
121
+
122
+ req.resource_name = @url.resource_name
123
+
124
+ return self
125
+ end
126
+
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,114 @@
1
+ module LibWebSocket
2
+ class Handshake
3
+ # Construct or parse a server WebSocket handshake. This module is written for
4
+ # convenience, since using request and response directly requires the same code
5
+ # again and again.
6
+ #
7
+ # SYNOPSIS
8
+ #
9
+ # h = LibWebSocket::Handshake::Server.new
10
+ #
11
+ # # Parse client request
12
+ # h.parse \<<EOF
13
+ # GET /demo HTTP/1.1
14
+ # Upgrade: WebSocket
15
+ # Connection: Upgrade
16
+ # Host: example.com
17
+ # Origin: http://example.com
18
+ # Sec-WebSocket-Key1: 18x 6]8vM;54 *(5: { U1]8 z [ 8
19
+ # Sec-WebSocket-Key2: 1_ tx7X d < nw 334J702) 7]o}` 0
20
+ #
21
+ # Tm[K T2u
22
+ # EOF
23
+ #
24
+ # h.error # Check if there were any errors
25
+ # h.idone? # Returns true
26
+ #
27
+ # # Create response
28
+ # h.to_s # HTTP/1.1 101 WebSocket Protocol Handshake
29
+ # # Upgrade: WebSocket
30
+ # # Connection: Upgrade
31
+ # # Sec-WebSocket-Origin: http://example.com
32
+ # # Sec-WebSocket-Location: ws://example.com/demo
33
+ # #
34
+ # # fQJ,fN/4F4!~K~MH
35
+ class Server < Handshake
36
+
37
+ # Parse client request
38
+ # @example
39
+ # h.parse \<<EOF
40
+ # GET /demo HTTP/1.1
41
+ # Upgrade: WebSocket
42
+ # Connection: Upgrade
43
+ # Host: example.com
44
+ # Origin: http://example.com
45
+ # Sec-WebSocket-Key1: 18x 6]8vM;54 *(5: { U1]8 z [ 8
46
+ # Sec-WebSocket-Key2: 1_ tx7X d < nw 334J702) 7]o}` 0
47
+ #
48
+ # Tm[K T2u
49
+ # EOF
50
+ def parse(opts)
51
+ req = self.req
52
+ res = self.res
53
+
54
+ unless req.done?
55
+ unless req.parse(opts)
56
+ self.error = req.error
57
+ return
58
+ end
59
+
60
+ if req.done?
61
+ res.version = req.version
62
+ res.host = req.host
63
+
64
+ # res.secure = req.secure
65
+ res.resource_name = req.resource_name
66
+ res.origin = req.origin
67
+
68
+ if req.version > 75
69
+ res.number1 = req.number1
70
+ res.number2 = req.number2
71
+ res.challenge = req.challenge
72
+ end
73
+ end
74
+ end
75
+
76
+ return true
77
+ end
78
+
79
+ # Check if request is correct and done
80
+ def done?
81
+ req.done?
82
+ end
83
+
84
+ # Create response in string format
85
+ # @example
86
+ # h.to_s # HTTP/1.1 101 WebSocket Protocol Handshake
87
+ # # Upgrade: WebSocket
88
+ # # Connection: Upgrade
89
+ # # Sec-WebSocket-Origin: http://example.com
90
+ # # Sec-WebSocket-Location: ws://example.com/demo
91
+ # #
92
+ # # fQJ,fN/4F4!~K~MH
93
+ def to_s
94
+ res.to_s
95
+ end
96
+
97
+ # Create response in rack format
98
+ # @example
99
+ # h.to_rack # [ 101,
100
+ # # {
101
+ # # 'Upgrade' => 'WebSocket'
102
+ # # 'Connection' => 'Upgrade'
103
+ # # 'Sec-WebSocket-Origin' => 'http://example.com'
104
+ # # 'Sec-WebSocket-Location' => 'ws://example.com/demo'
105
+ # # 'Content-Length' => 16
106
+ # # },
107
+ # # [ 'fQJ,fN/4F4!~K~MH' ] ]
108
+ def to_rack
109
+ res.to_rack
110
+ end
111
+
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,167 @@
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