libwebsocket 0.1.3 → 0.1.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.
- data/.gitignore +2 -0
- data/.travis.yml +9 -0
- data/CHANGELOG.md +6 -0
- data/Gemfile +3 -0
- data/README.md +3 -3
- data/Rakefile +4 -20
- data/examples/eventmachine_server.rb +1 -1
- data/examples/plain_client.rb +1 -1
- data/examples/thin_server.rb +1 -1
- data/lib/libwebsocket.rb +8 -9
- data/lib/libwebsocket/cookie.rb +1 -1
- data/lib/libwebsocket/cookie/request.rb +4 -4
- data/lib/libwebsocket/frame.rb +155 -28
- data/lib/libwebsocket/frame/error.rb +99 -0
- data/lib/libwebsocket/message.rb +3 -1
- data/lib/libwebsocket/version.rb +3 -0
- data/libwebsocket.gemspec +16 -66
- data/test/libwebsocket/{test_frame.rb → draft-ietf-hybi-00/test_frame.rb} +25 -19
- data/test/libwebsocket/draft-ietf-hybi-10/test_frame.rb +84 -0
- data/test/libwebsocket/{opening_handshake → handshake}/test_client.rb +0 -0
- data/test/libwebsocket/{opening_handshake → handshake}/test_server.rb +0 -0
- data/test/libwebsocket/test_cookie.rb +1 -0
- data/test/libwebsocket/test_url.rb +6 -0
- metadata +61 -12
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/CHANGELOG.md
CHANGED
data/Gemfile
ADDED
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
#
|
1
|
+
# LibWebSocket [](http://travis-ci.org/imanel/libwebsocket)
|
2
2
|
|
3
3
|
A WebSocket message parser/constructor. It is not a server and is not meant to
|
4
4
|
be one. It can be used in any server, event loop etc.
|
@@ -80,9 +80,9 @@ examples directory in the repository.
|
|
80
80
|
|
81
81
|
## Copyright
|
82
82
|
|
83
|
-
Copyright (C)
|
83
|
+
Copyright (C) 2012, Bernard Potocki.
|
84
84
|
|
85
85
|
Based on protocol-websocket perl distribution by Viacheslav Tykhanovskyi.
|
86
86
|
|
87
87
|
This program is free software, you can redistribute it and/or modify it under
|
88
|
-
the MIT License.
|
88
|
+
the MIT License.
|
data/Rakefile
CHANGED
@@ -1,27 +1,11 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
require 'lib/libwebsocket'
|
1
|
+
require 'bundler'
|
2
|
+
Bundler::GemHelper.install_tasks
|
4
3
|
|
5
|
-
|
4
|
+
require 'rake/testtask'
|
6
5
|
|
7
6
|
Rake::TestTask.new do |t|
|
8
7
|
t.libs << "test"
|
9
8
|
t.test_files = FileList['test/**/test_*.rb']
|
10
9
|
end
|
11
10
|
|
12
|
-
|
13
|
-
require 'jeweler'
|
14
|
-
Jeweler::Tasks.new do |gemspec|
|
15
|
-
gemspec.name = "libwebsocket"
|
16
|
-
gemspec.version = LibWebSocket::VERSION
|
17
|
-
gemspec.summary = "Universal Ruby library to handle WebSocket protocol"
|
18
|
-
gemspec.description = "Universal Ruby library to handle WebSocket protocol"
|
19
|
-
gemspec.email = "bernard.potocki@imanel.org"
|
20
|
-
gemspec.homepage = "http://github.com/imanel/libwebsocket"
|
21
|
-
gemspec.authors = ["Bernard Potocki"]
|
22
|
-
gemspec.files.exclude ".gitignore"
|
23
|
-
gemspec.add_dependency "addressable"
|
24
|
-
end
|
25
|
-
rescue LoadError
|
26
|
-
puts "Jeweler not available. Install it with: gem install jeweler"
|
27
|
-
end
|
11
|
+
task :default => :test
|
data/examples/plain_client.rb
CHANGED
data/examples/thin_server.rb
CHANGED
data/lib/libwebsocket.rb
CHANGED
@@ -2,16 +2,15 @@
|
|
2
2
|
# not provide a WebSocket server or client, but is made for using in http servers
|
3
3
|
# or clients to provide WebSocket support.
|
4
4
|
module LibWebSocket
|
5
|
+
class Error < RuntimeError; end # Universal LibWebSocket error class
|
5
6
|
|
6
|
-
|
7
|
-
|
8
|
-
autoload :Cookie, "#{File.dirname(__FILE__)}/libwebsocket/cookie"
|
9
|
-
autoload :Frame, "#{File.dirname(__FILE__)}/libwebsocket/frame"
|
7
|
+
autoload :Cookie, "#{File.dirname(__FILE__)}/libwebsocket/cookie"
|
8
|
+
autoload :Frame, "#{File.dirname(__FILE__)}/libwebsocket/frame"
|
10
9
|
autoload :OpeningHandshake, "#{File.dirname(__FILE__)}/libwebsocket/opening_handshake"
|
11
|
-
autoload :Message,
|
12
|
-
autoload :Request,
|
13
|
-
autoload :Response,
|
14
|
-
autoload :Stateful,
|
15
|
-
autoload :URL,
|
10
|
+
autoload :Message, "#{File.dirname(__FILE__)}/libwebsocket/message"
|
11
|
+
autoload :Request, "#{File.dirname(__FILE__)}/libwebsocket/request"
|
12
|
+
autoload :Response, "#{File.dirname(__FILE__)}/libwebsocket/response"
|
13
|
+
autoload :Stateful, "#{File.dirname(__FILE__)}/libwebsocket/stateful"
|
14
|
+
autoload :URL, "#{File.dirname(__FILE__)}/libwebsocket/url"
|
16
15
|
|
17
16
|
end
|
data/lib/libwebsocket/cookie.rb
CHANGED
@@ -20,12 +20,12 @@ module LibWebSocket
|
|
20
20
|
|
21
21
|
cookie = nil
|
22
22
|
self.pairs.each do |pair|
|
23
|
-
next
|
23
|
+
next unless pair[0]
|
24
24
|
|
25
25
|
if pair[0].match(/^[^\$]/)
|
26
|
-
cookies.push(cookie)
|
26
|
+
cookies.push(cookie) if cookie
|
27
27
|
|
28
|
-
cookie = self.build_cookie(
|
28
|
+
cookie = self.build_cookie(:name => pair[0], :value => pair[1], :version => version)
|
29
29
|
elsif pair[0] == '$Path'
|
30
30
|
cookie.path = pair[1]
|
31
31
|
elsif pair[0] == '$Domain'
|
@@ -33,7 +33,7 @@ module LibWebSocket
|
|
33
33
|
end
|
34
34
|
end
|
35
35
|
|
36
|
-
cookies.push(cookie)
|
36
|
+
cookies.push(cookie) if cookie
|
37
37
|
|
38
38
|
return cookies
|
39
39
|
end
|
data/lib/libwebsocket/frame.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
|
1
3
|
module LibWebSocket
|
2
4
|
# Construct or parse a WebSocket frame.
|
3
5
|
#
|
@@ -5,22 +7,50 @@ module LibWebSocket
|
|
5
7
|
#
|
6
8
|
# # Create frame
|
7
9
|
# frame = LibWebSocket::Frame.new('123')
|
8
|
-
# frame.
|
10
|
+
# frame.to_bytes
|
9
11
|
#
|
10
12
|
# # Parse frames
|
11
13
|
# frame = LibWebSocket::Frame.new
|
12
|
-
# frame.append(
|
13
|
-
# frame.next #
|
14
|
-
# frame.next #
|
14
|
+
# frame.append(...)
|
15
|
+
# frame.next # get next message
|
16
|
+
# frame.next # get another next message
|
15
17
|
class Frame
|
16
18
|
|
17
|
-
|
18
|
-
|
19
|
-
|
19
|
+
autoload :Error, "#{File.dirname(__FILE__)}/frame/error"
|
20
|
+
|
21
|
+
MAX_RAND_INT = 2 ** 32
|
22
|
+
TYPES = {
|
23
|
+
:text => 0x01,
|
24
|
+
:binary => 0x02,
|
25
|
+
:ping => 0x09,
|
26
|
+
:pong => 0x0a,
|
27
|
+
:close => 0x08
|
28
|
+
}
|
29
|
+
|
30
|
+
attr_accessor :buffer, :version, :max_fragments_amount, :max_payload_size, :fin, :rsv, :opcode, :masked
|
20
31
|
|
21
|
-
# Create new
|
22
|
-
|
23
|
-
|
32
|
+
# Create a new Frame instance. Automatically detect if the passed data is a string or bytes.
|
33
|
+
# Options can be buffer or hash with options:
|
34
|
+
# :buffer - content of buffer
|
35
|
+
# :type - frame type(allowed values: text, binary, ping, pong, close)
|
36
|
+
# :version - protocol version(see readme for supported versions)
|
37
|
+
# :max_fragments_amount - max number of message parts per single frame
|
38
|
+
# :max_payload_size - max bytesize of single message
|
39
|
+
# @example
|
40
|
+
# LibWebSocket::Frame->new('data')
|
41
|
+
# LibWebSocket::Frame->new(:buffer => 'data', :type => 'close')
|
42
|
+
def initialize(options = '')
|
43
|
+
if options.is_a?(Hash)
|
44
|
+
options.each {|k,v| instance_variable_set("@#{k}",v) }
|
45
|
+
else
|
46
|
+
@buffer = options
|
47
|
+
end
|
48
|
+
|
49
|
+
@buffer ||= ''
|
50
|
+
@version ||= 'draft-ietf-hybi-10'
|
51
|
+
@fragments = []
|
52
|
+
@max_fragments_amount ||= 128
|
53
|
+
@max_payload_size ||= 65536
|
24
54
|
end
|
25
55
|
|
26
56
|
# Append a frame chunk.
|
@@ -30,37 +60,134 @@ module LibWebSocket
|
|
30
60
|
def append(string = nil)
|
31
61
|
return unless string.is_a?(String)
|
32
62
|
|
33
|
-
|
63
|
+
string.force_encoding("ASCII-8BIT") if string.respond_to?(:force_encoding)
|
34
64
|
|
65
|
+
self.buffer += string
|
35
66
|
return self
|
36
67
|
end
|
37
68
|
|
38
69
|
# Return the next frame.
|
39
70
|
# @example
|
40
|
-
# frame.append(
|
41
|
-
# frame.
|
42
|
-
#
|
43
|
-
# frame.next; # => foo
|
44
|
-
# frame.next; # => bar
|
71
|
+
# frame.append(...)
|
72
|
+
# frame.next; # next message
|
45
73
|
def next
|
46
|
-
|
74
|
+
bytes = self.next_bytes
|
75
|
+
return unless bytes
|
47
76
|
|
48
|
-
|
49
|
-
|
77
|
+
bytes.force_encoding('UTF-8') if bytes.respond_to?(:force_encoding)
|
78
|
+
return bytes
|
79
|
+
end
|
50
80
|
|
51
|
-
|
81
|
+
def opcode
|
82
|
+
@opcode || 1
|
52
83
|
end
|
53
84
|
|
54
|
-
#
|
55
|
-
#
|
56
|
-
#
|
57
|
-
|
58
|
-
def
|
59
|
-
|
85
|
+
def ping?; opcode == TYPES[:ping]; end # Check if frame is a ping request.
|
86
|
+
def pong?; opcode == TYPES[:pong]; end # Check if frame is a pong response.
|
87
|
+
def close?; opcode == TYPES[:close]; end # Check if frame is of close type.
|
88
|
+
def text?; opcode == TYPES[:text]; end # Check if frame is of text type.
|
89
|
+
def binary?; opcode == TYPES[:binary]; end # Check if frame is of binary type.
|
90
|
+
|
91
|
+
# Return the next message as a UTF-8 encoded string.
|
92
|
+
def next_bytes
|
93
|
+
if ['draft-hixie-75', 'draft-ietf-hybi-00'].include? self.version
|
94
|
+
if self.buffer.slice!(/\A\xff\x00/m)
|
95
|
+
self.opcode = TYPES[:close]
|
96
|
+
return ''
|
97
|
+
end
|
98
|
+
|
99
|
+
return unless self.buffer.slice!(/^[^\x00]*\x00(.*?)\xff/m)
|
100
|
+
return $1
|
101
|
+
end
|
102
|
+
|
103
|
+
return unless self.buffer.length >= 2
|
104
|
+
|
105
|
+
while self.buffer.length > 0
|
106
|
+
hdr = self.buffer[0..0]
|
107
|
+
bits = hdr.unpack("B*").first.split(//)
|
108
|
+
|
109
|
+
self.fin = bits[0]
|
110
|
+
self.rsv = bits[1..3]
|
111
|
+
opcode = hdr.unpack('C').first & 0b00001111
|
112
|
+
offset = 1 # FIN,RSV[1-3],OPCODE
|
113
|
+
|
114
|
+
payload_len = buffer[1..1].unpack('C').first
|
115
|
+
self.masked = (payload_len & 0b10000000) >> 7
|
116
|
+
offset += 1 # + MASKED,PAYLOAD_LEN
|
117
|
+
|
118
|
+
payload_len = payload_len & 0b01111111
|
119
|
+
if payload_len == 126
|
120
|
+
return unless self.buffer.length >= offset + 2
|
121
|
+
|
122
|
+
payload_len = self.buffer[offset..offset+2].unpack('n').first
|
123
|
+
offset += 2
|
124
|
+
elsif payload_len > 126
|
125
|
+
return unless self.buffer.length >= offset + 4
|
126
|
+
bits = self.buffer[offset..offset+7].unpack('B*').first
|
127
|
+
bits.gsub!(/^./,'0') # Most significant bit must be 0.
|
128
|
+
bits = bits[32..-1] # No idea how to unpack 64-bit unsigned integer with big-endian byte order
|
129
|
+
payload_len = Array(bits).pack("B*").unpack("N").first
|
130
|
+
offset += 8
|
131
|
+
end
|
132
|
+
|
133
|
+
if payload_len > self.max_payload_size
|
134
|
+
self.buffer = ''
|
135
|
+
raise Error::MessageTooBig.new("Payload is too big. Deny big message (#{payload_len}) or increase max_payload_size (#{self.max_payload_size})")
|
136
|
+
end
|
137
|
+
|
138
|
+
mask = ''
|
139
|
+
if self.masked == 1
|
140
|
+
return unless self.buffer.length >= offset + 4
|
141
|
+
|
142
|
+
mask = self.buffer[offset..offset+4]
|
143
|
+
offset += 4
|
144
|
+
end
|
145
|
+
|
146
|
+
return if self.buffer.length < offset + payload_len
|
147
|
+
|
148
|
+
payload = self.buffer[offset..offset+payload_len-1]
|
149
|
+
payload = self.mask(payload, mask) if self.masked == 1
|
150
|
+
|
151
|
+
self.buffer[0..offset+payload_len-1] = ''
|
152
|
+
|
153
|
+
# Inject control frame
|
154
|
+
if !@fragments.empty? && (opcode & 0b1000 != 0)
|
155
|
+
self.opcode = opcode
|
156
|
+
return payload
|
157
|
+
end
|
158
|
+
|
159
|
+
if self.fin != '0'
|
160
|
+
if @fragments.empty?
|
161
|
+
self.opcode = opcode
|
162
|
+
else
|
163
|
+
self.opcode = @fragments.shift
|
164
|
+
end
|
165
|
+
|
166
|
+
payload = (@fragments + Array(payload)).join
|
167
|
+
@fragments = []
|
168
|
+
return payload
|
169
|
+
else
|
170
|
+
# Remember first fragment opcode
|
171
|
+
@fragments.push(opcode) if @fragments.empty?
|
172
|
+
|
173
|
+
@fragments.push(payload)
|
174
|
+
raise Error::PolicyViolation.new("Too many fragments") if @fragments.size > self.max_fragments_amount
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
return
|
179
|
+
end
|
60
180
|
|
61
|
-
|
181
|
+
protected
|
62
182
|
|
63
|
-
|
183
|
+
def mask(payload, mask)
|
184
|
+
mask = mask.bytes.to_a
|
185
|
+
payload = payload.bytes.to_a
|
186
|
+
payload.each_with_index do |p, i|
|
187
|
+
j = i % 4
|
188
|
+
payload[i] = p ^ mask[j]
|
189
|
+
end
|
190
|
+
return payload.collect(&:chr).join
|
64
191
|
end
|
65
192
|
|
66
193
|
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
module LibWebSocket
|
2
|
+
class Frame
|
3
|
+
|
4
|
+
# Generic close error command for frame. Should not be called directly - use one of subclasses.
|
5
|
+
# This should be catched and responded to according to clean_close? and status.
|
6
|
+
class Error < LibWebSocket::Error
|
7
|
+
|
8
|
+
# Should close frame be sent before closing?
|
9
|
+
# If so then status code should be used to build it.
|
10
|
+
def clean_close?
|
11
|
+
true
|
12
|
+
end
|
13
|
+
|
14
|
+
# Status code for closing frame.
|
15
|
+
def status_code
|
16
|
+
500 # This should not be called directly
|
17
|
+
end
|
18
|
+
|
19
|
+
# 1000 indicates a normal closure, meaning whatever purpose the
|
20
|
+
# connection was established for has been fulfilled.
|
21
|
+
class NormalClosure < Error
|
22
|
+
def status_code; 1000; end
|
23
|
+
end
|
24
|
+
|
25
|
+
# 1001 indicates that an endpoint is "going away", such as a server
|
26
|
+
# going down, or a browser having navigated away from a page.
|
27
|
+
class GoingAway < Error
|
28
|
+
def status_code; 1001; end
|
29
|
+
end
|
30
|
+
|
31
|
+
# 1002 indicates that an endpoint is terminating the connection due
|
32
|
+
# to a protocol error.
|
33
|
+
class ProtocolError < Error
|
34
|
+
def status_code; 1002; end
|
35
|
+
end
|
36
|
+
|
37
|
+
# 1003 indicates that an endpoint is terminating the connection
|
38
|
+
# because it has received a type of data it cannot accept (e.g. an
|
39
|
+
# endpoint that understands only text data MAY send this if it
|
40
|
+
# receives a binary message).
|
41
|
+
class UnsupportedData < Error
|
42
|
+
def status_code; 1003; end
|
43
|
+
end
|
44
|
+
|
45
|
+
# 1005 is a reserved value and MUST NOT be set as a status code in a
|
46
|
+
# Close control frame by an endpoint. It is designated for use in
|
47
|
+
# applications expecting a status code to indicate that no status
|
48
|
+
# code was actually present.
|
49
|
+
class NoStatusRcvd < Error
|
50
|
+
def status_code; 1005; end
|
51
|
+
end
|
52
|
+
|
53
|
+
# 1006 is a reserved value and MUST NOT be set as a status code in a
|
54
|
+
# Close control frame by an endpoint. It is designated for use in
|
55
|
+
# applications expecting a status code to indicate that the
|
56
|
+
# connection was closed abnormally, e.g. without sending or
|
57
|
+
# receiving a Close control frame.
|
58
|
+
class AbnormalClosure < Error
|
59
|
+
def status_code; 1006; end
|
60
|
+
end
|
61
|
+
|
62
|
+
# 1007 indicates that an endpoint is terminating the connection
|
63
|
+
# because it has received data within a message that was not
|
64
|
+
# consistent with the type of the message (e.g., non-UTF-8 [RFC3629]
|
65
|
+
# data within a text message).
|
66
|
+
class InvalidFramePayloadData < Error
|
67
|
+
def status_code; 1007; end
|
68
|
+
end
|
69
|
+
|
70
|
+
# 1008 indicates that an endpoint is terminating the connection
|
71
|
+
# because it has received a message that violates its policy. This
|
72
|
+
# is a generic status code that can be returned when there is no
|
73
|
+
# other more suitable status code (e.g. 1003 or 1009), or if there
|
74
|
+
# is a need to hide specific details about the policy.
|
75
|
+
class PolicyViolation < Error
|
76
|
+
def status_code; 1008; end
|
77
|
+
end
|
78
|
+
|
79
|
+
# 1009 indicates that an endpoint is terminating the connection
|
80
|
+
# because it has received a message which is too big for it to
|
81
|
+
# process.
|
82
|
+
class MessageTooBig < Error
|
83
|
+
def status_code; 1009; end
|
84
|
+
end
|
85
|
+
|
86
|
+
# 1010 indicates that an endpoint (client) is terminating the
|
87
|
+
# connection because it has expected the server to negotiate one or
|
88
|
+
# more extension, but the server didn't return them in the response
|
89
|
+
# message of the WebSocket handshake. The list of extensions which
|
90
|
+
# are needed SHOULD appear in the /reason/ part of the Close frame.
|
91
|
+
# Note that this status code is not used by the server, because it
|
92
|
+
# can fail the WebSocket handshake instead.
|
93
|
+
class MandatoryExtension < Error
|
94
|
+
def status_code; 1010; end
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
data/lib/libwebsocket/message.rb
CHANGED
@@ -126,7 +126,9 @@ module LibWebSocket
|
|
126
126
|
def append(data)
|
127
127
|
return if self.error
|
128
128
|
|
129
|
-
|
129
|
+
data = data.encode("ASCII-8BIT") if data.respond_to?(:encode)
|
130
|
+
|
131
|
+
@buffer << data
|
130
132
|
|
131
133
|
if @buffer.length > @max_message_size
|
132
134
|
self.error = 'Message is too long'
|
data/libwebsocket.gemspec
CHANGED
@@ -1,72 +1,22 @@
|
|
1
|
-
# Generated by jeweler
|
2
|
-
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
-
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
1
|
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "libwebsocket/version"
|
5
4
|
|
6
5
|
Gem::Specification.new do |s|
|
7
|
-
s.name
|
8
|
-
s.version
|
6
|
+
s.name = "libwebsocket"
|
7
|
+
s.version = LibWebSocket::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ["Bernard Potocki"]
|
10
|
+
s.email = ["bernard.potocki@imanel.org"]
|
11
|
+
s.homepage = "http://github.com/imanel/libwebsocket"
|
12
|
+
s.summary = %q{Universal Ruby library to handle WebSocket protocol}
|
13
|
+
s.description = %q{Universal Ruby library to handle WebSocket protocol}
|
9
14
|
|
10
|
-
s.
|
11
|
-
s.
|
12
|
-
s.date = "2012-03-20"
|
13
|
-
s.description = "Universal Ruby library to handle WebSocket protocol"
|
14
|
-
s.email = "bernard.potocki@imanel.org"
|
15
|
-
s.extra_rdoc_files = [
|
16
|
-
"README.md"
|
17
|
-
]
|
18
|
-
s.files = [
|
19
|
-
"CHANGELOG.md",
|
20
|
-
"README.md",
|
21
|
-
"Rakefile",
|
22
|
-
"examples/eventmachine_server.rb",
|
23
|
-
"examples/plain_client.rb",
|
24
|
-
"examples/thin_server.rb",
|
25
|
-
"lib/libwebsocket.rb",
|
26
|
-
"lib/libwebsocket/cookie.rb",
|
27
|
-
"lib/libwebsocket/cookie/request.rb",
|
28
|
-
"lib/libwebsocket/cookie/response.rb",
|
29
|
-
"lib/libwebsocket/frame.rb",
|
30
|
-
"lib/libwebsocket/message.rb",
|
31
|
-
"lib/libwebsocket/opening_handshake.rb",
|
32
|
-
"lib/libwebsocket/opening_handshake/client.rb",
|
33
|
-
"lib/libwebsocket/opening_handshake/server.rb",
|
34
|
-
"lib/libwebsocket/request.rb",
|
35
|
-
"lib/libwebsocket/response.rb",
|
36
|
-
"lib/libwebsocket/stateful.rb",
|
37
|
-
"lib/libwebsocket/url.rb",
|
38
|
-
"libwebsocket.gemspec",
|
39
|
-
"test/libwebsocket/cookie/request.rb",
|
40
|
-
"test/libwebsocket/cookie/response.rb",
|
41
|
-
"test/libwebsocket/opening_handshake/test_client.rb",
|
42
|
-
"test/libwebsocket/opening_handshake/test_server.rb",
|
43
|
-
"test/libwebsocket/test_cookie.rb",
|
44
|
-
"test/libwebsocket/test_frame.rb",
|
45
|
-
"test/libwebsocket/test_message.rb",
|
46
|
-
"test/libwebsocket/test_request_75.rb",
|
47
|
-
"test/libwebsocket/test_request_76.rb",
|
48
|
-
"test/libwebsocket/test_request_common.rb",
|
49
|
-
"test/libwebsocket/test_response_75.rb",
|
50
|
-
"test/libwebsocket/test_response_76.rb",
|
51
|
-
"test/libwebsocket/test_response_common.rb",
|
52
|
-
"test/libwebsocket/test_url.rb",
|
53
|
-
"test/test_helper.rb"
|
54
|
-
]
|
55
|
-
s.homepage = "http://github.com/imanel/libwebsocket"
|
56
|
-
s.require_paths = ["lib"]
|
57
|
-
s.rubygems_version = "1.8.11"
|
58
|
-
s.summary = "Universal Ruby library to handle WebSocket protocol"
|
59
|
-
|
60
|
-
if s.respond_to? :specification_version then
|
61
|
-
s.specification_version = 3
|
15
|
+
s.add_dependency 'addressable'
|
16
|
+
s.add_development_dependency 'rake'
|
62
17
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
end
|
68
|
-
else
|
69
|
-
s.add_dependency(%q<addressable>, [">= 0"])
|
70
|
-
end
|
18
|
+
s.files = `git ls-files`.split("\n")
|
19
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
20
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
21
|
+
s.require_paths = ["lib"]
|
71
22
|
end
|
72
|
-
|
@@ -1,9 +1,10 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
1
2
|
require 'test_helper'
|
2
3
|
|
3
4
|
class TestFrame < Test::Unit::TestCase
|
4
5
|
|
5
|
-
def
|
6
|
-
f = LibWebSocket::Frame.new
|
6
|
+
def test_append_ietf_hybi_00
|
7
|
+
f = LibWebSocket::Frame.new(:version => 'draft-ietf-hybi-00')
|
7
8
|
|
8
9
|
f.append
|
9
10
|
assert_nil f.next
|
@@ -21,6 +22,11 @@ class TestFrame < Test::Unit::TestCase
|
|
21
22
|
f.append("\xff")
|
22
23
|
assert_equal '', f.next
|
23
24
|
|
25
|
+
f.append("\xff")
|
26
|
+
f.append("\x00")
|
27
|
+
assert_equal '', f.next
|
28
|
+
assert f.close?
|
29
|
+
|
24
30
|
f.append("\x00")
|
25
31
|
assert_nil f.next
|
26
32
|
f.append("foo")
|
@@ -38,28 +44,28 @@ class TestFrame < Test::Unit::TestCase
|
|
38
44
|
assert_nil f.next
|
39
45
|
|
40
46
|
# We append bytes, but read characters
|
41
|
-
msg =
|
47
|
+
msg = '☺'
|
42
48
|
f.append("\x00" + msg + "\xff")
|
43
49
|
msg.force_encoding('UTF-8') if msg.respond_to?(:force_encoding)
|
44
50
|
assert_equal msg, f.next
|
45
51
|
end
|
46
52
|
|
47
|
-
def
|
48
|
-
f = LibWebSocket::Frame.new
|
49
|
-
msg = "\x00\xff"
|
50
|
-
msg.force_encoding('UTF-8') if msg.respond_to?(:force_encoding)
|
51
|
-
assert_equal msg, f.to_s
|
52
|
-
|
53
|
-
f = LibWebSocket::Frame.new('123')
|
54
|
-
msg = "\x00123\xff"
|
55
|
-
msg.force_encoding('UTF-8') if msg.respond_to?(:force_encoding)
|
56
|
-
assert_equal msg, f.to_s
|
57
|
-
|
58
|
-
# We pass characters, but send bytes
|
59
|
-
f = LibWebSocket::Frame.new("\342\230\272")
|
60
|
-
msg = "\x00\342\230\272\xff"
|
61
|
-
msg.force_encoding('UTF-8') if msg.respond_to?(:force_encoding)
|
62
|
-
assert_equal msg, f.to_s
|
53
|
+
def test_new_ietf_hybi_00
|
54
|
+
# f = LibWebSocket::Frame.new(:version => 'draft-ietf-hybi-00')
|
55
|
+
# msg = "\x00\xff"
|
56
|
+
# msg.force_encoding('UTF-8') if msg.respond_to?(:force_encoding)
|
57
|
+
# assert_equal msg, f.to_s
|
58
|
+
#
|
59
|
+
# f = LibWebSocket::Frame.new('123')
|
60
|
+
# msg = "\x00123\xff"
|
61
|
+
# msg.force_encoding('UTF-8') if msg.respond_to?(:force_encoding)
|
62
|
+
# assert_equal msg, f.to_s
|
63
|
+
#
|
64
|
+
# # We pass characters, but send bytes
|
65
|
+
# f = LibWebSocket::Frame.new("\342\230\272")
|
66
|
+
# msg = "\x00\342\230\272\xff"
|
67
|
+
# msg.force_encoding('UTF-8') if msg.respond_to?(:force_encoding)
|
68
|
+
# assert_equal msg, f.to_s
|
63
69
|
end
|
64
70
|
|
65
71
|
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
require 'test_helper'
|
3
|
+
|
4
|
+
class TestFrame < Test::Unit::TestCase
|
5
|
+
|
6
|
+
def test_append_ietf_hybi_10
|
7
|
+
f = LibWebSocket::Frame.new(:version => 'draft-ietf-hybi-10')
|
8
|
+
|
9
|
+
f.append
|
10
|
+
assert_nil f.next
|
11
|
+
f.append('')
|
12
|
+
assert_nil f.next
|
13
|
+
|
14
|
+
# Not masked
|
15
|
+
f.append ["810548656c6c6f"].pack('H*')
|
16
|
+
assert_equal 'Hello', f.next_bytes
|
17
|
+
assert_equal 1, f.opcode
|
18
|
+
assert f.text?
|
19
|
+
|
20
|
+
# Multi
|
21
|
+
f.append(["810548656c6c6f"].pack('H*') + ["810548656c6c6f"].pack('H*'))
|
22
|
+
assert_equal 'Hello', f.next_bytes
|
23
|
+
assert_equal 'Hello', f.next_bytes
|
24
|
+
|
25
|
+
# Masked
|
26
|
+
f.append ["818537fa213d7f9f4d5158"].pack('H*')
|
27
|
+
assert_equal 'Hello', f.next_bytes
|
28
|
+
assert_equal 1, f.opcode
|
29
|
+
|
30
|
+
# Fragments
|
31
|
+
f.append ["010348656c"].pack("H*")
|
32
|
+
assert_nil f.next_bytes
|
33
|
+
f.append ["80026c6f"].pack("H*")
|
34
|
+
assert_equal 'Hello', f.next_bytes
|
35
|
+
assert_equal 1, f.opcode
|
36
|
+
|
37
|
+
# Multi fragments
|
38
|
+
f.append(["010348656c"].pack('H*') + ["80026c6f"].pack('H*'))
|
39
|
+
assert_equal 'Hello', f.next_bytes
|
40
|
+
assert_equal 1, f.opcode
|
41
|
+
|
42
|
+
# Injected control frame (1 fragment, ping, 2 fragment)
|
43
|
+
f.append ["010348656c"].pack('H*')
|
44
|
+
f.append ["890548656c6c6f"].pack('H*')
|
45
|
+
f.append ["80026c6f"].pack('H*')
|
46
|
+
assert_equal 'Hello', f.next_bytes
|
47
|
+
assert_equal 9, f.opcode
|
48
|
+
assert_equal 'Hello', f.next_bytes
|
49
|
+
assert_equal 1, f.opcode
|
50
|
+
|
51
|
+
# Too many fragments
|
52
|
+
130.times { f.append(["010348656c"].pack('H*')) }
|
53
|
+
assert_raise(LibWebSocket::Frame::Error::PolicyViolation) { f.next_bytes }
|
54
|
+
|
55
|
+
# Ping request
|
56
|
+
f = LibWebSocket::Frame.new(:version => 'draft-ietf-hybi-10')
|
57
|
+
f.append ["890548656c6c6f"].pack('H*')
|
58
|
+
assert_equal 'Hello', f.next_bytes
|
59
|
+
assert_equal 9, f.opcode
|
60
|
+
assert f.ping?
|
61
|
+
|
62
|
+
# Ping response
|
63
|
+
f.append ["8a0548656c6c6f"].pack('H*')
|
64
|
+
assert_equal 'Hello', f.next_bytes
|
65
|
+
assert_equal 10, f.opcode
|
66
|
+
assert f.pong?
|
67
|
+
|
68
|
+
# 256 bytes
|
69
|
+
f.append ["827E0100" + '05' * 256].pack('H*')
|
70
|
+
assert_equal 256, f.next_bytes.length
|
71
|
+
assert_equal 2, f.opcode
|
72
|
+
assert f.binary?
|
73
|
+
|
74
|
+
# 64KiB
|
75
|
+
f.append ["827F0000000000010000" + '05' * 65536].pack('H*')
|
76
|
+
assert_equal 65536, f.next_bytes.length
|
77
|
+
assert_equal 2, f.opcode
|
78
|
+
|
79
|
+
# Too big frame
|
80
|
+
f.append ["827F0000000000100000" + '05' * (65536 + 1)].pack('H*')
|
81
|
+
assert_raise(LibWebSocket::Frame::Error::MessageTooBig) { f.next_bytes }
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
File without changes
|
File without changes
|
@@ -4,6 +4,7 @@ class TestCookie < Test::Unit::TestCase
|
|
4
4
|
|
5
5
|
def test_parse
|
6
6
|
cookie = LibWebSocket::Cookie.new
|
7
|
+
assert_nil cookie.parse
|
7
8
|
assert_nil cookie.parse('')
|
8
9
|
assert cookie.parse('foo=bar; baz = zab; hello= "the;re"; here')
|
9
10
|
assert_equal [['foo', 'bar'], ['baz', 'zab'], ['hello', 'the;re'], ['here', nil]], cookie.pairs
|
@@ -3,6 +3,12 @@ require 'test_helper'
|
|
3
3
|
class TestURL < Test::Unit::TestCase
|
4
4
|
|
5
5
|
def test_parse
|
6
|
+
url = LibWebSocket::URL.new
|
7
|
+
assert url.parse('ws://example.com')
|
8
|
+
assert !url.secure
|
9
|
+
assert_equal 'example.com', url.host
|
10
|
+
assert_equal '/', url.resource_name
|
11
|
+
|
6
12
|
url = LibWebSocket::URL.new
|
7
13
|
assert url.parse('ws://example.com/')
|
8
14
|
assert !url.secure
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: libwebsocket
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.4
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-
|
12
|
+
date: 2012-07-13 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: addressable
|
16
|
-
requirement:
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ! '>='
|
@@ -21,15 +21,39 @@ dependencies:
|
|
21
21
|
version: '0'
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
|
-
version_requirements:
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: rake
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
25
46
|
description: Universal Ruby library to handle WebSocket protocol
|
26
|
-
email:
|
47
|
+
email:
|
48
|
+
- bernard.potocki@imanel.org
|
27
49
|
executables: []
|
28
50
|
extensions: []
|
29
|
-
extra_rdoc_files:
|
30
|
-
- README.md
|
51
|
+
extra_rdoc_files: []
|
31
52
|
files:
|
53
|
+
- .gitignore
|
54
|
+
- .travis.yml
|
32
55
|
- CHANGELOG.md
|
56
|
+
- Gemfile
|
33
57
|
- README.md
|
34
58
|
- Rakefile
|
35
59
|
- examples/eventmachine_server.rb
|
@@ -40,6 +64,7 @@ files:
|
|
40
64
|
- lib/libwebsocket/cookie/request.rb
|
41
65
|
- lib/libwebsocket/cookie/response.rb
|
42
66
|
- lib/libwebsocket/frame.rb
|
67
|
+
- lib/libwebsocket/frame/error.rb
|
43
68
|
- lib/libwebsocket/message.rb
|
44
69
|
- lib/libwebsocket/opening_handshake.rb
|
45
70
|
- lib/libwebsocket/opening_handshake/client.rb
|
@@ -48,13 +73,15 @@ files:
|
|
48
73
|
- lib/libwebsocket/response.rb
|
49
74
|
- lib/libwebsocket/stateful.rb
|
50
75
|
- lib/libwebsocket/url.rb
|
76
|
+
- lib/libwebsocket/version.rb
|
51
77
|
- libwebsocket.gemspec
|
52
78
|
- test/libwebsocket/cookie/request.rb
|
53
79
|
- test/libwebsocket/cookie/response.rb
|
54
|
-
- test/libwebsocket/
|
55
|
-
- test/libwebsocket/
|
80
|
+
- test/libwebsocket/draft-ietf-hybi-00/test_frame.rb
|
81
|
+
- test/libwebsocket/draft-ietf-hybi-10/test_frame.rb
|
82
|
+
- test/libwebsocket/handshake/test_client.rb
|
83
|
+
- test/libwebsocket/handshake/test_server.rb
|
56
84
|
- test/libwebsocket/test_cookie.rb
|
57
|
-
- test/libwebsocket/test_frame.rb
|
58
85
|
- test/libwebsocket/test_message.rb
|
59
86
|
- test/libwebsocket/test_request_75.rb
|
60
87
|
- test/libwebsocket/test_request_76.rb
|
@@ -76,16 +103,38 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
76
103
|
- - ! '>='
|
77
104
|
- !ruby/object:Gem::Version
|
78
105
|
version: '0'
|
106
|
+
segments:
|
107
|
+
- 0
|
108
|
+
hash: -2462651630557065482
|
79
109
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
80
110
|
none: false
|
81
111
|
requirements:
|
82
112
|
- - ! '>='
|
83
113
|
- !ruby/object:Gem::Version
|
84
114
|
version: '0'
|
115
|
+
segments:
|
116
|
+
- 0
|
117
|
+
hash: -2462651630557065482
|
85
118
|
requirements: []
|
86
119
|
rubyforge_project:
|
87
|
-
rubygems_version: 1.8.
|
120
|
+
rubygems_version: 1.8.24
|
88
121
|
signing_key:
|
89
122
|
specification_version: 3
|
90
123
|
summary: Universal Ruby library to handle WebSocket protocol
|
91
|
-
test_files:
|
124
|
+
test_files:
|
125
|
+
- test/libwebsocket/cookie/request.rb
|
126
|
+
- test/libwebsocket/cookie/response.rb
|
127
|
+
- test/libwebsocket/draft-ietf-hybi-00/test_frame.rb
|
128
|
+
- test/libwebsocket/draft-ietf-hybi-10/test_frame.rb
|
129
|
+
- test/libwebsocket/handshake/test_client.rb
|
130
|
+
- test/libwebsocket/handshake/test_server.rb
|
131
|
+
- test/libwebsocket/test_cookie.rb
|
132
|
+
- test/libwebsocket/test_message.rb
|
133
|
+
- test/libwebsocket/test_request_75.rb
|
134
|
+
- test/libwebsocket/test_request_76.rb
|
135
|
+
- test/libwebsocket/test_request_common.rb
|
136
|
+
- test/libwebsocket/test_response_75.rb
|
137
|
+
- test/libwebsocket/test_response_76.rb
|
138
|
+
- test/libwebsocket/test_response_common.rb
|
139
|
+
- test/libwebsocket/test_url.rb
|
140
|
+
- test/test_helper.rb
|