jabber4r 0.8.0
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/CHANGES +8 -0
- data/LICENSE.txt +12 -0
- data/README +180 -0
- data/Rakefile.rb +143 -0
- data/lib/jabber4r/jabber4r.rb +22 -0
- data/lib/jabber4r/jid.rb +93 -0
- data/lib/jabber4r/protocol.rb +1384 -0
- data/lib/jabber4r/rexml_1.8_patch.rb +16 -0
- data/lib/jabber4r/roster.rb +322 -0
- data/lib/jabber4r/session.rb +615 -0
- data/lib/jabber4r/vcard.rb +42 -0
- metadata +55 -0
@@ -0,0 +1,1384 @@
|
|
1
|
+
# License: see LICENSE.txt
|
2
|
+
# Jabber4R - Jabber Instant Messaging Library for Ruby
|
3
|
+
# Copyright (C) 2002 Rich Kilmer <rich@infoether.com>
|
4
|
+
#
|
5
|
+
|
6
|
+
|
7
|
+
require 'singleton'
|
8
|
+
require 'socket'
|
9
|
+
|
10
|
+
module Jabber
|
11
|
+
|
12
|
+
class JabberConnectionException < RuntimeError
|
13
|
+
attr_reader :data
|
14
|
+
|
15
|
+
def initialize(writing, data)
|
16
|
+
@writing = writing
|
17
|
+
@data = data
|
18
|
+
end
|
19
|
+
|
20
|
+
def writing?
|
21
|
+
@writing
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
##
|
26
|
+
# The Protocol module contains helper methods for constructing
|
27
|
+
# Jabber protocol elements and classes that implement protocol
|
28
|
+
# elements.
|
29
|
+
#
|
30
|
+
module Protocol
|
31
|
+
|
32
|
+
USE_PARSER = :rexml # either :rexml or :xmlparser
|
33
|
+
|
34
|
+
##
|
35
|
+
# The parser to use for stream processing. The current
|
36
|
+
# available parsers are:
|
37
|
+
#
|
38
|
+
# * Jabber::Protocol::ExpatJabberParser uses XMLParser
|
39
|
+
# * Jabber::Protocol::REXMLJabberParser uses REXML
|
40
|
+
#
|
41
|
+
# return:: [Class] The parser class
|
42
|
+
#
|
43
|
+
def Protocol.Parser
|
44
|
+
if USE_PARSER==:xmlparser
|
45
|
+
Jabber::Protocol::ExpatJabberParser
|
46
|
+
else
|
47
|
+
Jabber::Protocol::REXMLJabberParser
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
##
|
52
|
+
# The connection class encapsulates the connection to the Jabber
|
53
|
+
# service including managing the socket and controlling the parsing
|
54
|
+
# of the Jabber XML stream.
|
55
|
+
#
|
56
|
+
class Connection
|
57
|
+
DISCONNECTED = 1
|
58
|
+
CONNECTED = 2
|
59
|
+
|
60
|
+
attr_reader :host, :port, :status, :input, :output
|
61
|
+
|
62
|
+
def initialize(host, port=5222)
|
63
|
+
@host = host
|
64
|
+
@port = port
|
65
|
+
@status = DISCONNECTED
|
66
|
+
@filters = {}
|
67
|
+
@threadBlocks = {}
|
68
|
+
@pollCounter = 10
|
69
|
+
end
|
70
|
+
|
71
|
+
##
|
72
|
+
# Connects to the Jabber server through a TCP Socket and
|
73
|
+
# starts the Jabber parser.
|
74
|
+
#
|
75
|
+
def connect
|
76
|
+
@socket = TCPSocket.new(@host, @port)
|
77
|
+
@parser = Jabber::Protocol.Parser.new(@socket, self)
|
78
|
+
@parserThread = Thread.new {@parser.parse}
|
79
|
+
@pollThread = Thread.new {poll}
|
80
|
+
@status = CONNECTED
|
81
|
+
end
|
82
|
+
|
83
|
+
##
|
84
|
+
# Mounts a block to handle exceptions if they occur during the
|
85
|
+
# poll send. This will likely be the first indication that
|
86
|
+
# the socket dropped in a Jabber Session.
|
87
|
+
#
|
88
|
+
def on_connection_exception(&block)
|
89
|
+
@exception_block = block
|
90
|
+
end
|
91
|
+
|
92
|
+
def parse_failure
|
93
|
+
Thread.new {@exception_block.call if @exception_block}
|
94
|
+
end
|
95
|
+
|
96
|
+
##
|
97
|
+
# Returns if this connection is connected to a Jabber service
|
98
|
+
#
|
99
|
+
# return:: [Boolean] Connection status
|
100
|
+
#
|
101
|
+
def is_connected?
|
102
|
+
return @status == CONNECTED
|
103
|
+
end
|
104
|
+
|
105
|
+
##
|
106
|
+
# Returns if this connection is NOT connected to a Jabber service
|
107
|
+
#
|
108
|
+
# return:: [Boolean] Connection status
|
109
|
+
#
|
110
|
+
def is_disconnected?
|
111
|
+
return @status == DISCONNECTED
|
112
|
+
end
|
113
|
+
|
114
|
+
##
|
115
|
+
# Processes a received ParsedXMLElement and executes
|
116
|
+
# registered thread blocks and filters against it.
|
117
|
+
#
|
118
|
+
# element:: [ParsedXMLElement] The received element
|
119
|
+
#
|
120
|
+
def receive(element)
|
121
|
+
while @threadBlocks.size==0 && @filters.size==0
|
122
|
+
sleep 0.1
|
123
|
+
end
|
124
|
+
Jabber::DEBUG && puts("RECEIVED:\n#{element.to_s}")
|
125
|
+
@threadBlocks.each do |thread, proc|
|
126
|
+
begin
|
127
|
+
proc.call(element)
|
128
|
+
if element.element_consumed?
|
129
|
+
@threadBlocks.delete(thread)
|
130
|
+
thread.wakeup if thread.alive?
|
131
|
+
return
|
132
|
+
end
|
133
|
+
rescue Exception => error
|
134
|
+
puts error.to_s
|
135
|
+
puts error.backtrace.join("\n")
|
136
|
+
end
|
137
|
+
end
|
138
|
+
@filters.each_value do |proc|
|
139
|
+
begin
|
140
|
+
proc.call(element)
|
141
|
+
return if element.element_consumed?
|
142
|
+
rescue Exception => error
|
143
|
+
puts error.to_s
|
144
|
+
puts error.backtrace.join("\n")
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
##
|
150
|
+
# Sends XML data to the socket and (optionally) waits
|
151
|
+
# to process received data.
|
152
|
+
#
|
153
|
+
# xml:: [String] The xml data to send
|
154
|
+
# proc:: [Proc = nil] The optional proc
|
155
|
+
# &block:: [Block] The optional block
|
156
|
+
#
|
157
|
+
def send(xml, proc=nil, &block)
|
158
|
+
Jabber::DEBUG && puts("SENDING:\n#{ xml.kind_of?(String) ? xml : xml.to_s }")
|
159
|
+
xml = xml.to_s if not xml.kind_of? String
|
160
|
+
block = proc if proc
|
161
|
+
@threadBlocks[Thread.current]=block if block
|
162
|
+
begin
|
163
|
+
@socket << xml
|
164
|
+
rescue
|
165
|
+
raise JabberConnectionException.new(true, xml)
|
166
|
+
end
|
167
|
+
@pollCounter = 10
|
168
|
+
end
|
169
|
+
|
170
|
+
##
|
171
|
+
# Starts a polling thread to send "keep alive" data to prevent
|
172
|
+
# the Jabber connection from closing for inactivity.
|
173
|
+
#
|
174
|
+
def poll
|
175
|
+
sleep 10
|
176
|
+
while true
|
177
|
+
sleep 2
|
178
|
+
@pollCounter = @pollCounter - 1
|
179
|
+
if @pollCounter < 0
|
180
|
+
begin
|
181
|
+
send(" \t ")
|
182
|
+
rescue
|
183
|
+
Thread.new {@exception_block.call if @exception_block}
|
184
|
+
break
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
##
|
191
|
+
# Adds a filter block/proc to process received XML messages
|
192
|
+
#
|
193
|
+
# xml:: [String] The xml data to send
|
194
|
+
# proc:: [Proc = nil] The optional proc
|
195
|
+
# &block:: [Block] The optional block
|
196
|
+
#
|
197
|
+
def add_filter(ref, proc=nil, &block)
|
198
|
+
block = proc if proc
|
199
|
+
raise "Must supply a block or Proc object to the addFilter method" if block.nil?
|
200
|
+
@filters[ref] = block
|
201
|
+
end
|
202
|
+
|
203
|
+
def delete_filter(ref)
|
204
|
+
@filters.delete(ref)
|
205
|
+
end
|
206
|
+
|
207
|
+
##
|
208
|
+
# Closes the connection to the Jabber service
|
209
|
+
#
|
210
|
+
def close
|
211
|
+
@parserThread.kill if @parserThread
|
212
|
+
@pollThread.kill
|
213
|
+
@socket.close if @socket
|
214
|
+
@status = DISCONNECTED
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
##
|
219
|
+
# Generates an open stream XML element
|
220
|
+
#
|
221
|
+
# host:: [String] The host being connected to
|
222
|
+
# return:: [String] The XML data to send
|
223
|
+
#
|
224
|
+
def Protocol.gen_open_stream(host)
|
225
|
+
return ('<?xml version="1.0" encoding="UTF-8" ?><stream:stream to="'+host+'" xmlns="jabber:client" xmlns:stream="http://etherx.jabber.org/streams">')
|
226
|
+
end
|
227
|
+
|
228
|
+
##
|
229
|
+
# Generates an close stream XML element
|
230
|
+
#
|
231
|
+
# return:: [String] The XML data to send
|
232
|
+
#
|
233
|
+
def Protocol.gen_close_stream
|
234
|
+
return "</stream:stream>"
|
235
|
+
end
|
236
|
+
|
237
|
+
##
|
238
|
+
# The presence class is used to construct presence messages to
|
239
|
+
# send to the Jabber service.
|
240
|
+
#
|
241
|
+
class Presence
|
242
|
+
attr_accessor :to, :from, :id, :type
|
243
|
+
|
244
|
+
# The state to show (chat, xa, dnd, away)
|
245
|
+
attr_accessor :show
|
246
|
+
|
247
|
+
# The status message
|
248
|
+
attr_accessor :status
|
249
|
+
attr_accessor :priority
|
250
|
+
|
251
|
+
##
|
252
|
+
# Constructs a Presence object w/the supplied id
|
253
|
+
#
|
254
|
+
# id:: [String] The message ID
|
255
|
+
# show:: [String] The state to show
|
256
|
+
# status:: [String] The status message
|
257
|
+
#
|
258
|
+
def initialize(id, show=nil, status=nil)
|
259
|
+
@id = id
|
260
|
+
@show = show if show
|
261
|
+
@status = status if status
|
262
|
+
end
|
263
|
+
|
264
|
+
##
|
265
|
+
# Generate a presence object for initial presence notification
|
266
|
+
#
|
267
|
+
# id:: [String] The message ID
|
268
|
+
# show:: [String] The state to show
|
269
|
+
# status:: [String] The status message
|
270
|
+
# return:: [Jabber::Protocol::Presence] The newly created Presence object
|
271
|
+
#
|
272
|
+
def Presence.gen_initial(id, show=nil, status=nil)
|
273
|
+
Presence.new(id, show, status)
|
274
|
+
end
|
275
|
+
|
276
|
+
##
|
277
|
+
# Generate a presence object w/show="normal" (normal availability)
|
278
|
+
#
|
279
|
+
# id:: [String] The message ID
|
280
|
+
# status:: [String=nil] The status message
|
281
|
+
# return:: [Jabber::Protocol::Presence] The newly created Presence object
|
282
|
+
#
|
283
|
+
def Presence.gen_normal(id, status=nil)
|
284
|
+
Presence.new(id, "normal", status)
|
285
|
+
end
|
286
|
+
|
287
|
+
##
|
288
|
+
# Generate a presence object w/show="chat" (free for chat)
|
289
|
+
#
|
290
|
+
# id:: [String] The message ID
|
291
|
+
# status:: [String=nil] The status message
|
292
|
+
# return:: [Jabber::Protocol::Presence] The newly created Presence object
|
293
|
+
#
|
294
|
+
def Presence.gen_chat(id, status=nil)
|
295
|
+
Presence.new(id, "chat", status)
|
296
|
+
end
|
297
|
+
|
298
|
+
##
|
299
|
+
# Generate a presence object w/show="xa" (extended away)
|
300
|
+
#
|
301
|
+
# id:: [String] The message ID
|
302
|
+
# status:: [String=nil] The status message
|
303
|
+
# return:: [Jabber::Protocol::Presence] The newly created Presence object
|
304
|
+
#
|
305
|
+
def Presence.gen_xa(id, status=nil)
|
306
|
+
Presence.new(id, "xa", status)
|
307
|
+
end
|
308
|
+
|
309
|
+
##
|
310
|
+
# Generate a presence object w/show="dnd" (do not disturb)
|
311
|
+
#
|
312
|
+
# id:: [String] The message ID
|
313
|
+
# status:: [String=nil] The status message
|
314
|
+
# return:: [Jabber::Protocol::Presence] The newly created Presence object
|
315
|
+
#
|
316
|
+
def Presence.gen_dnd(id, status=nil)
|
317
|
+
Presence.new(id, "dnd", status)
|
318
|
+
end
|
319
|
+
|
320
|
+
##
|
321
|
+
# Generate a presence object w/show="away" (away from resource)
|
322
|
+
#
|
323
|
+
# id:: [String] The message ID
|
324
|
+
# status:: [String=nil] The status message
|
325
|
+
# return:: [Jabber::Protocol::Presence] The newly created Presence object
|
326
|
+
#
|
327
|
+
def Presence.gen_away(id, status=nil)
|
328
|
+
Presence.new(id, "away", status)
|
329
|
+
end
|
330
|
+
|
331
|
+
##
|
332
|
+
# Generate a presence object w/show="unavailable" (not free for chat)
|
333
|
+
#
|
334
|
+
# id:: [String] The message ID
|
335
|
+
# status:: [String=nil] The status message
|
336
|
+
# return:: [Jabber::Protocol::Presence] The newly created Presence object
|
337
|
+
#
|
338
|
+
def Presence.gen_unavailable(id, status=nil)
|
339
|
+
p = Presence.new(id)
|
340
|
+
p.type="unavailable"
|
341
|
+
p
|
342
|
+
end
|
343
|
+
|
344
|
+
def Presence.gen_new_subscription(to)
|
345
|
+
p = Presence.new(Jabber.gen_random_id)
|
346
|
+
p.type = "subscribe"
|
347
|
+
p.to = to
|
348
|
+
p
|
349
|
+
end
|
350
|
+
|
351
|
+
def Presence.gen_accept_subscription(id, jid)
|
352
|
+
p = Presence.new(id)
|
353
|
+
p.type = "subscribed"
|
354
|
+
p.to = jid
|
355
|
+
p
|
356
|
+
end
|
357
|
+
|
358
|
+
def Presence.gen_accept_unsubscription(id, jid)
|
359
|
+
p = Presence.new(id)
|
360
|
+
p.type = "unsubscribed"
|
361
|
+
p.to = jid
|
362
|
+
p
|
363
|
+
end
|
364
|
+
|
365
|
+
##
|
366
|
+
# Generates the xml representation of this Presence object
|
367
|
+
#
|
368
|
+
# return:: [String] The presence XML message to send the Jabber service
|
369
|
+
#
|
370
|
+
def to_xml
|
371
|
+
e = XMLElement.new("presence")
|
372
|
+
e.add_attribute("id", @id) if @id
|
373
|
+
e.add_attribute("from", @from) if @from
|
374
|
+
e.add_attribute("to", @to) if @to
|
375
|
+
e.add_attribute("type", @type) if @type
|
376
|
+
e.add_child("show").add_data(@show) if @show
|
377
|
+
e.add_child("status").add_data(@status) if @status
|
378
|
+
e.add_child("priority") if @priority
|
379
|
+
e.to_s
|
380
|
+
end
|
381
|
+
|
382
|
+
##
|
383
|
+
# see _to_xml
|
384
|
+
#
|
385
|
+
def to_s
|
386
|
+
to_xml
|
387
|
+
end
|
388
|
+
end
|
389
|
+
|
390
|
+
##
|
391
|
+
# A class used to build/parse IQ requests/responses
|
392
|
+
#
|
393
|
+
class Iq
|
394
|
+
attr_accessor :session,:to, :from, :id, :type, :xmlns, :data,:error,:errorcode
|
395
|
+
ERROR="error"
|
396
|
+
GET="get"
|
397
|
+
SET="set"
|
398
|
+
RESULT="result"
|
399
|
+
|
400
|
+
##
|
401
|
+
# Factory to build an IQ object from xml element
|
402
|
+
#
|
403
|
+
# session:: [Jabber::Session] The Jabber session instance
|
404
|
+
# element:: [Jabber::Protocol::ParsedXMLElement] The received XML object
|
405
|
+
# return:: [Jabber::Protocol::Iq] The newly created Iq object
|
406
|
+
#
|
407
|
+
def Iq.from_element(session, element)
|
408
|
+
iq = Iq.new(session)
|
409
|
+
iq.from = Jabber::JID.new(element.attr_from) if element.attr_from
|
410
|
+
iq.to = Jabber::JID.new(element.attr_to) if element.attr_to
|
411
|
+
iq.type = element.attr_type
|
412
|
+
iq.id = element.attr_id
|
413
|
+
iq.session=session
|
414
|
+
iq.xmlns=element.query.attr_xmlns
|
415
|
+
iq.data=element.query
|
416
|
+
return iq
|
417
|
+
end
|
418
|
+
|
419
|
+
##
|
420
|
+
# Default constructor to build an Iq object
|
421
|
+
# session:: [Jabber::Session] The Jabber session instance
|
422
|
+
# id:: [String=nil] The (optional) id of the Iq object
|
423
|
+
def initialize(session,id=nil)
|
424
|
+
@session=session
|
425
|
+
@id=id
|
426
|
+
end
|
427
|
+
|
428
|
+
##
|
429
|
+
# Return an IQ object that uses the jabber:iq:private namespace
|
430
|
+
#
|
431
|
+
def Iq.get_private(session,id,ename,ns)
|
432
|
+
iq=Iq.new(session,id)
|
433
|
+
iq.type="get"
|
434
|
+
iq.xmlns="jabber:iq:private"
|
435
|
+
iq.data=XMLElement.new(ename,{'xmlns' => ns});
|
436
|
+
return iq
|
437
|
+
end
|
438
|
+
|
439
|
+
|
440
|
+
##
|
441
|
+
# Generates an IQ roster request XML element
|
442
|
+
#
|
443
|
+
# id:: [String] The message id
|
444
|
+
# return:: [String] The XML data to send
|
445
|
+
#
|
446
|
+
def Iq.gen_roster(session, id)
|
447
|
+
iq = Iq.new(session, id)
|
448
|
+
iq.type = "get"
|
449
|
+
iq.xmlns = "jabber:iq:roster"
|
450
|
+
return iq
|
451
|
+
#return XMLElement.new("iq", {"type"=>"get", "id"=>id}).add_child("query", {"xmlns"=>"jabber:iq:roster"}).to_s
|
452
|
+
end
|
453
|
+
|
454
|
+
##
|
455
|
+
# Generates an IQ authortization request XML element
|
456
|
+
#
|
457
|
+
# id:: [String] The message id
|
458
|
+
# username:: [String] The username
|
459
|
+
# password:: [String] The password
|
460
|
+
# email:: [String] The email address of the account
|
461
|
+
# name:: [String] The full name
|
462
|
+
# return:: [String] The XML data to send
|
463
|
+
#
|
464
|
+
def Iq.gen_registration(session, id, username, password, email, name)
|
465
|
+
iq = Iq.new(session, id)
|
466
|
+
iq.type = "set"
|
467
|
+
iq.xmlns = "jabber:iq:register"
|
468
|
+
iq.data = XMLElement.new("username").add_data(username).to_s
|
469
|
+
iq.data << XMLElement.new("password").add_data(password).to_s
|
470
|
+
iq.data << XMLElement.new("email").add_data(email).to_s
|
471
|
+
iq.data << XMLElement.new("name").add_data(name).to_s
|
472
|
+
return iq
|
473
|
+
end
|
474
|
+
|
475
|
+
##
|
476
|
+
# Generates an IQ Roster Item add request XML element
|
477
|
+
#
|
478
|
+
# session:: [Session] The session
|
479
|
+
# id:: [String] The message id
|
480
|
+
# jid:: [JID] The Jabber ID to add to the roster
|
481
|
+
# name:: [String] The full name
|
482
|
+
# return:: [String] The XML data to send
|
483
|
+
#
|
484
|
+
def Iq.gen_add_rosteritem(session, id, jid, name)
|
485
|
+
iq = Iq.new(session, id)
|
486
|
+
iq.type = "set"
|
487
|
+
iq.xmlns = "jabber:iq:roster"
|
488
|
+
iq.data = XMLElement.new("item").add_attribute("jid", jid).add_attribute("name", name).to_s
|
489
|
+
return iq
|
490
|
+
end
|
491
|
+
|
492
|
+
##
|
493
|
+
# Generates an IQ authortization request XML element
|
494
|
+
#
|
495
|
+
# id:: [String] The message id
|
496
|
+
# username:: [String] The username
|
497
|
+
# password:: [String] The password
|
498
|
+
# resource:: [String] The resource to bind this session to
|
499
|
+
# return:: [String] The XML data to send
|
500
|
+
#
|
501
|
+
def Iq.gen_auth(session, id, username, password, resource)
|
502
|
+
iq = Iq.new(session, id)
|
503
|
+
iq.type = "set"
|
504
|
+
iq.xmlns = "jabber:iq:auth"
|
505
|
+
iq.data = XMLElement.new("username").add_data(username).to_s
|
506
|
+
iq.data << XMLElement.new("password").add_data(password).to_s
|
507
|
+
iq.data << XMLElement.new("resource").add_data(resource).to_s
|
508
|
+
return iq
|
509
|
+
#element = XMLElement.new("iq", {"type"=>"set", "id"=>id}).add_child("query", {"xmlns"=>"jabber:iq:auth"}).add_child("username").add_data(username).to_parent.add_child("password").add_data(password).to_parent.add_child("resource").add_data(resource).to_parent.to_s
|
510
|
+
end
|
511
|
+
|
512
|
+
##
|
513
|
+
# Generates an IQ digest authortization request XML element
|
514
|
+
#
|
515
|
+
# id:: [String] The message id
|
516
|
+
# username:: [String] The username
|
517
|
+
# digest:: [String] The SHA-1 hash of the sessionid and the password
|
518
|
+
# resource:: [String] The resource to bind this session to
|
519
|
+
# return:: [String] The XML data to send
|
520
|
+
#
|
521
|
+
def Iq.gen_auth_digest(session, id, username, digest, resource)
|
522
|
+
iq = Iq.new(session, id)
|
523
|
+
iq.type = "set"
|
524
|
+
iq.xmlns = "jabber:iq:auth"
|
525
|
+
iq.data = XMLElement.new("username").add_data(username).to_s
|
526
|
+
iq.data << XMLElement.new("digest").add_data(digest).to_s
|
527
|
+
iq.data << XMLElement.new("resource").add_data(resource).to_s
|
528
|
+
return iq
|
529
|
+
#return XMLElement.new("iq", {"type"=>"set", "id"=>id}).add_child("query", {"xmlns"=>"jabber:iq:auth"}).add_child("username").add_data(username).to_parent.add_child("digest").add_data(digest).to_parent.add_child("resource").add_data(resource).to_parent.to_s
|
530
|
+
end
|
531
|
+
|
532
|
+
##
|
533
|
+
# Generates an IQ out of bounds XML element
|
534
|
+
#
|
535
|
+
# to:: [JID] The Jabber ID to send to
|
536
|
+
# url:: [String] The data to send
|
537
|
+
# desc:: [String=""] The description of the data
|
538
|
+
# return:: [String] The XML data to send
|
539
|
+
#
|
540
|
+
def Iq.gen_oob(session, to, url, desc="")
|
541
|
+
iq = Iq.new(session, nil)
|
542
|
+
iq.type = "set"
|
543
|
+
iq.xmlns = "jabber:iq:oob"
|
544
|
+
iq.data = XMLElement.new("url").add_data(url).to_s
|
545
|
+
iq.data << XMLElement.new("desc").add_data(desc).to_s
|
546
|
+
return iq
|
547
|
+
#return XMLElement.new("iq", {"type"=>"set"}).add_child("query", {"xmlns"=>"jabber:iq:oob"}).add_child("url").add_data(url).to_parent.add_child("desc").add_data(data).to_parent.to_s
|
548
|
+
end
|
549
|
+
|
550
|
+
##
|
551
|
+
# Generates an VCard request XML element
|
552
|
+
#
|
553
|
+
# id:: [String] The message ID
|
554
|
+
# to:: [JID] The jabber id of the account to get the VCard for
|
555
|
+
# return:: [String] The XML data to send
|
556
|
+
#
|
557
|
+
def Iq.gen_vcard(session, id, to)
|
558
|
+
iq = Iq.new(session, id)
|
559
|
+
iq.xmlns = "vcard-temp"
|
560
|
+
iq.type = "get"
|
561
|
+
iq.to = to
|
562
|
+
return iq
|
563
|
+
#return XMLElement.new("iq", {"type"=>"get", "id"=>id, "to"=>to}).add_child("query", {"xmlns"=>"vcard-temp"}).to_s
|
564
|
+
end
|
565
|
+
|
566
|
+
|
567
|
+
|
568
|
+
|
569
|
+
##
|
570
|
+
# Sends the IQ to the Jabber service for delivery
|
571
|
+
#
|
572
|
+
# wait:: [Boolean = false] Wait for reply before return?
|
573
|
+
# &block:: [Block] A block to process the message replies
|
574
|
+
#
|
575
|
+
def send(wait=false, &block)
|
576
|
+
if wait
|
577
|
+
iq = nil
|
578
|
+
blockedThread = Thread.current
|
579
|
+
@session.connection.send(self.to_s, block) do |je|
|
580
|
+
if je.element_tag == "iq" and je.attr_id == @id
|
581
|
+
je.consume_element
|
582
|
+
iq = Iq.from_element(@session, je)
|
583
|
+
blockedThread.wakeup
|
584
|
+
end
|
585
|
+
end
|
586
|
+
Thread.stop
|
587
|
+
rturn iq
|
588
|
+
else
|
589
|
+
@session.connection.send(self.to_s, block) if @session
|
590
|
+
end
|
591
|
+
end
|
592
|
+
|
593
|
+
##
|
594
|
+
# Builds a reply to an existing Iq
|
595
|
+
#
|
596
|
+
# return:: [Jabber::Protocol::Iq] The result Iq
|
597
|
+
#
|
598
|
+
def reply
|
599
|
+
iq = Iq.new(@session,@id)
|
600
|
+
iq.to = @from
|
601
|
+
iq.id = @id
|
602
|
+
iq.type = 'result'
|
603
|
+
@is_reply = true
|
604
|
+
return iq
|
605
|
+
end
|
606
|
+
|
607
|
+
##
|
608
|
+
# Generates XML that complies with the Jabber protocol for
|
609
|
+
# sending the Iq through the Jabber service.
|
610
|
+
#
|
611
|
+
# return:: [String] The XML string.
|
612
|
+
#
|
613
|
+
def to_xml
|
614
|
+
elem = XMLElement.new("iq", { "type"=>@type})
|
615
|
+
elem.add_attribute("to" ,@to) if @to
|
616
|
+
elem.add_attribute("id", @id) if @id
|
617
|
+
elem.add_child("query").add_attribute("xmlns",@xmlns).add_data(@data.to_s)
|
618
|
+
if @type=="error" then
|
619
|
+
e=elem.add_child("error");
|
620
|
+
e.add_attribute("code",@errorcode) if @errorcode
|
621
|
+
e.add_data(@error) if @error
|
622
|
+
end
|
623
|
+
return elem.to_s
|
624
|
+
end
|
625
|
+
|
626
|
+
##
|
627
|
+
# see to_xml
|
628
|
+
#
|
629
|
+
def to_s
|
630
|
+
to_xml
|
631
|
+
end
|
632
|
+
|
633
|
+
end
|
634
|
+
|
635
|
+
class Message
|
636
|
+
attr_accessor :to, :from, :id, :type, :body, :xhtml, :subject, :thread, :x, :oobData, :errorcode, :error
|
637
|
+
NORMAL = "normal"
|
638
|
+
ERROR="error"
|
639
|
+
CHAT="chat"
|
640
|
+
GROUPCHAT="groupchat"
|
641
|
+
HEADLINE="headline"
|
642
|
+
|
643
|
+
##
|
644
|
+
# Factory to build a Message from an XMLElement
|
645
|
+
#
|
646
|
+
# session:: [Jabber::Session] The Jabber session instance
|
647
|
+
# element:: [Jabber::Protocol::ParsedXMLElement] The received XML object
|
648
|
+
# return:: [Jabber::Protocol::Message] The newly created Message object
|
649
|
+
#
|
650
|
+
def Message.from_element(session, element)
|
651
|
+
message = Message.new(element.attr_to)
|
652
|
+
message.from = Jabber::JID.new(element.attr_from) if element.attr_from
|
653
|
+
message.type = element.attr_type
|
654
|
+
message.id = element.attr_id
|
655
|
+
message.thread = element.thread.element_data
|
656
|
+
message.body = element.body.element_data
|
657
|
+
message.xhtml = element.xhtml.element_data
|
658
|
+
message.subject = element.subject.element_data
|
659
|
+
message.oobData = element.x.element_data
|
660
|
+
message.session=session
|
661
|
+
return message
|
662
|
+
end
|
663
|
+
|
664
|
+
##
|
665
|
+
# Creates a Message
|
666
|
+
#
|
667
|
+
# to:: [String | Jabber::JID] The jabber id to send this message to (or from)
|
668
|
+
# type:: [Integer=NORMAL] The type of message...Message::(NORMAL, CHAT, GROUPCHAT, HEADLINE)
|
669
|
+
#
|
670
|
+
def initialize(to, type=NORMAL)
|
671
|
+
return unless to
|
672
|
+
to = Jabber::JID.new(to) if to.kind_of? String
|
673
|
+
@to = to if to.kind_of? Jabber::JID
|
674
|
+
@type = type
|
675
|
+
end
|
676
|
+
|
677
|
+
##
|
678
|
+
# Chaining method...sets the body of the message
|
679
|
+
#
|
680
|
+
# body:: [String] The message body
|
681
|
+
# return:: [Jabber::Protocol::Message] The current Message object
|
682
|
+
#
|
683
|
+
def set_body(body)
|
684
|
+
@body = body.gsub(/[&]/, '&').gsub(/[<]/, '<').gsub(/[']/, ''')
|
685
|
+
self
|
686
|
+
end
|
687
|
+
|
688
|
+
##
|
689
|
+
# Chaining method...sets the subject of the message
|
690
|
+
#
|
691
|
+
# subject:: [String] The message subject
|
692
|
+
# return:: [Jabber::Protocol::Message] The current Message object
|
693
|
+
#
|
694
|
+
def set_subject(subject)
|
695
|
+
@subject = subject.gsub(/[&]/, '&').gsub(/[<]/, '<').gsub(/[']/, ''')
|
696
|
+
self
|
697
|
+
end
|
698
|
+
|
699
|
+
##
|
700
|
+
# Chaining method...sets the XHTML body of the message
|
701
|
+
#
|
702
|
+
# body:: [String] The message body
|
703
|
+
# return:: [Jabber::Protocol::Message] The current message object
|
704
|
+
#
|
705
|
+
def set_xhtml(xhtml)
|
706
|
+
@xhtml=xhtml
|
707
|
+
self
|
708
|
+
end
|
709
|
+
|
710
|
+
##
|
711
|
+
# Chaining method...sets the thread of the message
|
712
|
+
#
|
713
|
+
# thread:: [String] The message thread id
|
714
|
+
# return:: [Jabber::Protocol::Message] The current Message object
|
715
|
+
#
|
716
|
+
def set_thread(thread)
|
717
|
+
@thread = thread
|
718
|
+
self
|
719
|
+
end
|
720
|
+
|
721
|
+
##
|
722
|
+
# Chaining method...sets the OOB data of the message
|
723
|
+
#
|
724
|
+
# data:: [String] The message OOB data
|
725
|
+
# return:: [Jabber::Protocol::Message] The current Message object
|
726
|
+
#
|
727
|
+
def set_outofband(data)
|
728
|
+
@oobData = data
|
729
|
+
self
|
730
|
+
end
|
731
|
+
|
732
|
+
##
|
733
|
+
# Chaining method...sets the extended data of the message
|
734
|
+
#
|
735
|
+
# x:: [String] The message x data
|
736
|
+
# return:: [Jabber::Protocol::Message] The current Message object
|
737
|
+
#
|
738
|
+
def set_x(x)
|
739
|
+
@x = x
|
740
|
+
self
|
741
|
+
end
|
742
|
+
|
743
|
+
##
|
744
|
+
# Sets an error code to be returned(chaining method)
|
745
|
+
#
|
746
|
+
# code:: [Integer] the jabber error code
|
747
|
+
# reason:: [String] Why the error was reported
|
748
|
+
# return:: [Jabber::Protocol::Message] The current Message object
|
749
|
+
#
|
750
|
+
|
751
|
+
def set_error(code,reason)
|
752
|
+
@errorcode=code
|
753
|
+
@error=reason
|
754
|
+
@type="error"
|
755
|
+
self
|
756
|
+
end
|
757
|
+
|
758
|
+
##
|
759
|
+
# Convenience method for send(true)
|
760
|
+
#
|
761
|
+
# ttl:: [Integer = nil] The time (in seconds) to wait for a reply before assuming nil
|
762
|
+
# &block:: [Block] A block to process the message replies
|
763
|
+
#
|
764
|
+
def request(ttl=nil, &block)
|
765
|
+
send(true, ttl, &block)
|
766
|
+
end
|
767
|
+
|
768
|
+
##
|
769
|
+
# Sends the message to the Jabber service for delivery
|
770
|
+
#
|
771
|
+
# wait:: [Boolean = false] Wait for reply before return?
|
772
|
+
# ttl:: [Integer = nil] The time (in seconds) to wait for a reply before assuming nil
|
773
|
+
# &block:: [Block] A block to process the message replies
|
774
|
+
#
|
775
|
+
def send(wait=false, ttl=nil, &block)
|
776
|
+
if wait
|
777
|
+
message = nil
|
778
|
+
blockedThread = Thread.current
|
779
|
+
timer_thread = nil
|
780
|
+
timeout = false
|
781
|
+
unless ttl.nil?
|
782
|
+
timer_thread = Thread.new {
|
783
|
+
sleep ttl
|
784
|
+
timeout = true
|
785
|
+
blockedThread.wakeup
|
786
|
+
}
|
787
|
+
end
|
788
|
+
@session.connection.send(self.to_s, block) do |je|
|
789
|
+
if je.element_tag == "message" and je.thread.element_data == @thread
|
790
|
+
je.consume_element
|
791
|
+
message = Message.from_element(@session, je)
|
792
|
+
blockedThread.wakeup unless timeout
|
793
|
+
unless timer_thread.nil?
|
794
|
+
timer_thread.kill
|
795
|
+
timer_thread = nil
|
796
|
+
end
|
797
|
+
end
|
798
|
+
end
|
799
|
+
Thread.stop
|
800
|
+
return message
|
801
|
+
else
|
802
|
+
@session.connection.send(self.to_s, block) if @session
|
803
|
+
end
|
804
|
+
end
|
805
|
+
|
806
|
+
##
|
807
|
+
# Sets the session instance
|
808
|
+
#
|
809
|
+
# session:: [Jabber::Session] The session instance
|
810
|
+
# return:: [Jabber::Protocol::Message] The current Message object
|
811
|
+
#
|
812
|
+
def session=(session)
|
813
|
+
@session = session
|
814
|
+
self
|
815
|
+
end
|
816
|
+
|
817
|
+
##
|
818
|
+
# Builds a reply to an existing message by setting:
|
819
|
+
# 1. to = from
|
820
|
+
# 2. id = id
|
821
|
+
# 3. thread = thread
|
822
|
+
# 4. type = type
|
823
|
+
# 5. session = session
|
824
|
+
#
|
825
|
+
# return:: [Jabber::Protocol::Message] The reply message
|
826
|
+
#
|
827
|
+
def reply
|
828
|
+
message = Message.new(nil)
|
829
|
+
message.to = @from
|
830
|
+
message.id = @id
|
831
|
+
message.thread = @thread
|
832
|
+
message.type = @type
|
833
|
+
message.session = @session
|
834
|
+
@is_reply = true
|
835
|
+
return message
|
836
|
+
end
|
837
|
+
|
838
|
+
##
|
839
|
+
# Generates XML that complies with the Jabber protocol for
|
840
|
+
# sending the message through the Jabber service.
|
841
|
+
#
|
842
|
+
# return:: [String] The XML string.
|
843
|
+
#
|
844
|
+
def to_xml
|
845
|
+
@thread = Jabber.gen_random_thread if @thread.nil? and (not @is_reply)
|
846
|
+
elem = XMLElement.new("message", {"to"=>@to, "type"=>@type})
|
847
|
+
elem.add_attribute("id", @id) if @id
|
848
|
+
elem.add_child("thread").add_data(@thread) if @thread
|
849
|
+
elem.add_child("subject").add_data(@subject) if @subject
|
850
|
+
elem.add_child("body").add_data(@body) if @body
|
851
|
+
if @xhtml then
|
852
|
+
t=elem.add_child("xhtml").add_attribute("xmlns","http://www.w3.org/1999/xhtml")
|
853
|
+
t.add_child("body").add_data(@xhtml)
|
854
|
+
end
|
855
|
+
if @type=="error" then
|
856
|
+
e=elem.add_child("error");
|
857
|
+
e.add_attribute("code",@errorcode) if @errorcode
|
858
|
+
e.add_data(@error) if @error
|
859
|
+
end
|
860
|
+
elem.add_child("x").add_attribute("xmlns", "jabber:x:oob").add_data(@oobData) if @oobData
|
861
|
+
elem.add_xml(@x.to_s) if @x
|
862
|
+
return elem.to_s
|
863
|
+
end
|
864
|
+
|
865
|
+
##
|
866
|
+
# see to_xml
|
867
|
+
#
|
868
|
+
def to_s
|
869
|
+
to_xml
|
870
|
+
end
|
871
|
+
|
872
|
+
end
|
873
|
+
|
874
|
+
##
|
875
|
+
# Utility class to create valid XML strings
|
876
|
+
#
|
877
|
+
class XMLElement
|
878
|
+
|
879
|
+
# The parent XMLElement
|
880
|
+
attr_accessor :parent
|
881
|
+
|
882
|
+
##
|
883
|
+
# Construct an XMLElement for the supplied tag and attributes
|
884
|
+
#
|
885
|
+
# tag:: [String] XML tag
|
886
|
+
# attributes:: [Hash = {}] The attribute hash[attribute]=value
|
887
|
+
def initialize(tag, attributes={})
|
888
|
+
@tag = tag
|
889
|
+
@elements = []
|
890
|
+
@attributes = attributes
|
891
|
+
@data = ""
|
892
|
+
end
|
893
|
+
|
894
|
+
##
|
895
|
+
# Adds an attribute to this element
|
896
|
+
#
|
897
|
+
# attrib:: [String] The attribute name
|
898
|
+
# value:: [String] The attribute value
|
899
|
+
# return:: [Jabber::Protocol::XMLElement] self for chaining
|
900
|
+
#
|
901
|
+
def add_attribute(attrib, value)
|
902
|
+
@attributes[attrib]=value
|
903
|
+
self
|
904
|
+
end
|
905
|
+
|
906
|
+
##
|
907
|
+
# Adds data to this element
|
908
|
+
#
|
909
|
+
# data:: [String] The data to add
|
910
|
+
# return:: [Jabber::Protocol::XMLElement] self for chaining
|
911
|
+
#
|
912
|
+
def add_data(data)
|
913
|
+
@data += data.to_s
|
914
|
+
self
|
915
|
+
end
|
916
|
+
|
917
|
+
##
|
918
|
+
# Sets the namespace for this tag
|
919
|
+
#
|
920
|
+
# ns:: [String] The namespace
|
921
|
+
# return:: [Jabber::Protocol::XMLElement] self for chaining
|
922
|
+
#
|
923
|
+
def set_namespace(ns)
|
924
|
+
@tag+=":#{ns}"
|
925
|
+
self
|
926
|
+
end
|
927
|
+
|
928
|
+
##
|
929
|
+
# Adds cdata to this element
|
930
|
+
#
|
931
|
+
# cdata:: [String] The cdata to add
|
932
|
+
# return:: [Jabber::Protocol::XMLElement] self for chaining
|
933
|
+
#
|
934
|
+
def add_cdata(cdata)
|
935
|
+
@data += "<![CDATA[#{cdata.to_s}]]>"
|
936
|
+
self
|
937
|
+
end
|
938
|
+
|
939
|
+
##
|
940
|
+
# Returns the parent element
|
941
|
+
#
|
942
|
+
# return:: [Jabber::Protocol::XMLElement] The parent XMLElement
|
943
|
+
#
|
944
|
+
def to_parent
|
945
|
+
@parent
|
946
|
+
end
|
947
|
+
|
948
|
+
##
|
949
|
+
# Adds a child to this element of the supplied tag
|
950
|
+
#
|
951
|
+
# tag:: [String] The element tag
|
952
|
+
# attributes:: [Hash = {}] The attributes hash[attribute]=value
|
953
|
+
# return:: [Jabber::Protocol::XMLElement] newly created child element
|
954
|
+
#
|
955
|
+
def add_child(tag, attributes={})
|
956
|
+
child = XMLElement.new(tag, attributes)
|
957
|
+
child.parent = self
|
958
|
+
@elements << child
|
959
|
+
return child
|
960
|
+
end
|
961
|
+
|
962
|
+
##
|
963
|
+
# Adds arbitrary XML data to this object
|
964
|
+
#
|
965
|
+
# xml:: [String] the xml to add
|
966
|
+
#
|
967
|
+
def add_xml(xml)
|
968
|
+
@xml = xml
|
969
|
+
end
|
970
|
+
|
971
|
+
##
|
972
|
+
# Recursively builds the XML string by traversing this element's
|
973
|
+
# children.
|
974
|
+
#
|
975
|
+
# format:: [Boolean] True to pretty-print (format) the output string
|
976
|
+
# indent:: [Integer = 0] The indent level (recursively more)
|
977
|
+
#
|
978
|
+
def to_xml(format, indent=0)
|
979
|
+
result = ""
|
980
|
+
result += " "*indent if format
|
981
|
+
result += "<#{@tag}"
|
982
|
+
@attributes.each {|attrib, value| result += (' '+attrib.to_s+'="'+value.to_s+'"') }
|
983
|
+
if @data=="" and @elements.size==0
|
984
|
+
result +="/>"
|
985
|
+
result +="\n" if format
|
986
|
+
return result
|
987
|
+
end
|
988
|
+
result += ">"
|
989
|
+
result += "\n" if format and @data==""
|
990
|
+
result += @data if @data!=""
|
991
|
+
@elements.each {|element| result+=element.to_xml(format, indent+4)}
|
992
|
+
result += @xml if not @xml.nil?
|
993
|
+
result += " "*indent if format and @data==""
|
994
|
+
result+="</#{@tag}>"
|
995
|
+
result+="\n" if format
|
996
|
+
return result
|
997
|
+
end
|
998
|
+
|
999
|
+
##
|
1000
|
+
# Climbs to the top of this elements parent tree and then returns
|
1001
|
+
# the to_xml XML string.
|
1002
|
+
#
|
1003
|
+
# return:: [String] The XML string of this element (from the topmost parent).
|
1004
|
+
#
|
1005
|
+
def to_s
|
1006
|
+
return @parent.to_s if @parent
|
1007
|
+
return to_xml(true)
|
1008
|
+
end
|
1009
|
+
end
|
1010
|
+
|
1011
|
+
##
|
1012
|
+
# This class is constructed from XML data elements that are received from
|
1013
|
+
# the Jabber service.
|
1014
|
+
#
|
1015
|
+
class ParsedXMLElement
|
1016
|
+
|
1017
|
+
##
|
1018
|
+
# This class is used to return nil element values to prevent errors (and
|
1019
|
+
# reduce the number of checks.
|
1020
|
+
#
|
1021
|
+
class NilParsedXMLElement
|
1022
|
+
|
1023
|
+
##
|
1024
|
+
# Override to return nil
|
1025
|
+
#
|
1026
|
+
# return:: [nil]
|
1027
|
+
#
|
1028
|
+
def method_missing(methId, *args)
|
1029
|
+
return nil
|
1030
|
+
end
|
1031
|
+
|
1032
|
+
##
|
1033
|
+
# Evaluate as nil
|
1034
|
+
#
|
1035
|
+
# return:: [Boolean] true
|
1036
|
+
#
|
1037
|
+
def nil?
|
1038
|
+
return true
|
1039
|
+
end
|
1040
|
+
|
1041
|
+
##
|
1042
|
+
# Return a zero count
|
1043
|
+
#
|
1044
|
+
# return:: [Integer] 0
|
1045
|
+
#
|
1046
|
+
def count
|
1047
|
+
0
|
1048
|
+
end
|
1049
|
+
|
1050
|
+
include Singleton
|
1051
|
+
end
|
1052
|
+
|
1053
|
+
# The <tag> as String
|
1054
|
+
attr_reader :element_tag
|
1055
|
+
|
1056
|
+
# The parent ParsedXMLElement
|
1057
|
+
attr_reader :element_parent
|
1058
|
+
|
1059
|
+
# A hash of ParsedXMLElement children
|
1060
|
+
attr_reader :element_children
|
1061
|
+
|
1062
|
+
# The data <tag>data</tag> for a tag
|
1063
|
+
attr_reader :element_data
|
1064
|
+
|
1065
|
+
##
|
1066
|
+
# Construct an instance for the given tag
|
1067
|
+
#
|
1068
|
+
# tag:: [String] The tag
|
1069
|
+
# parent:: [Jabber::Protocol::ParsedXMLElement = nil] The parent element
|
1070
|
+
#
|
1071
|
+
def initialize(tag, parent=nil)
|
1072
|
+
@element_tag = tag
|
1073
|
+
@element_parent = parent
|
1074
|
+
@element_children = {}
|
1075
|
+
@attributes = {}
|
1076
|
+
@element_consumed = false
|
1077
|
+
end
|
1078
|
+
|
1079
|
+
##
|
1080
|
+
# Add the attribute to the element
|
1081
|
+
# <tag name="value">data</tag>
|
1082
|
+
#
|
1083
|
+
# name:: [String] The attribute name
|
1084
|
+
# value:: [String] The attribute value
|
1085
|
+
# return:: [Jabber::Protocol::ParsedXMLElement] self for chaining
|
1086
|
+
#
|
1087
|
+
def add_attribute(name, value)
|
1088
|
+
@attributes[name]=value
|
1089
|
+
self
|
1090
|
+
end
|
1091
|
+
|
1092
|
+
##
|
1093
|
+
# Factory to build a child element from this element with the given tag
|
1094
|
+
#
|
1095
|
+
# tag:: [String] The tag name
|
1096
|
+
# return:: [Jabber::Protocol::ParsedXMLElement] The newly created child element
|
1097
|
+
#
|
1098
|
+
def add_child(tag)
|
1099
|
+
child = ParsedXMLElement.new(tag, self)
|
1100
|
+
@element_children[tag] = Array.new if not @element_children.has_key? tag
|
1101
|
+
@element_children[tag] << child
|
1102
|
+
return child
|
1103
|
+
end
|
1104
|
+
|
1105
|
+
##
|
1106
|
+
# When an xml is received from the Jabber service and a ParsedXMLElement is created,
|
1107
|
+
# it is propogated to all filters and listeners. Any one of those can consume the element
|
1108
|
+
# to prevent its propogation to other filters or listeners. This method marks the element
|
1109
|
+
# as consumed.
|
1110
|
+
#
|
1111
|
+
def consume_element
|
1112
|
+
@element_consumed = true
|
1113
|
+
end
|
1114
|
+
|
1115
|
+
##
|
1116
|
+
# Checks if the element is consumed
|
1117
|
+
#
|
1118
|
+
# return:: [Boolean] True if the element is consumed
|
1119
|
+
#
|
1120
|
+
def element_consumed?
|
1121
|
+
@element_consumed
|
1122
|
+
end
|
1123
|
+
|
1124
|
+
##
|
1125
|
+
# Appends data to the element
|
1126
|
+
#
|
1127
|
+
# data:: [String] The data to append
|
1128
|
+
# return:: [Jabber::Protocol::ParsedXMLElement] self for chaining
|
1129
|
+
#
|
1130
|
+
def append_data(data)
|
1131
|
+
@element_data = "" unless @element_data
|
1132
|
+
@element_data += data
|
1133
|
+
self
|
1134
|
+
end
|
1135
|
+
|
1136
|
+
##
|
1137
|
+
# Calls the parent's element_children (hash) index off of this elements
|
1138
|
+
# tag and gets the supplied index. In this sense it gets its sibling based
|
1139
|
+
# on offset.
|
1140
|
+
#
|
1141
|
+
# number:: [Integer] The number of the sibling to get
|
1142
|
+
# return:: [Jabber::Protocol::ParsedXMLElement] The sibling element
|
1143
|
+
#
|
1144
|
+
def [](number)
|
1145
|
+
return @element_parent.element_children[@element_tag][number] if @element_parent
|
1146
|
+
end
|
1147
|
+
|
1148
|
+
##
|
1149
|
+
# Returns the count of siblings with this element's tag
|
1150
|
+
#
|
1151
|
+
# return:: [Integer] The number of sibling elements
|
1152
|
+
#
|
1153
|
+
def count
|
1154
|
+
return @element_parent.element_children[@element_tag].size if @element_parent
|
1155
|
+
return 0
|
1156
|
+
end
|
1157
|
+
|
1158
|
+
##
|
1159
|
+
# see _count
|
1160
|
+
#
|
1161
|
+
def size
|
1162
|
+
count
|
1163
|
+
end
|
1164
|
+
|
1165
|
+
##
|
1166
|
+
# Overrides to allow for directly accessing child elements
|
1167
|
+
# and attributes. If prefaced by attr_ it looks for an attribute
|
1168
|
+
# that matches or checks for a child with a tag that matches
|
1169
|
+
# the method name. If no match occurs, it returns a
|
1170
|
+
# NilParsedXMLElement (singleton) instance.
|
1171
|
+
#
|
1172
|
+
# Example:: <alpha number="1"><beta number="2">Beta Data</beta></alpha>
|
1173
|
+
#
|
1174
|
+
# element.element_tag #=> alpha
|
1175
|
+
# element.attr_number #=> 1
|
1176
|
+
# element.beta.element_data #=> Beta Data
|
1177
|
+
#
|
1178
|
+
def method_missing(methId, *args)
|
1179
|
+
tag = methId.id2name
|
1180
|
+
if tag[0..4]=="attr_"
|
1181
|
+
return @attributes[tag[5..-1]]
|
1182
|
+
end
|
1183
|
+
list = @element_children[tag]
|
1184
|
+
return list[0] if list
|
1185
|
+
return NilParsedXMLElement.instance
|
1186
|
+
end
|
1187
|
+
|
1188
|
+
##
|
1189
|
+
# Returns the valid XML as a string
|
1190
|
+
#
|
1191
|
+
# return:: [String] XML string
|
1192
|
+
def to_s
|
1193
|
+
begin
|
1194
|
+
result = "\n<#{@element_tag}"
|
1195
|
+
@attributes.each {|key, value| result += (' '+key+'="'+value+'"') }
|
1196
|
+
if @element_children.size>0 or @element_data
|
1197
|
+
result += ">"
|
1198
|
+
else
|
1199
|
+
result += "/>"
|
1200
|
+
end
|
1201
|
+
result += @element_data if @element_data
|
1202
|
+
@element_children.each_value {|array| array.each {|je| result += je.to_s} }
|
1203
|
+
result += "\n" if @element_children.size>0
|
1204
|
+
result += "</#{@element_tag}>" if @element_children.size>0 or @element_data
|
1205
|
+
result
|
1206
|
+
rescue => exception
|
1207
|
+
puts exception.to_s
|
1208
|
+
end
|
1209
|
+
end
|
1210
|
+
end
|
1211
|
+
|
1212
|
+
if USE_PARSER == :xmlparser
|
1213
|
+
require 'xmlparser'
|
1214
|
+
##
|
1215
|
+
# The ExpatJabberParser uses XMLParser (expat) to parse the incoming XML stream
|
1216
|
+
# of the Jabber protocol and fires ParsedXMLElements at the Connection
|
1217
|
+
# instance.
|
1218
|
+
#
|
1219
|
+
class ExpatJabberParser
|
1220
|
+
|
1221
|
+
# status if the parser is started
|
1222
|
+
attr_reader :started
|
1223
|
+
|
1224
|
+
##
|
1225
|
+
# Constructs a parser for the supplied stream (socket input)
|
1226
|
+
#
|
1227
|
+
# stream:: [IO] Socket input stream
|
1228
|
+
# listener:: [#receive(ParsedXMLElement)] The listener (usually a Jabber::Protocol::Connection instance
|
1229
|
+
#
|
1230
|
+
def initialize(stream, listener)
|
1231
|
+
@stream = stream
|
1232
|
+
def @stream.gets
|
1233
|
+
super(">")
|
1234
|
+
end
|
1235
|
+
@listener = listener
|
1236
|
+
end
|
1237
|
+
|
1238
|
+
##
|
1239
|
+
# Begins parsing the XML stream and does not return until
|
1240
|
+
# the stream closes.
|
1241
|
+
#
|
1242
|
+
def parse
|
1243
|
+
@started = false
|
1244
|
+
|
1245
|
+
parser = XMLParser.new("UTF-8")
|
1246
|
+
def parser.unknownEncoding(e)
|
1247
|
+
raise "Unknown encoding #{e.to_s}"
|
1248
|
+
end
|
1249
|
+
def parser.default
|
1250
|
+
end
|
1251
|
+
|
1252
|
+
begin
|
1253
|
+
parser.parse(@stream) do |type, name, data|
|
1254
|
+
begin
|
1255
|
+
case type
|
1256
|
+
when XMLParser::START_ELEM
|
1257
|
+
case name
|
1258
|
+
when "stream:stream"
|
1259
|
+
openstream = ParsedXMLElement.new(name)
|
1260
|
+
data.each {|key, value| openstream.add_attribute(key, value)}
|
1261
|
+
@listener.receive(openstream)
|
1262
|
+
@started = true
|
1263
|
+
else
|
1264
|
+
if @current.nil?
|
1265
|
+
@current = ParsedXMLElement.new(name.clone)
|
1266
|
+
else
|
1267
|
+
@current = @current.add_child(name.clone)
|
1268
|
+
end
|
1269
|
+
data.each {|key, value| @current.add_attribute(key.clone, value.clone)}
|
1270
|
+
end
|
1271
|
+
when XMLParser::CDATA
|
1272
|
+
@current.append_data(data.clone) if @current
|
1273
|
+
when XMLParser::END_ELEM
|
1274
|
+
case name
|
1275
|
+
when "stream:stream"
|
1276
|
+
@started = false
|
1277
|
+
else
|
1278
|
+
@listener.receive(@current) unless @current.element_parent
|
1279
|
+
@current = @current.element_parent
|
1280
|
+
end
|
1281
|
+
end
|
1282
|
+
rescue
|
1283
|
+
puts "Error #{$!}"
|
1284
|
+
end
|
1285
|
+
end
|
1286
|
+
rescue XMLParserError
|
1287
|
+
line = parser.line
|
1288
|
+
print "XML Parsing error(#{line}): #{$!}\n"
|
1289
|
+
end
|
1290
|
+
end
|
1291
|
+
end
|
1292
|
+
else # USE REXML
|
1293
|
+
require 'rexml/document'
|
1294
|
+
require 'rexml/parsers/sax2parser'
|
1295
|
+
require 'rexml/source'
|
1296
|
+
|
1297
|
+
##
|
1298
|
+
# The REXMLJabberParser uses REXML to parse the incoming XML stream
|
1299
|
+
# of the Jabber protocol and fires ParsedXMLElements at the Connection
|
1300
|
+
# instance.
|
1301
|
+
#
|
1302
|
+
class REXMLJabberParser
|
1303
|
+
# status if the parser is started
|
1304
|
+
attr_reader :started
|
1305
|
+
|
1306
|
+
##
|
1307
|
+
# Constructs a parser for the supplied stream (socket input)
|
1308
|
+
#
|
1309
|
+
# stream:: [IO] Socket input stream
|
1310
|
+
# listener:: [Object.receive(ParsedXMLElement)] The listener (usually a Jabber::Protocol::Connection instance
|
1311
|
+
#
|
1312
|
+
def initialize(stream, listener)
|
1313
|
+
@stream = stream
|
1314
|
+
|
1315
|
+
# this hack fixes REXML version "2.7.3" and "2.7.4"
|
1316
|
+
if REXML::Version=="2.7.3" || REXML::Version=="2.7.4"
|
1317
|
+
def @stream.read(len=nil)
|
1318
|
+
len = 100 unless len
|
1319
|
+
super(len)
|
1320
|
+
end
|
1321
|
+
def @stream.gets(char=nil)
|
1322
|
+
super(">")
|
1323
|
+
end
|
1324
|
+
def @stream.readline(char=nil)
|
1325
|
+
super(">")
|
1326
|
+
end
|
1327
|
+
def @stream.readlines(char=nil)
|
1328
|
+
super(">")
|
1329
|
+
end
|
1330
|
+
end
|
1331
|
+
|
1332
|
+
@listener = listener
|
1333
|
+
@current = nil
|
1334
|
+
end
|
1335
|
+
|
1336
|
+
##
|
1337
|
+
# Begins parsing the XML stream and does not return until
|
1338
|
+
# the stream closes.
|
1339
|
+
#
|
1340
|
+
def parse
|
1341
|
+
@started = false
|
1342
|
+
begin
|
1343
|
+
parser = REXML::Parsers::SAX2Parser.new @stream
|
1344
|
+
parser.listen( :start_element ) do |uri, localname, qname, attributes|
|
1345
|
+
case qname
|
1346
|
+
when "stream:stream"
|
1347
|
+
openstream = ParsedXMLElement.new(qname)
|
1348
|
+
attributes.each { |attr, value| openstream.add_attribute(attr, value) }
|
1349
|
+
@listener.receive(openstream)
|
1350
|
+
@started = true
|
1351
|
+
else
|
1352
|
+
if @current.nil?
|
1353
|
+
@current = ParsedXMLElement.new(qname)
|
1354
|
+
else
|
1355
|
+
@current = @current.add_child(qname)
|
1356
|
+
end
|
1357
|
+
attributes.each { |attr, value| @current.add_attribute(attr, value) }
|
1358
|
+
end
|
1359
|
+
end
|
1360
|
+
parser.listen( :end_element ) do |uri, localname, qname|
|
1361
|
+
case qname
|
1362
|
+
when "stream:stream"
|
1363
|
+
@started = false
|
1364
|
+
else
|
1365
|
+
@listener.receive(@current) unless @current.element_parent
|
1366
|
+
@current = @current.element_parent
|
1367
|
+
end
|
1368
|
+
end
|
1369
|
+
parser.listen( :characters ) do | text |
|
1370
|
+
@current.append_data(text) if @current
|
1371
|
+
end
|
1372
|
+
parser.listen( :cdata ) do | text |
|
1373
|
+
@current.append_data(text) if @current
|
1374
|
+
end
|
1375
|
+
parser.parse
|
1376
|
+
rescue REXML::ParseException
|
1377
|
+
@listener.parse_failure
|
1378
|
+
end
|
1379
|
+
end
|
1380
|
+
end
|
1381
|
+
end # USE_PARSER
|
1382
|
+
end
|
1383
|
+
end
|
1384
|
+
|