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.
@@ -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(/[&]/, '&amp;').gsub(/[<]/, '&lt;').gsub(/[']/, '&apos;')
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(/[&]/, '&amp;').gsub(/[<]/, '&lt;').gsub(/[']/, '&apos;')
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
+