stomp 1.2.4 → 1.2.5
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.rdoc +11 -0
- data/README.rdoc +38 -26
- data/Rakefile +3 -0
- data/bin/catstomp +34 -34
- data/bin/stompcat +36 -36
- data/examples/client11_ex1.rb +64 -55
- data/examples/client11_putget1.rb +47 -35
- data/examples/conn11_ex1.rb +59 -51
- data/examples/conn11_ex2.rb +59 -50
- data/examples/conn11_hb1.rb +35 -26
- data/examples/consumer.rb +25 -12
- data/examples/get11conn_ex1.rb +97 -89
- data/examples/get11conn_ex2.rb +55 -47
- data/examples/logexamp.rb +66 -52
- data/examples/logexamp_ssl.rb +66 -52
- data/examples/publisher.rb +21 -10
- data/examples/put11conn_ex1.rb +35 -24
- data/examples/putget11_rh1.rb +66 -56
- data/examples/slogger.rb +65 -52
- data/examples/ssl_uc1.rb +24 -13
- data/examples/ssl_uc1_ciphers.rb +28 -15
- data/examples/ssl_uc2.rb +26 -16
- data/examples/ssl_uc2_ciphers.rb +31 -18
- data/examples/ssl_uc3.rb +25 -14
- data/examples/ssl_uc3_ciphers.rb +31 -18
- data/examples/ssl_uc4.rb +26 -15
- data/examples/ssl_uc4_ciphers.rb +32 -19
- data/examples/ssl_ucx_default_ciphers.rb +25 -12
- data/examples/stomp11_common.rb +16 -15
- data/examples/topic_consumer.rb +23 -10
- data/examples/topic_publisher.rb +22 -8
- data/lib/client/utils.rb +116 -0
- data/lib/connection/heartbeats.rb +173 -0
- data/lib/connection/netio.rb +322 -0
- data/lib/connection/utf8.rb +294 -0
- data/lib/connection/utils.rb +104 -0
- data/lib/stomp/client.rb +127 -179
- data/lib/stomp/codec.rb +5 -2
- data/lib/stomp/connection.rb +109 -865
- data/lib/stomp/constants.rb +52 -33
- data/lib/stomp/errors.rb +56 -5
- data/lib/stomp/ext/hash.rb +4 -0
- data/lib/stomp/message.rb +49 -29
- data/lib/stomp/sslparams.rb +83 -71
- data/lib/stomp/version.rb +3 -1
- data/lib/stomp.rb +18 -9
- data/stomp.gemspec +58 -3
- data/test/test_client.rb +28 -1
- data/test/test_codec.rb +8 -2
- data/test/test_connection.rb +29 -0
- data/test/test_connection1p.rb +31 -16
- data/test/test_helper.rb +20 -3
- data/test/test_message.rb +8 -3
- data/test/test_ssl.rb +10 -4
- data/test/tlogger.rb +16 -15
- metadata +59 -4
@@ -0,0 +1,322 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
require 'socket'
|
4
|
+
require 'timeout'
|
5
|
+
require 'io/wait'
|
6
|
+
require 'digest/sha1'
|
7
|
+
|
8
|
+
module Stomp
|
9
|
+
|
10
|
+
class Connection
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
# Really read from the wire.
|
15
|
+
def _receive(read_socket)
|
16
|
+
@read_semaphore.synchronize do
|
17
|
+
line = ''
|
18
|
+
if @protocol == Stomp::SPL_10 || (@protocol >= Stomp::SPL_11 && !@hbr)
|
19
|
+
line = read_socket.gets # The old way
|
20
|
+
else # We are >= 1.1 *AND* receiving heartbeats.
|
21
|
+
while true
|
22
|
+
line = read_socket.gets # Data from wire
|
23
|
+
break unless line == "\n"
|
24
|
+
line = ''
|
25
|
+
@lr = Time.now.to_f
|
26
|
+
end
|
27
|
+
end
|
28
|
+
return nil if line.nil?
|
29
|
+
# If the reading hangs for more than X seconds, abort the parsing process.
|
30
|
+
# X defaults to 5. Override allowed in connection hash parameters.
|
31
|
+
Timeout::timeout(@parse_timeout, Stomp::Error::PacketParsingTimeout) do
|
32
|
+
# Reads the beginning of the message until it runs into a empty line
|
33
|
+
message_header = ''
|
34
|
+
begin
|
35
|
+
message_header += line
|
36
|
+
line = read_socket.gets
|
37
|
+
raise Stomp::Error::StompServerError if line.nil?
|
38
|
+
end until line =~ /^\s?\n$/
|
39
|
+
|
40
|
+
# Checks if it includes content_length header
|
41
|
+
content_length = message_header.match /content-length\s?:\s?(\d+)\s?\n/
|
42
|
+
message_body = ''
|
43
|
+
|
44
|
+
# If content_length is present, read the specified amount of bytes
|
45
|
+
if content_length
|
46
|
+
message_body = read_socket.read content_length[1].to_i
|
47
|
+
raise Stomp::Error::InvalidMessageLength unless parse_char(read_socket.getc) == "\0"
|
48
|
+
# Else read the rest of the message until the first \0
|
49
|
+
else
|
50
|
+
message_body = read_socket.readline("\0")
|
51
|
+
message_body.chop!
|
52
|
+
end
|
53
|
+
|
54
|
+
# If the buffer isn't empty, reads trailing new lines.
|
55
|
+
#
|
56
|
+
# Note: experiments with JRuby seem to show that .ready? never
|
57
|
+
# returns true. This means that this code to drain trailing new
|
58
|
+
# lines never runs using JRuby.
|
59
|
+
#
|
60
|
+
# Note 2: the draining of new lines must be done _after_ a message
|
61
|
+
# is read. Do _not_ leave them on the wire and attempt to drain them
|
62
|
+
# at the start of the next read. Attempting to do that breaks the
|
63
|
+
# asynchronous nature of the 'poll' method.
|
64
|
+
while read_socket.ready?
|
65
|
+
last_char = read_socket.getc
|
66
|
+
break unless last_char
|
67
|
+
if parse_char(last_char) != "\n"
|
68
|
+
read_socket.ungetc(last_char)
|
69
|
+
break
|
70
|
+
end
|
71
|
+
end
|
72
|
+
# And so, a JRuby hack. Remove any new lines at the start of the
|
73
|
+
# next buffer.
|
74
|
+
message_header.gsub!(/^\n?/, "")
|
75
|
+
|
76
|
+
if @protocol >= Stomp::SPL_11
|
77
|
+
@lr = Time.now.to_f if @hbr
|
78
|
+
end
|
79
|
+
# Adds the excluded \n and \0 and tries to create a new message with it
|
80
|
+
msg = Message.new(message_header + "\n" + message_body + "\0", @protocol >= Stomp::SPL_11)
|
81
|
+
#
|
82
|
+
if @protocol >= Stomp::SPL_11 && msg.command != Stomp::CMD_CONNECTED
|
83
|
+
msg.headers = _decodeHeaders(msg.headers)
|
84
|
+
end
|
85
|
+
msg
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# transmit logically puts a Message on the wire.
|
91
|
+
def transmit(command, headers = {}, body = '')
|
92
|
+
# The transmit may fail so we may need to retry.
|
93
|
+
while TRUE
|
94
|
+
begin
|
95
|
+
used_socket = socket
|
96
|
+
_transmit(used_socket, command, headers, body)
|
97
|
+
return
|
98
|
+
rescue Stomp::Error::MaxReconnectAttempts => e
|
99
|
+
raise
|
100
|
+
rescue
|
101
|
+
@failure = $!
|
102
|
+
raise unless @reliable
|
103
|
+
errstr = "transmit to #{@host} failed: #{$!}\n"
|
104
|
+
if @logger && @logger.respond_to?(:on_miscerr)
|
105
|
+
@logger.on_miscerr(log_params, errstr)
|
106
|
+
else
|
107
|
+
$stderr.print errstr
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
# _transmit is the real wire write logic.
|
114
|
+
def _transmit(used_socket, command, headers = {}, body = '')
|
115
|
+
if @protocol >= Stomp::SPL_11 && command != Stomp::CMD_CONNECT
|
116
|
+
headers = _encodeHeaders(headers)
|
117
|
+
end
|
118
|
+
@transmit_semaphore.synchronize do
|
119
|
+
# Handle nil body
|
120
|
+
body = '' if body.nil?
|
121
|
+
# The content-length should be expressed in bytes.
|
122
|
+
# Ruby 1.8: String#length => # of bytes; Ruby 1.9: String#length => # of characters
|
123
|
+
# With Unicode strings, # of bytes != # of characters. So, use String#bytesize when available.
|
124
|
+
body_length_bytes = body.respond_to?(:bytesize) ? body.bytesize : body.length
|
125
|
+
|
126
|
+
# ActiveMQ interprets every message as a BinaryMessage
|
127
|
+
# if content_length header is included.
|
128
|
+
# Using :suppress_content_length => true will suppress this behaviour
|
129
|
+
# and ActiveMQ will interpret the message as a TextMessage.
|
130
|
+
# For more information refer to http://juretta.com/log/2009/05/24/activemq-jms-stomp/
|
131
|
+
# Lets send this header in the message, so it can maintain state when using unreceive
|
132
|
+
headers[:'content-length'] = "#{body_length_bytes}" unless headers[:suppress_content_length]
|
133
|
+
headers[:'content-type'] = "text/plain; charset=UTF-8" unless headers[:'content-type']
|
134
|
+
used_socket.puts command
|
135
|
+
headers.each do |k,v|
|
136
|
+
if v.is_a?(Array)
|
137
|
+
v.each do |e|
|
138
|
+
used_socket.puts "#{k}:#{e}"
|
139
|
+
end
|
140
|
+
else
|
141
|
+
used_socket.puts "#{k}:#{v}"
|
142
|
+
end
|
143
|
+
end
|
144
|
+
used_socket.puts
|
145
|
+
used_socket.write body
|
146
|
+
used_socket.write "\0"
|
147
|
+
used_socket.flush if autoflush
|
148
|
+
|
149
|
+
if @protocol >= Stomp::SPL_11
|
150
|
+
@ls = Time.now.to_f if @hbs
|
151
|
+
end
|
152
|
+
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
# open_tcp_socket opens a TCP socket.
|
157
|
+
def open_tcp_socket()
|
158
|
+
tcp_socket = nil
|
159
|
+
|
160
|
+
if @logger && @logger.respond_to?(:on_connecting)
|
161
|
+
@logger.on_connecting(log_params)
|
162
|
+
end
|
163
|
+
|
164
|
+
Timeout::timeout(@connect_timeout, Stomp::Error::SocketOpenTimeout) do
|
165
|
+
tcp_socket = TCPSocket.open(@host, @port)
|
166
|
+
end
|
167
|
+
|
168
|
+
tcp_socket
|
169
|
+
end
|
170
|
+
|
171
|
+
# open_ssl_socket opens an SSL socket.
|
172
|
+
def open_ssl_socket()
|
173
|
+
require 'openssl' unless defined?(OpenSSL)
|
174
|
+
begin # Any raised SSL exceptions
|
175
|
+
ctx = OpenSSL::SSL::SSLContext.new
|
176
|
+
ctx.verify_mode = OpenSSL::SSL::VERIFY_NONE # Assume for now
|
177
|
+
#
|
178
|
+
# Note: if a client uses :ssl => true this results in the gem using
|
179
|
+
# the _default_ Ruby ciphers list. This is _known_ to fail in later
|
180
|
+
# Ruby releases. The gem provides a default cipher list that may
|
181
|
+
# function in these cases. To use this connect with:
|
182
|
+
# * :ssl => Stomp::SSLParams.new
|
183
|
+
# * :ssl => Stomp::SSLParams.new(..., :ciphers => Stomp::DEFAULT_CIPHERS)
|
184
|
+
#
|
185
|
+
# If connecting with an SSLParams instance, and the _default_ Ruby
|
186
|
+
# ciphers list is required, use:
|
187
|
+
# * :ssl => Stomp::SSLParams.new(..., :use_ruby_ciphers => true)
|
188
|
+
#
|
189
|
+
# If a custom ciphers list is required, connect with:
|
190
|
+
# * :ssl => Stomp::SSLParams.new(..., :ciphers => custom_ciphers_list)
|
191
|
+
#
|
192
|
+
if @ssl != true
|
193
|
+
#
|
194
|
+
# Here @ssl is:
|
195
|
+
# * an instance of Stomp::SSLParams
|
196
|
+
# Control would not be here if @ssl == false or @ssl.nil?.
|
197
|
+
#
|
198
|
+
|
199
|
+
# Back reference the SSLContext
|
200
|
+
@ssl.ctx = ctx
|
201
|
+
|
202
|
+
# Server authentication parameters if required
|
203
|
+
if @ssl.ts_files
|
204
|
+
ctx.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
205
|
+
truststores = OpenSSL::X509::Store.new
|
206
|
+
fl = @ssl.ts_files.split(",")
|
207
|
+
fl.each do |fn|
|
208
|
+
# Add next cert file listed
|
209
|
+
raise Stomp::Error::SSLNoTruststoreFileError if !File::exists?(fn)
|
210
|
+
raise Stomp::Error::SSLUnreadableTruststoreFileError if !File::readable?(fn)
|
211
|
+
truststores.add_file(fn)
|
212
|
+
end
|
213
|
+
ctx.cert_store = truststores
|
214
|
+
end
|
215
|
+
|
216
|
+
# Client authentication parameters.
|
217
|
+
# Both cert file and key file must be present or not, it can not be a mix.
|
218
|
+
raise Stomp::Error::SSLClientParamsError if @ssl.cert_file.nil? && !@ssl.key_file.nil?
|
219
|
+
raise Stomp::Error::SSLClientParamsError if !@ssl.cert_file.nil? && @ssl.key_file.nil?
|
220
|
+
if @ssl.cert_file # Any check will do here
|
221
|
+
raise Stomp::Error::SSLNoCertFileError if !File::exists?(@ssl.cert_file)
|
222
|
+
raise Stomp::Error::SSLUnreadableCertFileError if !File::readable?(@ssl.cert_file)
|
223
|
+
ctx.cert = OpenSSL::X509::Certificate.new(File.read(@ssl.cert_file))
|
224
|
+
raise Stomp::Error::SSLNoKeyFileError if !File::exists?(@ssl.key_file)
|
225
|
+
raise Stomp::Error::SSLUnreadableKeyFileError if !File::readable?(@ssl.key_file)
|
226
|
+
ctx.key = OpenSSL::PKey::RSA.new(File.read(@ssl.key_file), @ssl.key_password)
|
227
|
+
end
|
228
|
+
|
229
|
+
# Cipher list
|
230
|
+
if !@ssl.use_ruby_ciphers # No Ruby ciphers (the default)
|
231
|
+
if @ssl.ciphers # User ciphers list?
|
232
|
+
ctx.ciphers = @ssl.ciphers # Accept user supplied ciphers
|
233
|
+
else
|
234
|
+
ctx.ciphers = Stomp::DEFAULT_CIPHERS # Just use Stomp defaults
|
235
|
+
end
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
#
|
240
|
+
ssl = nil
|
241
|
+
if @logger && @logger.respond_to?(:on_ssl_connecting)
|
242
|
+
@logger.on_ssl_connecting(log_params)
|
243
|
+
end
|
244
|
+
|
245
|
+
Timeout::timeout(@connect_timeout, Stomp::Error::SocketOpenTimeout) do
|
246
|
+
ssl = OpenSSL::SSL::SSLSocket.new(open_tcp_socket, ctx)
|
247
|
+
end
|
248
|
+
def ssl.ready?
|
249
|
+
! @rbuffer.empty? || @io.ready?
|
250
|
+
end
|
251
|
+
ssl.connect
|
252
|
+
if @ssl != true
|
253
|
+
# Pass back results if possible
|
254
|
+
if RUBY_VERSION =~ /1\.8\.[56]/
|
255
|
+
@ssl.verify_result = "N/A for Ruby #{RUBY_VERSION}"
|
256
|
+
else
|
257
|
+
@ssl.verify_result = ssl.verify_result
|
258
|
+
end
|
259
|
+
@ssl.peer_cert = ssl.peer_cert
|
260
|
+
end
|
261
|
+
if @logger && @logger.respond_to?(:on_ssl_connected)
|
262
|
+
@logger.on_ssl_connected(log_params)
|
263
|
+
end
|
264
|
+
ssl
|
265
|
+
rescue Exception => ex
|
266
|
+
if @logger && @logger.respond_to?(:on_ssl_connectfail)
|
267
|
+
lp = log_params.clone
|
268
|
+
lp[:ssl_exception] = ex
|
269
|
+
@logger.on_ssl_connectfail(lp)
|
270
|
+
end
|
271
|
+
#
|
272
|
+
raise # Reraise
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
# close_socket closes the current open socket, and hence the connection.
|
277
|
+
def close_socket()
|
278
|
+
begin
|
279
|
+
# Need to set @closed = true before closing the socket
|
280
|
+
# within the @read_semaphore thread
|
281
|
+
@closed = true
|
282
|
+
@read_semaphore.synchronize do
|
283
|
+
@socket.close
|
284
|
+
end
|
285
|
+
rescue
|
286
|
+
#Ignoring if already closed
|
287
|
+
end
|
288
|
+
@closed
|
289
|
+
end
|
290
|
+
|
291
|
+
# open_socket opens a TCP or SSL soclet as required.
|
292
|
+
def open_socket()
|
293
|
+
used_socket = @ssl ? open_ssl_socket : open_tcp_socket
|
294
|
+
# try to close the old connection if any
|
295
|
+
close_socket
|
296
|
+
|
297
|
+
@closed = false
|
298
|
+
# Use keepalive
|
299
|
+
used_socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, true)
|
300
|
+
used_socket
|
301
|
+
end
|
302
|
+
|
303
|
+
# connect performs a basic STOMP CONNECT operation.
|
304
|
+
def connect(used_socket)
|
305
|
+
@connect_headers = {} unless @connect_headers # Caller said nil/false
|
306
|
+
headers = @connect_headers.clone
|
307
|
+
headers[:login] = @login
|
308
|
+
headers[:passcode] = @passcode
|
309
|
+
_pre_connect
|
310
|
+
_transmit(used_socket, "CONNECT", headers)
|
311
|
+
@connection_frame = _receive(used_socket)
|
312
|
+
_post_connect
|
313
|
+
@disconnect_receipt = nil
|
314
|
+
@session = @connection_frame.headers["session"] if @connection_frame
|
315
|
+
# replay any subscriptions.
|
316
|
+
@subscriptions.each { |k,v| _transmit(used_socket, Stomp::CMD_SUBSCRIBE, v) }
|
317
|
+
end
|
318
|
+
|
319
|
+
end # class
|
320
|
+
|
321
|
+
end # module
|
322
|
+
|
@@ -0,0 +1,294 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
require 'socket'
|
4
|
+
require 'timeout'
|
5
|
+
require 'io/wait'
|
6
|
+
require 'digest/sha1'
|
7
|
+
|
8
|
+
module Stomp
|
9
|
+
|
10
|
+
class Connection
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
# Ref:
|
15
|
+
# http://unicode.org/mail-arch/unicode-ml/y2003-m02/att-0467/01-The_Algorithm_to_Valide_an_UTF-8_String
|
16
|
+
#
|
17
|
+
# *CONSIDER* replacing this with a dependency on the utf8_validator gem.
|
18
|
+
# This code has been copied from there.
|
19
|
+
#
|
20
|
+
def _valid_utf8?(string)
|
21
|
+
case RUBY_VERSION
|
22
|
+
when /1\.8\.[56]/
|
23
|
+
bytes = []
|
24
|
+
0.upto(string.length-1) {|i|
|
25
|
+
bytes << string[i]
|
26
|
+
}
|
27
|
+
else
|
28
|
+
bytes = string.bytes
|
29
|
+
end
|
30
|
+
|
31
|
+
#
|
32
|
+
valid = true
|
33
|
+
index = -1
|
34
|
+
nb_hex = nil
|
35
|
+
ni_hex = nil
|
36
|
+
state = "start"
|
37
|
+
next_byte_save = nil
|
38
|
+
#
|
39
|
+
bytes.each do |next_byte|
|
40
|
+
index += 1
|
41
|
+
next_byte_save = next_byte
|
42
|
+
ni_hex = sprintf "%x", index
|
43
|
+
nb_hex = sprintf "%x", next_byte
|
44
|
+
# puts "Top: #{next_byte}(0x#{nb_hex}), index: #{index}(0x#{ni_hex})" if DEBUG
|
45
|
+
case state
|
46
|
+
|
47
|
+
# State: 'start'
|
48
|
+
# The 'start' state:
|
49
|
+
# * handles all occurrences of valid single byte characters i.e., the ASCII character set
|
50
|
+
# * provides state transition logic for start bytes of valid characters with 2-4 bytes
|
51
|
+
# * signals a validation failure for all other single bytes
|
52
|
+
#
|
53
|
+
when "start"
|
54
|
+
# puts "state: start" if DEBUG
|
55
|
+
case next_byte
|
56
|
+
|
57
|
+
# ASCII
|
58
|
+
# * Input = 0x00-0x7F : change state to START
|
59
|
+
when (0x00..0x7f)
|
60
|
+
# puts "state: start 1" if DEBUG
|
61
|
+
state = "start"
|
62
|
+
|
63
|
+
# Start byte of two byte characters
|
64
|
+
# * Input = 0xC2-0xDF: change state to A
|
65
|
+
when (0xc2..0xdf)
|
66
|
+
# puts "state: start 2" if DEBUG
|
67
|
+
state = "a"
|
68
|
+
|
69
|
+
# Start byte of some three byte characters
|
70
|
+
# * Input = 0xE1-0xEC, 0xEE-0xEF: change state to B
|
71
|
+
when (0xe1..0xec)
|
72
|
+
# puts "state: start 3" if DEBUG
|
73
|
+
state = "b"
|
74
|
+
when (0xee..0xef)
|
75
|
+
# puts "state: start 4" if DEBUG
|
76
|
+
state = "b"
|
77
|
+
|
78
|
+
# Start byte of special three byte characters
|
79
|
+
# * Input = 0xE0: change state to C
|
80
|
+
when 0xe0
|
81
|
+
# puts "state: start 5" if DEBUG
|
82
|
+
state = "c"
|
83
|
+
|
84
|
+
# Start byte of the remaining three byte characters
|
85
|
+
# * Input = 0xED: change state to D
|
86
|
+
when 0xed
|
87
|
+
# puts "state: start 6" if DEBUG
|
88
|
+
state = "d"
|
89
|
+
|
90
|
+
# Start byte of some four byte characters
|
91
|
+
# * Input = 0xF1-0xF3:change state to E
|
92
|
+
when (0xf1..0xf3)
|
93
|
+
# puts "state: start 7" if DEBUG
|
94
|
+
state = "e"
|
95
|
+
|
96
|
+
# Start byte of special four byte characters
|
97
|
+
# * Input = 0xF0: change state to F
|
98
|
+
when 0xf0
|
99
|
+
# puts "state: start 8" if DEBUG
|
100
|
+
state = "f"
|
101
|
+
|
102
|
+
# Start byte of very special four byte characters
|
103
|
+
# * Input = 0xF4: change state to G
|
104
|
+
when 0xf4
|
105
|
+
# puts "state: start 9" if DEBUG
|
106
|
+
state = "g"
|
107
|
+
|
108
|
+
# All other single characters are invalid
|
109
|
+
# * Input = Others (0x80-0xBF,0xC0-0xC1, 0xF5-0xFF): ERROR
|
110
|
+
else
|
111
|
+
valid = false
|
112
|
+
break
|
113
|
+
end # of the inner case, the 'start' state
|
114
|
+
|
115
|
+
# The last continuation byte of a 2, 3, or 4 byte character
|
116
|
+
# State: 'a'
|
117
|
+
# o Input = 0x80-0xBF: change state to START
|
118
|
+
# o Others: ERROR
|
119
|
+
when "a"
|
120
|
+
# puts "state: a" if DEBUG
|
121
|
+
if (0x80..0xbf) === next_byte
|
122
|
+
state = "start"
|
123
|
+
else
|
124
|
+
valid = false
|
125
|
+
break
|
126
|
+
end
|
127
|
+
|
128
|
+
# The first continuation byte for most 3 byte characters
|
129
|
+
# (those with start bytes in: 0xe1-0xec or 0xee-0xef)
|
130
|
+
# State: 'b'
|
131
|
+
# o Input = 0x80-0xBF: change state to A
|
132
|
+
# o Others: ERROR
|
133
|
+
when "b"
|
134
|
+
# puts "state: b" if DEBUG
|
135
|
+
if (0x80..0xbf) === next_byte
|
136
|
+
state = "a"
|
137
|
+
else
|
138
|
+
valid = false
|
139
|
+
break
|
140
|
+
end
|
141
|
+
|
142
|
+
# The first continuation byte for some special 3 byte characters
|
143
|
+
# (those with start byte 0xe0)
|
144
|
+
# State: 'c'
|
145
|
+
# o Input = 0xA0-0xBF: change state to A
|
146
|
+
# o Others: ERROR
|
147
|
+
when "c"
|
148
|
+
# puts "state: c" if DEBUG
|
149
|
+
if (0xa0..0xbf) === next_byte
|
150
|
+
state = "a"
|
151
|
+
else
|
152
|
+
valid = false
|
153
|
+
break
|
154
|
+
end
|
155
|
+
|
156
|
+
# The first continuation byte for the remaining 3 byte characters
|
157
|
+
# (those with start byte 0xed)
|
158
|
+
# State: 'd'
|
159
|
+
# o Input = 0x80-0x9F: change state to A
|
160
|
+
# o Others: ERROR
|
161
|
+
when "d"
|
162
|
+
# puts "state: d" if DEBUG
|
163
|
+
if (0x80..0x9f) === next_byte
|
164
|
+
state = "a"
|
165
|
+
else
|
166
|
+
valid = false
|
167
|
+
break
|
168
|
+
end
|
169
|
+
|
170
|
+
# The first continuation byte for some 4 byte characters
|
171
|
+
# (those with start bytes in: 0xf1-0xf3)
|
172
|
+
# State: 'e'
|
173
|
+
# o Input = 0x80-0xBF: change state to B
|
174
|
+
# o Others: ERROR
|
175
|
+
when "e"
|
176
|
+
# puts "state: e" if DEBUG
|
177
|
+
if (0x80..0xbf) === next_byte
|
178
|
+
state = "b"
|
179
|
+
else
|
180
|
+
valid = false
|
181
|
+
break
|
182
|
+
end
|
183
|
+
|
184
|
+
# The first continuation byte for some special 4 byte characters
|
185
|
+
# (those with start byte 0xf0)
|
186
|
+
# State: 'f'
|
187
|
+
# o Input = 0x90-0xBF: change state to B
|
188
|
+
# o Others: ERROR
|
189
|
+
when "f"
|
190
|
+
# puts "state: f" if DEBUG
|
191
|
+
if (0x90..0xbf) === next_byte
|
192
|
+
state = "b"
|
193
|
+
else
|
194
|
+
valid = false
|
195
|
+
break
|
196
|
+
end
|
197
|
+
|
198
|
+
# The first continuation byte for the remaining 4 byte characters
|
199
|
+
# (those with start byte 0xf4)
|
200
|
+
# State: 'g'
|
201
|
+
# o Input = 0x80-0x8F: change state to B
|
202
|
+
# o Others: ERROR
|
203
|
+
when "g"
|
204
|
+
# puts "state: g" if DEBUG
|
205
|
+
if (0x80..0x8f) === next_byte
|
206
|
+
state = "b"
|
207
|
+
else
|
208
|
+
valid = false
|
209
|
+
break
|
210
|
+
end
|
211
|
+
|
212
|
+
#
|
213
|
+
else
|
214
|
+
raise RuntimeError, "state: default"
|
215
|
+
end
|
216
|
+
end
|
217
|
+
#
|
218
|
+
# puts "State at end: #{state}" if DEBUG
|
219
|
+
# Catch truncation at end of string
|
220
|
+
if valid and state != 'start'
|
221
|
+
# puts "Resetting valid value" if DEBUG
|
222
|
+
valid = false
|
223
|
+
end
|
224
|
+
#
|
225
|
+
valid
|
226
|
+
end # of _valid_utf8?
|
227
|
+
|
228
|
+
# Stomp 1.1+ header check for UTF8 validity. Raises Stomp::Error::UTF8ValidationError if header data is not valid UTF8.
|
229
|
+
def _headerCheck(h)
|
230
|
+
return if @protocol == Stomp::SPL_10 # Do nothing for this environment
|
231
|
+
#
|
232
|
+
h.each_pair do |k,v|
|
233
|
+
# Keys here are symbolized
|
234
|
+
ks = k.to_s
|
235
|
+
ks.force_encoding(Stomp::UTF8) if ks.respond_to?(:force_encoding)
|
236
|
+
raise Stomp::Error::UTF8ValidationError unless valid_utf8?(ks)
|
237
|
+
#
|
238
|
+
if v.is_a?(Array)
|
239
|
+
v.each do |e|
|
240
|
+
e.force_encoding(Stomp::UTF8) if e.respond_to?(:force_encoding)
|
241
|
+
raise Stomp::Error::UTF8ValidationError unless valid_utf8?(e)
|
242
|
+
end
|
243
|
+
else
|
244
|
+
vs = v.to_s + "" # Values are usually Strings, but could be TrueClass or Symbol
|
245
|
+
# The + "" above forces an 'unfreeze' if necessary
|
246
|
+
vs.force_encoding(Stomp::UTF8) if vs.respond_to?(:force_encoding)
|
247
|
+
raise Stomp::Error::UTF8ValidationError unless valid_utf8?(vs)
|
248
|
+
end
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
# encode returns a Hash of encoded headers per the Stomp 1.1 specification.
|
253
|
+
def _encodeHeaders(h)
|
254
|
+
eh = {}
|
255
|
+
h.each_pair do |k,v|
|
256
|
+
# Keys are symbolized
|
257
|
+
ks = k.to_s
|
258
|
+
if v.is_a?(Array)
|
259
|
+
kenc = Stomp::HeaderCodec::encode(ks)
|
260
|
+
eh[kenc] = []
|
261
|
+
v.each do |e|
|
262
|
+
eh[kenc] << Stomp::HeaderCodec::encode(e)
|
263
|
+
end
|
264
|
+
else
|
265
|
+
vs = v.to_s
|
266
|
+
eh[Stomp::HeaderCodec::encode(ks)] = Stomp::HeaderCodec::encode(vs)
|
267
|
+
end
|
268
|
+
end
|
269
|
+
eh
|
270
|
+
end
|
271
|
+
|
272
|
+
# decode returns a Hash of decoded headers per the Stomp 1.1 specification.
|
273
|
+
def _decodeHeaders(h)
|
274
|
+
dh = {}
|
275
|
+
h.each_pair do |k,v|
|
276
|
+
# Keys here are NOT! symbolized
|
277
|
+
if v.is_a?(Array)
|
278
|
+
kdec = Stomp::HeaderCodec::decode(k)
|
279
|
+
dh[kdec] = []
|
280
|
+
v.each do |e|
|
281
|
+
dh[kdec] << Stomp::HeaderCodec::decode(e)
|
282
|
+
end
|
283
|
+
else
|
284
|
+
vs = v.to_s
|
285
|
+
dh[Stomp::HeaderCodec::decode(k)] = Stomp::HeaderCodec::decode(vs)
|
286
|
+
end
|
287
|
+
end
|
288
|
+
dh
|
289
|
+
end
|
290
|
+
|
291
|
+
end # class
|
292
|
+
|
293
|
+
end # module
|
294
|
+
|