xmpp4r 0.3
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/COPYING +340 -0
- data/ChangeLog +28 -0
- data/LICENSE +59 -0
- data/README +20 -0
- data/Rakefile +103 -0
- data/UPDATING +40 -0
- data/data/doc/xmpp4r/examples/advanced/adventure/README +57 -0
- data/data/doc/xmpp4r/examples/advanced/adventure/adventure.rb +23 -0
- data/data/doc/xmpp4r/examples/advanced/adventure/adventuremuc.rb +136 -0
- data/data/doc/xmpp4r/examples/advanced/adventure/cube.xml +15 -0
- data/data/doc/xmpp4r/examples/advanced/adventure/tower.xml +69 -0
- data/data/doc/xmpp4r/examples/advanced/adventure/world.rb +425 -0
- data/data/doc/xmpp4r/examples/advanced/fileserve.conf +11 -0
- data/data/doc/xmpp4r/examples/advanced/fileserve.rb +344 -0
- data/data/doc/xmpp4r/examples/advanced/getonline.rb +56 -0
- data/data/doc/xmpp4r/examples/advanced/gtkmucclient.rb +315 -0
- data/data/doc/xmpp4r/examples/advanced/migrate.rb +89 -0
- data/data/doc/xmpp4r/examples/advanced/minimuc.rb +266 -0
- data/data/doc/xmpp4r/examples/advanced/recvfile.rb +83 -0
- data/data/doc/xmpp4r/examples/advanced/rosterdiscovery.rb +130 -0
- data/data/doc/xmpp4r/examples/advanced/sendfile.conf +10 -0
- data/data/doc/xmpp4r/examples/advanced/sendfile.rb +72 -0
- data/data/doc/xmpp4r/examples/advanced/shellmgr/shellmgr.rb +51 -0
- data/data/doc/xmpp4r/examples/advanced/shellmgr/shellmgr_jabber.rb +43 -0
- data/data/doc/xmpp4r/examples/advanced/shellmgr/shellmgr_test.rb +10 -0
- data/data/doc/xmpp4r/examples/advanced/versionpoll.rb +90 -0
- data/data/doc/xmpp4r/examples/advanced/xmpping.rb +134 -0
- data/data/doc/xmpp4r/examples/advanced/xmppingrc.sample +9 -0
- data/data/doc/xmpp4r/examples/basic/change_password.rb +41 -0
- data/data/doc/xmpp4r/examples/basic/client.rb +68 -0
- data/data/doc/xmpp4r/examples/basic/component.rb +11 -0
- data/data/doc/xmpp4r/examples/basic/echo_nonthreaded.rb +32 -0
- data/data/doc/xmpp4r/examples/basic/echo_threaded.rb +32 -0
- data/data/doc/xmpp4r/examples/basic/jabbersend.rb +41 -0
- data/data/doc/xmpp4r/examples/basic/mass_sender.rb +67 -0
- data/data/doc/xmpp4r/examples/basic/mucinfo.rb +39 -0
- data/data/doc/xmpp4r/examples/basic/mucsimplebot.rb +83 -0
- data/data/doc/xmpp4r/examples/basic/register.rb +25 -0
- data/data/doc/xmpp4r/examples/basic/remove_registration.rb +18 -0
- data/data/doc/xmpp4r/examples/basic/roster.rb +42 -0
- data/data/doc/xmpp4r/examples/basic/rosterprint.rb +50 -0
- data/data/doc/xmpp4r/examples/basic/rosterrename.rb +34 -0
- data/data/doc/xmpp4r/examples/basic/rosterwatch.rb +172 -0
- data/data/doc/xmpp4r/examples/basic/send_vcard.rb +68 -0
- data/data/doc/xmpp4r/examples/basic/versionbot.rb +75 -0
- data/data/doc/xmpp4r/examples/buggy/jabber2jabber/jabber2jabber.rb +18 -0
- data/data/doc/xmpp4r/examples/buggy/miniedgarr_cgi.rb +192 -0
- data/data/doc/xmpp4r/examples/buggy/miniedgarr_watch.rb +82 -0
- data/lib/callbacks.rb +122 -0
- data/lib/xmpp4r/authenticationfailure.rb +13 -0
- data/lib/xmpp4r/bytestreams/helper/filetransfer.rb +315 -0
- data/lib/xmpp4r/bytestreams/helper/ibb/base.rb +256 -0
- data/lib/xmpp4r/bytestreams/helper/ibb/initiator.rb +30 -0
- data/lib/xmpp4r/bytestreams/helper/ibb/target.rb +46 -0
- data/lib/xmpp4r/bytestreams/helper/socks5bytestreams/base.rb +151 -0
- data/lib/xmpp4r/bytestreams/helper/socks5bytestreams/initiator.rb +85 -0
- data/lib/xmpp4r/bytestreams/helper/socks5bytestreams/server.rb +178 -0
- data/lib/xmpp4r/bytestreams/helper/socks5bytestreams/socks5.rb +56 -0
- data/lib/xmpp4r/bytestreams/helper/socks5bytestreams/target.rb +61 -0
- data/lib/xmpp4r/bytestreams/iq/bytestreams.rb +177 -0
- data/lib/xmpp4r/bytestreams/iq/si.rb +221 -0
- data/lib/xmpp4r/bytestreams.rb +11 -0
- data/lib/xmpp4r/client.rb +249 -0
- data/lib/xmpp4r/component.rb +103 -0
- data/lib/xmpp4r/connection.rb +166 -0
- data/lib/xmpp4r/dataforms/x/data.rb +248 -0
- data/lib/xmpp4r/dataforms.rb +1 -0
- data/lib/xmpp4r/debuglog.rb +34 -0
- data/lib/xmpp4r/delay/x/delay.rb +100 -0
- data/lib/xmpp4r/delay.rb +1 -0
- data/lib/xmpp4r/discovery/iq/discoinfo.rb +225 -0
- data/lib/xmpp4r/discovery/iq/discoitems.rb +162 -0
- data/lib/xmpp4r/discovery.rb +2 -0
- data/lib/xmpp4r/error.rb +230 -0
- data/lib/xmpp4r/errorexception.rb +32 -0
- data/lib/xmpp4r/feature_negotiation/iq/feature.rb +42 -0
- data/lib/xmpp4r/feature_negotiation.rb +1 -0
- data/lib/xmpp4r/idgenerator.rb +37 -0
- data/lib/xmpp4r/iq.rb +229 -0
- data/lib/xmpp4r/jid.rb +167 -0
- data/lib/xmpp4r/message.rb +171 -0
- data/lib/xmpp4r/muc/helper/mucbrowser.rb +107 -0
- data/lib/xmpp4r/muc/helper/mucclient.rb +382 -0
- data/lib/xmpp4r/muc/helper/simplemucclient.rb +222 -0
- data/lib/xmpp4r/muc/x/muc.rb +98 -0
- data/lib/xmpp4r/muc/x/mucuserinvite.rb +58 -0
- data/lib/xmpp4r/muc/x/mucuseritem.rb +148 -0
- data/lib/xmpp4r/muc.rb +6 -0
- data/lib/xmpp4r/presence.rb +255 -0
- data/lib/xmpp4r/query.rb +43 -0
- data/lib/xmpp4r/rexmladdons.rb +826 -0
- data/lib/xmpp4r/roster/helper/roster.rb +514 -0
- data/lib/xmpp4r/roster/iq/roster.rb +244 -0
- data/lib/xmpp4r/roster/x/roster.rb +155 -0
- data/lib/xmpp4r/roster.rb +4 -0
- data/lib/xmpp4r/sasl.rb +167 -0
- data/lib/xmpp4r/stream.rb +543 -0
- data/lib/xmpp4r/streamparser.rb +77 -0
- data/lib/xmpp4r/vcard/helper/vcard.rb +86 -0
- data/lib/xmpp4r/vcard/iq/vcard.rb +102 -0
- data/lib/xmpp4r/vcard.rb +3 -0
- data/lib/xmpp4r/version/helper/responder.rb +71 -0
- data/lib/xmpp4r/version/helper/simpleresponder.rb +44 -0
- data/lib/xmpp4r/version/iq/version.rb +118 -0
- data/lib/xmpp4r/version.rb +3 -0
- data/lib/xmpp4r/x.rb +43 -0
- data/lib/xmpp4r/xmlstanza.rb +174 -0
- data/lib/xmpp4r/xmpp4r.rb +16 -0
- data/lib/xmpp4r.rb +122 -0
- data/setup.rb +1360 -0
- data/test/bytestreams/tc_ibb.rb +186 -0
- data/test/bytestreams/tc_socks5bytestreams.rb +57 -0
- data/test/delay/tc_xdelay.rb +51 -0
- data/test/lib/clienttester.rb +110 -0
- data/test/muc/tc_muc_mucclient.rb +569 -0
- data/test/muc/tc_muc_simplemucclient.rb +72 -0
- data/test/roster/.tc_helper.rb.swp +0 -0
- data/test/roster/tc_helper.rb +389 -0
- data/test/roster/tc_iqqueryroster.rb +140 -0
- data/test/roster/tc_xroster.rb +70 -0
- data/test/tc_callbacks.rb +128 -0
- data/test/tc_class_names.rb +129 -0
- data/test/tc_client.rb +30 -0
- data/test/tc_error.rb +103 -0
- data/test/tc_idgenerator.rb +30 -0
- data/test/tc_iq.rb +109 -0
- data/test/tc_iqquery.rb +31 -0
- data/test/tc_jid.rb +202 -0
- data/test/tc_message.rb +114 -0
- data/test/tc_presence.rb +148 -0
- data/test/tc_stream.rb +182 -0
- data/test/tc_streamError.rb +87 -0
- data/test/tc_streamSend.rb +59 -0
- data/test/tc_streamThreaded.rb +168 -0
- data/test/tc_xmlstanza.rb +76 -0
- data/test/ts_xmpp4r.rb +34 -0
- data/test/vcard/tc_iqvcard.rb +52 -0
- data/test/version/tc_helper.rb +46 -0
- data/test/version/tc_iqqueryversion.rb +96 -0
- data/tools/doctoweb.bash +30 -0
- data/tools/gen_requires.bash +10 -0
- metadata +232 -0
|
@@ -0,0 +1,543 @@
|
|
|
1
|
+
# =XMPP4R - XMPP Library for Ruby
|
|
2
|
+
# License:: Ruby's license (see the LICENSE file) or GNU GPL, at your option.
|
|
3
|
+
# Website::http://home.gna.org/xmpp4r/
|
|
4
|
+
|
|
5
|
+
require 'callbacks'
|
|
6
|
+
require 'socket'
|
|
7
|
+
require 'thread'
|
|
8
|
+
Thread::abort_on_exception = true
|
|
9
|
+
require 'xmpp4r/streamparser'
|
|
10
|
+
require 'xmpp4r/presence'
|
|
11
|
+
require 'xmpp4r/message'
|
|
12
|
+
require 'xmpp4r/iq'
|
|
13
|
+
require 'xmpp4r/errorexception'
|
|
14
|
+
require 'xmpp4r/debuglog'
|
|
15
|
+
require 'xmpp4r/idgenerator'
|
|
16
|
+
|
|
17
|
+
module Jabber
|
|
18
|
+
##
|
|
19
|
+
# The stream class manages a connection stream (a file descriptor using which
|
|
20
|
+
# XML messages are read and sent)
|
|
21
|
+
class Stream
|
|
22
|
+
DISCONNECTED = 1
|
|
23
|
+
CONNECTED = 2
|
|
24
|
+
|
|
25
|
+
# file descriptor used
|
|
26
|
+
attr_reader :fd
|
|
27
|
+
|
|
28
|
+
# connection status
|
|
29
|
+
attr_reader :status
|
|
30
|
+
|
|
31
|
+
##
|
|
32
|
+
# Create a new stream
|
|
33
|
+
# (just initializes)
|
|
34
|
+
def initialize(threaded = true)
|
|
35
|
+
@fd = nil
|
|
36
|
+
@status = DISCONNECTED
|
|
37
|
+
@xmlcbs = CallbackList::new
|
|
38
|
+
@stanzacbs = CallbackList::new
|
|
39
|
+
@messagecbs = CallbackList::new
|
|
40
|
+
@iqcbs = CallbackList::new
|
|
41
|
+
@presencecbs = CallbackList::new
|
|
42
|
+
unless threaded
|
|
43
|
+
$stderr.puts "Non-threaded mode is currently broken, re-enabling threaded"
|
|
44
|
+
threaded = true
|
|
45
|
+
end
|
|
46
|
+
@threaded = threaded
|
|
47
|
+
@stanzaqueue = []
|
|
48
|
+
@stanzaqueue_lock = Mutex::new
|
|
49
|
+
@exception_block = nil
|
|
50
|
+
@threadblocks = []
|
|
51
|
+
# @pollCounter = 10
|
|
52
|
+
@waiting_thread = nil
|
|
53
|
+
@wakeup_thread = nil
|
|
54
|
+
@streamid = nil
|
|
55
|
+
@features_lock = Mutex.new
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
##
|
|
59
|
+
# Start the XML parser on the fd
|
|
60
|
+
def start(fd)
|
|
61
|
+
@stream_mechanisms = []
|
|
62
|
+
@stream_features = {}
|
|
63
|
+
|
|
64
|
+
@fd = fd
|
|
65
|
+
@parser = StreamParser.new(@fd, self)
|
|
66
|
+
@parserThread = Thread.new do
|
|
67
|
+
begin
|
|
68
|
+
@parser.parse
|
|
69
|
+
rescue Exception => e
|
|
70
|
+
Jabber::debuglog("EXCEPTION:\n#{e.class}\n#{e.message}\n#{e.backtrace.join("\n")}")
|
|
71
|
+
|
|
72
|
+
if @exception_block
|
|
73
|
+
Thread.new { close; @exception_block.call(e, self, :start) }
|
|
74
|
+
else
|
|
75
|
+
puts "Exception caught in Parser thread!"
|
|
76
|
+
close
|
|
77
|
+
raise
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
# @pollThread = Thread.new do
|
|
82
|
+
# begin
|
|
83
|
+
# poll
|
|
84
|
+
# rescue
|
|
85
|
+
# puts "Exception caught in Poll thread, dumping backtrace and" +
|
|
86
|
+
# " exiting...\n" + $!.exception + "\n"
|
|
87
|
+
# puts $!.backtrace
|
|
88
|
+
# exit
|
|
89
|
+
# end
|
|
90
|
+
# end
|
|
91
|
+
@status = CONNECTED
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def stop
|
|
95
|
+
@parserThread.kill
|
|
96
|
+
@parser = nil
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
##
|
|
100
|
+
# Mounts a block to handle exceptions if they occur during the
|
|
101
|
+
# poll send. This will likely be the first indication that
|
|
102
|
+
# the socket dropped in a Jabber Session.
|
|
103
|
+
#
|
|
104
|
+
# The block has to take three arguments:
|
|
105
|
+
# * the Exception
|
|
106
|
+
# * the Jabber::Stream object (self)
|
|
107
|
+
# * a symbol where it happened, namely :start, :parser, :sending and :end
|
|
108
|
+
def on_exception(&block)
|
|
109
|
+
@exception_block = block
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
##
|
|
113
|
+
# This method is called by the parser when a failure occurs
|
|
114
|
+
def parse_failure(e)
|
|
115
|
+
Jabber::debuglog("EXCEPTION:\n#{e.class}\n#{e.message}\n#{e.backtrace.join("\n")}")
|
|
116
|
+
|
|
117
|
+
# A new thread has to be created because close will cause the thread
|
|
118
|
+
# to commit suicide(???)
|
|
119
|
+
if @exception_block
|
|
120
|
+
# New thread, because close will kill the current thread
|
|
121
|
+
Thread.new {
|
|
122
|
+
close
|
|
123
|
+
@exception_block.call(e, self, :parser)
|
|
124
|
+
}
|
|
125
|
+
else
|
|
126
|
+
puts "Stream#parse_failure was called by XML parser. Dumping " +
|
|
127
|
+
"backtrace...\n" + e.exception + "\n"
|
|
128
|
+
puts e.backtrace
|
|
129
|
+
close
|
|
130
|
+
raise
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
##
|
|
135
|
+
# This method is called by the parser upon receiving <tt></stream:stream></tt>
|
|
136
|
+
def parser_end
|
|
137
|
+
if @exception_block
|
|
138
|
+
Thread.new {
|
|
139
|
+
close
|
|
140
|
+
@exception_block.call(nil, self, :close)
|
|
141
|
+
}
|
|
142
|
+
else
|
|
143
|
+
close
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
##
|
|
148
|
+
# Returns if this connection is connected to a Jabber service
|
|
149
|
+
# return:: [Boolean] Connection status
|
|
150
|
+
def is_connected?
|
|
151
|
+
return @status == CONNECTED
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
##
|
|
155
|
+
# Returns if this connection is NOT connected to a Jabber service
|
|
156
|
+
#
|
|
157
|
+
# return:: [Boolean] Connection status
|
|
158
|
+
def is_disconnected?
|
|
159
|
+
return @status == DISCONNECTED
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
##
|
|
163
|
+
# Processes a received REXML::Element and executes
|
|
164
|
+
# registered thread blocks and filters against it.
|
|
165
|
+
#
|
|
166
|
+
# If in threaded mode, a new thread will be spawned
|
|
167
|
+
# for the call to receive_nonthreaded.
|
|
168
|
+
# element:: [REXML::Element] The received element
|
|
169
|
+
def receive(element)
|
|
170
|
+
if @threaded
|
|
171
|
+
# Don't spawn a new thread here. An implicit feature
|
|
172
|
+
# of XMPP is constant order of stanzas.
|
|
173
|
+
receive_nonthreaded(element)
|
|
174
|
+
else
|
|
175
|
+
receive_nonthreaded(element)
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def receive_nonthreaded(element)
|
|
180
|
+
Jabber::debuglog("RECEIVED:\n#{element.to_s}")
|
|
181
|
+
case element.prefix
|
|
182
|
+
when 'stream'
|
|
183
|
+
case element.name
|
|
184
|
+
when 'stream'
|
|
185
|
+
stanza = element
|
|
186
|
+
@streamid = element.attributes['id']
|
|
187
|
+
unless element.attributes['version'] # isn't XMPP compliant, so
|
|
188
|
+
Jabber::debuglog("FEATURES: server not XMPP compliant, will not wait for features")
|
|
189
|
+
@features_lock.unlock # don't wait for <stream:features/>
|
|
190
|
+
end
|
|
191
|
+
when 'features'
|
|
192
|
+
stanza = element
|
|
193
|
+
element.each { |e|
|
|
194
|
+
if e.name == 'mechanisms' and e.namespace == 'urn:ietf:params:xml:ns:xmpp-sasl'
|
|
195
|
+
e.each_element('mechanism') { |mech|
|
|
196
|
+
@stream_mechanisms.push(mech.text)
|
|
197
|
+
}
|
|
198
|
+
else
|
|
199
|
+
@stream_features[e.name] = e.namespace
|
|
200
|
+
end
|
|
201
|
+
}
|
|
202
|
+
Jabber::debuglog("FEATURES: received")
|
|
203
|
+
@features_lock.unlock
|
|
204
|
+
else
|
|
205
|
+
stanza = element
|
|
206
|
+
end
|
|
207
|
+
else
|
|
208
|
+
case element.name
|
|
209
|
+
when 'message'
|
|
210
|
+
stanza = Message::import(element)
|
|
211
|
+
when 'iq'
|
|
212
|
+
stanza = Iq::import(element)
|
|
213
|
+
when 'presence'
|
|
214
|
+
stanza = Presence::import(element)
|
|
215
|
+
else
|
|
216
|
+
stanza = element
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
# Iterate through blocked threads (= waiting for an answer)
|
|
221
|
+
#
|
|
222
|
+
# We're dup'ping the @threadblocks here, so that we won't end up in an
|
|
223
|
+
# endless loop if Stream#send is being nested. That means, the nested
|
|
224
|
+
# threadblock won't receive the stanza currently processed, but the next
|
|
225
|
+
# one.
|
|
226
|
+
threadblocks = @threadblocks.dup
|
|
227
|
+
threadblocks.each { |threadblock|
|
|
228
|
+
exception = nil
|
|
229
|
+
r = false
|
|
230
|
+
begin
|
|
231
|
+
r = threadblock.call(stanza)
|
|
232
|
+
rescue Exception => e
|
|
233
|
+
exception = e
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
if r == true
|
|
237
|
+
@threadblocks.delete(threadblock)
|
|
238
|
+
threadblock.wakeup
|
|
239
|
+
return
|
|
240
|
+
elsif exception
|
|
241
|
+
@threadblocks.delete(threadblock)
|
|
242
|
+
threadblock.raise(exception)
|
|
243
|
+
end
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
if @threaded
|
|
247
|
+
process_one(stanza)
|
|
248
|
+
else
|
|
249
|
+
# stanzaqueue will be read when the user call process
|
|
250
|
+
@stanzaqueue_lock.lock
|
|
251
|
+
@stanzaqueue.push(stanza)
|
|
252
|
+
@stanzaqueue_lock.unlock
|
|
253
|
+
@waiting_thread.wakeup if @waiting_thread
|
|
254
|
+
end
|
|
255
|
+
end
|
|
256
|
+
private :receive_nonthreaded
|
|
257
|
+
|
|
258
|
+
##
|
|
259
|
+
# Process |element| until it is consumed. Returns element.consumed?
|
|
260
|
+
# element The element to process
|
|
261
|
+
def process_one(stanza)
|
|
262
|
+
Jabber::debuglog("PROCESSING:\n#{stanza.to_s}")
|
|
263
|
+
return true if @xmlcbs.process(stanza)
|
|
264
|
+
return true if @stanzacbs.process(stanza)
|
|
265
|
+
case stanza
|
|
266
|
+
when Message
|
|
267
|
+
return true if @messagecbs.process(stanza)
|
|
268
|
+
when Iq
|
|
269
|
+
return true if @iqcbs.process(stanza)
|
|
270
|
+
when Presence
|
|
271
|
+
return true if @presencecbs.process(stanza)
|
|
272
|
+
end
|
|
273
|
+
end
|
|
274
|
+
private :process_one
|
|
275
|
+
|
|
276
|
+
##
|
|
277
|
+
# Process |max| XML stanzas and call listeners for all of them.
|
|
278
|
+
#
|
|
279
|
+
# max:: [Integer] the number of stanzas to process (nil means process
|
|
280
|
+
# all available)
|
|
281
|
+
def process(max = nil)
|
|
282
|
+
n = 0
|
|
283
|
+
@stanzaqueue_lock.lock
|
|
284
|
+
while @stanzaqueue.size > 0 and (max == nil or n < max)
|
|
285
|
+
e = @stanzaqueue.shift
|
|
286
|
+
@stanzaqueue_lock.unlock
|
|
287
|
+
process_one(e)
|
|
288
|
+
n += 1
|
|
289
|
+
@stanzaqueue_lock.lock
|
|
290
|
+
end
|
|
291
|
+
@stanzaqueue_lock.unlock
|
|
292
|
+
n
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
##
|
|
296
|
+
# Process an XML stanza and call the listeners for it. If no stanza is
|
|
297
|
+
# currently available, wait for max |time| seconds before returning.
|
|
298
|
+
#
|
|
299
|
+
# time:: [Integer] time to wait in seconds. If nil, wait infinitely.
|
|
300
|
+
# all available)
|
|
301
|
+
def wait_and_process(time = nil)
|
|
302
|
+
if time == 0
|
|
303
|
+
return process(1)
|
|
304
|
+
end
|
|
305
|
+
@stanzaqueue_lock.lock
|
|
306
|
+
if @stanzaqueue.size > 0
|
|
307
|
+
e = @stanzaqueue.shift
|
|
308
|
+
@stanzaqueue_lock.unlock
|
|
309
|
+
process_one(e)
|
|
310
|
+
return 1
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
@waiting_thread = Thread.current
|
|
314
|
+
@wakeup_thread = Thread.new { sleep time ; @waiting_thread.wakeup if @waiting_thread }
|
|
315
|
+
@waiting_thread.stop
|
|
316
|
+
@wakeup_thread.kill if @wakeup_thread
|
|
317
|
+
@wakeup_thread = nil
|
|
318
|
+
@waiting_thread = nil
|
|
319
|
+
|
|
320
|
+
@stanzaqueue_lock.lock
|
|
321
|
+
if @stanzaqueue.size > 0
|
|
322
|
+
e = @stanzaqueue.shift
|
|
323
|
+
@stanzaqueue_lock.unlock
|
|
324
|
+
process_one(e)
|
|
325
|
+
return 1
|
|
326
|
+
end
|
|
327
|
+
return 0
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
##
|
|
331
|
+
# This is used by Jabber::Stream internally to
|
|
332
|
+
# keep track of any blocks which were passed to
|
|
333
|
+
# Stream#send.
|
|
334
|
+
class ThreadBlock
|
|
335
|
+
def initialize(block)
|
|
336
|
+
@thread = Thread.current
|
|
337
|
+
@block = block
|
|
338
|
+
end
|
|
339
|
+
def call(*args)
|
|
340
|
+
@block.call(*args)
|
|
341
|
+
end
|
|
342
|
+
def wakeup
|
|
343
|
+
# TODO: Handle threadblock removal if !alive?
|
|
344
|
+
@thread.wakeup if @thread.alive?
|
|
345
|
+
end
|
|
346
|
+
def raise(exception)
|
|
347
|
+
@thread.raise(exception) if @thread.alive?
|
|
348
|
+
end
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
##
|
|
352
|
+
# Sends XML data to the socket and (optionally) waits
|
|
353
|
+
# to process received data.
|
|
354
|
+
#
|
|
355
|
+
# xml:: [String] The xml data to send
|
|
356
|
+
# &block:: [Block] The optional block
|
|
357
|
+
def send(xml, &block)
|
|
358
|
+
Jabber::debuglog("SENDING:\n#{xml}")
|
|
359
|
+
@threadblocks.unshift(ThreadBlock.new(block)) if block
|
|
360
|
+
Thread.critical = true # we don't want to be interupted before we stop!
|
|
361
|
+
begin
|
|
362
|
+
@fd << xml.to_s
|
|
363
|
+
@fd.flush
|
|
364
|
+
rescue Exception => e
|
|
365
|
+
Jabber::debuglog("EXCEPTION:\n#{e.class}\n#{e.message}\n#{e.backtrace.join("\n")}")
|
|
366
|
+
|
|
367
|
+
if @exception_block
|
|
368
|
+
Thread.new { close!; @exception_block.call(e, self, :sending) }
|
|
369
|
+
else
|
|
370
|
+
puts "Exception caught while sending!"
|
|
371
|
+
close!
|
|
372
|
+
raise
|
|
373
|
+
end
|
|
374
|
+
end
|
|
375
|
+
Thread.critical = false
|
|
376
|
+
# The parser thread might be running this (think of a callback running send())
|
|
377
|
+
# If this is the case, we mustn't stop (or we would cause a deadlock)
|
|
378
|
+
Thread.stop if block and Thread.current != @parserThread
|
|
379
|
+
@pollCounter = 10
|
|
380
|
+
end
|
|
381
|
+
|
|
382
|
+
##
|
|
383
|
+
# Send an XMMP stanza with an Jabber::XMLStanza#id. The id will be
|
|
384
|
+
# generated by Jabber::IdGenerator if not already set.
|
|
385
|
+
#
|
|
386
|
+
# The block will be called once: when receiving a stanza with the
|
|
387
|
+
# same Jabber::XMLStanza#id. It *must* return true to complete this!
|
|
388
|
+
#
|
|
389
|
+
# Be aware that if a stanza with <tt>type='error'</tt> is received
|
|
390
|
+
# the function does not yield but raises an ErrorException with
|
|
391
|
+
# the corresponding error element.
|
|
392
|
+
#
|
|
393
|
+
# Please read the note about nesting at Stream#send
|
|
394
|
+
# xml:: [XMLStanza]
|
|
395
|
+
def send_with_id(xml, &block)
|
|
396
|
+
if xml.id.nil?
|
|
397
|
+
xml.id = Jabber::IdGenerator.instance.generate_id
|
|
398
|
+
end
|
|
399
|
+
|
|
400
|
+
error = nil
|
|
401
|
+
send(xml) do |received|
|
|
402
|
+
if received.kind_of? XMLStanza and received.id == xml.id
|
|
403
|
+
if received.type == :error
|
|
404
|
+
error = (received.error ? received.error : Error.new)
|
|
405
|
+
true
|
|
406
|
+
else
|
|
407
|
+
yield(received)
|
|
408
|
+
end
|
|
409
|
+
else
|
|
410
|
+
false
|
|
411
|
+
end
|
|
412
|
+
end
|
|
413
|
+
|
|
414
|
+
unless error.nil?
|
|
415
|
+
raise ErrorException.new(error)
|
|
416
|
+
end
|
|
417
|
+
end
|
|
418
|
+
|
|
419
|
+
##
|
|
420
|
+
# Starts a polling thread to send "keep alive" data to prevent
|
|
421
|
+
# the Jabber connection from closing for inactivity.
|
|
422
|
+
#
|
|
423
|
+
# Currently not working!
|
|
424
|
+
def poll
|
|
425
|
+
sleep 10
|
|
426
|
+
while true
|
|
427
|
+
sleep 2
|
|
428
|
+
# @pollCounter = @pollCounter - 1
|
|
429
|
+
# if @pollCounter < 0
|
|
430
|
+
# begin
|
|
431
|
+
# send(" \t ")
|
|
432
|
+
# rescue
|
|
433
|
+
# Thread.new {@exception_block.call if @exception_block}
|
|
434
|
+
# break
|
|
435
|
+
# end
|
|
436
|
+
# end
|
|
437
|
+
end
|
|
438
|
+
end
|
|
439
|
+
|
|
440
|
+
##
|
|
441
|
+
# Adds a callback block to process received XML messages
|
|
442
|
+
#
|
|
443
|
+
# priority:: [Integer] The callback's priority, the higher, the sooner
|
|
444
|
+
# ref:: [String] The callback's reference
|
|
445
|
+
# &block:: [Block] The optional block
|
|
446
|
+
def add_xml_callback(priority = 0, ref = nil, &block)
|
|
447
|
+
@xmlcbs.add(priority, ref, block)
|
|
448
|
+
end
|
|
449
|
+
|
|
450
|
+
##
|
|
451
|
+
# Delete an XML-messages callback
|
|
452
|
+
#
|
|
453
|
+
# ref:: [String] The reference of the callback to delete
|
|
454
|
+
def delete_xml_callback(ref)
|
|
455
|
+
@xmlcbs.delete(ref)
|
|
456
|
+
end
|
|
457
|
+
|
|
458
|
+
##
|
|
459
|
+
# Adds a callback block to process received Messages
|
|
460
|
+
#
|
|
461
|
+
# priority:: [Integer] The callback's priority, the higher, the sooner
|
|
462
|
+
# ref:: [String] The callback's reference
|
|
463
|
+
# &block:: [Block] The optional block
|
|
464
|
+
def add_message_callback(priority = 0, ref = nil, &block)
|
|
465
|
+
@messagecbs.add(priority, ref, block)
|
|
466
|
+
end
|
|
467
|
+
|
|
468
|
+
##
|
|
469
|
+
# Delete an Message callback
|
|
470
|
+
#
|
|
471
|
+
# ref:: [String] The reference of the callback to delete
|
|
472
|
+
def delete_message_callback(ref)
|
|
473
|
+
@messagecbs.delete(ref)
|
|
474
|
+
end
|
|
475
|
+
|
|
476
|
+
##
|
|
477
|
+
# Adds a callback block to process received Stanzas
|
|
478
|
+
#
|
|
479
|
+
# priority:: [Integer] The callback's priority, the higher, the sooner
|
|
480
|
+
# ref:: [String] The callback's reference
|
|
481
|
+
# &block:: [Block] The optional block
|
|
482
|
+
def add_stanza_callback(priority = 0, ref = nil, &block)
|
|
483
|
+
@stanzacbs.add(priority, ref, block)
|
|
484
|
+
end
|
|
485
|
+
|
|
486
|
+
##
|
|
487
|
+
# Delete a Stanza callback
|
|
488
|
+
#
|
|
489
|
+
# ref:: [String] The reference of the callback to delete
|
|
490
|
+
def delete_stanza_callback(ref)
|
|
491
|
+
@stanzacbs.delete(ref)
|
|
492
|
+
end
|
|
493
|
+
|
|
494
|
+
##
|
|
495
|
+
# Adds a callback block to process received Presences
|
|
496
|
+
#
|
|
497
|
+
# priority:: [Integer] The callback's priority, the higher, the sooner
|
|
498
|
+
# ref:: [String] The callback's reference
|
|
499
|
+
# &block:: [Block] The optional block
|
|
500
|
+
def add_presence_callback(priority = 0, ref = nil, &block)
|
|
501
|
+
@presencecbs.add(priority, ref, block)
|
|
502
|
+
end
|
|
503
|
+
|
|
504
|
+
##
|
|
505
|
+
# Delete a Presence callback
|
|
506
|
+
#
|
|
507
|
+
# ref:: [String] The reference of the callback to delete
|
|
508
|
+
def delete_presence_callback(ref)
|
|
509
|
+
@presencecbs.delete(ref)
|
|
510
|
+
end
|
|
511
|
+
|
|
512
|
+
##
|
|
513
|
+
# Adds a callback block to process received Iqs
|
|
514
|
+
#
|
|
515
|
+
# priority:: [Integer] The callback's priority, the higher, the sooner
|
|
516
|
+
# ref:: [String] The callback's reference
|
|
517
|
+
# &block:: [Block] The optional block
|
|
518
|
+
def add_iq_callback(priority = 0, ref = nil, &block)
|
|
519
|
+
@iqcbs.add(priority, ref, block)
|
|
520
|
+
end
|
|
521
|
+
|
|
522
|
+
##
|
|
523
|
+
# Delete an Iq callback
|
|
524
|
+
#
|
|
525
|
+
# ref:: [String] The reference of the callback to delete
|
|
526
|
+
#
|
|
527
|
+
def delete_iq_callback(ref)
|
|
528
|
+
@iqcbs.delete(ref)
|
|
529
|
+
end
|
|
530
|
+
##
|
|
531
|
+
# Closes the connection to the Jabber service
|
|
532
|
+
def close
|
|
533
|
+
close!
|
|
534
|
+
end
|
|
535
|
+
|
|
536
|
+
def close!
|
|
537
|
+
@parserThread.kill if @parserThread
|
|
538
|
+
# @pollThread.kill
|
|
539
|
+
@fd.close if @fd and !@fd.closed?
|
|
540
|
+
@status = DISCONNECTED
|
|
541
|
+
end
|
|
542
|
+
end
|
|
543
|
+
end
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# =XMPP4R - XMPP Library for Ruby
|
|
2
|
+
# License:: Ruby's license (see the LICENSE file) or GNU GPL, at your option.
|
|
3
|
+
# Website::http://home.gna.org/xmpp4r/
|
|
4
|
+
|
|
5
|
+
require 'rexml/parsers/sax2parser'
|
|
6
|
+
require 'rexml/source'
|
|
7
|
+
require 'xmpp4r/rexmladdons'
|
|
8
|
+
|
|
9
|
+
module Jabber
|
|
10
|
+
|
|
11
|
+
##
|
|
12
|
+
# The StreamParser uses REXML to parse the incoming XML stream
|
|
13
|
+
# of the Jabber protocol and fires XMLStanzas at the Connection
|
|
14
|
+
# instance.
|
|
15
|
+
#
|
|
16
|
+
class StreamParser
|
|
17
|
+
# status if the parser is started
|
|
18
|
+
attr_reader :started
|
|
19
|
+
|
|
20
|
+
##
|
|
21
|
+
# Constructs a parser for the supplied stream (socket input)
|
|
22
|
+
#
|
|
23
|
+
# stream:: [IO] Socket input stream
|
|
24
|
+
# listener:: [Object.receive(XMLStanza)] The listener (usually a Jabber::Protocol::Connection instance)
|
|
25
|
+
#
|
|
26
|
+
def initialize(stream, listener)
|
|
27
|
+
@stream = stream
|
|
28
|
+
@listener = listener
|
|
29
|
+
@current = nil
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
##
|
|
33
|
+
# Begins parsing the XML stream and does not return until
|
|
34
|
+
# the stream closes.
|
|
35
|
+
#
|
|
36
|
+
def parse
|
|
37
|
+
@started = false
|
|
38
|
+
begin
|
|
39
|
+
parser = REXML::Parsers::SAX2Parser.new @stream
|
|
40
|
+
|
|
41
|
+
parser.listen( :start_element ) do |uri, localname, qname, attributes|
|
|
42
|
+
e = REXML::Element::new(qname)
|
|
43
|
+
e.add_attributes attributes
|
|
44
|
+
@current = @current.nil? ? e : @current.add_element(e)
|
|
45
|
+
|
|
46
|
+
if @current.name == 'stream' and !@started
|
|
47
|
+
@started = true
|
|
48
|
+
@listener.receive(@current)
|
|
49
|
+
@current = nil
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
parser.listen( :end_element ) do |uri, localname, qname|
|
|
54
|
+
if qname == 'stream:stream' and @current.nil?
|
|
55
|
+
@started = false
|
|
56
|
+
@listener.parser_end
|
|
57
|
+
else
|
|
58
|
+
@listener.receive(@current) unless @current.parent
|
|
59
|
+
@current = @current.parent
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
parser.listen( :characters ) do | text |
|
|
64
|
+
@current.text = @current.text.to_s + text if @current
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
parser.listen( :cdata ) do | text |
|
|
68
|
+
raise "Not implemented !"
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
parser.parse
|
|
72
|
+
rescue REXML::ParseException => e
|
|
73
|
+
@listener.parse_failure(e)
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# =XMPP4R - XMPP Library for Ruby
|
|
2
|
+
# License:: Ruby's license (see the LICENSE file) or GNU GPL, at your option.
|
|
3
|
+
# Website::http://home.gna.org/xmpp4r/
|
|
4
|
+
|
|
5
|
+
require 'xmpp4r/iq'
|
|
6
|
+
require 'xmpp4r/errorexception'
|
|
7
|
+
|
|
8
|
+
module Jabber
|
|
9
|
+
module Vcard
|
|
10
|
+
##
|
|
11
|
+
# The Vcard helper retrieves vCards
|
|
12
|
+
class Helper
|
|
13
|
+
##
|
|
14
|
+
# Initialize a new Vcard helper
|
|
15
|
+
def initialize(stream)
|
|
16
|
+
@stream = stream
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
##
|
|
20
|
+
# Retrieve vCard of an entity
|
|
21
|
+
#
|
|
22
|
+
# Raises exception upon retrieval error, please catch that!
|
|
23
|
+
# (The exception is ErrorException and is raisen by
|
|
24
|
+
# Stream#send_with_id.
|
|
25
|
+
#
|
|
26
|
+
# Usage of Threads is suggested here as vCards can be very
|
|
27
|
+
# big (see <tt>/iq/vCard/PHOTO/BINVAL</tt>).
|
|
28
|
+
#
|
|
29
|
+
# jid:: [Jabber::JID] or nil (should be stripped, nil for the client's own vCard)
|
|
30
|
+
# result:: [Jabber::IqVcard] or nil (nil results may be handled as empty vCards)
|
|
31
|
+
def get(jid=nil)
|
|
32
|
+
res = nil
|
|
33
|
+
request = Iq.new(:get, jid)
|
|
34
|
+
request.from = @stream.jid # Enable components to use this
|
|
35
|
+
request.add(IqVcard.new)
|
|
36
|
+
@stream.send_with_id(request) { |answer|
|
|
37
|
+
# No check for sender or queryns needed (see send_with_id)
|
|
38
|
+
if answer.type == :result
|
|
39
|
+
res = answer.vcard
|
|
40
|
+
true
|
|
41
|
+
else
|
|
42
|
+
false
|
|
43
|
+
end
|
|
44
|
+
}
|
|
45
|
+
res
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
##
|
|
49
|
+
# Set your own vCard (Clients only)
|
|
50
|
+
#
|
|
51
|
+
# Raises exception when setting fails
|
|
52
|
+
#
|
|
53
|
+
# Usage of Threads suggested here, too. The function
|
|
54
|
+
# waits for approval from the server.
|
|
55
|
+
#
|
|
56
|
+
# iqvcard:: [Jabber::IqVcard]
|
|
57
|
+
def set(iqvcard)
|
|
58
|
+
iq = Iq.new(:set)
|
|
59
|
+
iq.add(iqvcard)
|
|
60
|
+
|
|
61
|
+
@stream.send_with_id(iq) { |answer|
|
|
62
|
+
if answer.type == :result
|
|
63
|
+
true
|
|
64
|
+
else
|
|
65
|
+
false
|
|
66
|
+
end
|
|
67
|
+
}
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
##
|
|
71
|
+
# Quickly initialize a Vcard helper and get
|
|
72
|
+
# a vCard. See Vcard#get
|
|
73
|
+
def Vcard.get(stream, jid=nil)
|
|
74
|
+
Vcard.new(stream).get(jid)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
##
|
|
78
|
+
# Quickly initialize a Vcard helper and set
|
|
79
|
+
# your vCard. See Vcard#set
|
|
80
|
+
def Vcard.set(stream, iqvcard)
|
|
81
|
+
Vcard.new(stream).set(iqvcard)
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|