diff --git a/docs/jabber b/docs/jabber new file mode 100644 index 000000000..e1a172163 --- /dev/null +++ b/docs/jabber @@ -0,0 +1,16 @@ +Jabber +====== + +Install Notes +------------- + + 1. user is the Jabber ID (e.g.: myusername@gmail.com) + +Developer Notes +--------------- + +data + - user + +payload + - refer to docs/github_payload diff --git a/github-services.rb b/github-services.rb index 6e008c40e..6e844499c 100644 --- a/github-services.rb +++ b/github-services.rb @@ -1,5 +1,5 @@ $:.unshift *Dir["#{File.dirname(__FILE__)}/vendor/**/lib"] -%w( rack sinatra tinder twitter json net/http net/https socket timeout xmlrpc/client openssl ).each { |f| require f } +%w( rack sinatra tinder twitter json net/http net/https socket timeout xmlrpc/client openssl xmpp4r xmpp4r-simple ).each { |f| require f } module GitHub def service(name, &block) diff --git a/services/jabber.rb b/services/jabber.rb new file mode 100644 index 000000000..0143ef10b --- /dev/null +++ b/services/jabber.rb @@ -0,0 +1,23 @@ +JABBER_USER = "github-services@jabber.org" +JABBER_PASSWORD = "g1thub" + +service :jabber do |data, payload| + repository = payload['repository']['name'] + branch = payload['ref'].split('/').last + recipient = data['user'] + puts "*** Connecting" if $DEBUG + im = Jabber::Simple.new(JABBER_USER, JABBER_PASSWORD) + + # Accept any friend request + im.accept_subscriptions = true + + payload['commits'].each do |sha1, commit| + puts "*** Sending commit #{sha1[0..6]}" if $DEBUG + im.deliver recipient, < + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/vendor/xmpp4r-0.3.2/ChangeLog b/vendor/xmpp4r-0.3.2/ChangeLog new file mode 100644 index 000000000..7f8363a0e --- /dev/null +++ b/vendor/xmpp4r-0.3.2/ChangeLog @@ -0,0 +1,50 @@ +XMPP4R 0.3.2 (15/10/2007) +========================= +* Serious bug involving Ruby threading fixed (caused exceptions with + ruby 1.8.6) +* vCard helper fixes +* Jabber RPC (JEP0009) support +* HTTP Binding (JEP0124) support +* Publish-Subscribe support +* XMPPElement: a framework for classes representing XML elements +* Ad-hoc commands support +* Improvements to Dataforms: XData, XDataTitle and XDataInstructions + +XMPP4R 0.3.1 (23/04/2007) +========================= +* SASL fixes +* Message#x and Presence#x support element selection by namespace +* Proper XML entity escaping for REXML text nodes +* Improvements to FileTransfer::Helper and SOCKS5BytestreamsServer +* Vcard::Helper fixes +* Update Digest module usage to reflect recent Ruby versions +* More documentation + +XMPP4R 0.3 (20/07/2006) +======================= +* SRV lookup capability in Client#connect +* Stringprep support for JIDs +* TLS & SASL support +* Basic Dataforms support +* Multi-User Chat Helpers +* Helpers for File-Transfer, SOCKS5 Bytestreams, In-Band Bytestreams +* Roster helper has modified subscription-request semantics (see Roster#add_subscription_request_callback) +* A lot of features have renamed namespaces (see UPDATING file) + +XMPP4R 0.2 (20/10/2005) +======================= +* Workarounds for REXML bugs. +* Presences are now Comparable according to priority or interest +* fixed a serious bug in Stream#send which caused some lockups. Reported by + chunlinyao@gmail.com. +* Moved REXML::Element#add to REXML::Element#typed_add to keep add accessible +* Rewritten Roster helper +* Added Vcard helper and improved IqVcard +* XMLStanza id generator +* Support for Roster Item Exchange (JEP-0093 and JEP-0144) +* Client#password= to change client's account password +* Documentation fixes + +XMPP4R 0.1 (12/09/2005) +======================= +* first public release. diff --git a/vendor/xmpp4r-0.3.2/LICENSE b/vendor/xmpp4r-0.3.2/LICENSE new file mode 100644 index 000000000..bd730c0c2 --- /dev/null +++ b/vendor/xmpp4r-0.3.2/LICENSE @@ -0,0 +1,59 @@ +XMPP4R is copyrighted free software by Lucas Nussbaum +, Stephan Maka , and others. +You can redistribute it and/or modify it under either the terms of the GPL (see +COPYING file), or the conditions below: + + 1. You may make and give away verbatim copies of the source form of the + software without restriction, provided that you duplicate all of the + original copyright notices and associated disclaimers. + + 2. You may modify your copy of the software in any way, provided that + you do at least ONE of the following: + + a) place your modifications in the Public Domain or otherwise + make them Freely Available, such as by posting said + modifications to Usenet or an equivalent medium, or by allowing + the author to include your modifications in the software. + + b) use the modified software only within your corporation or + organization. + + c) rename any non-standard executables so the names do not conflict + with standard executables, which must also be provided. + + d) make other distribution arrangements with the author. + + 3. You may distribute the software in object code or executable + form, provided that you do at least ONE of the following: + + a) distribute the executables and library files of the software, + together with instructions (in the manual page or equivalent) + on where to get the original distribution. + + b) accompany the distribution with the machine-readable source of + the software. + + c) give non-standard executables non-standard names, with + instructions on where to get the original software distribution. + + d) make other distribution arrangements with the author. + + 4. You may modify and include the part of the software into any other + software (possibly commercial). But some files in the distribution + are not written by the author, so that they are not under this terms. + + They are gc.c(partly), utils.c(partly), regex.[ch], st.[ch] and some + files under the ./missing directory. See each file for the copying + condition. + + 5. The scripts and library files supplied as input to or produced as + output from the software do not automatically fall under the + copyright of the software, but belong to whomever generated them, + and may be sold commercially, and may be aggregated with this + software. + + 6. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + PURPOSE. + diff --git a/vendor/xmpp4r-0.3.2/README b/vendor/xmpp4r-0.3.2/README new file mode 100644 index 000000000..2daf1f93f --- /dev/null +++ b/vendor/xmpp4r-0.3.2/README @@ -0,0 +1,28 @@ + XMPP4R +---------- +by: + Lucas Nussbaum + Stephan Maka + Kirill A. Shutemov + Yuki Mitsui + Peter Schrammel + Olli + Vojtech Vobr + Andreas Wiese + +Currently, all the information is provided on + + http://home.gna.org/xmpp4r/ + +The integrated HTML documentation can be built with + + rake rdoc + +If you need to ask questions, feel free to ask them on the +xmpp4r-devel@gna.org mailing list. When reporting problems, +please include a protocol dump which can be enabled with: + Jabber::debug = true + +XMPP4R is released under the Ruby license (see the LICENSE file), which is +compatible with the GNU GPL (see the COPYING file) via an explicit +dual-licensing clause. diff --git a/vendor/xmpp4r-0.3.2/UPDATING b/vendor/xmpp4r-0.3.2/UPDATING new file mode 100644 index 000000000..1332c256d --- /dev/null +++ b/vendor/xmpp4r-0.3.2/UPDATING @@ -0,0 +1,40 @@ +Updating from XMPP4R 0.2 to 0.3 +=============================== + + +There has been a redesign process in the development of XMPP4R 0.3. If +you encounter any problems, read the following instructions. + + +1) All extension libraries have different paths and namespaces: + +Roster: +require 'xmpp4r/roster' +Jabber::Helpers::Roster -> Jabber::Roster::Helper + +Multi-User Chat: +require 'xmpp4r/muc' +Jabber::Helpers::MUCBrowser -> Jabber::MUC::MUCBrowser +Jabber::Helpers::MUCClient -> Jabber::MUC::MUCClient +Jabber::Helpers::SimpleMUCClient -> Jabber::MUC::SimpleMUCClient + +Software Version: +require 'xmpp4r/version' +Jabber::Helpers::Version -> Jabber::Version::SimpleResponder + +vCard: +require 'xmpp4r/vcard' +Jabber::Helpers::Vcard -> Jabber::Vcard::Helper + +Bytestreams: +require 'xmpp4r/bytestreams' +Jabber::Helpers::FileTransfer -> Jabber::FileTransfer::Helper + +For a complete list of new class names see test/tc_class_names.rb + + +2) add_*_callback methods do not accept procs anymore. procs maybe +instead passed with the & sign. + +Example: client.add_message_callback(&my_message_handler) + diff --git a/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/advanced/adventure/README b/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/advanced/adventure/README new file mode 100644 index 000000000..92be06378 --- /dev/null +++ b/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/advanced/adventure/README @@ -0,0 +1,57 @@ +What is this? + +This is an example of what you can do with XMPP4R. It is a conferencing +component in which you can walk around, travel to various places, look +at things and talk to other visitors on the same places. If you like +Multi-User Dungeons (MUDs) this is for you! + +--- + +How does it work? + +The component loads a few worlds from a few XML files. Each world is a +component. Once joined the chat will tell you what you can do. Remember +that you can get a command listing anytime by saying '?'. + +Reading 'You can go north, west' you may say 'go north' to go in the +northern direction and 'go west' to go in the western direction. You'll +then find yourself at some other place but still in the same MUC +conference. Your groupchat roster will change as you'll notice different +people and things that are just in the same place, not other places. + +Before starting to hack the scripts you may want to take a look at +tower.xml. Note that users are s, too and the whole world could +be serialized back to XML by just issuing "world.to_s". + +Please note that the code, especially the error handling, is of extreme +poor quality and was mostly written in one afternoon. If I'm going to +develop this further everything should be rewritten... + +--- + +How to try? + +Because this is a component you are going to need your own Jabber +Daemon - which you'll need anyways if you're going to experiment with +XMPP4R. ;-) + +Syntax: +./adventure.rb + +Example: +./adventure.rb mud.example.com geheimnis localhost + +--- + +Messages seem to have random order? + +I don't know any solution for this. One may add short delays between +messages, but that would only be a very dirty hack. + +RFC3920: + +"10. Server Rules for Handling XML Stanzas + +Compliant server implementations MUST ensure in-order processing of +XML stanzas between any two entities." + diff --git a/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/advanced/adventure/adventure.rb b/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/advanced/adventure/adventure.rb new file mode 100755 index 000000000..0449ea988 --- /dev/null +++ b/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/advanced/adventure/adventure.rb @@ -0,0 +1,23 @@ +#!/usr/bin/ruby + +$:.unshift '../../lib' + +require 'xmpp4r' +require 'xmpp4r/discovery' +require 'xmpp4r/muc/x/muc' + +require 'adventuremuc' + + +#Jabber::debug = true + +if ARGV.size != 3 + puts "Syntax: ./adventure.rb " + puts "See README for further help" + exit +end + +muc = AdventureMUC::new(Jabber::JID::new(ARGV[0]), ARGV[1], ARGV[2]) +muc.add_world('tower.xml') +muc.add_world('cube.xml') +Thread.stop diff --git a/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/advanced/adventure/adventuremuc.rb b/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/advanced/adventure/adventuremuc.rb new file mode 100644 index 000000000..6d70f072d --- /dev/null +++ b/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/advanced/adventure/adventuremuc.rb @@ -0,0 +1,136 @@ +require 'world' + +class AdventureMUC + def initialize(jid, secret, addr, port=5347) + @worlds = {} + + @component = Jabber::Component::new(jid) + @component.connect(addr, port) + @component.auth(secret) + @component.on_exception { |e,| + puts "#{e.class}: #{e}\n#{e.backtrace.join("\n")}" + } + + @component.add_iq_callback { |iq| + handle_iq(iq) + } + @component.add_presence_callback { |pres| + handle_presence(pres) + } + @component.add_message_callback { |msg| + handle_message(msg) + } + + puts "Adventure component up and running" + end + + def add_world(file) + print "Adding world from #{file}..." + begin + world = World.new_from_file(self, file) + rescue Exception => e + puts " #{e.to_s}" + exit + end + @worlds[world.node] = world + puts " #{world.iname}" + end + + def send(worldnode, worldresource, stanza) + stanza.from = Jabber::JID::new(worldnode, @component.jid.domain, worldresource) + @component.send(stanza) + end + + def handle_iq(iq) + puts "iq: from #{iq.from} type #{iq.type} to #{iq.to}: #{iq.queryns}" + + if iq.query.kind_of?(Jabber::Discovery::IqQueryDiscoInfo) + handle_disco_info(iq) + true + elsif iq.query.kind_of?(Jabber::Discovery::IqQueryDiscoItems) + handle_disco_items(iq) + true + else + false + end + end + + def handle_disco_info(iq) + if iq.type != :get + answer = iq.answer + answer.type = :error + answer.add(Jabber::Error.new('bad-request')) + @component.send(answer) if iq.type != :error + return + end + answer = iq.answer + answer.type = :result + if iq.to.node == nil + answer.query.add(Jabber::Discovery::Identity.new('conference', 'Adventure component', 'text')) + answer.query.add(Jabber::Discovery::Feature.new(Jabber::Discovery::IqQueryDiscoInfo.new.namespace)) + answer.query.add(Jabber::Discovery::Feature.new(Jabber::Discovery::IqQueryDiscoItems.new.namespace)) + else + world = @worlds[iq.to.node] + if world.nil? + answer.type = :error + answer.query.add(Jabber::Error.new('item-not-found', 'The world you are trying to reach is currently unavailable.')) + else + answer.query.add(Jabber::Discovery::Identity.new('conference', world.iname, 'text')) + answer.query.add(Jabber::Discovery::Feature.new(Jabber::Discovery::IqQueryDiscoInfo.new.namespace)) + answer.query.add(Jabber::Discovery::Feature.new(Jabber::Discovery::IqQueryDiscoItems.new.namespace)) + answer.query.add(Jabber::Discovery::Feature.new(Jabber::MUC::XMUC.new.namespace)) + answer.query.add(Jabber::Discovery::Feature.new(Jabber::MUC::XMUCUser.new.namespace)) + end + end + @component.send(answer) + end + + def handle_disco_items(iq) + if iq.type != :get + answer = iq.answer + answer.add(Jabber::Error.new('bad-request')) + @component.send(answer) + return + end + answer = iq.answer + answer.type = :result + if iq.to.node == nil + @worlds.each { |node,world| + answer.query.add(Jabber::Discovery::Item.new(Jabber::JID::new(node, @component.jid.domain), world.iname)) + } + end + @component.send(answer) + end + + def handle_presence(pres) + puts "presence: from #{pres.from} type #{pres.type} to #{pres.to}" + + world = @worlds[pres.to.node] + if world.nil? + answer = pres.answer + answer.type = :error + answer.add(Jabber::Error.new('item-not-found', 'The world you are trying to reach is currently unavailable.')) + @component.send(answer) + else + world.handle_presence(pres) + end + + true + end + + def handle_message(msg) + puts "message: from #{msg.from} type #{msg.type} to #{msg.to}: #{msg.body.inspect}" + + world = @worlds[msg.to.node] + if world.nil? + answer = msg.answer + answer.type = :error + answer.add(Jabber::Error.new('item-not-found', 'The world you are trying to reach is currently unavailable.')) + @component.send(answer) + else + world.handle_message(msg) + end + + true + end +end diff --git a/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/advanced/adventure/cube.xml b/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/advanced/adventure/cube.xml new file mode 100644 index 000000000..8bb337ee3 --- /dev/null +++ b/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/advanced/adventure/cube.xml @@ -0,0 +1,15 @@ + + + + This is a big white room. + There are doors up, down, left, right, in the back and in front of you. + There's nothing else. + + + + + + + + + diff --git a/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/advanced/adventure/tower.xml b/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/advanced/adventure/tower.xml new file mode 100644 index 000000000..7b2695ae3 --- /dev/null +++ b/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/advanced/adventure/tower.xml @@ -0,0 +1,69 @@ + + + + You are in a beautiful garden. + There's an impressive tower beneath. Its door is open. + + + + + + Welcome to the tower's lower room. + There's a ladder to upstairs. + + + + + + This is the upper room. + There's only a bed here. A wizard is currently sleeping in it. + + + + + There's a very beautiful well in the middle of this place. + A frog sits on the well's edge. It looks frightening happy. + + + + + + + Hello great hero! + Welcome to %place% + + + The %self% waves towards %actor% + + + I would love to come with you, great hero. But I'm immobile due to the simple nature of this game. Would *YOU* like to /code/ me movable? So we could enjoy great adventures together... + + + %actor% touches the %self% + Quaaack! That feels good. + + + chat + Happy + + + + + + *snork* + + + dnd + Sleeping... go bother my assistant, the frog. + + + + + + You see a beautiful garden. There's a well behind the garden. But a huge green monster is sitting on it's edge. + + + There's a great view from up here + + + diff --git a/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/advanced/adventure/world.rb b/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/advanced/adventure/world.rb new file mode 100644 index 000000000..07e3e8492 --- /dev/null +++ b/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/advanced/adventure/world.rb @@ -0,0 +1,425 @@ +require 'rexml/document' + +class World < REXML::Element + def initialize(muc) + super('world') + + @muc = muc + end + + def send(resource, stanza) + # Avoid sending to things without JID + if stanza.to != nil + @muc.send(node, resource, stanza) + end + end + + def World.new_from_file(muc, filename) + file = File.new(filename) + world = World.new(muc) + world.import(REXML::Document.new(file).root) + file.close + world + end + + def add(xmlelement) + if xmlelement.kind_of?(REXML::Element) && (xmlelement.name == 'place') + super(Place::new.import(xmlelement)) + elsif xmlelement.kind_of?(REXML::Element) && (xmlelement.name == 'thing') && !xmlelement.kind_of?(Player) + super(Thing::new(self).import(xmlelement)) + else + super(xmlelement) + end + end + + def node + attributes['node'] + end + + def iname + attributes['name'] + end + + def place(placename) + pl = nil + each_element('place') { |place| + if place.iname == placename + pl = place + end + } + pl + end + + def each_thing_by_place(place, &block) + each_element('thing') { |t| + if t.place == place + yield(t) + end + } + end + + def move_thing(thing, newplace) + each_thing_by_place(thing.place) { |t| + # Call leave hooks + t.on_leave(thing, newplace) + + # Broadcast unavailability presence to leaver + unless t.presence.nil? + pres = Jabber::Presence.import(t.presence) + pres.type = :unavailable + pres.to = thing.jid + send(t.iname, pres) unless t.jid == thing.jid + end + + # Broadcast unavailability presence to all who are here + unless thing.presence.nil? + pres = Jabber::Presence.import(thing.presence) + pres.type = :unavailable + pres.to = t.jid + send(thing.iname, pres) unless thing.jid == t.jid + end + } + + # Enter new place + oldplace = thing.place + thing.place = newplace + + each_thing_by_place(thing.place) { |t| + # Broadcast availability presence to enterer + unless t.presence.nil? + pres = Jabber::Presence.import(t.presence) + pres.to = thing.jid + send(t.iname, pres) + end + + # Broadcast availability presence to all who are here + unless thing.presence.nil? + pres = Jabber::Presence.import(thing.presence) + pres.to = t.jid + send(thing.iname, pres) + end + } + + thing.send_message(nil, " ") + subject = newplace.nil? ? " " : newplace.dup + subject[0] = subject[0,1].upcase + thing.send_message(nil, "Entering #{newplace}", subject) + thing.send_message(nil, " ") + thing.see(place(newplace)) + + each_thing_by_place(thing.place) { |t| + # Call enter hooks + t.on_enter(thing, oldplace) + } + end + + def handle_presence(pres) + # A help for the irritated first: + if pres.type == :subscribe + msg = Jabber::Message.new(pres.from) + msg.type = :normal + msg.subject = "Adventure component help" + msg.body = "You don't need to subscribe to my presence. Simply use your Jabber client to join the MUC or conference at #{pres.to.strip}" + send(nil, msg) + return(true) + end + + # Look if player is already known + player = nil + each_element('thing') { |thing| + if thing.kind_of?(Player) && pres.to.resource == thing.iname + player = thing + end + + # Disallow nick changes + if thing.kind_of?(Player) && (pres.from == thing.jid) && (player != thing) + answer = pres.answer(false) + answer.type = :error + answer.add(Jabber::Error.new('not-acceptable', 'Nickchange not allowed')) + send(thing.iname, answer) + return(true) + end + } + + # Either nick-collission or empty nick + unless (player.nil? || pres.from == player.jid) && (pres.to.resource.to_s.size > 1) + answer = pres.answer + answer.type = :error + if (pres.to.resource.to_s.size > 1) + answer.add(Jabber::Error::new('conflict', 'Nickname already used')) + else + answer.add(Jabber::Error::new('not-acceptable', 'Please use a nickname')) + end + send(nil, answer) + return(true) + end + + # Add the valid player + if player.nil? + player = add(Player.new(self, pres.to.resource, pres.from)) + player.presence = pres + move_thing(player, attributes['start']) + player.send_message('Help!', 'Send "?" to get a list of available commands any time.') + # Or broadcast updated presence + else + player.presence = pres + + each_thing_by_place(player.place) { |t| + # Broadcast presence to all who are here + pres = Jabber::Presence.import(player.presence) + pres.to = t.jid + send(player.iname, pres) + } + end + + # Remove the player instantly + if pres.type == :error || pres.type == :unavailable + move_thing(player, nil) + delete_element(player) + end + end + + def handle_message(msg) + player = nil + each_element('thing') { |thing| + if thing.kind_of?(Player) && msg.to.resource == nil && msg.from == thing.jid + player = thing + end + } + + if player.nil? + answer = msg.answer + answer.type = :error + answer.add(Jabber::Error::new('forbidden')) + send(msg.to.resource, answer) + return(true) + end + + unless command(player, msg.body) + each_thing_by_place(player.place) { |thing| + thing.send_message(player.iname, msg.body) + } + end + end + + def command(player, text) + if text == '?' + player.send_message(nil, "(Command) who") + place(player.place).each_element('go') { |go| + player.send_message(nil, "(Command) go #{go.attributes['spec']}") + } + each_thing_by_place(player.place) { |thing| + thing.each_element('on-command') { |c| + player.send_message(nil, "(Command) #{c.attributes['command']} #{thing.command_name}") + } + } + return(true) + else + words = text.split(/ /) + cmd = words.shift + what = words.shift || "" + if cmd == 'go' + oldplace = place(player.place) + newplace = nil + + oldplace.each_element('go') { |go| + if go.attributes['spec'] == what + newplace = go.attributes['place'] + end + } + + if newplace.nil? + player.send_message(nil, 'You cannot go there') + else + move_thing(player, newplace) + end + return(true) + elsif cmd == 'who' + player.send_message(nil, "Players in \"#{iname}\":") + each_element('thing') { |thing| + if thing.kind_of?(Player) + player.send_message(nil, "#{thing.iname} is at/in #{thing.place}") + end + } + return(true) + else + handled = false + each_thing_by_place(player.place) { |thing| + if what.downcase == thing.command_name.downcase + thing.each_element('on-command') { |c| + if c.attributes['command'] == cmd + thing.command(player, c, words) + handled = true + end + } + end + } + return(true) if handled + end + end + false + end +end + +class Place < REXML::Element + def initialize + super('place') + end + + def iname + attributes['name'] + end +end + +class Thing < REXML::Element + def initialize(world) + super('thing') + @world = world + end + + def add(xmlelement) + if xmlelement.kind_of?(REXML::Element) && (xmlelement.name == 'presence') + super(Jabber::Presence.import(xmlelement)) + else + super(xmlelement) + end + end + + def iname + attributes['name'] + end + + def command_name + attributes['command-name'].nil? ? iname : attributes['command-name'] + end + + def place + attributes['place'] + end + + def place=(p) + attributes['place'] = p + end + + def jid + nil + end + + def presence + xe = nil + each_element('presence') { |pres| + xe = Jabber::Presence.import(pres) + } + if self.kind_of?(Player) + xe.add(Jabber::MUC::XMUCUser.new).add(Jabber::MUC::XMUCUserItem.new(:none, :participant)) + else + xe.add(Jabber::MUC::XMUCUser.new).add(Jabber::MUC::XMUCUserItem.new(:owner, :moderator)) + end + xe + end + + def presence=(pres) + delete_elements('presence') + add(pres) + end + + def see(place) + end + + def send_message(fromresource, text, subject=nil) + end + + def send_message_to_place(fromresource, text) + @world.each_element('thing') { |thing| + if thing.place == place + thing.send_message(fromresource, text) + end + } + end + + def on_enter(thing, from) + each_element('on-enter') { |c| + command(thing, c, [from]) + } + end + + def on_leave(thing, to) + each_element('on-leave') { |c| + command(thing, c, [to]) + } + end + + def command(source, command, arguments) + command.each_element { |action| + text = action.text.nil? ? "" : action.text.dup + text.gsub!('%self%', iname) + text.gsub!('%actor%', source.iname) + text.gsub!('%place%', place) + if action.name == 'say' || action.name == 'tell' + sender = nil + sender = iname if action.name == 'say' + if action.attributes['to'] == 'all' + send_message_to_place(sender, text) + else + source.send_message(sender, text) + end + end + } + end +end + +class Player < Thing + def initialize(world, iname, jid) + super(world) + attributes['name'] = iname + attributes['jid'] = jid.to_s + end + + def jid + attributes['jid'].nil? ? nil : Jabber::JID::new(attributes['jid']) + end + + def see(place) + return if place.nil? + + place.text.strip.split(/\n/).each do |line| + send_message(nil, line.strip) + end + + send_message(nil, " ") + + directions = [] + place.each_element('go') { |go| + directions.push(go.attributes['spec']) + } + send_message(nil, "You can go #{directions.join(', ')}") + end + + def send_message(fromresource, text, subject=nil) + msg = Jabber::Message.new(jid, text) + msg.type = :groupchat + msg.subject = subject unless subject.nil? + @world.send(fromresource, msg) + end + + def on_enter(thing, from) + if thing != self + if from.nil? + send_message(nil, "#{thing.iname} spawns") + else + send_message(nil, "#{thing.iname} enters #{place} coming from #{from}") + end + end + end + + def on_leave(thing, to) + if thing != self + if to.nil? + send_message(nil, "#{thing.iname} disintegrates") + else + send_message(nil, "#{thing.iname} leaves #{place} going to #{to}") + end + end + end +end + diff --git a/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/advanced/fileserve.conf b/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/advanced/fileserve.conf new file mode 100644 index 000000000..4399925f8 --- /dev/null +++ b/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/advanced/fileserve.conf @@ -0,0 +1,11 @@ +jabber: + jid: user@server.com/fileserve + password: *** +directory: /tmp/fileserve +socks: + proxies: + - transfer.jabber.freenet.de + - proxy.jabber.org + addresses: + - 172.22.99.197 + port: 65023 diff --git a/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/advanced/fileserve.rb b/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/advanced/fileserve.rb new file mode 100644 index 000000000..e2af237bd --- /dev/null +++ b/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/advanced/fileserve.rb @@ -0,0 +1,346 @@ +#!/usr/bin/env ruby + +require 'yaml' +require 'xmpp4r' +require 'xmpp4r/bytestreams' +require 'xmpp4r/roster' +require 'xmpp4r/version' + +Jabber::debug = true + +def human_readable(num) + unit = '' + units = %w(K M G T P E) + while num > 10240 and units.size > 0 + num /= 1024 + unit = units.shift + end + "#{num} #{unit}B" +end + +class Transfer + attr_reader :peer, :filename, :bytes, :filesize + + def initialize(filetransfer, peer, filename, filesize, msgblock) + @filetransfer = filetransfer + @peer = peer + @filename = filename + @filesize = filesize + @msgblock = msgblock + + @done = false + @bytes = 0 + @stats = [0] + @stats_lock = Mutex.new + @stats_thread = Thread.new { stats_loop } + + + @streamhost_cb = lambda { |streamhost,state,e| + case state + when :connecting + say "Connecting to #{streamhost.jid} (#{streamhost.host}:#{streamhost.port})" + when :success + say "Successfully using #{streamhost.jid} (#{streamhost.host}:#{streamhost.port})" + when :failure + say "Error using #{streamhost.jid} (#{streamhost.host}:#{streamhost.port}): #{e}" + end + } + end + + def done? + @stats_thread.kill if @done + @done + end + + def stats_loop + loop { + sleep 1 + @stats_lock.synchronize { + @stats.push @bytes + @stats.shift if @stats.size > 5 + } + } + end + + def stats + @stats_lock.synchronize { + if @stats.first >= @stats.last + 0 + else + (@stats.last - @stats.first) / (@stats.size - 1) + end + } + end + + def say(text) + @msgblock.call(text) + end + + def transfer(from, to) + while buf = from.read + @bytes += to.write buf + end + end +end + +class Upload < Transfer + def initialize(filetransfer, iq, filename, filesize, can_range, msgblock) + super(filetransfer, iq.from, filename, filesize, msgblock) + + if filename.size < 1 + say "What is this file for?" + @done = true + return + end + + offset = nil + if File::exist?(filename) + if File::size(filename) < filesize and can_range + @bytes = offset = File::size(filename) + say "Will retrieve #{filename} starting at #{offset}" + else + say "#{filename} already exists" + filetransfer.decline(iq) + @done = true + return + end + end + + Thread.new { + begin + stream = filetransfer.accept(iq, offset) + if stream.kind_of?(Jabber::Bytestreams::SOCKS5Bytestreams) + stream.connect_timeout = 5 + stream.add_streamhost_callback(nil, nil, &@streamhost_cb) + end + + if stream.accept + outfile = File.new(filename, (offset ? 'a' : 'w')) + + transfer(stream, outfile) + + outfile.close + stream.close + @done = true + else + say "Stream failed" + @done = true + end + rescue + puts $!.backtrace.first + say "Error: #{$!}" + @done = true + end + } + end +end + +class Download < Transfer + def initialize(filetransfer, peer, filename, msgblock, socksconf) + begin + filesize = File.size(filename) + rescue + filesize = 0 + end + + super(filetransfer, peer, filename, filesize, msgblock) + + Thread.new { + begin + raise "No regular file" unless File.file?(filename) + + source = Jabber::FileTransfer::FileSource.new(filename) + stream = filetransfer.offer(peer, source) + unless stream + raise "Well, you should accept what you request..." + @done = true + end + + if stream.kind_of?(Jabber::Bytestreams::SOCKS5Bytestreams) + socksconf.call(stream) + stream.add_streamhost_callback(nil, nil, &@streamhost_cb) + end + + stream.open + transfer(source, stream) + stream.close + @done = true + rescue + say "Error: #{$!}" + @done = true + end + } + end +end + +class FileServe + def initialize(conf) + @uploads = 0 + @downloads = 0 + + @transfers = [] + @transfers_lock = Mutex.new + + @client = Jabber::Client.new Jabber::JID.new(conf['jabber']['jid']) + @client.connect + @client.auth(conf['jabber']['password']) + + @ft = Jabber::FileTransfer::Helper.new(@client) + Jabber::Version::SimpleResponder.new(@client, + "XMPP4R FileServe example", + Jabber::XMPP4R_VERSION, + IO.popen('uname -sr').readlines.to_s.strip) + register_handlers + + @directory = conf['directory'] + @directory.gsub!(/\/+$/, '') + + @socksserver = Jabber::Bytestreams::SOCKS5BytestreamsServer.new(conf['socks']['port']) + + conf['socks']['addresses'].each { |addr| @socksserver.add_address(addr) } + + @proxies = [] + conf['socks']['proxies'].collect { |jid| + puts "Querying proxy #{jid}..." + begin + @proxies.push(Jabber::Bytestreams::SOCKS5Bytestreams::query_streamhost(@client, jid)) + rescue + puts "Error: #{$!}" + end + } + + Thread.new { presence } + Thread.new { cleaner } + + # Panic reboot ;-) + @client.on_exception { initialize(conf) } + end + + def presence + rate_upload = 0 + rate_download = 0 + + old_status = nil + loop { + # Update the @rate_* variables + @transfers_lock.synchronize { + @transfers.each { |t| + if t.kind_of?(Upload) and t.stats > rate_upload + rate_upload = t.stats + elsif t.kind_of?(Download) and t.stats > rate_download + rate_download = t.stats + end + } + } + status = "Attempted #{@downloads} downloads (max. #{human_readable rate_download}/s) and #{@uploads} uploads (max. #{human_readable rate_upload}/s)" + + @client.send(Jabber::Presence.new(:chat, status)) if status != old_status + old_status = status + + sleep 1 + + } + end + + def register_handlers + @client.add_message_callback { |msg| + if msg.type == :chat and msg.body and msg.from != 'pentabarf@pentabarf.org/rails' + puts "<#{msg.from}> #{msg.body.strip}" + cmd, arg = msg.body.split(/ /, 2) + + command(msg.from, cmd, arg) + end + } + + @ft.add_incoming_callback { |iq,file| + + say = lambda { |text| + say(iq.from, text) + } + + puts "Incoming file transfer from #{iq.from}: #{file.fname} (#{file.size / 1024} KB)" + filename = file.fname.split(/\//).last + filename.gsub!(/^\.+/, '') + + puts "Range: #{file.range != nil}" + transfer = Upload.new(@ft, iq, "#{@directory}/#{filename}", file.size, file.range != nil, say) + @uploads += 1 + + @transfers_lock.synchronize { + @transfers.push(transfer) + } + + } + + roster = Jabber::Roster::Helper.new(@client) + + roster.add_subscription_request_callback { |item,presence| + roster.accept_subscription(presence.from) + } + + end + + def command(from, cmd, arg) + say = lambda { |text| + say(from, text) + } + socksconf = lambda { |stream| + stream.add_streamhost(@socksserver) + @proxies.each { |sh| + stream.add_streamhost(sh) + } + } + + case cmd + when 'get' + arg.gsub!(/\//, '') + arg.gsub!(/^\.+/, '') + transfer = Download.new(@ft, from, "#{@directory}/#{arg}", say, socksconf) + @downloads += 1 + @transfers_lock.synchronize { + @transfers.push(transfer) + } + when 'ls' + text = "" + Dir.foreach(@directory) { |file| + next if file =~ /^\./ + path = "#{@directory}/#{file}" + text += "#{file} (#{human_readable File.size(path)})\n" if File.file?(path) + } + say.call(text.strip) + when 'stat' + @transfers_lock.synchronize { + text = "#{@transfers.size} transfers:\n" + @transfers.each { |t| + text += "#{t.filename} (#{t.peer}): #{(t.bytes * 100) / t.filesize}% (#{human_readable t.stats}/s)\n" + } + } + say.call(text.strip) + when 'help' + say.call "Download a file: get \nList directory contents: ls\nLook who is currently wasting bandwidth: stat\nUpload a file, simply send this file" + else + say.call "Unknown command: #{cmd}, try help" + end + end + + def say(to, text) + puts ">#{to}< #{text.strip}" + @client.send(Jabber::Message.new(to, text).set_type(:chat)) + end + + def cleaner + loop { + + @transfers_lock.synchronize { + @transfers.delete_if { |t| t.done? } + } + + sleep 1 + } + end +end + + +FileServe.new(YAML::load(File.new('fileserve.conf'))) +puts "Up and running!" +Thread.stop diff --git a/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/advanced/getonline.rb b/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/advanced/getonline.rb new file mode 100755 index 000000000..cb35a6137 --- /dev/null +++ b/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/advanced/getonline.rb @@ -0,0 +1,56 @@ +#!/usr/bin/ruby + +# This script can get all roster entries + +require 'optparse' +require 'xmpp4r' +include Jabber + +get = true +jid = JID::new('lucastest@linux.ensimag.fr/rosterget') +password = 'lucastest' +domains = [] + +OptionParser::new do |opts| + opts.banner = 'Usage: roster.rb -j jid -p password -d domain' + opts.separator '' + opts.on('-j', '--jid JID', 'sets the jid') { |j| jid = JID::new(j) } + opts.on('-p', '--password PASSWORD', 'sets the password') { |p| password = p } + opts.on('-d', '--domain DOMAIN', 'sets the domain') { |d| domains << d } + opts.on_tail('-h', '--help', 'Show this message') { + puts opts + exit + } + opts.parse!(ARGV) +end + +cl = Client::new(jid, false) +cl.connect +cl.auth(password) +exit = false +nb = 0 +cl.add_iq_callback { |i| + fjid = JID::new(i.from) + if i.type == :result and fjid.resource == "admin" + domain = fjid.domain + items = nil + i.each_element('item') { |e| items = e } + raise "items nil" if items.nil? + puts "--- Online users for #{domain} (seconds, sent, received)" + c = 0 + items.each_element('user') do |e| + puts "#{e.attribute('jid')} #{e.attribute('name')}" + c += 1 + end + puts "--- #{domain} : #{c} online users" + nb -= 1 + end +} +for d in domains do + cl.send(Iq::new_browseget.set_to("#{d}/admin")) + nb += 1 +end +while nb > 0 + cl.process(1) +end +cl.close diff --git a/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/advanced/gtkmucclient.rb b/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/advanced/gtkmucclient.rb new file mode 100644 index 000000000..6a9d5eedd --- /dev/null +++ b/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/advanced/gtkmucclient.rb @@ -0,0 +1,315 @@ +#!/usr/bin/env ruby + +require 'gtk2' + +$:.unshift '../../../../../lib' +require 'xmpp4r' +require 'xmpp4r/muc/helper/simplemucclient' +require 'xmpp4r/version/helper/simpleresponder' + +#Jabber::debug = true + +class Gtk::Label + def self.new_show(str = nil, mnemonic = false) + label = new(str, mnemonic) + label.show + label + end +end + +class SetupWindow < Gtk::Window + def initialize + super(Gtk::Window::TOPLEVEL) + self.title = "GtkMUCClient setup" + signal_connect("delete_event") { cancel } + + vbox = Gtk::VBox.new + vbox.set_border_width(4) + add(vbox) + vbox.show + + frame1 = Gtk::Frame.new('Jabber Account Settings') + frame1.set_border_width(4) + frame1.show + vbox.add(frame1) + + layout1 = Gtk::Table.new(4, 2) + layout1.row_spacings = 4 + layout1.column_spacings = 8 + layout1.show + frame1.add(layout1) + + layout1.attach(Gtk::Label.new_show('Jabber ID:'), 0, 1, 1, 2) + @entry_jid = Gtk::Entry.new + @entry_jid.text = 'collector@jabber.ccc.de' + @entry_jid.show + layout1.attach(@entry_jid, 1, 2, 1, 2) + + layout1.attach(Gtk::Label.new_show('Password:'), 0, 1, 2, 3) + @entry_password = Gtk::Entry.new + @entry_password.visibility = false + @entry_password.show + layout1.attach(@entry_password, 1, 2, 2, 3) + + layout1.attach(Gtk::Label.new_show('Resource:'), 0, 1, 3, 4) + @entry_resource = Gtk::Entry.new + @entry_resource.text = 'gtkmucclient' + @entry_resource.show + layout1.attach(@entry_resource, 1, 2, 3, 4) + + + frame2 = Gtk::Frame.new('Multi-User Chat Settings') + frame2.set_border_width(4) + frame2.show + vbox.add(frame2) + + layout2 = Gtk::Table.new(3, 2) + layout2.row_spacings = 4 + layout2.column_spacings = 8 + layout2.show + frame2.add(layout2) + + layout2.attach(Gtk::Label.new_show('Room:'), 0, 1, 1, 2) + @entry_room = Gtk::Entry.new + @entry_room.text = 'test@conference.jabber.org' + @entry_room.show + layout2.attach(@entry_room, 1, 2, 1, 2) + + layout2.attach(Gtk::Label.new_show('Nick:'), 0, 1, 2, 3) + @entry_nick = Gtk::Entry.new + @entry_nick.text = 'XMPP4R-Fan' + @entry_nick.show + layout2.attach(@entry_nick, 1, 2, 2, 3) + + + hbox = Gtk::HBox.new + hbox.show + vbox.add(hbox) + + button_ok = Gtk::Button.new("Ok") + button_ok.set_border_width(4) + hbox.add(button_ok) + button_ok.signal_connect("clicked") { ok } + button_ok.can_default = true + button_ok.grab_default + button_ok.show + button_cancel = Gtk::Button.new("Cancel") + button_cancel.set_border_width(4) + hbox.add(button_cancel) + button_cancel.signal_connect("clicked") { cancel } + button_cancel.show + end + + def error_dialog(msg) + dialog = Gtk::MessageDialog.new(self, Gtk::Dialog::MODAL, Gtk::MessageDialog::ERROR, Gtk::MessageDialog::BUTTONS_OK, msg) + dialog.signal_connect("response") { dialog.destroy } + dialog.run + end + + def ok + jid = Jabber::JID.new(@entry_jid.text) + mucjid = Jabber::JID.new(@entry_room.text) + + if jid.node.nil? + error_dialog("Your Jabber ID must contain a user name and therefore contain one @ character.") + elsif jid.resource + error_dialog("If you intend to set a custom resource, put that in the right text field. Remove the slash!") + elsif @entry_resource.text.empty? + error_dialog("Please set a resource. This is a somewhat unimportant setting...") + elsif mucjid.node.nil? + error_dialog("Please set a room name, e.g. myroom@conference.jabber.org") + elsif mucjid.resource + error_dialog("The MUC room must not contain a resource. Remove the slash!") + elsif @entry_nick.text.empty? + error_dialog("Please set a nick for MUC.") + else + jid.resource = @entry_resource.text + mucjid.resource = @entry_nick.text + password = @entry_password.text + + destroy + + ChatWindow.new(jid, password, mucjid).show + end + end + + def cancel + destroy + Gtk.main_quit + end +end + +class ChatWindow < Gtk::Window + def initialize(jid, password, mucjid) + super(Gtk::Window::TOPLEVEL) + self.title = "GtkMUCClient: #{mucjid.resource} in #{mucjid.strip}" + signal_connect("delete_event") { destroy; Gtk.main_quit } + + layout = Gtk::VBox.new + + @topic = Gtk::Entry.new + @topic.editable = false + @topic.show + layout.pack_start(@topic, false, false, 2) + + layout_mid = Gtk::HPaned.new + layout_mid.position = 500 + layout_mid.show + layout.pack_start(layout_mid) + + @buffer_scroll = Gtk::ScrolledWindow.new + @buffer_scroll.show + @buffer_scroll.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC) + layout_mid.pack1(@buffer_scroll, true, true) + + @buffer_view = Gtk::TextView.new + @buffer_view.editable = false + @buffer_view.cursor_visible = false + @buffer_view.wrap_mode = Gtk::TextTag::WRAP_WORD + @buffer_view.modify_font(Pango::FontDescription.new('monospace 12')) + @buffer_view.show + @buffer_scroll.add_with_viewport(@buffer_view) + + roster_scroll = Gtk::ScrolledWindow.new + roster_scroll.show + roster_scroll.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC) + layout_mid.pack2(roster_scroll, true, true) + + @roster = Gtk::ListStore.new(String) + @roster.set_sort_column_id(0) + roster_view = Gtk::TreeView.new(@roster) + roster_view.append_column Gtk::TreeViewColumn.new("Participant", Gtk::CellRendererText.new, {:text => 0}) + roster_view.show + roster_scroll.add_with_viewport(roster_view) + + @input = Gtk::Entry.new + @input.grab_focus + @input.signal_connect("activate") { + on_input(@input.text) + @input.text = '' + } + @input.show + layout.pack_start(@input, false, false, 2) + + layout.show + add(layout) + + print_buffer "Welcome to the XMPP4R sample GTK2 Multi-User Chat client" + print_buffer "Commands start with a slash, type \"/help\" for a list" + + @client = Jabber::Client.new(jid) + Jabber::Version::SimpleResponder.new(@client, "XMPP4R example: GtkMUCClient", Jabber::XMPP4R_VERSION, IO.popen("uname -sr").readlines.to_s.strip) + Thread.new { + begin + print_buffer "Connecting for domain #{jid.domain}..." + @client.connect + print_buffer "Authenticating for #{jid.strip}..." + @client.auth(password) + print_buffer "Attempting to join #{mucjid.strip} as #{mucjid.resource}..." + @muc = Jabber::MUC::SimpleMUCClient.new(@client) + register_handlers + @muc.join(mucjid) + rescue Exception => e + puts "#{e.class}: #{e}\n#{e.backtrace.join("\n")}" + print_buffer("Error: #{e}") + end + } + + set_size_request(600, 400) + end + + def print_buffer(s, time=nil) + @buffer_view.buffer.insert(@buffer_view.buffer.end_iter, "[#{(time || Time.new).getlocal.strftime('%I:%M')}] #{s}\n") + va = @buffer_scroll.vadjustment + va.value = va.upper + end + + def register_handlers + @muc.on_room_message { |time,text| + print_buffer("*** #{text}", time) + } + @muc.on_message { |time,nick,text| + if text =~ /^\/me (.+)$/ + print_buffer("*#{nick} #{$1}", time) + else + print_buffer("<#{nick}> #{text}", time) + end + } + @muc.on_private_message { |time,nick,text| + print_buffer("<-(#{nick}) #{text}", time) + } + @muc.on_join { |time,nick| + @roster.append[0] = nick + } + @muc.on_self_leave { |time| + print_buffer("You have exited the room", time) + } + @muc.on_leave { |time,nick| + del_iter = nil + @roster.each { |m,p,iter| + del_iter = iter if iter[0] == nick + } + @roster.remove(del_iter) if del_iter + } + @muc.on_subject { |time,nick,subject| + @topic.text = subject + } + end + + def on_input(line) + commands = { + 'help' => [ + 'Display this help', + lambda { + commands.each { |cmd,a| + print_buffer "/#{cmd.ljust(10)} - #{a[0]}" + } + } + ], + 'msg' => [ + 'Send a private message to a user', + lambda { |args| + # Limitation: it is not possible to send private messages + # to a user with a space in his nickname + to = args.shift + text = args.join(' ') + @muc.say(text, to) + print_buffer "->(#{to}) #{text}" + } + ], + 'subject' => [ + 'Change the room\'s subject', + lambda { |args| + @muc.subject = args.join(' ') + } + ], + 'quit' => [ + 'Leave room with optional message, then disconnect client and shut down', + lambda { |args| + @muc.exit(args.join(' ')) if @muc.active? + @client.close + Gtk.main_quit + } + ] + } + + if line =~ /^\// + args = line.split(/ /) + cmd = args.shift[1..-1].downcase + + command = commands[cmd] + if command + help, func = command + func.call(args) + else + print_buffer "Unknown command: #{cmd}, use \"/help\"" + end + else + @muc.say(line) + end + end +end + +Gtk.init +SetupWindow.new.show +Gtk.main diff --git a/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/advanced/migrate.rb b/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/advanced/migrate.rb new file mode 100755 index 000000000..bdc6fb419 --- /dev/null +++ b/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/advanced/migrate.rb @@ -0,0 +1,89 @@ +#!/usr/bin/ruby + +# This script can be used to migrate from one jabber account to another + +$:.unshift '../lib' + +require 'optparse' +require 'xmpp4r' +include Jabber + +jidfrom = JID::new('lucas@linux.ensimag.fr/JabberMigrate') +pwfrom = 'z' +jidto = JID::new('lucas@nussbaum.fr/JabberMigrate') +pwto = 'z' + +BOTHOLD = "Hi, you are subscribed to my presence. I just changed my JID. The new one is #{jidto.strip}. You might want to update your roster. Thank you, and sorry for the inconvenience !" +BOTHNEW = "Hi, you are subscribed to the presence of my previous JID : #{jidfrom.strip}. I just changed my JID, and this is the new one. You might want to update your roster. Thank you, and sorry for the inconvenience !" + +clfrom = Client::new(jidfrom, false) +clfrom.connect +clfrom.auth(pwfrom) +clto = Client::new(jidto, false) +clto.connect +clto.auth(pwto) +#clfrom.send(Presence::new) +#clto.send(Presence::new) +clfrom.send(Iq::new_rosterget) +exit = false +clfrom.add_iq_callback { |i| + if i.type == :result and i.queryns == 'jabber:iq:roster' + i.query.each_element do |e| + e.text = '' + jid = e.attribute('jid') + name = e.attribute('name') + subscription = e.attribute('subscription') + ask = e.attribute('ask') + jid &&= jid.value + next if jid =~ /@(icq|msn|aim|yahoo).blop.info/ +# next if jid !~ /lucas@im.apinc.org/ + puts "Processing #{e.to_s}" +# Jabber::debug = true + name &&= name.value + subscription &&= subscription.value + ask &&= ask.value + puts subscription + case subscription + when 'from' + # il veut me voir, je veux pas le voir. + # envoi unsubscribed + clfrom.send(Presence::new.set_to(jid).set_type(:unsubscribed)) + # envoi message d'info OLD & NEW + clfrom.send(Message::new(jid, BOTHOLD).set_type(:chat)) + clto.send(Message::new(jid, BOTHNEW).set_type(:chat)) + when 'to' + # je veux le voir, il veut pas me voir + # envoi unsubscribe + clfrom.send(Presence::new.set_to(jid).set_type(:unsubscribe)) + # envoi subscribe avec message + pres = Presence::new.set_to(jid).set_type(:subscribe) + pres.add(Element::new('status').add_text("Hi, I was previously subscribed to your presence with my JID #{jidfrom.strip}. Can I re-subscribe to your presence ? Thank you.")) + clto.send(pres) + when 'both' + # envoi unsubscribed + clfrom.send(Presence::new.set_to(jid).set_type(:unsubscribed)) + # envoi unsubscribe + clfrom.send(Presence::new.set_to(jid).set_type(:unsubscribe)) + # update roster + iq = Iq::new_rosterset + e.delete_attribute('ask') + e.delete_attribute('subscription') + iq.query.add_element(e) + clto.send(iq) + # envoi message d'info OLD & NEW + clfrom.send(Message::new(jid, BOTHOLD).set_type(:chat)) + pres = Presence::new.set_to(jid).set_type(:subscribe) + pres.add(Element::new('status').add_text("Hi, I was previously subscribed to your presence with my JID #{jidfrom.strip}. Can I re-subscribe to your presence ? Thank you.")) + clto.send(pres) + clto.send(Message::new(jid, BOTHNEW).set_type(:chat)) + end + end + end +} +while not exit + clfrom.process + clto.process +end +clfrom.close +clto.close + diff --git a/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/advanced/minimuc.rb b/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/advanced/minimuc.rb new file mode 100644 index 000000000..58c6f6743 --- /dev/null +++ b/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/advanced/minimuc.rb @@ -0,0 +1,266 @@ +#!/usr/bin/ruby + +$:.unshift '../../../../../lib' + +require 'xmpp4r' +require 'xmpp4r/iq/query/discoinfo' +require 'xmpp4r/iq/query/discoitems' +require 'xmpp4r/x/muc' + + +#Jabber::debug = true + +class Room + class RoomException < Exception + attr_reader :error + def initialize(error, msg) + @error = Jabber::Error.new(error, msg) + end + end + + def initialize(stream, name) + @users = {} # nick => jid + @stream = stream + @name = name + end + + def num_users + @users.size + end + + def each_user(&block) + @users.each { |n,j| yield(n, j) } + end + + def handle_message(msg) + origin = msg.from + msg.from = nil + @users.each { |nick,jid| + if jid == origin + msg.from = Jabber::JID::new(@name.node, @name.domain, nick) + end + } + unless msg.from.nil? + broadcast(msg) + end + + true + end + + def handle_presence(pres) + reason = nil + + # Check if nick already registered + @users.each { |nick,jid| + if pres.from != jid && pres.to.resource == nick + puts "#{pres.from} tried to use already active #{pres.to}" + raise RoomException.new('conflict', "Nick already used") + end + } + # New user? + unless @users.has_key?(pres.to.resource) + # Check nick length + if pres.to.resource.size < 1 + puts "#{pres.from} tried to use empty nick" + raise RoomException.new('not-acceptable', "Nick must contain characters") + end + + # Push user-list + userinfo = Jabber::Presence.import(pres) + userinfo.to = pres.from + userinfo.add(Jabber::XMUCUser.new).add(Jabber::XMUCUserItem.new(:none, :participant)) + print "Sending all users for #{pres.to} to #{pres.from}:" + @users.each { |nick,jid| + userinfo.from = Jabber::JID::new(@name.node, @name.domain, nick) + print " #{nick} (#{jid})" + @stream.send(userinfo) + } + puts " Ok" + + # Add the new user + puts "Adding #{pres.from} as #{pres.to}" + @users[pres.to.resource] = pres.from + + send_message("#{pres.to.resource} joins #{@name.node}") + reason = "#{pres.to.resource} joins #{@name.node}" + end + + # Remove dead rats + if pres.type == :error || pres.type == :unavailable + was_nick = nil + @users.delete_if { |nick,jid| + was_nick = nick + jid == pres.from + } + unless was_nick.nil? + send_message("#{was_nick} has left #{@name.node}") + reason = "#{was_nick} has left #{@name.node}" + end + end + + # Advertise users presence to all + puts "Advertising user to all" + x = Jabber::XMUCUserItem.new(:none, :participant, pres.from) + x.reason = reason + pres.add(Jabber::XMUCUser.new).add(x) + pres.from = pres.to + broadcast(pres) + end + + def send_message(body) + msg = Jabber::Message::new + msg.from = Jabber::JID::new(@name.node, @name.domain, "") + msg.type = :groupchat + msg.body = body + broadcast(msg) + end + + def broadcast(stanza) + x = stanza.class::import(stanza) + @users.each { |nick,jid| + x.to = jid + @stream.send(x) + } + end +end + +class MUC + def initialize(jid, secret, addr, port=5347) + @rooms = {} + + @component = Jabber::Component::new(jid, addr, port) + @component.connect + @component.auth(secret) + + @component.add_iq_callback { |iq| + handle_iq(iq) + } + @component.add_presence_callback { |pres| + handle_presence(pres) + } + @component.add_message_callback { |msg| + handle_message(msg) + } + end + + def handle_iq(iq) + puts "#{iq.from} #{iq.queryns} to #{iq.to}" + if iq.query.kind_of?(Jabber::IqQueryDiscoInfo) + handle_disco_info(iq) + true + elsif iq.query.kind_of?(Jabber::IqQueryDiscoItems) + handle_disco_items(iq) + true + else + false + end + end + + def handle_presence(pres) + room = nil + @rooms.each { |name,r| + room = r if name == pres.to.strip + } + + if room.nil? && pres.type.nil? && (pres.to.node.to_s != '') + room = Room.new(@component, pres.to.strip) + @rooms[pres.to.strip] = room + elsif room.nil? && pres.type != :error + pres.to, pres.from = pres.from, pres.to + pres.type = :error + pres.add(Jabber::Error.new('item-not-found')) + @component.send(pres) + return(true) + end + + begin + room.handle_presence(pres) + rescue Room::RoomException => e + pres.to, pres.from = pres.from, pres.to + pres.type = :error + pres.add(e.error) + @component.send(pres) + return(true) + end + return(false) + end + + def handle_message(msg) + puts "Message from #{msg.from} to #{msg.to} (#{msg.type}): #{msg.body.inspect}" + return if msg.type == :error + + room = @rooms[msg.to] + unless room.nil? + room.handle_message(msg) + else + msg.to, msg.from = msg.from, msg.to + msg.type = :error + msg.add(Jabber::Error.new('item-not-found', 'The chatroom you are trying to reach is currently not available.')) + @component.send(msg) + end + end + + def handle_disco_info(iq) + if (iq.type == :get) + iq.type = :result + iq.query = Jabber::IqQueryDiscoInfo.new + if iq.to.node == nil + iq.query.add(Jabber::DiscoIdentity.new('conference', 'Minimal Multi-User Chat', 'text')) + else + room = @rooms[iq.to] + if room.nil? + iq.type = :error + iq.add_element(Jabber::Error.new('item-not-found')) + else + iq.query.add(Jabber::DiscoIdentity.new('conference', "#{iq.to.node} (#{room.num_users})", 'text')) + end + end + [Jabber::IqQueryDiscoInfo.new.namespace, + Jabber::IqQueryDiscoItems.new.namespace, + Jabber::XMUC.new.namespace, + Jabber::XMUCUser.new.namespace].each { |ns| + iq.query.add(Jabber::DiscoFeature.new(ns)) + } + else + iq.type = :error + iq.add_element(Jabber::Error.new('bad-request')) + end + iq.to, iq.from = iq.from, iq.to + @component.send(iq) + end + + def handle_disco_items(iq) + if (iq.type == :get) + iq.type = :result + if iq.to.node == nil + @rooms.each { |name,room| + iq.query.add(Jabber::DiscoItem::new(Jabber::JID::new(name, @component.jid.domain), name)) + } + elsif iq.to.resource == nil + room = @rooms[iq.to.strip] + unless room.nil? + room.each_user { |nick,jid| + iq.query.add(Jabber::DiscoItem::new(jid, nick)) + } + else + iq.type = :error + iq.add_element(Jabber::Error.new('item-not-found')) + end + end + else + iq.type = :error + iq.add_element(Jabber::Error.new('bad-request')) + end + iq.to, iq.from = iq.from, iq.to + @component.send(iq) + end +end + +if ARGV.size != 3 + puts "Syntax: ./minimuc.rb " + puts "Example: ./minimuc.rb conference.xmpp4r.mil minimuc localhost" + exit +end + +muc = MUC::new(Jabber::JID::new(ARGV[0]), ARGV[1], ARGV[2]) +Thread.stop diff --git a/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/advanced/recvfile.rb b/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/advanced/recvfile.rb new file mode 100644 index 000000000..dcd747abc --- /dev/null +++ b/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/advanced/recvfile.rb @@ -0,0 +1,85 @@ +#!/usr/bin/env ruby + +require 'xmpp4r' +require 'xmpp4r/helpers/filetransfer' + + +if ARGV.size != 3 + puts "Usage: #{$0} " + exit +end + + +#Jabber::debug = true + +cl = Jabber::Client.new(Jabber::JID.new(ARGV[0])) +puts "Connecting Jabber client..." +cl.connect +puts "Authenticating..." +cl.auth ARGV[1] + +ft = Jabber::Helpers::FileTransfer.new(cl) +ft.add_incoming_callback { |iq,file| + puts "Incoming file transfer from #{iq.from}: #{file.fname} (#{file.size / 1024} KB)" + + filename = "#{ARGV[2]}/#{file.fname.split(/\//).last}" + offset = nil + if File::exist?(filename) + puts "#{filename} already exists" + if File::size(filename) < file.size and file.range + offset = File::size(filename) + puts "Peer supports , will retrieve #{file.fname} starting at #{offset}" + else + puts "#{file.fname} is already fully retrieved, declining file-transfer" + ft.decline(iq) + next + end + end + + Thread.new { + begin + puts "Accepting #{file.fname}" + stream = ft.accept(iq, offset) + if stream.kind_of?(Jabber::Helpers::SOCKS5Bytestreams) + stream.connect_timeout = 5 + stream.add_streamhost_callback { |streamhost,state,e| + case state + when :connecting + puts "Connecting to #{streamhost.jid} (#{streamhost.host}:#{streamhost.port})" + when :success + puts "Successfully using #{streamhost.jid} (#{streamhost.host}:#{streamhost.port})" + when :failure + puts "Error using #{streamhost.jid} (#{streamhost.host}:#{streamhost.port}): #{e}" + end + } + end + + puts "Waiting for stream configuration" + if stream.accept + puts "Stream established" + outfile = File.new(filename, (offset ? 'a' : 'w')) + while buf = stream.read + outfile.write(buf) + print '.' + $stdout.flush + end + puts '!' + outfile.close + stream.close + else + puts "Stream failed" + end + rescue Exception => e + puts "#{e.class}: #{e}\n#{e.backtrace.join("\n")}" + end + } +} + +cl.send(Jabber::Presence.new(:chat, 'Send me files!')) + +puts "Waiting." + +Thread.stop + +cl.close + diff --git a/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/advanced/rosterdiscovery.rb b/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/advanced/rosterdiscovery.rb new file mode 100755 index 000000000..624bfb3fd --- /dev/null +++ b/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/advanced/rosterdiscovery.rb @@ -0,0 +1,130 @@ +#!/usr/bin/ruby +# +# XMPP4R - XMPP Library for Ruby +# Copyright (C) 2005 Stephan Maka +# Released under Ruby's license (see the LICENSE file) or GPL, at your option +# +# +# Roster-Discovery example +# +# If you don't understand this code read JEP-0030 (Service Discovery) first: +# http://www.jabber.org/jeps/jep-0030.html +# +# This examples exposes your roster to Service Discovery and can be browsed +# by anyone. Please use with care if you want to keep your roster private! +# +# The Psi client has a very pretty Service Discovery dialog. But be sure +# to turn off "Auto-browse into objects" for big rosters. +# + +$:.unshift '../../../../../lib' + +require 'thread' + +require 'xmpp4r' +require 'xmpp4r/helpers/roster' +require 'xmpp4r/iq/query/discoinfo' +require 'xmpp4r/iq/query/discoitems' + +# Command line argument checking + +if ARGV.size != 2 + puts("Usage: ./rosterdiscovery.rb ") + exit +end + +# Building up the connection + +#Jabber::debug = true + +jid = Jabber::JID.new(ARGV[0]) + +cl = Jabber::Client.new(jid) +cl.connect +cl.auth(ARGV[1]) + + +# The roster instance +roster = Jabber::Helpers::Roster.new(cl) + + +cl.add_iq_callback { |iq| + if iq.query.kind_of?(Jabber::IqQueryDiscoInfo) || iq.query.kind_of?(Jabber::IqQueryDiscoItems) + + # Prepare the stanza for result + iq.from, iq.to = iq.to, iq.from + iq.type = :result + + + if iq.query.kind_of?(Jabber::IqQueryDiscoInfo) + # iq.to and iq.from are already switched here: + puts("#{iq.to} requests info of #{iq.from} node #{iq.query.node.inspect}") + + if iq.query.node.nil? + iq.query.add(Jabber::DiscoIdentity.new('directory', 'Roster discovery', 'user')) + else + # Count contacts in group + in_group = 0 + roster.items.each { |jid,item| + if item.groups.include?(iq.query.node) + in_group += 1 + end + } + + iq.query.add(Jabber::DiscoIdentity.new('directory', "#{iq.query.node} (#{in_group})", 'group')) + end + + iq.query.add(Jabber::DiscoFeature.new(Jabber::IqQueryDiscoInfo.new.namespace)) + iq.query.add(Jabber::DiscoFeature.new(Jabber::IqQueryDiscoItems.new.namespace)) + + elsif iq.query.kind_of?(Jabber::IqQueryDiscoItems) + # iq.to and iq.from are already switched here: + puts("#{iq.to} requests items of #{iq.from} node #{iq.query.node.inspect}") + + if iq.query.node.nil? + # Make items from group names + groups = [] + + roster.items.each { |jid,item| + groups += item.groups + } + + groups.uniq.each { |group| + iq.query.add(Jabber::DiscoItem.new(cl.jid, group, group)) + } + + # Collect all ungrouped roster items + roster.items.each { |jid,item| + if item.groups == [] + iq.query.add(Jabber::DiscoItem.new(item.jid, item.iname.to_s == '' ? item.jid : item.iname)) + end + } + else + # Add a discovery item for each roster item in that group + roster.items.each { |jid,item| + if item.groups.include?(iq.query.node) + iq.query.add(Jabber::DiscoItem.new(item.jid, item.iname.to_s == '' ? item.jid : item.iname)) + end + } + end + + end + + cl.send(iq) + + true + end +} + +# Initial presence +cl.send(Jabber::Presence.new.set_status("Discover my roster at #{jid}")) + +# Main loop: + +loop do + cl.process + Thread.stop +end + +cl.close + diff --git a/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/advanced/sendfile.conf b/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/advanced/sendfile.conf new file mode 100644 index 000000000..7f08fe491 --- /dev/null +++ b/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/advanced/sendfile.conf @@ -0,0 +1,10 @@ +--- +jabber: + jid: user@server/resource + password: password +local: + port: 65010 + addresses: [172.22.16.3] +proxies: + - proxy.jabber.org + - transfer.jabber.freenet.de diff --git a/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/advanced/sendfile.rb b/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/advanced/sendfile.rb new file mode 100644 index 000000000..f5ad3f6ca --- /dev/null +++ b/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/advanced/sendfile.rb @@ -0,0 +1,72 @@ +require 'xmpp4r' +require 'xmpp4r/bytestreams' +require 'yaml' + +Jabber::debug = true + +if ARGV.size != 3 + puts "Usage: #{$0} " + exit +end + +conf = YAML::load File.new('sendfile.conf') + +cl = Jabber::Client.new(Jabber::JID.new(conf['jabber']['jid'])) +puts "Connecting Jabber client..." +cl.connect +puts "Authenticating..." +cl.auth conf['jabber']['password'] + +puts "Starting local Bytestreams server" +bss = Jabber::Bytestreams::SOCKS5BytestreamsServer.new(conf['local']['port']) +conf['local']['addresses'].each { |address| + bss.add_address(address) +} +ft = Jabber::FileTransfer::Helper.new(cl) +#ft.allow_bytestreams = false +source = Jabber::FileTransfer::FileSource.new(ARGV[2]) +puts "Offering #{source.filename} to #{ARGV[0]}" +stream = ft.offer(Jabber::JID.new(ARGV[0]), source, ARGV[1]) + +if stream + puts "Starting stream initialization (#{stream.class})" + + if stream.kind_of? Jabber::Bytestreams::SOCKS5BytestreamsInitiator + stream.add_streamhost(bss) + (conf['proxies'] || []).each { |proxy| + puts "Querying proxy #{proxy}" + stream.add_streamhost proxy + } + puts "Offering streamhosts " + stream.streamhosts.collect { |sh| sh.jid }.join(' ') + + stream.add_streamhost_callback { |streamhost,state,e| + case state + when :connecting + puts "Connecting to #{streamhost.jid} (#{streamhost.host}:#{streamhost.port})" + when :success + puts "Successfully using #{streamhost.jid} (#{streamhost.host}:#{streamhost.port})" + when :failure + puts "Error using #{streamhost.jid} (#{streamhost.host}:#{streamhost.port}): #{e}" + end + } + end + + stream.open + if stream.kind_of? Jabber::Bytestreams::SOCKS5BytestreamsInitiator + puts "Using streamhost #{stream.streamhost_used.jid} (#{stream.streamhost_used.host}:#{stream.streamhost_used.port})" + end + + while buf = source.read + print "." + $stdout.flush + stream.write buf + stream.flush + end + puts "!" + stream.close + +else + puts "Peer declined" +end + +cl.close diff --git a/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/advanced/shellmgr/shellmgr.rb b/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/advanced/shellmgr/shellmgr.rb new file mode 100644 index 000000000..60cb24e6c --- /dev/null +++ b/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/advanced/shellmgr/shellmgr.rb @@ -0,0 +1,51 @@ +require 'thread' + +class Shell + + PROMPT = "_-=READY=-_" + + def initialize(shell = "/bin/bash", delay = 1, &block) + @cb = block + @shell = shell + @delay = delay + @parent_to_child_read, @parent_to_child_write = IO.pipe + @child_to_parent_read, @child_to_parent_write = IO.pipe + @child_pid = fork do + @parent_to_child_write.close + @child_to_parent_read.close + $stdin.reopen(@parent_to_child_read) + $stdout.reopen(@child_to_parent_write) + $stderr.reopen(@child_to_parent_write) + exec("/bin/bash") + end + buffer = "" + semaphore = Mutex::new + Thread::new do + while true + c = @child_to_parent_read.read(1) + semaphore.synchronize { buffer += c } + end + end + Thread::new do + ch = "" + while true do + sleep @delay + semaphore.synchronize { + if buffer == ch and ch != "" + @cb.call buffer + buffer = "" + end + ch = buffer + } + end + end + end + + def puts(str) + @parent_to_child_write.puts(str) + end + + def kill + @child_pid.kill + end +end diff --git a/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/advanced/shellmgr/shellmgr_jabber.rb b/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/advanced/shellmgr/shellmgr_jabber.rb new file mode 100755 index 000000000..b7e04175e --- /dev/null +++ b/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/advanced/shellmgr/shellmgr_jabber.rb @@ -0,0 +1,43 @@ +#!/usr/bin/ruby -w + +$:.unshift '../../lib' +require 'shellmgr' +require 'xmpp4r' +include Jabber + +if ARGV.length != 3 + puts "usage: ./shellmgr_jabber.rb jid_to_use password jid_to_authorize" + exit(1) +end + +myjid = JID::new(ARGV[0]) +mypassword = ARGV[1] +authjid = JID::new(ARGV[2]) + +myjid = JID::new(myjid.node, myjid.domain, 'RSM') +cl = Client::new(myjid) +cl.connect +cl.auth(mypassword) +mainthread = Thread.current +sh = Shell::new { |str| + puts "-----RECEIVING-----\n#{str}" + cl.send(Message::new(authjid, str)) } +cl.add_message_callback do |m| + if JID::new(m.from).strip.to_s != authjid.strip.to_s + puts "Received message from non-authorized user #{m.from}" + else + if m.body == "killshell" + cl.send(Message::new(authjid, "Exiting...")) + mainthread.wakeup + else + puts "-----EXECUTING-----\n#{m.body}" + sh.puts(m.body) + end + end +end +cl.send(Presence::new) +puts "Connected ! Ask #{authjid.to_s} to send commands to #{myjid.to_s}" +cl.send(Message::new(authjid, "I'm ready to receive commands from you.")) +Thread.stop +cl.close +sh.kill diff --git a/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/advanced/shellmgr/shellmgr_test.rb b/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/advanced/shellmgr/shellmgr_test.rb new file mode 100755 index 000000000..9f5d41682 --- /dev/null +++ b/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/advanced/shellmgr/shellmgr_test.rb @@ -0,0 +1,10 @@ +#!/usr/bin/ruby -w + +require 'shellmgr' + +sh = Shell::new { |s| puts s } + +while true do + l = gets + sh.puts l +end diff --git a/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/advanced/versionpoll.rb b/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/advanced/versionpoll.rb new file mode 100644 index 000000000..8a46e2d51 --- /dev/null +++ b/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/advanced/versionpoll.rb @@ -0,0 +1,90 @@ +#!/usr/bin/ruby + +# This script will request the version information of a list of JID given +# on stdin. + +$:.unshift '../lib' + +require 'optparse' +require 'xmpp4r/client' +require 'xmpp4r/version/iq/version' +include Jabber +#Jabber::debug = true + +# settings +jid = JID::new('bot@localhost/Bot') +password = 'bot' +domains = [] +OptionParser::new do |opts| + opts.banner = 'Usage: versionpoll.rb -j jid -p password -d DOMAINS' + opts.separator '' + opts.on('-j', '--jid JID', 'sets the jid') { |j| jid = JID::new(j) } + opts.on('-p', '--password PASSWORD', 'sets the password') { |p| password = p } + opts.on('-d', '--domain DOMAIN', 'sets the domain') { |d| domains << d } + opts.on_tail('-h', '--help', 'Show this message') { + puts opts + exit + } + opts.parse!(ARGV) +end + +cl = Client::new(jid) +cl.connect +cl.auth(password) +sent = [] +queried = [] +activity = false +cl.add_iq_callback do |i| + fjid = JID::new(i.from) + if i.type == :result and fjid.resource == "admin" + domain = fjid.domain + items = i.first_element('item') + raise "items nil" if items.nil? + items.each_element('user') do |e| + j = e.attribute('jid') + if not queried.include?(j) + activity = true + queried << j + cl.send(Iq::new_browseget.set_to(j)) + end + end + end +end + +cl.add_iq_callback do |i| + if i.type == :result + u = i.first_element('user') + if u + u.each_element('user') do |e| + if (a = e.attribute('type')) + if a.value == 'client' + activity = true + iq = Iq::new(:get) + iq.query = Version::IqQueryVersion::new + iq.set_to(JID::new(e.attribute('jid').to_s)) + cl.send(iq) + end + end + end + end + end +end +cl.add_iq_callback do |iq| + if iq.type == :result and iq.query.class == Version::IqQueryVersion + activity = true + r = [ iq.from.to_s, iq.query.iname, iq.query.version, iq.query.os ] + puts r.inspect + end +end +cl.send(Presence::new) +for d in domains do + cl.send(Iq::new_browseget.set_to("#{d}/admin")) +end + +activity = true +while activity + activity = false + # other threads might set activity to true + sleep 10 +end +cl.close diff --git a/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/advanced/xmpping.rb b/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/advanced/xmpping.rb new file mode 100644 index 000000000..4c8ed715c --- /dev/null +++ b/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/advanced/xmpping.rb @@ -0,0 +1,147 @@ +#!/usr/bin/env ruby +# =XMPP4R - XMPP Library for Ruby +# License:: Ruby's license (see the LICENSE file) or GNU GPL, at your option. +# Website::http://home.gna.org/xmpp4r/ + + +# This is PING for Jabber +# +# Please customize your ~/.xmppingrc + + +require 'xmpp4r' +require 'xmpp4r/httpbinding' +require 'xmpp4r/version/iq/version' +require 'xmpp4r/discovery/iq/discoinfo' +require 'optparse' +require 'yaml' +require 'thread' + + +## +# Options +## + +interval = 5 +jid = nil +conf_filename = "#{ENV['HOME']}/.xmppingrc" +accountname = 'default' + +OptionParser.new { |opts| + opts.banner = 'Usage: xmpping.rb [-d] [-a ACCOUNT] [-c FILENAME] [-i SECONDS] -t ' + opts.separator 'Ping a destination JID with various stanzas' + opts.on('-t', '--to JID', 'Destionation Jabber-ID') { |j| jid = Jabber::JID.new(j) } + opts.on('-a', '--account ACCOUNT', 'Account tag to use (default: default)') { |a| accountname = a } + opts.on('-c', '--config FILENAME', 'Configuration file (default: ~/.xmppingrc)') { |c| conf_filename = c } + opts.on('-i', '--interval SECONDS', 'Wait SECONDS between each stanza (default: 5)') { |sec| interval = sec.to_i } + opts.on('-d', '--debug', 'Enable XMPP4R debugging (print stanzas)') { Jabber::debug = true } + opts.on_tail('-h', '--help', 'Show help') { + puts opts + exit + } + opts.parse!(ARGV) + + unless jid + puts opts + exit + end +} + + +## +# Configuration +## + +begin + conf_file = File.new(conf_filename) +rescue Exception => e + puts "Unable to open config file: #{e.to_s}" + exit +end + +conf = YAML::load(conf_file) +unless conf + puts "#{conf_filename} is no valid YAML document" + exit +end +account = conf[accountname] +unless account + puts "Account #{accountname} not found in #{conf_filename}" + exit +end + +## +# Connection +## + +if account['http bind'] + cl = Jabber::HTTPBinding::Client.new(Jabber::JID.new(account['jid'])) + cl.connect(account['http bind']) +else + cl = Jabber::Client.new(Jabber::JID.new(account['jid'])) + cl.connect(account['host'], (account['port'] ? account['port'].to_i : 5222)) +end +cl.auth(account['password']) + +## +# Reply printer +## + +def print_reply(iq, roundtrip) + roundtrip_s = ((roundtrip * 100).round / 100.0).to_s + " sec" + output = "Received a #{iq.query.namespace} #{iq.type} (id: #{iq.id}) from #{iq.from} (#{roundtrip_s}): " + + if iq.query.kind_of?(Jabber::Version::IqQueryVersion) + output += "#{iq.query.iname}-#{iq.query.version} #{iq.query.os}" + elsif iq.query.namespace == 'jabber:iq:time' + output += "#{iq.query.first_element_text('display')} (#{iq.query.first_element_text('tz')})" + elsif iq.query.kind_of?(Jabber::Discovery::IqQueryDiscoInfo) + identity = iq.query.identity + if identity + output += "#{identity.iname} (#{identity.category} #{identity.type})" + else + output += " missing" + end + else + output += iq.query.to_s + end + + puts output +end + +## +# Main loop +## + + require 'mprofiler' + MemoryProfiler.start(:string_debug=>true) + +puts "XMPPING #{cl.jid} -> #{jid}" +query_methods = ['jabber:iq:version', 'jabber:iq:time', 'http://jabber.org/protocol/disco#info'] +query_method = 0 + +loop { + Thread.new { + iq = Jabber::Iq.new_query(:get, jid) + iq.query.add_namespace(query_methods[query_method]) + + time1 = Time.new + + begin + cl.send_with_id(iq) { |reply| + print_reply(reply, Time.new - time1) + true + } + rescue Jabber::ErrorException => e + puts "Error for #{iq.query.namespace} to #{iq.to}: #{e.error.to_s.inspect}" + end + } + + query_method += 1 + if query_method >= query_methods.size + query_method = 0 + end + + sleep(interval) +} + diff --git a/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/advanced/xmppingrc.sample b/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/advanced/xmppingrc.sample new file mode 100644 index 000000000..28431b8f1 --- /dev/null +++ b/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/advanced/xmppingrc.sample @@ -0,0 +1,14 @@ +default: + jid: user@host/xmpping + password: secret + +other_account: + jid: me@domain/ping + password: xxx + host: 127.0.0.1 + port: 65222 + +web_account: + jid: me@domain/ping + password: xxx + http bind: http://domain/http-bind/ diff --git a/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/basic/change_password.rb b/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/basic/change_password.rb new file mode 100755 index 000000000..fd88548aa --- /dev/null +++ b/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/basic/change_password.rb @@ -0,0 +1,41 @@ +#!/usr/bin/ruby +# +# A tool to change the password of a Jabber account +# + + +$:.unshift('../../../../../lib') + +require 'xmpp4r' +include Jabber + +def with_status(str, &block) + print "#{str}..." + $stdout.flush + begin + yield + puts " Ok" + rescue Exception => e + puts " Error: #{e.to_s}" + raise e + end +end + + +# settings +if ARGV.length != 3 + puts "Run with ./change_password.rb user@server/resource oldpassword newpassword" + exit 1 +end +my_jid = JID::new(ARGV[0]) +my_jid.resource = 'change_password' if my_jid.resource.nil? +old_password = ARGV[1] +new_password = ARGV[2] + +cl = Client::new(my_jid) + +with_status('Connecting') { cl.connect } +with_status('Authenticating') { cl.auth(old_password) } +with_status('Changing password') { cl.password = new_password } + +cl.close diff --git a/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/basic/client.rb b/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/basic/client.rb new file mode 100755 index 000000000..140b97ad3 --- /dev/null +++ b/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/basic/client.rb @@ -0,0 +1,68 @@ +#!/usr/bin/ruby + +# Basic console client that does nothing, but easy to modify to test things. +# to test, start, then type : +# connect login@server/resource password +# auth + +require 'xmpp4r/client' +include Jabber + +Jabber::debug = true + +class BasicClient + def initialize + puts "Welcome to this Basic Console Jabber Client!" + quit = false + # main loop + while not quit do + print "> " + $defout.flush + line = gets + quit = true if line.nil? + if not quit + command, args = line.split(' ', 2) + args = args.to_s.chomp + # main case + case command + when 'exit' + quit = true + when 'connect' + do_connect(args) + when 'help' + do_help + when 'auth' + do_auth + else + puts "Command \"#{command}\" unknown" + end + end + end + puts "Goodbye!" + end + + def do_help + puts <<-EOF +# exit - exits +# connect user@server/resource password - connects +# auth - sends authentification + EOF + end + + ## + # connect + def do_connect(args) + @jid, @password = args.split(' ', 2) + @jid = JID::new(@jid) + @cl = Client::new(@jid) + @cl.connect + end + + ## + # auth + def do_auth + @cl.auth(@password, false) + end +end + +BasicClient::new diff --git a/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/basic/component.rb b/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/basic/component.rb new file mode 100755 index 000000000..10a4060e3 --- /dev/null +++ b/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/basic/component.rb @@ -0,0 +1,11 @@ +#!/usr/bin/ruby + +$:.unshift '../../../../../lib' + +require 'xmpp4r' +include Jabber + +c = Component::new('tada', 'localhost', 60001) +c.connect +c.auth('jabber-rocks') +Thread.stop diff --git a/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/basic/echo_threaded.rb b/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/basic/echo_threaded.rb new file mode 100755 index 000000000..9918c8def --- /dev/null +++ b/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/basic/echo_threaded.rb @@ -0,0 +1,36 @@ +#!/usr/bin/ruby + +# This bot will reply to every message it receives. To end the game, send 'exit' +# THREADED VERSION + +require 'xmpp4r/client' +include Jabber + +# settings +if ARGV.length != 2 + puts "Run with ./echo_thread.rb user@server/resource password" + exit 1 +end +myJID = JID::new(ARGV[0]) +myPassword = ARGV[1] +cl = Client::new(myJID) +cl.connect +cl.auth(myPassword) +cl.send(Presence::new) +puts "Connected ! send messages to #{myJID.strip.to_s}." +mainthread = Thread.current +cl.add_message_callback do |m| + if m.type != :error + m2 = Message::new(m.from, "You sent: #{m.body}") + m2.type = m.type + cl.send(m2) + if m.body == 'exit' + m2 = Message::new(m.from, "Exiting ...") + m2.type = m.type + cl.send(m2) + mainthread.wakeup + end + end +end +Thread.stop +cl.close diff --git a/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/basic/jabbersend.rb b/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/basic/jabbersend.rb new file mode 100755 index 000000000..333d4859a --- /dev/null +++ b/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/basic/jabbersend.rb @@ -0,0 +1,41 @@ +#!/usr/bin/ruby + +# This script will send a jabber message to the specified JID. The subject can be +# specified using the '-s' option, and the message will be taken from stdin. + +$:.unshift '../../../../../lib' + +require 'optparse' +require 'xmpp4r' +include Jabber + +# settings +myJID = JID::new('bot@localhost/Bot') +myPassword = 'bot' + +to = nil +subject = '' +OptionParser::new do |opts| + opts.banner = 'Usage: jabbersend.rb -s \'subject\' -t dest@domain' + opts.separator '' + opts.on('-s', '--subject SUBJECT', 'sets the message\'s subject') { |s| subject = s } + opts.on('-t', '--to DESTJID', 'sets the receiver') { |t| to = JID::new(t) } + opts.on_tail('-h', '--help', 'Show this message') { + puts opts + exit + } + opts.parse!(ARGV) +end + +if to.nil? + puts "No receiver specified. See jabbersend -h" +end + +cl = Client::new(myJID, false) +cl.connect +cl.auth(myPassword) +body = STDIN.readlines.join +m = Message::new(to, body).set_type(:normal).set_id('1').set_subject(subject) +puts m.to_s +cl.send(m) +cl.close diff --git a/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/basic/mass_sender.rb b/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/basic/mass_sender.rb new file mode 100755 index 000000000..ec29deb99 --- /dev/null +++ b/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/basic/mass_sender.rb @@ -0,0 +1,68 @@ +#!/usr/bin/ruby + +# This script will send a jabber message to a list of JID given on stdin. + +$:.unshift '../../../../../lib' + +require 'optparse' +require 'xmpp4r' +include Jabber +#Jabber::debug = true + +# - message in file +# - subject in command line +# - JID list on stdin + +# settings +jid = JID::new('bot@localhost/Bot') +password = 'bot' +filename = 'message.txt' + +subject = "Message de test" + +OptionParser::new do |opts| + opts.banner = 'Usage: mass_sender.rb -j jid -p password' + opts.separator '' + opts.on('-j', '--jid JID', 'sets the jid') { |j| jid = JID::new(j) } + opts.on('-p', '--password PASSWORD', 'sets the password') { |p| password = p } + opts.on('-f', '--filename MESSAGE', 'sets the filename containing the message') { |f| filename = f } + opts.on('-s', '--subject SUBJECT', 'sets the subject') { |s| subject = s } + opts.on_tail('-h', '--help', 'Show this message') { + puts opts + exit + } + opts.parse!(ARGV) +end + +body = IO::read(filename).chomp + +cl = Client::new(jid, false) +cl.connect +cl.auth(password) +exit = false +sent = [] +cl.add_message_callback do |m| + if m.type != :error + if !sent.include?(m.from) + cl.send(Message::new(m.from, "Je suis un robot. Si tu souhaites contacter un administrateur du serveur, envoie un message à lucas@nussbaum.fr ou rejoins la salle jabberfr@chat.jabberfr.org.")) + sent << m.from + end + if m.body == 'exitnowplease' + cl.send(Message::new(m.from, "Exiting ...")) + exit = true + end + cl.send(Message::new('lucas@nussbaum.fr', "From #{m.from}: #{m.body.to_s}")) + end +end +cl.send(Presence::new) +m = Message::new(nil, body) +m.subject = subject +STDIN.each_line { |l| + l.chomp! + m.set_to(JID::new(l).to_s) + cl.send(m) +} +while not exit do + cl.process(1) +end +cl.close diff --git a/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/basic/muc_owner_config.rb b/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/basic/muc_owner_config.rb new file mode 100644 index 000000000..9e3cf21db --- /dev/null +++ b/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/basic/muc_owner_config.rb @@ -0,0 +1,13 @@ +require 'xmpp4r' +require 'xmpp4r/muc' + +client = Jabber::Client.new('admin@myserver.co.uk/ruby') +muc = Jabber::MUC::MUCClient.new(client) +client.connect +client.auth('admin') +muc.join('room@conference.myserver.co.uk/admin') +muc.configure( + 'muc#roomconfig_roomname' => 'roomname', + 'muc#roomconfig_persistentroom' => 1 +) + diff --git a/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/basic/mucinfo.rb b/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/basic/mucinfo.rb new file mode 100755 index 000000000..cc6ce012f --- /dev/null +++ b/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/basic/mucinfo.rb @@ -0,0 +1,39 @@ +#!/usr/bin/env ruby + +require 'xmpp4r' +require 'xmpp4r/muc/helper/mucbrowser' + +if ARGV.size != 3 + puts "Usage: #{$0} " + exit +end + +jid, password, muc_jid = Jabber::JID.new(ARGV.shift), ARGV.shift, Jabber::JID.new(ARGV.shift) + +cl = Jabber::Client.new(jid) +cl.connect +cl.auth(password) + +browser = Jabber::MUC::MUCBrowser.new(cl) + +print "Querying #{muc_jid} for identity..."; $stdout.flush +name = browser.muc_name(muc_jid) + +if name.nil? + puts " Sorry, but the queried MUC component doesn't seem to support MUC or Groupchat." +else + puts " #{name}" + + print "Querying #{muc_jid} for its rooms..."; $stdout.flush + rooms = browser.muc_rooms(muc_jid) + puts " #{rooms.size} rooms found" + + max_room_length = 0 + rooms.each_key { |jid| max_room_length = jid.to_s.size if jid.to_s.size > max_room_length } + + rooms.each { |jid,name| + puts "#{jid.to_s.ljust(max_room_length)} #{name}" + } +end + +cl.close diff --git a/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/basic/mucsimplebot.rb b/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/basic/mucsimplebot.rb new file mode 100755 index 000000000..05293b4e9 --- /dev/null +++ b/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/basic/mucsimplebot.rb @@ -0,0 +1,83 @@ +#!/usr/bin/env ruby +$:.unshift '../../../../../lib/' +require 'xmpp4r' +require 'xmpp4r/muc/helper/simplemucclient' + + +if ARGV.size != 3 + puts "Usage: #{$0} " + exit +end + +# Print a line formatted depending on time.nil? +def print_line(time, line) + if time.nil? + puts line + else + puts "#{time.strftime('%I:%M')} #{line}" + end +end + + +#Jabber::debug = true +cl = Jabber::Client.new(Jabber::JID.new(ARGV[0])) +cl.connect +cl.auth(ARGV[1]) + +# For waking up... +mainthread = Thread.current + +# This is the SimpleMUCClient helper! +m = Jabber::MUC::SimpleMUCClient.new(cl) + +# SimpleMUCClient callback-blocks + +m.on_join { |time,nick| + print_line time, "#{nick} has joined!" + puts "Users: " + m.roster.keys.join(', ') +} +m.on_leave { |time,nick| + print_line time, "#{nick} has left!" +} + +m.on_message { |time,nick,text| + print_line time, "<#{nick}> #{text}" + + # Avoid reacting on messaged delivered as room history + unless time + # Bot: invite astro@spaceboyz.net + if text.strip =~ /^(.+?): invite (.+)$/ + jid = $2 + if $1.downcase == m.jid.resource.downcase + m.invite(jid => "Inviting you on behalf of #{nick}") + m.say("Inviting #{jid}...") + end + # Bot: subject This is room is powered by XMPP4R + elsif text.strip =~ /^(.+?): subject (.+)$/ + if $1.downcase == m.jid.resource.downcase + m.subject = $2 + end + # Bot: exit please + elsif text.strip =~ /^(.+?): exit please$/ + if $1.downcase == m.jid.resource.downcase + puts "exiting" + m.exit "Exiting on behalf of #{nick}" + mainthread.wakeup + end + end + end +} +m.on_room_message { |time,text| + print_line time, "- #{text}" +} +m.on_subject { |time,nick,subject| + print_line time, "*** (#{nick}) #{subject}" +} + +m.join(ARGV[2]) + +# Wait for being waken up by m.on_message +Thread.stop + +cl.close + diff --git a/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/basic/register.rb b/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/basic/register.rb new file mode 100755 index 000000000..0963d4e21 --- /dev/null +++ b/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/basic/register.rb @@ -0,0 +1,25 @@ +#!/usr/bin/ruby + +$:.unshift '../../../../../lib' +require 'xmpp4r' + + +# Argument checking +if ARGV.size != 2 + puts("Usage: #{$0} ") + exit +end + + +# The usual procedure +cl = Jabber::Client.new(Jabber::JID.new(ARGV[0])) +puts "Connecting" +cl.connect + +# Registration of the new user account +puts "Registering..." +cl.register(ARGV[1]) +puts "Successful" + +# Shutdown +cl.close diff --git a/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/basic/remove_registration.rb b/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/basic/remove_registration.rb new file mode 100644 index 000000000..1131ba258 --- /dev/null +++ b/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/basic/remove_registration.rb @@ -0,0 +1,18 @@ +#!/usr/bin/env ruby + +$:.unshift '../../../../../lib' +require 'xmpp4r' +include Jabber + +if ARGV.size != 2 + puts "Warning! This example UNREGISTERS user accounts!" + puts "Usage: #{$0} " + exit +end + +cl = Client.new(JID.new(ARGV[0])) +cl.connect +cl.auth(ARGV[1]) +cl.remove_registration + +cl.close diff --git a/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/basic/roster.rb b/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/basic/roster.rb new file mode 100755 index 000000000..b623ec4dc --- /dev/null +++ b/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/basic/roster.rb @@ -0,0 +1,42 @@ +#!/usr/bin/ruby + +# This script can get all roster entries + +require 'optparse' +require 'xmpp4r' +require 'xmpp4r/roster/iq/roster' +include Jabber + +jid = JID::new('lucastest@linux.ensimag.fr/rosterget') +password = 'lucastest' + +OptionParser::new do |opts| + opts.banner = 'Usage: roster.rb -t get -j jid -p password' + opts.separator '' + opts.on('-j', '--jid JID', 'sets the jid') { |j| jid = JID::new(j) } + opts.on('-p', '--password PASSWORD', 'sets the password') { |p| password = p } + opts.on_tail('-h', '--help', 'Show this message') { + puts opts + exit + } + opts.parse!(ARGV) +end + +cl = Client::new(jid, false) +cl.connect +cl.auth(password) +cl.send(Iq::new_rosterget) +exit = false +cl.add_iq_callback { |i| + if i.type == :result and i.query.kind_of?(Roster::IqQueryRoster) + i.query.each_element { |e| + e.text = '' + puts e.to_s + } + exit = true + end +} +while not exit + cl.process +end +cl.close diff --git a/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/basic/rosterprint.rb b/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/basic/rosterprint.rb new file mode 100755 index 000000000..4f14e3423 --- /dev/null +++ b/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/basic/rosterprint.rb @@ -0,0 +1,50 @@ +#!/usr/bin/ruby + +$:.unshift '../../../../../lib/' + +require 'xmpp4r' +require 'xmpp4r/roster/helper/roster' + +# Command line argument checking + +if ARGV.size != 2 + puts("Usage: ./rosterprint.rb ") + exit +end + +# Building up the connection + +#Jabber::debug = true + +jid = Jabber::JID.new(ARGV[0]) + +cl = Jabber::Client.new(jid) +cl.connect +cl.auth(ARGV[1]) + +# The roster instance +roster = Jabber::Roster::Helper.new(cl) + +mainthread = Thread.current + +roster.add_query_callback { |iq| + mainthread.wakeup +} + +Thread.stop + +roster.groups.each { |group| + if group.nil? + puts "*** Ungrouped ***" + else + puts "*** #{group} ***" + end + + roster.find_by_group(group).each { |item| + puts "- #{item.iname} (#{item.jid})" + } + + print "\n" +} + +cl.close diff --git a/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/basic/rosterrename.rb b/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/basic/rosterrename.rb new file mode 100755 index 000000000..a2c4afc54 --- /dev/null +++ b/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/basic/rosterrename.rb @@ -0,0 +1,34 @@ +#!/usr/bin/ruby + +$:.unshift '../../../../../lib' + +require 'xmpp4r' +require 'xmpp4r/roster/iq/roster' + +# Command line argument checking + +if ARGV.size < 4 + puts("Usage: ./rosterrename.rb [ ... ]") + exit +end + +# Building up the connection + +#Jabber::debug = true + +jid = Jabber::JID.new(ARGV[0]) + +cl = Jabber::Client.new(jid, false) +cl.connect +cl.auth(ARGV[1]) + +# The iq stanza +iq = Jabber::Iq::new(:set) +# The new roster instance and item element +iq.add(Jabber::Roster::IqQueryRoster.new).add(Jabber::Roster::RosterItem.new(ARGV[2], ARGV[3])).groups = ARGV[4..ARGV.size] + +# Sending the stanza +cl.send(iq) + +# Don't look at the results: +cl.close diff --git a/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/basic/rosterwatch.rb b/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/basic/rosterwatch.rb new file mode 100755 index 000000000..9bab4b390 --- /dev/null +++ b/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/basic/rosterwatch.rb @@ -0,0 +1,172 @@ +#!/usr/bin/ruby +# +# XMPP4R - XMPP Library for Ruby +# Copyright (C) 2005 Stephan Maka +# Released under Ruby's license (see the LICENSE file) or GPL, at your option +# +# +# Roster-Watch example +# +# +# Learn how a roster looks like +# how presences are received +# about subscription requests and answers +# what vCards contain +# +# It's recommended to insert 'p' commands in this script. :-) +# +# This script does: +# +# * Listing roster changes +# +# * Subscribe to roster items which have a subscription of "none" or "from" +# WARNING: Chances are that you don't want that :-) +# +# * Requesting vCards for unnamed items in your roster and renaming them +# to the or field in the vCard +# +# * Listing presence changes +# +# * Listing subscription and unsubscription requests and answers + +$:.unshift '../../../../../lib/' + +require 'xmpp4r' +require 'xmpp4r/roster/helper/roster' +require 'xmpp4r/vcard/helper/vcard' + +# Command line argument checking + +if ARGV.size != 2 + puts("Usage: ./rosterwatch.rb ") + exit +end + +# Building up the connection + +#Jabber::debug = true + +jid = Jabber::JID.new(ARGV[0]) + +cl = Jabber::Client.new(jid) +cl.connect +cl.auth(ARGV[1]) + +# The roster instance +roster = Jabber::Roster::Helper.new(cl) + +# Callback to handle updated roster items +roster.add_update_callback { |olditem,item| + if [:from, :none].include?(item.subscription) && item.ask != :subscribe + puts("Subscribing to #{item.jid}") + item.subscribe + end + + # Print the item + if olditem.nil? + # We didn't knew before: + puts("#{item.iname} (#{item.jid}, #{item.subscription}) #{item.groups.join(', ')}") + else + # Showing whats different: + puts("#{olditem.iname} (#{olditem.jid}, #{olditem.subscription}) #{olditem.groups.join(', ')} -> #{item.iname} (#{item.jid}, #{item.subscription}) #{item.groups.join(', ')}") + end + + # If the item has no name associated... + unless item.iname + Thread.new do + puts("#{item.jid} has no nickname... getting vCard") + begin + # ...get a vCard + vcard = Jabber::Vcard::Helper.new(cl).get(item.jid.strip) + + unless vcard.nil? + # Rename him to vCard's field + if vcard['NICKNAME'] + item.iname = vcard['NICKNAME'] + puts("Renaming #{item.jid} to #{vcard['NICKNAME']}") + item.send + # Rename him to vCard's field + elsif vcard['FN'] + item.iname = vcard['FN'] + puts("Renaming #{item.jid} to #{vcard['FN']}") + item.send + # We've got a lazy one + else + puts("#{item.jid} provided no details in vCard") + end + end + + rescue Exception => e + # This will be (mostly) thrown by Jabber::Vcard::Helper#get + puts("Error getting vCard for #{item.jid}: #{e.to_s}") + end + end + end +} + +# Presence updates: +roster.add_presence_callback { |item,oldpres,pres| + # Can't look for something that just does not exist... + if pres.nil? + # ...so create it: + pres = Jabber::Presence.new + end + if oldpres.nil? + # ...so create it: + oldpres = Jabber::Presence.new + end + + # Print name and jid: + name = "#{pres.from}" + if item.iname + name = "#{item.iname} (#{pres.from})" + end + puts(name) + + # Print type changes: + unless oldpres.type.nil? && pres.type.nil? + puts(" Type: #{oldpres.type.inspect} -> #{pres.type.inspect}") + end + # Print show changes: + unless oldpres.show.nil? && pres.show.nil? + puts(" Show: #{oldpres.show.to_s.inspect} -> #{pres.show.to_s.inspect}") + end + # Print status changes: + unless oldpres.status.nil? && pres.status.nil? + puts(" Status: #{oldpres.status.to_s.inspect} -> #{pres.status.to_s.inspect}") + end + # Print priority changes: + unless oldpres.priority.nil? && pres.priority.nil? + puts(" Priority: #{oldpres.priority.inspect} -> #{pres.priority.inspect}") + end + + # Note: presences with type='error' will reflect our own show/status/priority + # as it is mostly just a reply from a server. This is *not* a bug. +} + +# Subscription requests and responses: +subscription_callback = lambda { |item,pres| + name = pres.from + if item != nil && item.iname != nil + name = "#{item.iname} (#{pres.from})" + end + case pres.type + when :subscribe then puts("Subscription request from #{name}") + when :subscribed then puts("Subscribed to #{name}") + when :unsubscribe then puts("Unsubscription request from #{name}") + when :unsubscribed then puts("Unsubscribed from #{name}") + else raise "The Roster Helper is buggy!!! subscription callback with type=#{pres.type}" + end +} +roster.add_subscription_callback(0, nil, &subscription_callback) +roster.add_subscription_request_callback(0, nil, &subscription_callback) + +# Send initial presence +# this is important for receiving presence of subscribed users +cl.send(Jabber::Presence.new.set_show(:dnd).set_status('Watching my roster change...')) + +# Main loop: +Thread.stop + +cl.close + diff --git a/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/basic/send_vcard.rb b/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/basic/send_vcard.rb new file mode 100755 index 000000000..0c511659a --- /dev/null +++ b/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/basic/send_vcard.rb @@ -0,0 +1,68 @@ +#!/usr/bin/ruby + +# Demonstration of the Vcard helper class +# +# * Retrieves your own vCard +# * Modify fields given on the command line +# * Dumps the vCard +# * Send your vCard to the server + +$:.unshift('../../../../../lib') + +require 'xmpp4r' +require 'xmpp4r/vcard/helper/vcard' +include Jabber + + +# settings +if ARGV.length < 2 + puts "Usage:\t./send_vcard.rb [= [... =] ]" + puts "Example:\t./send_vcard.rb user@server/resource password NICKNAME=User \"FN=A user\"" + exit 1 +end + +# Do the client stuff... +myJID = JID::new(ARGV.shift) +myPassword = ARGV.shift +cl = Client::new(myJID) +cl.connect +cl.auth(myPassword) + +# The Vcard helper +vcard_helper = Vcard::Helper.new(cl) + +begin + puts "Retrieving vCard information for #{cl.jid.strip}" + vcard = vcard_helper.get + + + # Inspect the command line for vCard fields to be changed + ARGV.each { |arg| + arg.scan(/^(.+?)=(.*)$/) { |field,text| + puts "field #{field}: #{vcard[field].inspect} => #{text.inspect}" + vcard[field] = text + } + } + + # Dump the vCard + vcard.fields.each { |field| + if field.split(/\//).pop == 'BINVAL' + puts "#{field}:\tBINVAL" + else + puts "#{field}:\t#{vcard[field].inspect}" + end + } + + begin + puts "Sending vCard information for #{cl.jid.strip}" + vcard_helper.set(vcard) + rescue Exception => e + puts "Sorry, we stumbled upon the following when sending the vCard of #{cl.jid.strip}: #{e.to_s.inspect}" + end + +rescue Exception => e + puts "Sorry, we stumbled upon the following when requesting the vCard of #{cl.jid.strip}: #{e.to_s.inspect}" +end + +cl.close + diff --git a/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/basic/versionbot.rb b/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/basic/versionbot.rb new file mode 100755 index 000000000..e3e37edbf --- /dev/null +++ b/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/basic/versionbot.rb @@ -0,0 +1,75 @@ +#!/usr/bin/ruby + +$:.unshift '../../../../../lib' + +require 'xmpp4r' +require 'xmpp4r/version/iq/version' +require 'xmpp4r/version/helper/simpleresponder' + + +# A Hash containing all Version Query answers with their JIDs as keys: +versions = {} + +# Command line argument checking + +if ARGV.size != 2 + puts("Usage: ./versionbot.rb ") + exit +end + +# Building up the connection + +#Jabber::debug = true + +jid = Jabber::JID.new(ARGV[0]) + +cl = Jabber::Client.new(jid, false) +cl.connect +cl.auth(ARGV[1]) + +cl.on_exception { |*a| + p a[0].backtrace + exit! +} + +# I'm not sure about the portability of 'uname -sr' here ;-) +# but that's all needed to answer version queries: +Jabber::Version::SimpleResponder.new(cl, 'xmpp4r Versionbot example', Jabber::XMPP4R_VERSION, IO.popen('uname -sr').readlines.to_s.strip) + + +cl.add_iq_callback { |iq| + # Filter for version query results + if (iq.type == :result) && iq.query.kind_of?(Jabber::Version::IqQueryVersion) + puts "Version query result from #{iq.from}" + # Keep track of results per JID + versions[iq.from] = iq.query + # Print details + puts " Name: #{iq.query.iname.inspect}" + puts " Version: #{iq.query.version.inspect}" + puts " OS: #{iq.query.os.inspect}" + end +} + +cl.add_presence_callback { |pres| + # Already fingerprinted or offline? + unless versions.has_key?(pres.from) || (pres.type == :unavailable) || (pres.type == :error) + # Construct a new query + iq = Jabber::Iq.new(:get, pres.from) + # and ask for the version + iq.query = Jabber::Version::IqQueryVersion.new + puts "Asking #{iq.to} for his/her/its version" + versions[pres.from] = :asking + cl.send(iq) + end +} + +# Send initial presence +cl.send(Jabber::Presence.new.set_show(:xa).set_status('I am the evil fingerprinting robot')) + +# Main loop: +loop do + cl.process + sleep(1) +end + +cl.close diff --git a/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/buggy/jabber2jabber/jabber2jabber.rb b/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/buggy/jabber2jabber/jabber2jabber.rb new file mode 100755 index 000000000..2e98af98a --- /dev/null +++ b/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/buggy/jabber2jabber/jabber2jabber.rb @@ -0,0 +1,18 @@ +#!/usr/bin/ruby + +$:.unshift '../lib' + +require 'xmpp4r' +include Jabber + +c = Component::new('example.blop.info', 'linux.ensimag.fr', 2609) +c.connect +c.auth('BenEuh') +c.add_iq_callback { |i| +puts i.to_s +} + +c.add_message_callback { |m| + puts m.to_s +} +Thread.stop diff --git a/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/buggy/miniedgarr_cgi.rb b/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/buggy/miniedgarr_cgi.rb new file mode 100755 index 000000000..50b1488bf --- /dev/null +++ b/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/buggy/miniedgarr_cgi.rb @@ -0,0 +1,192 @@ +#!/usr/bin/env ruby +# +# cp /usr/local/share/psi/iconsets/roster/lightbulb/{away,online,offline,xa,chat,dnd}.png . + +$:.unshift '../lib' + +require 'GD' +require 'cgi' +require 'digest/md5' + +require 'rexml/document' + +require 'xmpp4r' +require 'xmpp4r/iqqueryroster' + +# Handle CGI parameters +cgi = CGI.new +jid = Jabber::JID.new(cgi['jid']).strip +jidhash = cgi['hash'] +transparency = (cgi['transparency'] == 'true') + +# Create data + +roster = Jabber::IqQueryRoster.new +presences = {} + +# Load state + +doc = REXML::Document.new(File.new('edgarrstate.xml')) + +doc.root.each_element { |e| + if (e.name == 'query') && (e.namespace == 'jabber:iq:roster') + roster.import(e) + elsif e.name == 'presence' + pres = Jabber::Presence.new.import(e) + + if (pres.from.strip == jid) || (Digest::MD5.hexdigest(pres.from.strip.to_s) == jidhash) + if (jid == '') && !jidhash.nil? + jid = pres.from.strip + end + presences[pres.from] = pres + end + end +} + +resources = (presences.size < 1) ? 1 : presences.size + + +class BannerTable + def initialize + @lines = [] + end + + def last + @lines[@lines.size - 1] + end + + def add_line + @lines.push(BannerLine.new) + end + + def paint + width = 0 + height = 0 + @lines.each do |line| + if width < line.w + width = line.w + end + height += line.h + end + + gd = GD::Image.new(width + 6, height + 6) + white = gd.colorAllocate(255,255,255) + black = gd.colorAllocate(0,0,0) + + gd.fill(0, 0, black) + gd.interlace = true + gd.rectangle(0,0,width + 5,height + 5,white) + y = 2 + + @lines.each do |line| + line.paint(gd, 3, y + (line.h / 2)) + y += line.h + end + + gd + end +end + +class BannerLine + def initialize + @items = [] + end + + def add(type, text) + @items.push(BannerItem.new(type, text)) + end + + def w + w = 1 + @items.each do |item| + w += item.w + end + w + end + + def h + h = 0 + @items.each do |item| + if h < item.h + h = item.h + end + end + h + end + + def paint(gd, x_start, y_center) + x = x_start + @items.each do |item| + item.paint(gd, x, y_center - (item.h / 2)) + x += item.w + end + end +end + +class BannerItem + def initialize(type, text) + @type = type + @text = text + end + + def w + if @type.kind_of?(GD::Font) + (@type.width * @text.size) + 2 + elsif @type.kind_of?(GD::Image) + @type.width + 2 + end + end + + def h + if @type.kind_of?(GD::Font) || @type.kind_of?(GD::Image) + @type.height + 2 + end + end + + def paint(gd, x, y) + if @type.kind_of?(GD::Font) && (@text.size > 0) + gd.string(@type, x + 1, y + 1, @text, gd.colorAllocate(255,255,255)) + elsif @type.kind_of?(GD::Image) + type = @type.to_paletteImage(false, 256) + type.copy(gd, x + 1, y + 1, 0, 0, type.height, type.height) + end + end +end + +# Paint the image + +table = BannerTable.new +table.add_line + +# Put JID at top +if roster[jid.strip].nil? + iname = 'Unknown' +else + iname = roster[jid.strip].iname.to_s +end +table.last.add(GD::Font::MediumFont, iname == '' ? jid.to_s : iname) + +if (presences.size < 1) + table.add_line + table.last.add(GD::Font::SmallFont, 'Unavailable') +else + presences.each { |jid,pres| + show = pres.show.to_s + if pres.type == :unavailable + show = 'offline' + elsif pres.show.nil? + show = 'online' + end + + table.add_line + table.last.add(GD::Image.newFromPng(File.new("miniedgarr_bulbs/#{show}.png")), nil) + prio = pres.priority.nil? ? 0 : pres.priority + table.last.add(GD::Font::SmallFont, "#{pres.from.resource} (#{prio})") + table.add_line + table.last.add(GD::Font::TinyFont, pres.status.to_s.strip) + } +end + +# Convert the image to PNG and print it on standard output +print "Content-type: image/png\r\n\r\n" +table.paint.png STDOUT diff --git a/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/buggy/miniedgarr_watch.rb b/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/buggy/miniedgarr_watch.rb new file mode 100755 index 000000000..6e817cf2c --- /dev/null +++ b/vendor/xmpp4r-0.3.2/data/doc/xmpp4r/examples/buggy/miniedgarr_watch.rb @@ -0,0 +1,82 @@ +#!/usr/bin/ruby + +$:.unshift '../lib' + +require 'rexml/document' + +require 'xmpp4r' +require 'xmpp4r/iqqueryroster' + +# Command line argument checking + +if ARGV.size != 3 + puts("Usage: ./rosterwatch.rb ") + exit +end + + +def write_state(statefile, roster, presences) + doc = REXML::Document.new + state = doc.add(REXML::Element.new('state')) + state.add(roster) + presences.each { |jid,pres| state.add(pres) } + + file = File.new(statefile, "w") + doc.write(file, 0) + file.close +end + + +# Building up the connection + +Jabber::debug = true + +jid = Jabber::JID.new(ARGV[0]) + +cl = Jabber::Client.new(jid, false) +cl.connect +cl.auth(ARGV[1]) + + +roster = Jabber::IqQueryRoster.new +presences = {} + +cl.add_iq_callback { |iq| + if (iq.type == :result) && iq.query.kind_of?(Jabber::IqQueryRoster) + roster.import(iq.query) + write_state(ARGV[2], roster, presences) + end +} + +# TODO: ... +cl.add_presence_callback { |pres| + # Handle subscription request + if pres.type == :subscribe + # Accept subscription + cl.send(Jabber::Presence.new.set_to(pres.from).set_type(:subscribed)) + # Subscribe to sender + cl.send(Jabber::Presence.new.set_to(pres.from).set_type(:subscribe)) + # Add to roster + # TODO: Resolve Nickname from vCard + roster_set_iq = Jabber::Iq.new(:set) + roster_set_iq.add(Jabber::IqQueryRoster.new).add(Jabber::RosterItem.new(pres.from.strip)) + cl.send(roster_set_iq) + end + + presences[pres.from] = pres + write_state(ARGV[2], roster, presences) +} + +# Send request for roster +cl.send(Jabber::Iq.new_rosterget) +# Send initial presence +# This is important as it ensures reception of +# further stanzas +cl.send(Jabber::Presence.new.set_show(:dnd).set_status('Watching my roster change...')) + +loop do + cl.process + sleep(1) +end + +cl.close diff --git a/vendor/xmpp4r-0.3.2/lib/xmpp4r.rb b/vendor/xmpp4r-0.3.2/lib/xmpp4r.rb new file mode 100755 index 000000000..84a72751c --- /dev/null +++ b/vendor/xmpp4r-0.3.2/lib/xmpp4r.rb @@ -0,0 +1,116 @@ +# =XMPP4R - XMPP Library for Ruby +# License:: Ruby's license (see the LICENSE file) or GNU GPL, at your option. +# Website::http://home.gna.org/xmpp4r/ +# +# ==Introduction +# +# XMPP4R is a XMPP/Jabber library for Ruby. It can be used to build scripts +# using Jabber, full-featured Jabber clients, and components. It is written +# with extensibility in mind. +# +# ==XML management +# +# All the XML parsing is REXML's, and XML stanzas like (class +# Jabber::Message) or (class Jabber::Iq) are indirect +# derivatives from REXML's Element class. This provide a maximum flexibity: +# the user can access attributes and childs using either the XMPP4R's helpers +# or directly using REXML's methods. +# +# ===Automatic element casting +# +# Because there are special classes derived from REXML::Element to ease +# development on the protocol level, Elements must be cast to them. This is +# done via REXML::Element.import. This method is also used in import class +# methods of some Element classes. +# +# The first occurance of this feature is in Jabber::Stream::receive: +# * stanzas are cast to Jabber::Message class +# * stanzas are cast to Jabber::Presence class +# * stanzas are cast to Jabber::Iq class +# +# This is not only useful for stanzas but all other XML processing, too: +# * children elements of and are converted to Jabber::X +# * children elements of all three stanzas are converted to Jabber::Error +# * children elements of are converted to Jabber::IqQuery +# * children elements of are converted to Jabber::IqVcard +# +# The following conversion facilities are only executed if the respective +# library parts are loaded. See below for more details on Non-basic features. +# * Jabber::IqQuery elements are converted to Jabber::Roster::IqQueryRoster if their +# namespace is 'jabber:iq:roster' +# * Jabber::IqQuery elements are converted to Jabber::Version::IqQueryVersion if their +# namespace is 'jabber:iq:version' +# * Jabber::IqQuery elements are converted to Jabber::Discovery::IqQueryDiscoInfo if their +# namespace is 'http://jabber.org/protocol/disco#info' +# * Jabber::IqQuery elements are converted to Jabber::Discovery::IqQueryDiscoItems if their +# namespace is 'http://jabber.org/protocol/disco#items' +# * children elements of Jabber::Roster::IqQueryRoster are converted +# to Jabber::Roster::RosterItem +# * children elements of Jabber::IqQueryDiscoInfo are converted +# to Jabber::Discovery::DiscoIdentity +# * children elements of Jabber::IqQueryDiscoInfo are converted +# to Jabber::Discovery::DiscoFeature +# * children elements of Jabber::IqQueryDiscoItems are converted +# to Jabber::Discovery::DiscoItem +# +# To use this, don't check for: +# iq.queryns == 'http://jabber.org/protocol/disco#info' +# +# But instead check for the query's class: +# iq.query.kind_of?(Jabber::IqQueryDiscoInfo) +# +# ==Where to begin? +# +# Because it is built in an extensible way, it might be hard for newcomers to +# understand where to look at documentation for a specific method. For example, +# Client heritates from Connection, which itself heritates from Stream. +# +# A newcomer should have a look at the Jabber::Client and +# Jabber::Component classes, and their parent classes +# Jabber::Connection and Jabber::Stream. The best way to +# understand how to use them is probably to look at the examples in the +# examples/ dir. +# +# ==Non-basic features +# +# require 'xmpp4r' does only include basic functionality as +# Connections, Authentication, Stream processing, Callbacks, Stanza handling +# and Debugging to keep the library's footprint small. +# +# There is code for features that aren't required by a *basic* client. These +# must be additionally included to use them. +# +# ===Protocol-level features +# +# You're highly advised to read the according RFCs and JEPs if you intend to +# use them. The benefit will be that you'll understand the protocols and be +# going to be more efficient when programming with them. +# +# * Jabber::Bytestreams, Jabber::FileTransfer: require 'xmpp4r/bytestreams' +# * Jabber::Dataforms: require 'xmpp4r/dataforms' +# * Jabber::Delay: require 'xmpp4r/delay' +# * Jabber::Discovery: require 'xmpp4r/discovery' +# * Jabber::FeatureNegotiation: require 'xmpp4r/feature_negotiation' +# * Jabber::MUC: require 'xmpp4r/muc' +# * Jabber::Roster: require 'xmpp4r/roster' +# * Jabber::Vcard: require 'xmpp4r/vcard' +# * Jabber::Version: require 'xmpp4r/version' +# +# ===Helpers +# +# Helpers are intended to give more simplistic interfaces to various tasks +# of Jabber clients at the cost of flexibility. But you won't need that +# order of flexibility for the most things. +# +# * Jabber::Roster::Helper: require 'xmpp4r/roster' +# * Jabber::MUC::MUCBrowser, Jabber::MUC::MUCClient, Jabber::MUC::SimpleMUCClient: require 'xmpp4r/muc' +# * Jabber::Version::SimpleResponder, Jabber::Version::Responder: require 'xmpp4r/version' +# * Jabber::Vcard::Helper: require 'xmpp4r/vcard' +# * Jabber::FileTransfer::Helper, Jabber::Bytestreams::SOCKS5BytestreamsServer: require 'xmpp4r/bytestreams' +# +# ==Debugging +# +# Dumping your Jabber stream can be enabled this way: +# Jabber::debug = true + +require 'xmpp4r/xmpp4r' diff --git a/vendor/xmpp4r-0.3.2/lib/xmpp4r/authenticationfailure.rb b/vendor/xmpp4r-0.3.2/lib/xmpp4r/authenticationfailure.rb new file mode 100644 index 000000000..d92d0c7da --- /dev/null +++ b/vendor/xmpp4r-0.3.2/lib/xmpp4r/authenticationfailure.rb @@ -0,0 +1,13 @@ +# =XMPP4R - XMPP Library for Ruby +# License:: Ruby's license (see the LICENSE file) or GNU GPL, at your option. +# Website::http://home.gna.org/xmpp4r/ + +module Jabber + ## + # The AuthenticationFailure is an Exception to be raised + # when Client or Component authentication fails + # + # There are no special arguments + class AuthenticationFailure < RuntimeError + end +end diff --git a/vendor/xmpp4r-0.3.2/lib/xmpp4r/bytestreams.rb b/vendor/xmpp4r-0.3.2/lib/xmpp4r/bytestreams.rb new file mode 100644 index 000000000..0d6acc45e --- /dev/null +++ b/vendor/xmpp4r-0.3.2/lib/xmpp4r/bytestreams.rb @@ -0,0 +1,15 @@ +# =XMPP4R - XMPP Library for Ruby +# License:: Ruby's license (see the LICENSE file) or GNU GPL, at your option. +# Website::http://home.gna.org/xmpp4r/ + +require 'xmpp4r/bytestreams/iq/si.rb' +require 'xmpp4r/bytestreams/iq/bytestreams.rb' +require 'xmpp4r/bytestreams/helper/ibb/base.rb' +require 'xmpp4r/bytestreams/helper/ibb/initiator.rb' +require 'xmpp4r/bytestreams/helper/ibb/target.rb' +require 'xmpp4r/bytestreams/helper/filetransfer.rb' +require 'xmpp4r/bytestreams/helper/socks5bytestreams/base.rb' +require 'xmpp4r/bytestreams/helper/socks5bytestreams/initiator.rb' +require 'xmpp4r/bytestreams/helper/socks5bytestreams/server.rb' +require 'xmpp4r/bytestreams/helper/socks5bytestreams/target.rb' +require 'xmpp4r/bytestreams/helper/socks5bytestreams/socks5.rb' diff --git a/vendor/xmpp4r-0.3.2/lib/xmpp4r/bytestreams/helper/filetransfer.rb b/vendor/xmpp4r-0.3.2/lib/xmpp4r/bytestreams/helper/filetransfer.rb new file mode 100644 index 000000000..4ef6faf85 --- /dev/null +++ b/vendor/xmpp4r-0.3.2/lib/xmpp4r/bytestreams/helper/filetransfer.rb @@ -0,0 +1,320 @@ +# =XMPP4R - XMPP Library for Ruby +# License:: Ruby's license (see the LICENSE file) or GNU GPL, at your option. +# Website::http://home.gna.org/xmpp4r/ + +require 'xmpp4r/callbacks' +require 'xmpp4r/bytestreams/iq/si' +require 'xmpp4r/dataforms/x/data' +require 'xmpp4r/bytestreams/helper/ibb/base' +require 'xmpp4r/bytestreams/helper/socks5bytestreams/base' + +module Jabber + module FileTransfer + ## + # The TransferSource is an interface (Mix-in) + # which sources for FileTransfer#offer should include + module TransferSource + ## + # Filename of the offered file + def filename + end + ## + # Mime-type of the offered file, can be nil + def mime + end + ## + # Size of the offered file + def size + end + ## + # MD5-Sum of the offered file, can be nil + def md5 + end + ## + # Date of the offered file, can be nil + def date + end + ## + # Read a chunk from the source + # + # If this is a ranged transfer, it should + # implement length checking + # length:: [Fixnum] + def read(length=nil) + end + ## + # Seek in the source for ranged transfers + def seek(position) + end + ## + # Set the amount of data to send for ranged transfers + def length=(l) + end + ## + # Does implement the methods seek and length= ? + # + # FileTransfer will only then offer a ranged transfer. + # result:: [false] or [true] + def can_range? + false + end + end + + ## + # Simple implementation of TransferSource + # for sending simple files + # (supports ranged transfers) + class FileSource + include TransferSource + + def initialize(filename) + @file = File.new(filename) + @filename = filename + @bytes_read = 0 + @length = nil + end + + def filename + File::basename @filename + end + + ## + # Everything is 'application/octet-stream' + def mime + 'application/octet-stream' + end + + def size + File.size @filename + end + + def date + @file.mtime + end + + ## + # Because it can_range?, this method implements length checking + def read(length=512) + if @length + return nil if @bytes_read >= @length # Already read everything requested + if @bytes_read + length > @length # Will we read more than requested? + length = @length - @bytes_read # Truncate it! + end + end + + buf = @file.read(length) + @bytes_read += buf.size if buf + buf + end + + def seek(position) + @file.seek(position) + end + + def length=(l) + @length = l + end + + def can_range? + true + end + end + + ## + # The FileTransfer helper provides the ability to respond + # to incoming and to offer outgoing file-transfers. + class Helper + ## + # Set this if you want to use this helper in a Component + attr_accessor :my_jid + ## + # Set this to false if you don't want to use SOCKS5Bytestreams + attr_accessor :allow_bytestreams + ## + # Set this to false if you don't want to use IBB + attr_accessor :allow_ibb + + ## + # Create a new FileTransfer instance + def initialize(stream) + @stream = stream + @my_jid = nil + @allow_bytestreams = true + @allow_ibb = true + + @incoming_cbs = CallbackList.new + + @stream.add_iq_callback(150, self) { |iq| + if iq.type == :set + file = iq.first_element('si/file') + field = nil + iq.each_element('si/feature/x') { |e| field = e.field('stream-method') } + + if file and field + @incoming_cbs.process(iq, file) + true + else + false + end + else + false + end + } + end + + ## + # Add a callback which will be invoked upon an incoming file-transfer + # + # block takes two arguments: + # * Iq + # * Bytestreams::IqSiFile in the Iq + # You may then invoke accept or decline + def add_incoming_callback(priority = 0, ref = nil, &block) + @incoming_cbs.add(priority, ref, block) + end + + ## + # Accept an incoming file-transfer, + # to be used in a block given to add_incoming_callback + # + # offset and length will be ignored if there is no + # 'si/file/range' in iq. + # iq:: [Iq] of file-transfer we want to accept + # offset:: [Fixnum] or [nil] + # length:: [Fixnum] or [nil] + # result:: [Bytestreams::SOCKS5BytestreamsTarget] or [Bytestreams::IBBTarget] or [nil] if no valid stream-method + def accept(iq, offset=nil, length=nil) + oldsi = iq.first_element('si') + + answer = iq.answer(false) + answer.type = :result + + si = answer.add(Bytestreams::IqSi.new) + if (offset or length) and oldsi.file.range + si.add(Bytestreams::IqSiFile.new) + si.file.add(Bytestreams::IqSiFileRange.new(offset, length)) + end + si.add(FeatureNegotiation::IqFeature.new.import(oldsi.feature)) + si.feature.x.type = :submit + stream_method = si.feature.x.field('stream-method') + + if stream_method.options.keys.include?(Bytestreams::NS_BYTESTREAMS) and @allow_bytestreams + stream_method.values = [Bytestreams::NS_BYTESTREAMS] + stream_method.options = [] + @stream.send(answer) + + Bytestreams::SOCKS5BytestreamsTarget.new(@stream, oldsi.id, iq.from, iq.to) + elsif stream_method.options.keys.include?(Bytestreams::IBB::NS_IBB) and @allow_ibb + stream_method.values = [Bytestreams::IBB::NS_IBB] + stream_method.options = [] + @stream.send(answer) + + Bytestreams::IBBTarget.new(@stream, oldsi.id, iq.from, iq.to) + else + eanswer = iq.answer(false) + eanswer.type = :error + eanswer.add(Error.new('bad-request')).type = :cancel + eanswer.error.add(REXML::Element.new('no-valid-streams')).add_namespace('http://jabber.org/protocol/si') + @stream.send(eanswer) + + nil + end + end + + ## + # Decline an incoming file-transfer, + # to be used in a block given to add_incoming_callback + # iq:: [Iq] of file-transfer we want to decline + def decline(iq) + answer = iq.answer(false) + answer.type = :error + error = answer.add(Error.new('forbidden', 'Offer declined')) + error.type = :cancel + @stream.send(answer) + end + + ## + # Offer a file to somebody + # + # Will wait for a response from the peer + # + # The result is a stream which you can configure, or nil + # if the peer responded with an invalid stream-method. + # + # May raise an ErrorException + # jid:: [JID] to send the file to + # source:: File-transfer source, implementing the FileSource interface + # desc:: [String] or [nil] Optional file description + # from:: [String] or [nil] Optional jid for components + # result:: [Bytestreams::SOCKS5BytestreamsInitiator] or [Bytestreams::IBBInitiator] or [nil] + def offer(jid, source, desc=nil, from=nil) + from = from || @my_jid || @stream.jid + session_id = Jabber::IdGenerator.instance.generate_id + + offered_methods = {} + if @allow_bytestreams + offered_methods[Bytestreams::NS_BYTESTREAMS] = nil + end + if @allow_ibb + offered_methods[Bytestreams::IBB::NS_IBB] = nil + end + + iq = Iq::new(:set, jid) + iq.from = from + si = iq.add(Bytestreams::IqSi.new(session_id, Bytestreams::PROFILE_FILETRANSFER, source.mime)) + + file = si.add(Bytestreams::IqSiFile.new(source.filename, source.size)) + file.hash = source.md5 + file.date = source.date + file.description = desc if desc + file.add(Bytestreams::IqSiFileRange.new) if source.can_range? + + feature = si.add(REXML::Element.new('feature')) + feature.add_namespace 'http://jabber.org/protocol/feature-neg' + x = feature.add(Dataforms::XData.new(:form)) + stream_method_field = x.add(Dataforms::XDataField.new('stream-method', :list_single)) + stream_method_field.options = offered_methods + + begin + stream_method = nil + response = nil + @stream.send_with_id(iq) { |r| + response = r + si = response.first_element('si') + if response.type == :result and si and si.feature and si.feature.x + stream_method = si.feature.x.field('stream-method').values.first + + if si.file and si.file.range + if source.can_range? + source.seek(si.file.range.offset) if si.file.range.offset + source.length = si.file.range.length if si.file.range.length + else + source.read(si.file.range.offset) + end + end + end + true + } + rescue ErrorException => e + if e.error.code == 403 # Declined + return false + else + raise e + end + end + + if stream_method == Bytestreams::NS_BYTESTREAMS and @allow_bytestreams + Bytestreams::SOCKS5BytestreamsInitiator.new(@stream, session_id, from, jid) + elsif stream_method == Bytestreams::IBB::NS_IBB and @allow_ibb + Bytestreams::IBBInitiator.new(@stream, session_id, from, jid) + else # Target responded with a stream_method we didn't offer + eanswer = response.answer + eanswer.type = :error + eanswer.add Error::new('bad-request') + @stream.send(eanswer) + nil + end + end + end + end +end diff --git a/vendor/xmpp4r-0.3.2/lib/xmpp4r/bytestreams/helper/ibb/base.rb b/vendor/xmpp4r-0.3.2/lib/xmpp4r/bytestreams/helper/ibb/base.rb new file mode 100644 index 000000000..a4c2ab102 --- /dev/null +++ b/vendor/xmpp4r-0.3.2/lib/xmpp4r/bytestreams/helper/ibb/base.rb @@ -0,0 +1,259 @@ +# =XMPP4R - XMPP Library for Ruby +# License:: Ruby's license (see the LICENSE file) or GNU GPL, at your option. +# Website::http://home.gna.org/xmpp4r/ + +require 'base64' + +module Jabber + module Bytestreams + ## + # In-Band Bytestreams (JEP-0047) implementation + # + # Don't use directly, use IBBInitiator and IBBTarget + # + # In-Band Bytestreams should only be used when transferring + # very small amounts of binary data, because it is slow and + # increases server load drastically. + # + # Note that the constructor takes a lot of arguments. In-Band + # Bytestreams do not specify a way to initiate the stream, + # this should be done via Stream Initiation. + class IBB + NS_IBB = 'http://jabber.org/protocol/ibb' + + ## + # Create a new bytestream + # + # Will register a callback to intercept data + # of this stream. This data will be buffered, you can retrieve + # it with receive + def initialize(stream, session_id, my_jid, peer_jid) + @stream = stream + @session_id = session_id + @my_jid = (my_jid.kind_of?(String) ? JID.new(my_jid) : my_jid) + @peer_jid = (peer_jid.kind_of?(String) ? JID.new(peer_jid) : peer_jid) + + @active = false + @seq_send = 0 + @seq_recv = 0 + @queue = [] + @queue_lock = Mutex.new + @pending = Semaphore.new + @sendbuf = '' + @sendbuf_lock = Mutex.new + + @block_size = 4096 # Recommended by JEP0047 + end + + def active? + @active + end + + ## + # Send data + # + # Data is buffered to match block_size in each packet. + # If you need the data to be sent immediately, use + # flush afterwards. + # buf:: [String] + def write(buf) + @sendbuf_lock.synchronize { + @sendbuf += buf + + while @sendbuf.size >= @block_size + send_data(@sendbuf[0..@block_size-1]) + @sendbuf = @sendbuf[@block_size..-1].to_s + end + } + end + + ## + # Empty the send-buffer by sending remaining data + def flush + @sendbuf_lock.synchronize { + while @sendbuf.size > 0 + send_data(@sendbuf[0..@block_size-1]) + @sendbuf = @sendbuf[@block_size..-1].to_s + end + } + end + + ## + # Receive data + # + # Will wait until the Message with the next sequence number + # is in the stanza queue. + def read + if active? + res = nil + + while res.nil? + @queue_lock.synchronize { + @queue.each { |item| + # Find next data + if item.type == :data and item.seq == @seq_recv.to_s + res = item + break + # No data? Find close + elsif item.type == :close and res.nil? + res = item + end + } + + @queue.delete_if { |item| item == res } + } + + # No data? Wait for next to arrive... + @pending.wait unless res + end + + if res.type == :data + @seq_recv += 1 + @seq_recv = 0 if @seq_recv > 65535 + res.data + elsif res.type == :close + deactivate + nil # Closed + end + else + nil + end + end + + ## + # Close the stream + # + # Waits for acknowledge from peer, + # may throw ErrorException + def close + if active? + flush + deactivate + + iq = Iq.new(:set, @peer_jid) + close = iq.add REXML::Element.new('close') + close.add_namespace IBB::NS_IBB + close.attributes['sid'] = @session_id + + @stream.send_with_id(iq) { |answer| + answer.type == :result + } + end + end + + private + + ## + # Send data directly + # data:: [String] + def send_data(databuf) + if active? + msg = Message.new + msg.from = @my_jid + msg.to = @peer_jid + + data = msg.add REXML::Element.new('data') + data.add_namespace NS_IBB + data.attributes['sid'] = @session_id + data.attributes['seq'] = @seq_send.to_s + data.text = Base64::encode64 databuf + + # TODO: Implement AMP correctly + amp = msg.add REXML::Element.new('amp') + amp.add_namespace 'http://jabber.org/protocol/amp' + deliver_at = amp.add REXML::Element.new('rule') + deliver_at.attributes['condition'] = 'deliver-at' + deliver_at.attributes['value'] = 'stored' + deliver_at.attributes['action'] = 'error' + match_resource = amp.add REXML::Element.new('rule') + match_resource.attributes['condition'] = 'match-resource' + match_resource.attributes['value'] = 'exact' + match_resource.attributes['action'] = 'error' + + @stream.send(msg) + + @seq_send += 1 + @seq_send = 0 if @seq_send > 65535 + else + raise 'Attempt to send data when not activated' + end + end + + def activate + unless active? + @stream.add_message_callback(200, self) { |msg| + data = msg.first_element('data') + if msg.from == @peer_jid and msg.to == @my_jid and data and data.attributes['sid'] == @session_id + if msg.type == nil + @queue_lock.synchronize { + @queue.push IBBQueueItem.new(:data, data.attributes['seq'], data.text.to_s) + @pending.run + } + elsif msg.type == :error + @queue_lock.synchronize { + @queue << IBBQueueItem.new(:close) + @pending.run + } + end + true + else + false + end + } + + @stream.add_iq_callback(200, self) { |iq| + close = iq.first_element('close') + if iq.type == :set and close and close.attributes['sid'] == @session_id + answer = iq.answer(false) + answer.type = :result + @stream.send(answer) + + @queue_lock.synchronize { + @queue << IBBQueueItem.new(:close) + @pending.run + } + true + else + false + end + } + + @active = true + end + end + + def deactivate + if active? + @stream.delete_message_callback(self) + @stream.delete_iq_callback(self) + + @active = false + end + end + end + + ## + # Represents an item in the internal data queue + class IBBQueueItem + attr_reader :type, :seq + def initialize(type, seq=nil, data_text='') + unless [:data, :close].include? type + raise "Unknown IBBQueueItem type: #{type}" + end + + @type = type + @seq = seq + @data = data_text + end + + ## + # Return the Base64-*decoded* data + # + # There's no need to catch Exceptions here, + # as none are thrown. + def data + Base64::decode64(@data) + end + end + end +end diff --git a/vendor/xmpp4r-0.3.2/lib/xmpp4r/bytestreams/helper/ibb/initiator.rb b/vendor/xmpp4r-0.3.2/lib/xmpp4r/bytestreams/helper/ibb/initiator.rb new file mode 100644 index 000000000..462d121a6 --- /dev/null +++ b/vendor/xmpp4r-0.3.2/lib/xmpp4r/bytestreams/helper/ibb/initiator.rb @@ -0,0 +1,34 @@ +# =XMPP4R - XMPP Library for Ruby +# License:: Ruby's license (see the LICENSE file) or GNU GPL, at your option. +# Website::http://home.gna.org/xmpp4r/ + +module Jabber + module Bytestreams + ## + # Implementation of IBB at the initiator side + class IBBInitiator < IBB + # You may set the block-size before open + attr_accessor :block_size + + ## + # Open the stream to the peer, + # waits for successful result + # + # May throw ErrorException + def open + iq = Iq.new(:set, @peer_jid) + open = iq.add REXML::Element.new('open') + open.add_namespace IBB::NS_IBB + open.attributes['sid'] = @session_id + open.attributes['block-size'] = @block_size + + @stream.send_with_id(iq) { |answer| + answer.type == :result + } + + activate + end + end + end +end + diff --git a/vendor/xmpp4r-0.3.2/lib/xmpp4r/bytestreams/helper/ibb/target.rb b/vendor/xmpp4r-0.3.2/lib/xmpp4r/bytestreams/helper/ibb/target.rb new file mode 100644 index 000000000..1375898a0 --- /dev/null +++ b/vendor/xmpp4r-0.3.2/lib/xmpp4r/bytestreams/helper/ibb/target.rb @@ -0,0 +1,48 @@ +# =XMPP4R - XMPP Library for Ruby +# License:: Ruby's license (see the LICENSE file) or GNU GPL, at your option. +# Website::http://home.gna.org/xmpp4r/ + +module Jabber + module Bytestreams + ## + # Implementation of IBB at the target side + class IBBTarget < IBB + # You may read the block-size after accept + attr_reader :block_size + + def initialize(stream, session_id, initiator_jid, target_jid) + # Target and Initiator are swapped here, because we're the target + super(stream, session_id, target_jid, initiator_jid) + end + + ## + # Wait for the initiator side to start + # the stream. + def accept + connect_sem = Semaphore.new + + @stream.add_iq_callback(200, self) { |iq| + open = iq.first_element('open') + if iq.type == :set and iq.from == @peer_jid and iq.to == @my_jid and open and open.attributes['sid'] == @session_id + @stream.delete_iq_callback(self) + activate + @block_size = (open.attributes['block-size'] || 4096).to_i + + reply = iq.answer(false) + reply.type = :result + @stream.send(reply) + + connect_sem.run + true + else + false + end + } + + connect_sem.wait + true + end + end + end +end + diff --git a/vendor/xmpp4r-0.3.2/lib/xmpp4r/bytestreams/helper/socks5bytestreams/base.rb b/vendor/xmpp4r-0.3.2/lib/xmpp4r/bytestreams/helper/socks5bytestreams/base.rb new file mode 100644 index 000000000..085e75ad4 --- /dev/null +++ b/vendor/xmpp4r-0.3.2/lib/xmpp4r/bytestreams/helper/socks5bytestreams/base.rb @@ -0,0 +1,155 @@ +# =XMPP4R - XMPP Library for Ruby +# License:: Ruby's license (see the LICENSE file) or GNU GPL, at your option. +# Website::http://home.gna.org/xmpp4r/ + +require 'socket' +require 'thread' +require 'timeout' +require 'digest/sha1' + +require 'xmpp4r/callbacks' + +module Jabber + module Bytestreams + ## + # SOCKS5 Bytestreams (JEP-0065) implementation + # + # Don't use directly, use SOCKS5BytestreamsInitiator + # and SOCKS5BytestreamsTarget + class SOCKS5Bytestreams + ## + # [StreamHost] the SOCKS connection is using + attr_reader :streamhost_used + + ## + # SOCKS connection timeout (for trying multiple streamhosts) + # + # default: nil, use the OS' default timeout + attr_accessor :connect_timeout + + def initialize(stream, session_id, initiator_jid, target_jid) + @stream = stream + @session_id = session_id + @initiator_jid = (initiator_jid.kind_of?(String) ? JID.new(initiator_jid) : initiator_jid) + @target_jid = (target_jid.kind_of?(String) ? JID.new(target_jid) : target_jid) + @socks = nil + @connect_timeout = nil + @streamhost_used = nil + @streamhost_cbs = CallbackList.new + end + + ## + # Add a callback that will be called when there is action regarding + # SOCKS stream-hosts + # + # Usage of this callback is optional and serves informational purposes only. + # + # block takes three arguments: + # * The StreamHost instance that is currently being tried + # * State information (is either :connecting, :authenticating, :success or :failure) + # * The exception value for the state :failure, else nil + def add_streamhost_callback(priority = 0, ref = nil, &block) + @streamhost_cbs.add(priority, ref, block) + end + + ## + # Receive from the stream-host + # length:: [Fixnum] Amount of bytes (Will be passed to TCPSocket#read for the underlying SOCKS5 connection) + # result:: [String] (or [nil] if finished) + def read(length=nil) + @socks.read(length) + end + + ## + # Flush the SOCKS5 socket + def flush + @socks.flush + end + + ## + # Send to the stream-host + # buf:: [String] Data + # result:: [Fixnum] Amount of bytes sent + def write(buf) + @socks.write(buf) + # FIXME: On FreeBSD this throws Errno::EPERM after it has already written a few + # kilobytes, and when there are multiple sockets. ktrace told, that this originates + # from the syscall, not ruby. + end + + ## + # Close the stream-host connection + def close + @socks.close + end + + ## + # Query a JID for its stream-host information + # + # SOCKS5BytestreamsInitiator#add_streamhost can do this for you. + # Use this method if you plan to do multiple transfers, so + # you can cache the result. + # stream:: [Stream] to operate on + # streamhost:: [JID] of the proxy + # my_jid:: [JID] Optional sender JID for Component operation + def self.query_streamhost(stream, streamhost, my_jid=nil) + res = nil + + iq = Iq::new(:get, streamhost) + iq.from = my_jid + iq.add(IqQueryBytestreams.new) + stream.send_with_id(iq) { |reply| + if reply.type == :result + reply.query.each_element { |e| + if e.kind_of?(StreamHost) + e.jid = reply.from # Help misconfigured proxys + res = e + end + } + end + true + } + + if res and res.jid and res.host and res.port + res + else + nil + end + end + + private + + ## + # The address the stream-host expects from us. + # According to JEP-0096 this is the SHA1 hash + # of the concatenation of session_id, + # initiator_jid and target_jid. + # result:: [String] SHA1 hash + def stream_address + Digest::SHA1.hexdigest("#{@session_id}#{@initiator_jid}#{@target_jid}") + end + + ## + # Try a streamhost + # result:: [SOCKS5Socket] + def connect_socks(streamhost) + Timeout::timeout(@connect_timeout, Errno::ETIMEDOUT) { + Jabber::debuglog("SOCKS5 Bytestreams: connecting to proxy #{streamhost.jid} (#{streamhost.host}:#{streamhost.port})") + @streamhost_cbs.process(streamhost, :connecting, nil) + socks = SOCKS5Socket.new(streamhost.host, streamhost.port) + + Jabber::debuglog("SOCKS5 Bytestreams: connected, authenticating") + @streamhost_cbs.process(streamhost, :authenticating, nil) + socks.auth + + socks.connect_domain(stream_address, 0) + + Jabber::debuglog("SOCKS5 Bytestreams: connected") + @streamhost_cbs.process(streamhost, :success, nil) + + socks + } + end + end + end +end diff --git a/vendor/xmpp4r-0.3.2/lib/xmpp4r/bytestreams/helper/socks5bytestreams/initiator.rb b/vendor/xmpp4r-0.3.2/lib/xmpp4r/bytestreams/helper/socks5bytestreams/initiator.rb new file mode 100644 index 000000000..712ea5e58 --- /dev/null +++ b/vendor/xmpp4r-0.3.2/lib/xmpp4r/bytestreams/helper/socks5bytestreams/initiator.rb @@ -0,0 +1,89 @@ +# =XMPP4R - XMPP Library for Ruby +# License:: Ruby's license (see the LICENSE file) or GNU GPL, at your option. +# Website::http://home.gna.org/xmpp4r/ + +module Jabber + module Bytestreams + ## + # SOCKS5Bytestreams implementation for the initiator side + class SOCKS5BytestreamsInitiator < SOCKS5Bytestreams + attr_reader :streamhosts + + def initialize(stream, session_id, initiator_jid, target_jid) + super + @streamhosts = [] + end + + ## + # Add a streamhost which will be offered to the target + # + # streamhost:: can be: + # * [StreamHost] if already got all information (host/port) + # * [SOCKS5BytestreamsServer] if this is the local streamhost + # * [String] or [JID] if information should be automatically resolved by SOCKS5Bytestreams::query_streamhost + def add_streamhost(streamhost) + if streamhost.kind_of?(StreamHost) + @streamhosts << streamhost + elsif streamhost.kind_of?(SOCKS5BytestreamsServer) + streamhost.each_streamhost(@initiator_jid) { |sh| + @streamhosts << sh + } + elsif streamhost.kind_of?(String) or streamhost.kind_of?(JID) + @streamhosts << SOCKS5Bytestreams::query_streamhost(@stream, streamhost, @initiator_jid) + else + raise "Unknwon streamhost type: #{streamhost.class}" + end + end + + ## + # Send the configured streamhosts to the target, + # wait for an answer and + # connect to the host the target chose. + def open + iq1 = Iq::new(:set, @target_jid) + iq1.from = @initiator_jid + bs = iq1.add IqQueryBytestreams.new(@session_id) + @streamhosts.each { |se| + bs.add(se) + } + + peer_used = nil + @stream.send_with_id(iq1) { |response| + if response.type == :result and response.query.kind_of?(IqQueryBytestreams) + peer_used = response.query.streamhost_used + raise "No streamhost-used" unless peer_used + raise "Invalid streamhost-used" unless peer_used.jid + end + true + } + + @streamhost_used = nil + @streamhosts.each { |sh| + if peer_used.jid == sh.jid + @streamhost_used = sh + break + end + } + if @streamhost_used.jid == @initiator_jid + # This is our own JID, so the target chose SOCKS5BytestreamsServer + @socks = @streamhost_used.server.peer_sock(stream_address) + raise "Target didn't connect" unless @socks + @streamhost_cbs.process(@streamhost_used, :success, nil) + else + begin + @socks = connect_socks(@streamhost_used) + rescue Exception => e + Jabber::debuglog("SOCKS5 Bytestreams: #{e.class}: #{e}\n#{e.backtrace.join("\n")}") + @streamhost_cbs.process(@streamhost_used, :failure, e) + raise e + end + iq2 = Iq::new(:set, @streamhost_used.jid) + iq2.add(IqQueryBytestreams.new(@session_id)).activate = @target_jid.to_s + @stream.send_with_id(iq2) { |reply| + reply.type == :result + } + end + end + end + end +end diff --git a/vendor/xmpp4r-0.3.2/lib/xmpp4r/bytestreams/helper/socks5bytestreams/server.rb b/vendor/xmpp4r-0.3.2/lib/xmpp4r/bytestreams/helper/socks5bytestreams/server.rb new file mode 100644 index 000000000..4e6515d37 --- /dev/null +++ b/vendor/xmpp4r-0.3.2/lib/xmpp4r/bytestreams/helper/socks5bytestreams/server.rb @@ -0,0 +1,194 @@ +# =XMPP4R - XMPP Library for Ruby +# License:: Ruby's license (see the LICENSE file) or GNU GPL, at your option. +# Website::http://home.gna.org/xmpp4r/ + +module Jabber + module Bytestreams + ## + # The SOCKS5BytestreamsServer is an implementation of a SOCKS5 server. + # + # You can use it if you're reachable by your SOCKS5Bytestreams peers, + # thus avoiding use of an external proxy. + # + # ==Usage: + # * Instantiate with an unfirewalled port + # * Add your external IP addresses with SOCKS5BytestreamsServer#add_address + # * Once you've got an *outgoing* SOCKS5BytestreamsInitiator, do + # SOCKS5BytestreamsInitiator#add_streamhost(my_socks5bytestreamsserver) + # *before* you do SOCKS5BytestreamsInitiator#open + class SOCKS5BytestreamsServer + ## + # Start a local SOCKS5BytestreamsServer + # + # Will start to listen on the given TCP port and + # accept new peers + # port:: [Fixnum] TCP port to listen on + # listen_on:: [String] Optional address for the server socket to listen on (i.e. '0.0.0.0' or '::') + def initialize(port, listen_on=nil) + @port = port + @addresses = [] + @peers = [] + @peers_lock = Mutex.new + if listen_on + socket = TCPServer.new(listen_on, port) + else + socket = TCPServer.new(port) + end + + Thread.new do + Thread.current.abort_on_exception = true + loop do + peer = SOCKS5BytestreamsPeer.new(socket.accept) + Thread.new do + Thread.current.abort_on_exception = true + begin + peer.start + rescue + Jabber::debuglog("SOCKS5 BytestreamsServer: Error accepting peer: #{$!}") + end + end + @peers_lock.synchronize do + @peers << peer + end + end + end + end + + ## + # Find the socket a peer is associated to + # + # This method also performs some housekeeping, ie. removing + # peers with closed sockets. + # addr:: [String] Address like SOCKS5Bytestreams#stream_address + # result:: [TCPSocker] or [nil] + def peer_sock(addr) + res = nil + @peers_lock.synchronize { + removes = [] + + @peers.each { |peer| + if peer.socket and peer.socket.closed? + # Queue peers with closed socket for removal + removes << peer + elsif peer.address == addr and res.nil? + res = peer.socket + end + + # If we sent multiple addresses of our own, clients may + # connect multiple times. DO NOT close any other connections + # here. These may belong to other concurrent bytestreams, + # believe that the peer will close any unneeded sockets + # which will then be picked up by the next call to peer_sock. + } + + # If we sent multiple addresses of our own, clients may + # connect multiple times. Close these connections here. + @peers.delete_if { |peer| + if removes.include? peer + peer.socket.close rescue IOError + true + else + false + end + } + } + + res + end + + ## + # Add an external IP address + # + # This is a must-have, as SOCKS5BytestreamsInitiator must inform + # the target where to connect + def add_address(address) + @addresses << address + end + + ## + # Iterate through all configured addresses, + # yielding SOCKS5BytestreamsServerStreamHost + # instances, which should be passed to + # SOCKS5BytestreamsInitiator#add_streamhost + # + # This will be automatically invoked if you pass an instance + # of SOCKS5BytestreamsServer to + # SOCKS5BytestreamsInitiator#add_streamhost + # my_jid:: [JID] My Jabber-ID + def each_streamhost(my_jid, &block) + @addresses.each { |address| + yield SOCKS5BytestreamsServerStreamHost.new(self, my_jid, address, @port) + } + end + end + + ## + # A subclass of StreamHost which possesses a + # server attribute, to let SOCKS5BytestreamsInitiator + # know this is the local SOCKS5BytestreamsServer + class SOCKS5BytestreamsServerStreamHost < StreamHost + attr_reader :server + def initialize(server, jid=nil, host=nil, port=nil) + super(jid, host, port) + @server = server + end + end + + ## + # This class will be instantiated by SOCKS5BytestreamsServer + # upon accepting a new connection + class SOCKS5BytestreamsPeer + attr_reader :address, :socket + + ## + # Initialize a new peer + # socket:: [TCPSocket] + def initialize(socket) + @socket = socket + Jabber::debuglog("SOCKS5 BytestreamsServer: accepted peer #{@socket.peeraddr[2]}:#{@socket.peeraddr[1]}") + end + + ## + # Start handshake process + def start + auth_ver = @socket.getc + if auth_ver != 5 + # Unsupported version + @socket.close + return + end + + auth_nmethods = @socket.getc + auth_methods = @socket.read(auth_nmethods) + unless auth_methods.index("\x00") + # Client won't accept no authentication + @socket.write("\x05\xff") + @socket.close + return + end + @socket.write("\x05\x00") + Jabber::debuglog("SOCKS5 BytestreamsServer: peer #{@socket.peeraddr[2]}:#{@socket.peeraddr[1]} authenticated") + + req = @socket.read(4) + if req != "\x05\x01\x00\x03" + # Unknown version, command, reserved, address-type + @socket.close + return + end + req_addrlen = @socket.getc + req_addr = @socket.read(req_addrlen) + req_port = @socket.read(2) + if req_port != "\x00\x00" + # Port is not 0 + @socket.write("\x05\x01") + @socket.close + return + end + @socket.write("\x05\x00\x00\x03#{req_addrlen.chr}#{req_addr}\x00\x00") + Jabber::debuglog("SOCKS5 BytestreamsServer: peer #{@socket.peeraddr[2]}:#{@socket.peeraddr[1]} connected for #{req_addr}") + + @address = req_addr + end + end + end +end diff --git a/vendor/xmpp4r-0.3.2/lib/xmpp4r/bytestreams/helper/socks5bytestreams/socks5.rb b/vendor/xmpp4r-0.3.2/lib/xmpp4r/bytestreams/helper/socks5bytestreams/socks5.rb new file mode 100644 index 000000000..a201ac424 --- /dev/null +++ b/vendor/xmpp4r-0.3.2/lib/xmpp4r/bytestreams/helper/socks5bytestreams/socks5.rb @@ -0,0 +1,60 @@ +# =XMPP4R - XMPP Library for Ruby +# License:: Ruby's license (see the LICENSE file) or GNU GPL, at your option. +# Website::http://home.gna.org/xmpp4r/ + +require 'socket' + +module Jabber + module Bytestreams + ## + # Can be thrown upon communication error with + # a SOCKS5 proxy + class SOCKS5Error < RuntimeError; end + + ## + # A SOCKS5 client implementation + # + # ==Usage: + # * Initialize with proxy's address and port + # * Authenticate + # * Connect to target host + class SOCKS5Socket < TCPSocket + ## + # Connect to SOCKS5 proxy + def initialize(socks_host, socks_port) + super(socks_host, socks_port) + end + + ## + # Authenticate for SOCKS5 proxy + # + # Currently supports only 'no authentication required' + def auth + write("\x05\x01\x00") + buf = read(2) + if buf.nil? or buf != "\x05\x00" + close + raise SOCKS5Error.new("Invalid SOCKS5 authentication: #{buf.inspect}") + end + + self + end + + ## + # Issue a CONNECT request to a host name + # which is to be resolved by the proxy. + # domain:: [String] Host name + # port:: [Fixnum] Port number + def connect_domain(domain, port) + write("\x05\x01\x00\x03#{domain.size.chr}#{domain}#{[port].pack("n")}") + buf = read(7 + domain.size) + if buf.nil? or buf[0..1] != "\005\000" + close + raise SOCKS5Error.new("Invalid SOCKS5 connect: #{buf.inspect}") + end + + self + end + end + end +end diff --git a/vendor/xmpp4r-0.3.2/lib/xmpp4r/bytestreams/helper/socks5bytestreams/target.rb b/vendor/xmpp4r-0.3.2/lib/xmpp4r/bytestreams/helper/socks5bytestreams/target.rb new file mode 100644 index 000000000..ee57737e4 --- /dev/null +++ b/vendor/xmpp4r-0.3.2/lib/xmpp4r/bytestreams/helper/socks5bytestreams/target.rb @@ -0,0 +1,63 @@ +# =XMPP4R - XMPP Library for Ruby +# License:: Ruby's license (see the LICENSE file) or GNU GPL, at your option. +# Website::http://home.gna.org/xmpp4r/ + +module Jabber + module Bytestreams + ## + # SOCKS5 Bytestreams implementation of the target site + class SOCKS5BytestreamsTarget < SOCKS5Bytestreams + ## + # Wait until the stream has been established + # + # May raise various exceptions + def accept + error = nil + connect_sem = Semaphore.new + + @stream.add_iq_callback(200, self) { |iq| + if iq.type == :set and iq.from == @initiator_jid and iq.to == @target_jid and iq.query.kind_of?(IqQueryBytestreams) + begin + @stream.delete_iq_callback(self) + + iq.query.each_element('streamhost') { |streamhost| + if streamhost.host and streamhost.port and not @socks + begin + @socks = connect_socks(streamhost) + @streamhost_used = streamhost + rescue Exception => e + Jabber::debuglog("SOCKS5 Bytestreams: #{e.class}: #{e}\n#{e.backtrace.join("\n")}") + @streamhost_cbs.process(streamhost, :failure, e) + end + end + } + + reply = iq.answer(false) + if @streamhost_used + reply.type = :result + reply.add(IqQueryBytestreams.new) + reply.query.add(StreamHostUsed.new(@streamhost_used.jid)) + else + reply.type = :error + reply.add(Error.new('item-not-found')) + end + @stream.send(reply) + rescue Exception => e + error = e + end + + connect_sem.run + true + else + false + end + } + + connect_sem.wait + raise error if error + (@socks != nil) + end + end + end +end + diff --git a/vendor/xmpp4r-0.3.2/lib/xmpp4r/bytestreams/iq/bytestreams.rb b/vendor/xmpp4r-0.3.2/lib/xmpp4r/bytestreams/iq/bytestreams.rb new file mode 100755 index 000000000..3a0df8f93 --- /dev/null +++ b/vendor/xmpp4r-0.3.2/lib/xmpp4r/bytestreams/iq/bytestreams.rb @@ -0,0 +1,171 @@ +# =XMPP4R - XMPP Library for Ruby +# License:: Ruby's license (see the LICENSE file) or GNU GPL, at your option. +# Website::http://home.gna.org/xmpp4r/ + +module Jabber + module Bytestreams + NS_BYTESTREAMS = 'http://jabber.org/protocol/bytestreams' + + ## + # Class for accessing elements with + # xmlns='http://jabber.org/protocol/bytestreams' + # in stanzas. + class IqQueryBytestreams < IqQuery + name_xmlns 'query', NS_BYTESTREAMS + + ## + # Initialize such a + # sid:: [String] Session-ID + # mode:: [Symbol] :tcp or :udp + def initialize(sid=nil, mode=nil) + super() + self.sid = sid + self.mode = mode + end + + ## + # Session-ID + def sid + attributes['sid'] + end + + ## + # Set Session-ID + def sid=(s) + attributes['sid'] = s + end + + ## + # Transfer mode + # result:: :tcp or :udp + def mode + case attributes['mode'] + when 'udp' then :udp + else :tcp + end + end + + ## + # Set the transfer mode + # m:: :tcp or :udp + def mode=(m) + case m + when :udp then attributes['mode'] = 'udp' + else attributes['mode'] = 'tcp' + end + end + + ## + # Get the child + # result:: [StreamHostUsed] + def streamhost_used + first_element('streamhost-used') + end + + ## + # Get the text of the child + # result:: [JID] or [nil] + def activate + j = first_element_text('activate') + j ? JID.new(j) : nil + end + + ## + # Set the text of the child + # s:: [JID] + def activate=(s) + replace_element_text('activate', s ? s.to_s : nil) + end + end + + + ## + # element, normally appear + # as children of IqQueryBytestreams + class StreamHost < XMPPElement + name_xmlns 'streamhost', NS_BYTESTREAMS + + ## + # Initialize a element + # jid:: [JID] + # host:: [String] Hostname or IP address + # port:: [Fixnum] Port number + def initialize(jid=nil, host=nil, port=nil) + super() + self.jid = jid + self.host = host + self.port = port + end + + ## + # Get the JID of the streamhost + def jid + (a = attributes['jid']) ? JID.new(a) : nil + end + + ## + # Set the JID of the streamhost + def jid=(j) + attributes['jid'] = (j ? j.to_s : nil) + end + + ## + # Get the host address of the streamhost + def host + attributes['host'] + end + + ## + # Set the host address of the streamhost + def host=(h) + attributes['host'] = h + end + + ## + # Get the zeroconf attribute of the streamhost + def zeroconf + attributes['zeroconf'] + end + + ## + # Set the zeroconf attribute of the streamhost + def zeroconf=(s) + attributes['zeroconf'] = s + end + + ## + # Get the port number of the streamhost + def port + p = attributes['port'].to_i + (p == 0 ? nil : p) + end + + ## + # Set the port number of the streamhost + def port=(p) + attributes['port'] = p.to_s + end + end + + ## + # element, normally appears + # as child of IqQueryBytestreams + class StreamHostUsed < XMPPElement + name_xmlns 'streamhost-used', NS_BYTESTREAMS + + def initialize(jid=nil) + super() + self.jid = jid + end + + def jid + (a = attributes['jid']) ? JID.new(a) : nil + end + + def jid=(j) + attributes['jid'] = (j ? j.to_s : nil) + end + end + end +end + diff --git a/vendor/xmpp4r-0.3.2/lib/xmpp4r/bytestreams/iq/si.rb b/vendor/xmpp4r-0.3.2/lib/xmpp4r/bytestreams/iq/si.rb new file mode 100644 index 000000000..0732f7454 --- /dev/null +++ b/vendor/xmpp4r-0.3.2/lib/xmpp4r/bytestreams/iq/si.rb @@ -0,0 +1,206 @@ +# =XMPP4R - XMPP Library for Ruby +# License:: Ruby's license (see the LICENSE file) or GNU GPL, at your option. +# Website::http://home.gna.org/xmpp4r/ + +require 'time' # For Time#xmlschema + +require 'xmpp4r/feature_negotiation/iq/feature' + +module Jabber + module Bytestreams + PROFILE_FILETRANSFER = 'http://jabber.org/protocol/si/profile/file-transfer' + + ## + # Iq child 'si' for Stream-Initiation + class IqSi < XMPPElement + name_xmlns 'si', 'http://jabber.org/protocol/si' + force_xmlns true + + def initialize(id=nil, profile=nil, mime_type=nil) + super() + + self.id = id + self.profile = profile + self.mime_type = mime_type + end + + ## + # Session ID of this stream + def id + attributes['id'] + end + + ## + # Set Session ID of this stream + def id=(s) + attributes['id'] = s + end + + ## + # MIME type of this stream + def mime_type + attributes['mime-type'] + end + + ## + # Set MIME type of this stream + def mime_type=(s) + attributes['mime-type'] = s + end + + ## + # Stream profile, can indicate file-transfer + def profile + attributes['profile'] + end + + ## + # Set stream profile + def profile=(s) + attributes['profile'] = s + end + + ## + # child + # result:: [IqSiFile] + def file + first_element('file') + end + + ## + # child + # result:: [IqFeature] + def feature + first_element('feature') + end + end + + + ## + # File-transfer meta-information, + # may appear as in IqSi + class IqSiFile < XMPPElement + name_xmlns 'file', PROFILE_FILETRANSFER + + def initialize(fname=nil, size=nil) + super() + self.fname = fname + self.size = size + end + + ## + # Get filename (attribute 'name') + def fname + attributes['name'] + end + + ## + # Set filename (attribute 'name') + def fname=(s) + attributes['name'] = s + end + + ## + # Get MD5 hash + def hash + attributes['hash'] + end + + ## + # Set MD5 hash + def hash=(s) + attributes['hash'] = s + end + + ## + # Get file date + # result:: [Time] or nil + def date + begin + Time.xmlschema(attributes['date']) + rescue ArgumentError + nil + end + end + + ## + # Set file date + # d:: [Time] or nil + def date=(d) + attributes['date'] = (d ? d.xmlschema : nil) + end + + ## + # File size in bytes + # result:: [Fixnum] + def size + (attributes['size'] =~ /^\d+$/) ? attributes['size'].to_i : nil + end + + ## + # Set file size + def size=(s) + attributes['size'] = s ? s.to_s : nil + end + + ## + # File description + def description + first_element_text('desc') + end + + ## + # Set file description + def description=(s) + replace_element_text('desc', s) + end + + ## + # child + # + # A file-transfer offer may contain this with + # no attributes set, indicating the ability to + # do ranged transfers. + # result:: [IqSiFileRange] + def range + first_element('range') + end + end + + ## + # Information for ranged transfers + class IqSiFileRange < XMPPElement + name_xmlns 'range', PROFILE_FILETRANSFER + def initialize(offset=nil, length=nil) + super() + + self.offset = offset + self.length = length + end + + ## + # File offset (for continuing an interrupted transfer) + def offset + (attributes['offset'] =~ /^\d+$/) ? attributes['offset'].to_i : nil + end + + ## + # Set file offset + def offset=(o) + attributes['offset'] = (o ? o.to_s : nil) + end + + ## + # File length (if not to transfer whole file) + def length + (attributes['length'] =~ /^\d+$/) ? attributes['length'].to_i : nil + end + + ## + # Set file length + def length=(o) + attributes['length'] = (o ? o.to_s : nil) + end + end + end +end diff --git a/vendor/xmpp4r-0.3.2/lib/xmpp4r/callbacks.rb b/vendor/xmpp4r-0.3.2/lib/xmpp4r/callbacks.rb new file mode 100644 index 000000000..919e561a0 --- /dev/null +++ b/vendor/xmpp4r-0.3.2/lib/xmpp4r/callbacks.rb @@ -0,0 +1,124 @@ +# =XMPP4R - XMPP Library for Ruby +# License:: Ruby's license (see the LICENSE file) or GNU GPL, at your option. +# Website::http://home.gna.org/xmpp4r/ + +module Jabber + ## + # This class manages a list of callbacks. + # + # ==Callbacks management and priority + # + # Callbacks are managed by the class CallbackList. When they are added, a + # priority (just a number or anything Comparable with other priorities) is + # specified. The biggest the priority is, the earliest the callback will be + # considered. + # + # Callbacks are processed for a given set of objects as long as they return + # false. If you want to stop processing, you must return true. Example : + # cbl = CallbackList::new + # c1 = false + # c2 = false + # c3 = false + # cbl.add(10) { c1 = true; 1 } + # cbl.add(5) { c2 = true; true } + # cbl.add(0) { c3 = true } + # cbl.process('aa') + # puts "#{c1} #{c2} #{c3}" + # This example would display "true true false" as callbacks processing was + # stopped after the second callback returned true. + # + # In XMPP4R, callbacks' priorities are quite normalized since we want to be + # able to "cascade" callbacks in a clean way. Here are values your code should + # take into account : + # + # >= 200:: logging & debugging callbacks. Those callbacks should not consume + # elements. + # 100-199:: Where Helpers register their callbacks. The normal value is 100, + # and Helpers shouldn't register something else unless there's a + # very good reason to. + # < 100:: all those numbers are normally available for your application. + # That's enough, don't you think ? + class CallbackList + + # Create a new list of callbacks + def initialize + @list = [] + end + + ## + # Add a callback to the list + # + # List will be sorted afterwards + # + # prio:: [Integer] the callback's priority, the higher, the sooner. + # ref:: [String] the callback's reference + # block:: [Block] a block to execute + # return:: [Jabber::CallbackList] The list, for chaining + def add(prio = 0, ref = nil, proc = nil, &block) + block = proc if proc + @list.push(Callback::new(prio, ref, block)) + @list.sort! { |a, b| b.priority <=> a.priority } + self + end + + ## + # Delete a callback by reference + # ref:: [String] the reference of the callback to delete + # return:: [CallBackList] The list, for chaining + def delete(ref) + @list.delete_if { |item| item.ref == ref } + self + end + + ## + # Number of elements in the list + # return:: [Integer] The number of elements + def length + @list.length + end + + ## + # Process an element through all my callbacks. returns e.consumed? + # e:: [Object] The elements to pass to the callback. You can pass + # several, but of course, you block must know how to handle them. + # return:: [Boolean] true if the element has been consumed + def process(*e) + # If somebody adds a new callback the list will get modified + # and sorted(!) while still iterating through it. So we use a + # local copy of @list. Any freshly added callback will receive + # the next stanzas, not the current. + list = @list.dup + + # process through callbacks + list.each do |item| + return true if item.block.call(*e) == true + end + false + end + end + + # This class is used to store callbacks inside CallbackList. See the + # CallbackList class for more detailed explanations. + class Callback + + # The Callback's priority + attr_reader :priority + + # The Callback's reference, using for deleting purposes + attr_reader :ref + + # The Callback's block to execute + attr_reader :block + + ## + # Create a new callback + # priority:: [Integer] the callback's priority. The higher, the sooner it + # will be executed + # ref:: [String] The callback's reference + def initialize(priority = 0, ref = nil, block = Proc::new {}) + @priority = priority + @ref = ref + @block = block + end + end +end diff --git a/vendor/xmpp4r-0.3.2/lib/xmpp4r/client.rb b/vendor/xmpp4r-0.3.2/lib/xmpp4r/client.rb new file mode 100644 index 000000000..45a8c4220 --- /dev/null +++ b/vendor/xmpp4r-0.3.2/lib/xmpp4r/client.rb @@ -0,0 +1,246 @@ +# =XMPP4R - XMPP Library for Ruby +# License:: Ruby's license (see the LICENSE file) or GNU GPL, at your option. +# Website::http://home.gna.org/xmpp4r/ + +require 'resolv' + +require 'xmpp4r/connection' +require 'xmpp4r/authenticationfailure' +require 'xmpp4r/sasl' + +module Jabber + + # The client class provides everything needed to build a basic XMPP + # Client. + # + # If you want your connection to survive disconnects and timeouts, + # catch exception in Stream#on_exception and re-call Client#connect + # and Client#auth. Don't forget to re-send initial Presence and + # everything else you need to setup your session. + class Client < Connection + + # The client's JID + attr_reader :jid + + ## + # Create a new Client. + # + # Remember to *always* put a resource in your JID unless the server can do SASL. + def initialize(jid, threaded = true) + super(threaded) + @jid = (jid.kind_of?(JID) ? jid : JID.new(jid.to_s)) + end + + ## + # connect to the server + # (chaining-friendly) + # + # If you omit the optional host argument SRV records for your jid will + # be resolved. If none works, fallback is connecting to the domain part + # of the jid. + # host:: [String] Optional c2s host, will be extracted from jid if nil + # return:: self + def connect(host = nil, port = 5222) + if host.nil? + begin + srv = [] + Resolv::DNS.open { |dns| + # If ruby version is too old and SRV is unknown, this will raise a NameError + # which is catched below + Jabber::debuglog("RESOLVING:\n_xmpp-client._tcp.#{@jid.domain} (SRV)") + srv = dns.getresources("_xmpp-client._tcp.#{@jid.domain}", Resolv::DNS::Resource::IN::SRV) + } + # Sort SRV records: lowest priority first, highest weight first + srv.sort! { |a,b| (a.priority != b.priority) ? (a.priority <=> b.priority) : (b.weight <=> a.weight) } + + srv.each { |record| + begin + connect(record.target.to_s, record.port) + # Success + return self + rescue SocketError + # Try next SRV record + end + } + rescue NameError + $stderr.puts "Resolv::DNS does not support SRV records. Please upgrade to ruby-1.8.3 or later!" + end + # Fallback to normal connect method + end + + super(host.nil? ? jid.domain : host, port) + self + end + + ## + # Close the connection, + # sends tag first + def close + send("") + super + end + + ## + # Start the stream-parser and send the client-specific stream opening element + def start + super + send(generate_stream_start(@jid.domain)) { |e| + if e.name == 'stream' + true + else + false + end + } + end + + ## + # Authenticate with the server + # + # Throws AuthenticationFailure + # + # Authentication mechanisms are used in the following preference: + # * SASL DIGEST-MD5 + # * SASL PLAIN + # * Non-SASL digest + # password:: [String] + def auth(password) + begin + if @stream_mechanisms.include? 'DIGEST-MD5' + auth_sasl SASL.new(self, 'DIGEST-MD5'), password + elsif @stream_mechanisms.include? 'PLAIN' + auth_sasl SASL.new(self, 'PLAIN'), password + else + auth_nonsasl(password) + end + rescue + Jabber::debuglog("#{$!.class}: #{$!}\n#{$!.backtrace.join("\n")}") + raise AuthenticationFailure.new, $!.to_s + end + end + + ## + # Use a SASL authentication mechanism and bind to a resource + # + # If there was no resource given in the jid, the jid/resource + # generated by the server will be accepted. + # + # This method should not be used directly. Instead, Client#auth + # may look for the best mechanism suitable. + # sasl:: Descendant of [Jabber::SASL::Base] + # password:: [String] + def auth_sasl(sasl, password) + sasl.auth(password) + + # Restart stream after SASL auth + stop + start + # And wait for features - again + @features_sem.wait + + # Resource binding (RFC3920 - 7) + if @stream_features.has_key? 'bind' + iq = Iq.new(:set) + bind = iq.add REXML::Element.new('bind') + bind.add_namespace @stream_features['bind'] + if jid.resource + resource = bind.add REXML::Element.new('resource') + resource.text = jid.resource + end + + send_with_id(iq) { |reply| + reported_jid = reply.first_element('jid') + if reply.type == :result and reported_jid and reported_jid.text + @jid = JID.new(reported_jid.text) + end + + true + } + end + + # Session starting + if @stream_features.has_key? 'session' + iq = Iq.new(:set) + session = iq.add REXML::Element.new('session') + session.add_namespace @stream_features['session'] + + send_with_id(iq) { true } + end + end + + ## + # Send auth with given password and wait for result + # (non-SASL) + # + # Throws ErrorException + # password:: [String] the password + # digest:: [Boolean] use Digest authentication + def auth_nonsasl(password, digest=true) + authset = nil + if digest + authset = Iq::new_authset_digest(@jid, @streamid.to_s, password) + else + authset = Iq::new_authset(@jid, password) + end + send_with_id(authset) do |r| + true + end + $defout.flush + + true + end + + ## + # Register a new user account + # (may be used instead of Client#auth) + # + # This method may raise ErrorException if the registration was + # not successful. + def register(password) + reg = Iq.new_register(jid.node, password) + reg.to = jid.domain + send_with_id(reg) { |answer| + true + } + end + + ## + # Remove the registration of a user account + # + # *WARNING:* this deletes your roster and everything else + # stored on the server! + def remove_registration + reg = Iq.new_register + reg.to = jid.domain + reg.query.add(REXML::Element.new('remove')) + send_with_id(reg) { |answer| + p answer.to_s + true + } + end + + ## + # Change the client's password + # + # Threading is suggested, as this code waits + # for an answer. + # + # Raises an exception upon error response (ErrorException from + # Stream#send_with_id). + # new_password:: [String] New password + def password=(new_password) + iq = Iq::new_query(:set, @jid.domain) + iq.query.add_namespace('jabber:iq:register') + iq.query.add(REXML::Element.new('username')).text = @jid.node + iq.query.add(REXML::Element.new('password')).text = new_password + + err = nil + send_with_id(iq) { |answer| + if answer.type == :result + true + else + false + end + } + end + end +end diff --git a/vendor/xmpp4r-0.3.2/lib/xmpp4r/command/helper/responder.rb b/vendor/xmpp4r-0.3.2/lib/xmpp4r/command/helper/responder.rb new file mode 100644 index 000000000..a8da46628 --- /dev/null +++ b/vendor/xmpp4r-0.3.2/lib/xmpp4r/command/helper/responder.rb @@ -0,0 +1,53 @@ +module Jabber + + module Command + + ## + # The Responder Helper handles the low-level stuff of the + # Ad-hoc commands (JEP 0050). + class Responder + + ## + # Initialize a Responder + def initialize(stream) + @stream = stream + @commandsdiscocbs = CallbackList.new + @commandexeccbs = CallbackList.new + + stream.add_iq_callback(180, self) { |iq| + iq_callback(iq) + } + end + + ## + # Add a callback for stanzas asking for the list + # of ad-hoc commands + def add_commands_disco_callback(priority = 0, ref = nil, &block) + @commandsdiscocbs.add(priority, ref, block) + end + + ## + # Add a callback for stanzas asking for the execution + # of an ad-hoc command + def add_commands_exec_callback(priority = 0, ref = nil, &block) + @commandexeccbs.add(priority, ref, block) + end + + ## + # Handles stanzas and execute callbacks + def iq_callback(iq) + if iq.type == :get + if iq.query.kind_of?(Jabber::Discovery::IqQueryDiscoItems) && + iq.query.node == "http://jabber.org/protocol/commands" + @commandsdiscocbs.process(iq) + end + elsif iq.type == :set && iq.command + @commandexeccbs.process(iq) + end + end + + end + + end + +end diff --git a/vendor/xmpp4r-0.3.2/lib/xmpp4r/command/iq/command.rb b/vendor/xmpp4r-0.3.2/lib/xmpp4r/command/iq/command.rb new file mode 100644 index 000000000..363a82be4 --- /dev/null +++ b/vendor/xmpp4r-0.3.2/lib/xmpp4r/command/iq/command.rb @@ -0,0 +1,154 @@ +require 'xmpp4r/iq' + +module Jabber + + module Command + + ## + # Class for handling ad-hoc commands + # (JEP 0050) + # + # A command is uniquely identified by its node attribute. + class IqCommand < Iq + name_xmlns 'command', 'http://jabber.org/protocol/commands' + + def initialize(node=nil, action=nil) + super() + set_node(node) + set_action(action) + end + + ## + # Get the node of the Command stanza + # result:: [String] or nil + def node + attributes['node'] + end + + ## + # Set the node of the Command stanza + # v:: [String] or nil + def node=(v) + attributes['node'] = v + end + + ## + # Set the node of the Command stanza (chaining-friendly) + # v:: [String] or nil + def set_node(v) + self.node = v + self + end + + ## + # Get the sessionid of the Command stanza + # result:: [String] or nil + def sessionid + attributes['sessionid'] + end + + ## + # Set the sessionid of the Command stanza + # v:: [String] or nil + def sessionid=(v) + attributes['sessionid'] = v + end + + ## + # Set the sessionid of the Command stanza (chaining-friendly) + # v:: [String] or nil + def set_sessionid(v) + self.sessionid = v + self + end + + ## + # Get the action of the Command stanza + # + # The following Symbols are allowed: + # * :execute + # * :cancel + # * :prev + # * :next + # * :complete + # return:: [Symbol] or nil + def action + case attributes['action'] + when 'execute' then :execute + when 'cancel' then :cancel + when 'prev' then :prev + when 'next' then :next + when 'complete' then :complete + else nil + end + end + + ## + # Set the action of the Command stanza (see IqCommand#action for details) + # v:: [Symbol] or nil + def action=(v) + attributes['action'] = case v + when :execute then 'execute' + when :cancel then 'cancel' + when :prev then 'prev' + when :next then 'next' + when :complete then 'complete' + else nil + end + end + + ## + # Set the action of the Command stanza (chaining-friendly) + # v:: [Symbol] or nil + def set_action(v) + self.action = v + self + end + + ## + # Get the status of the Command stanza + # + # The following Symbols are allowed: + # * :executing + # * :completed + # * :canceled + # return:: [Symbol] or nil + def status + case attributes['status'] + when 'executing' then :executing + when 'completed' then :completed + when 'canceled' then :canceled + else nil + end + end + + ## + # Set the status of the Command stanza (see IqCommand#status for details) + # v:: [Symbol] or nil + def status=(v) + attributes['status'] = case v + when :executing then 'executing' + when :completed then 'completed' + when :canceled then 'canceled' + else nil + end + end + + ## + # Set the status of the Command stanza (chaining-friendly) + # v:: [Symbol] or nil + def set_status(v) + self.status = v + self + end + + ## + # Get the actions allowed + # return:: [REXML::Element] or nil + def actions + first_element('actions') + end + + end + end +end diff --git a/vendor/xmpp4r-0.3.2/lib/xmpp4r/component.rb b/vendor/xmpp4r-0.3.2/lib/xmpp4r/component.rb new file mode 100644 index 000000000..88e6e7c90 --- /dev/null +++ b/vendor/xmpp4r-0.3.2/lib/xmpp4r/component.rb @@ -0,0 +1,103 @@ +# =XMPP4R - XMPP Library for Ruby +# License:: Ruby's license (see the LICENSE file) or GNU GPL, at your option. +# Website::http://home.gna.org/xmpp4r/ + +require 'xmpp4r/connection' + +module Jabber + ## + # The component class provides everything needed to build a XMPP Component. + # + # Components are more flexible as they are only restricted in the use of a + # fixed domain. node and resource of JIDs are freely choosable for all stanzas. + class Component < Connection + + # The component's JID + attr_reader :jid + + # The server's address + attr_reader :server_address + + # The server's port + attr_reader :server_port + + # Create a new Component + # jid:: [JID] + def initialize(jid, server_address=nil, server_port=5347, threaded = true) + super(threaded) + @jid = (jid.kind_of?(JID) ? jid : JID.new(jid.to_s)) + + if server_address + $stderr.puts "Passing server and port to Jabber::Component::new is " + + "obsolete and will vanish in some later XMPP4R release. " + + "Please use these arguments when calling " + + "Jabber::Client#connect" + @server_address = server_address + @server_port = server_port + end + end + + # Connect to the server + # (chaining-friendly) + # server:: [String] Hostname + # port:: [Integer] TCP port (5347) + # return:: self + def connect(server=nil, port=5347) + if server + super(server, port) + else + super(@server_address, @server_port) + end + self + end + + ## + # Close the connection, + # sends tag first + def close + send("") + super + end + + def generate_stream_start(to=nil, from=nil, id=nil, xml_lang="en", xmlns="jabber:component:accept", version="1.0") + super + end + private :generate_stream_start + + ## + # Start the stream-parser and send the component-specific stream opening element + def start + super + send(generate_stream_start(@jid)) { |e| + if e.name == 'stream' + true + else + false + end + } + end + + ## + # Send auth with given secret and wait for result + # + # Throws AuthenticationFailure + # secret:: [String] the shared secret + def auth(secret) + hash = Digest::SHA1::hexdigest(@streamid.to_s + secret) + authenticated = false + send("#{hash}") { |r| + if r.prefix == 'stream' and r.name == 'error' + true + elsif r.name == 'handshake' + authenticated = true + true + else + false + end + } + unless authenticated + raise AuthenticationFailure.new, "Component authentication failed" + end + end + end +end diff --git a/vendor/xmpp4r-0.3.2/lib/xmpp4r/connection.rb b/vendor/xmpp4r-0.3.2/lib/xmpp4r/connection.rb new file mode 100644 index 000000000..406b80d8b --- /dev/null +++ b/vendor/xmpp4r-0.3.2/lib/xmpp4r/connection.rb @@ -0,0 +1,203 @@ +# =XMPP4R - XMPP Library for Ruby +# License:: Ruby's license (see the LICENSE file) or GNU GPL, at your option. +# Website::http://home.gna.org/xmpp4r/ + +require 'openssl' +require 'xmpp4r/stream' +require 'xmpp4r/errorexception' + +module Jabber + ## + # The connection class manages the TCP connection to the Jabber server + # + class Connection < Stream + attr_reader :host, :port + + # How many seconds to wait for + # before proceeding + attr_accessor :features_timeout + + # Keep-alive interval in seconds, defaults to 60 + # (see private method keepalive_loop for implementation details) + attr_accessor :keepalive_interval + + # Allow TLS negotiation? Defaults to true + attr_accessor :allow_tls + + # Optional CA-Path for TLS-handshake + attr_accessor :ssl_capath + + # Optional callback for verification of SSL peer + attr_accessor :ssl_verifycb + + ## + # Create a new connection to the given host and port, using threaded mode + # or not. + def initialize(threaded = true) + super(threaded) + @host = nil + @port = nil + @allow_tls = true + @tls = false + @ssl_capath = nil + @ssl_verifycb = nil + @features_timeout = 10 + @keepalive_interval = 60 + end + + ## + # Connect to the Jabber server through a TCP Socket, + # start the Jabber parser, + # invoke to accept_features to wait for TLS, + # start the keep-alive thread + def connect(host, port) + @host = host + @port = port + # Reset is_tls?, so that it works when reconnecting + @tls = false + + Jabber::debuglog("CONNECTING:\n#{@host}:#{@port}") + @socket = TCPSocket.new(@host, @port) + start + + accept_features + + @keepaliveThread = Thread.new do + Thread.current.abort_on_exception = true + keepalive_loop + end + end + + ## + # Closing connection: + # first kill keepaliveThread, then call Stream#close! + def close! + @keepaliveThread.kill if @keepaliveThread and @keepaliveThread.alive? + super + end + + def accept_features + begin + Timeout::timeout(@features_timeout) { + Jabber::debuglog("FEATURES: waiting...") + @features_sem.wait + Jabber::debuglog("FEATURES: waiting finished") + } + rescue Timeout::Error + Jabber::debuglog("FEATURES: timed out when waiting, stream peer seems not XMPP compliant") + end + + if @allow_tls and not is_tls? and @stream_features['starttls'] == 'urn:ietf:params:xml:ns:xmpp-tls' + begin + starttls + rescue + Jabber::debuglog("STARTTLS:\nFailure: #{$!}") + end + end + end + + ## + # Start the parser on the previously connected socket + def start + super(@socket) + end + + ## + # Do a + # (will be automatically done by connect if stream peer supports this) + def starttls + stls = REXML::Element.new('starttls') + stls.add_namespace('urn:ietf:params:xml:ns:xmpp-tls') + + reply = nil + send(stls) { |r| + reply = r + true + } + if reply.name != 'proceed' + raise ErrorException(reply.first_element('error')) + end + # Don't be interrupted + stop + + begin + error = nil + + # Context/user set-able stuff + ctx = OpenSSL::SSL::SSLContext.new('TLSv1') + if @ssl_capath + ctx.verify_mode = OpenSSL::SSL::VERIFY_PEER + ctx.ca_path = @ssl_capath + else + ctx.verify_mode = OpenSSL::SSL::VERIFY_NONE + end + ctx.verify_callback = @ssl_verifycb + + # SSL connection establishing + sslsocket = OpenSSL::SSL::SSLSocket.new(@socket, ctx) + sslsocket.sync_close = true + Jabber::debuglog("TLSv1: OpenSSL handshake in progress") + sslsocket.connect + + # Make REXML believe it's a real socket + class << sslsocket + def kind_of?(o) + o == IO ? true : super + end + end + + # We're done and will use it + @tls = true + @socket = sslsocket + rescue + error = $! + ensure + Jabber::debuglog("TLSv1: restarting parser") + start + accept_features + raise error if error + end + end + + ## + # Have we gone to TLS mode? + # result:: [true] or [false] + def is_tls? + @tls + end + + def generate_stream_start(to=nil, from=nil, id=nil, xml_lang="en", xmlns="jabber:client", version="1.0") + stream_start_string = " child)? + def required? + res = false + each_element('required') { res = true } + res + end + + ## + # Set if this field is required + # r:: [true] or [false] + def required=(r) + delete_elements('required') + if r + add REXML::Element.new('required') + end + end + + ## + # Get the values (in a Data Form with type='submit') + def values + res = [] + each_element('value') { |e| + res << e.text + } + res + end + + ## + # Set the values + def values=(ary) + delete_elements('value') + ary.each { |v| + add(REXML::Element.new('value')).text = v + } + end + + ## + # Get the options (in a Data Form with type='form') + def options + res = {} + each_element('option') { |e| + value = nil + e.each_element('value') { |ve| value = ve.text } + res[value] = e.attributes['label'] + } + res + end + + ## + # Set the options + def options=(hsh) + delete_elements('option') + hsh.each { |value,label| + o = add(REXML::Element.new('option')) + o.attributes['label'] = label + o.add(REXML::Element.new('value')).text = value + } + end + end + + ## + # The element, can contain XDataField elements + class XDataReported < XMPPElement + name_xmlns 'reported', 'jabber:x:data' + end + end +end + diff --git a/vendor/xmpp4r-0.3.2/lib/xmpp4r/debuglog.rb b/vendor/xmpp4r-0.3.2/lib/xmpp4r/debuglog.rb new file mode 100644 index 000000000..8f79ae3af --- /dev/null +++ b/vendor/xmpp4r-0.3.2/lib/xmpp4r/debuglog.rb @@ -0,0 +1,34 @@ +# =XMPP4R - XMPP Library for Ruby +# License:: Ruby's license (see the LICENSE file) or GNU GPL, at your option. +# Website::http://home.gna.org/xmpp4r/ + +module Jabber + # Is debugging mode enabled ? + @@debug = false + + # Enable/disable debugging mode. When debug mode is enabled, information + # can be logged using Jabber::debuglog. When debug mode is disabled, calls + # to Jabber::debuglog are just ignored. + def Jabber::debug=(debug) + @@debug = debug + if @@debug + debuglog('Debugging mode enabled.') + end + end + + # returns true if debugging mode is enabled. If you just want to log + # something if debugging is enabled, use Jabber::debuglog instead. + def Jabber::debug + @@debug + end + + # Outputs a string only if debugging mode is enabled. If the string includes + # several lines, 4 spaces are added at the begginning of each line but the + # first one. Time is prepended to the string. + def Jabber::debuglog(string) + return if not @@debug + s = string.chomp.gsub("\n", "\n ") + t = Time::new.strftime('%H:%M:%S') + puts "#{t} #{s}" + end +end diff --git a/vendor/xmpp4r-0.3.2/lib/xmpp4r/delay.rb b/vendor/xmpp4r-0.3.2/lib/xmpp4r/delay.rb new file mode 100644 index 000000000..66d2850b5 --- /dev/null +++ b/vendor/xmpp4r-0.3.2/lib/xmpp4r/delay.rb @@ -0,0 +1,5 @@ +# =XMPP4R - XMPP Library for Ruby +# License:: Ruby's license (see the LICENSE file) or GNU GPL, at your option. +# Website::http://home.gna.org/xmpp4r/ + +require 'xmpp4r/delay/x/delay.rb' diff --git a/vendor/xmpp4r-0.3.2/lib/xmpp4r/delay/x/delay.rb b/vendor/xmpp4r-0.3.2/lib/xmpp4r/delay/x/delay.rb new file mode 100644 index 000000000..2e6b8ce99 --- /dev/null +++ b/vendor/xmpp4r-0.3.2/lib/xmpp4r/delay/x/delay.rb @@ -0,0 +1,99 @@ +# =XMPP4R - XMPP Library for Ruby +# License:: Ruby's license (see the LICENSE file) or GNU GPL, at your option. +# Website::http://home.gna.org/xmpp4r/ + +require 'xmpp4r/x' +require 'xmpp4r/jid' +require 'time' + +module Jabber + module Delay + ## + # Implementation of JEP 0091 + # for + # applied on and stanzas + # + # One may also use XDelay#text for a descriptive reason + # for the delay. + # + # Please note that you must require 'xmpp4r/xdelay' to use + # this class as it's not required by a basic XMPP implementation. + # elements with the specific namespace will then be + # converted to XDelay automatically. + class XDelay < X + name_xmlns 'x', 'jabber:x:delay' + + ## + # Initialize a new XDelay element + # + # insertnow:: [Boolean] Set the stamp to [Time::now] + def initialize(insertnow=true) + super() + + if insertnow + set_stamp(Time.now) + end + end + + ## + # Get the timestamp + # result:: [Time] or nil + def stamp + if attributes['stamp'] + begin + # Actually this should be Time.xmlschema, + # but "unfortunately, the 'jabber:x:delay' namespace predates" JEP 0082 + Time.parse(attributes['stamp']) + rescue ArgumentError + nil + end + else + nil + end + end + + ## + # Set the timestamp + # t:: [Time] or nil + def stamp=(t) + if t.nil? + attributes['stamp'] = nil + else + attributes['stamp'] = t.strftime("%Y%m%dT%H:%M:%S") + end + end + + ## + # Set the timestamp (chaining-friendly) + def set_stamp(t) + self.stamp = t + self + end + + ## + # Get the timestamp's origin + # result:: [JID] + def from + if attributes['from'] + JID::new(attributes['from']) + else + nil + end + end + + ## + # Set the timestamp's origin + # jid:: [JID] + def from=(jid) + attributes['from'] = jid.nil? ? nil : jid.to_s + end + + ## + # Set the timestamp's origin (chaining-friendly) + def set_from(jid) + self.from = jid + self + end + end + end +end diff --git a/vendor/xmpp4r-0.3.2/lib/xmpp4r/discovery.rb b/vendor/xmpp4r-0.3.2/lib/xmpp4r/discovery.rb new file mode 100644 index 000000000..2fee10a9c --- /dev/null +++ b/vendor/xmpp4r-0.3.2/lib/xmpp4r/discovery.rb @@ -0,0 +1,6 @@ +# =XMPP4R - XMPP Library for Ruby +# License:: Ruby's license (see the LICENSE file) or GNU GPL, at your option. +# Website::http://home.gna.org/xmpp4r/ + +require 'xmpp4r/discovery/iq/discoinfo.rb' +require 'xmpp4r/discovery/iq/discoitems.rb' diff --git a/vendor/xmpp4r-0.3.2/lib/xmpp4r/discovery/iq/discoinfo.rb b/vendor/xmpp4r-0.3.2/lib/xmpp4r/discovery/iq/discoinfo.rb new file mode 100755 index 000000000..421f944c0 --- /dev/null +++ b/vendor/xmpp4r-0.3.2/lib/xmpp4r/discovery/iq/discoinfo.rb @@ -0,0 +1,212 @@ +# =XMPP4R - XMPP Library for Ruby +# License:: Ruby's license (see the LICENSE file) or GNU GPL, at your option. +# Website::http://home.gna.org/xmpp4r/ + +require 'xmpp4r/query' + +module Jabber + module Discovery + ## + # Class for handling Service Discovery queries, + # info + # (JEP 0030) + # + # This may contain multiple Identity and Feature + # elements, describing the type and the supported namespaces of + # the service. + class IqQueryDiscoInfo < IqQuery + name_xmlns 'query', 'http://jabber.org/protocol/disco#info' + + ## + # Get the queried Service Discovery node or nil + # + # See IqQueryDiscoItems#node for a + # small explanation of this. + def node + attributes['node'] + end + + ## + # Get the queried Service Discovery node or nil + # val:: [String] + def node=(val) + attributes['node'] = val + end + + ## + # Get the queried Service Discovery node or nil + # (chaining-friendly) + # val:: [String] + def set_node(val) + self.node = val + self + end + + ## + # Get the first identity child + # result:: [Identity] + def identity + first_element('identity') + end + + ## + # Get list of identities + # result:: [Array] of [Identity] + def identities + res = [] + each_element('identity') { |id| + res.push(id) + } + res + end + + ## + # Get list of features + # result:: [Array] of [String] + def features + res = [] + each_element('feature') { |feature| + res.push(feature.var) + } + res + end + end + + + ## + # Service Discovery identity to add() to IqQueryDiscoInfo + # + # Please note that JEP 0030 requires both category and type to occur + class Identity < XMPPElement + name_xmlns 'identity', 'http://jabber.org/protocol/disco#info' + + ## + # Initialize a new Identity + # category:: [String] Initial category or nil + # iname:: [String] Initial identity name or nil + # type:: [String] Initial type or nil + def initialize(category=nil, iname=nil, type=nil) + super() + set_category(category) + set_iname(iname) + set_type(type) + end + + ## + # Get the identity's category or nil + # result:: [String] + def category + attributes['category'] + end + + ## + # Set the identity's category + # + # Service Discovery categories should be somewhat + # standardized by some registry, so clients may + # represent specific categories by specific icons... + # (see http://www.jabber.org/registrar/disco-categories.html) + # val:: [String] + def category=(val) + attributes['category'] = val + end + + ## + # Set the identity's category (chaining-friendly) + # val:: [String] + def set_category(val) + self.category = val + self + end + + ## + # Get the identity's name or nil + # + # This has been renamed from to "iname" here + # to keep REXML::Element#name accessible + # result:: [String] + def iname + attributes['name'] + end + + ## + # Set the identity's name + # val:: [String] + def iname=(val) + attributes['name'] = val + end + + ## + # Set the identity's name (chaining-friendly) + # val:: [String] + def set_iname(val) + self.iname = val + self + end + + ## + # Get the identity's type or nil + # result:: [String] + def type + attributes['type'] + end + + ## + # Set the identity's type + # (see http://www.jabber.org/registrar/disco-categories.html) + # val:: [String] + def type=(val) + attributes['type'] = val + end + + ## + # Set the identity's type (chaining-friendly) + # val:: [String] + def set_type(val) + self.type = val + self + end + end + + ## + # Service Discovery feature to add() to IqQueryDiscoInfo + # + # Please note that JEP 0030 requires var to be set + class Feature < XMPPElement + name_xmlns 'feature', 'http://jabber.org/protocol/disco#info' + + ## + # Create a new element + # var:: [String] New var + def initialize(var=nil) + super() + set_var(var) + end + + ## + # Get the feature's var or nil + # result:: [String] + def var + attributes['var'] + end + + ## + # Set the feature's var + # + # This is a namespace the identity supports. + # val:: [String] + def var=(val) + attributes['var'] = val + end + + ## + # Set the feature's var (chaining-friendly) + # val:: [String] + def set_var(val) + self.var = val + self + end + end + end +end + diff --git a/vendor/xmpp4r-0.3.2/lib/xmpp4r/discovery/iq/discoitems.rb b/vendor/xmpp4r-0.3.2/lib/xmpp4r/discovery/iq/discoitems.rb new file mode 100755 index 000000000..1299a9e8c --- /dev/null +++ b/vendor/xmpp4r-0.3.2/lib/xmpp4r/discovery/iq/discoitems.rb @@ -0,0 +1,139 @@ +# =XMPP4R - XMPP Library for Ruby +# License:: Ruby's license (see the LICENSE file) or GNU GPL, at your option. +# Website::http://home.gna.org/xmpp4r/ + +require 'xmpp4r/query' + +module Jabber + module Discovery + ## + # Class for handling Service Discovery queries, + # items + # (JEP 0030) + # + # This may contain multiple Item elements, + # describing multiple services to be browsed by Jabber clients. + # These may then get further information about these items by + # querying IqQueryDiscoInfo and further sub-items by querying + # IqQueryDiscoItems. + class IqQueryDiscoItems < IqQuery + name_xmlns 'query', 'http://jabber.org/protocol/disco#items' + + ## + # Get the queried Service Discovery node or nil + # + # A Service Discovery node is _not_ a JID node, + # this may be a bit confusing. It's just to make + # Service Discovery browsing a bit more structured. + def node + attributes['node'] + end + + ## + # Get the queried Service Discovery node or nil + def node=(val) + attributes['node'] = val + end + + ## + # Get the queried Service Discovery node or nil + # (chaining-friendly) + def set_node(val) + self.node = val + self + end + end + + + ## + # Service Discovery item to add() to IqQueryDiscoItems + # + # Please note that JEP 0030 requires the jid to occur + class Item < XMPPElement + name_xmlns 'item', 'http://jabber.org/protocol/disco#items' + + ## + # Initialize a new Service Discovery + # to be added to IqQueryDiscoItems + # jid:: [JID] + # iname:: [String] Item name + # node:: [String] Service Discovery node (_not_ JID#node) + def initialize(jid=nil, iname=nil, node=nil) + super() + set_jid(jid) + set_iname(iname) + set_node(node) + end + + ## + # Get the item's jid or nil + # result:: [String] + def jid + JID::new(attributes['jid']) + end + + ## + # Set the item's jid + # val:: [JID] + def jid=(val) + attributes['jid'] = val.to_s + end + + ## + # Set the item's jid (chaining-friendly) + # val:: [JID] + def set_jid(val) + self.jid = val + self + end + + ## + # Get the item's name or nil + # + # This has been renamed from to "iname" here + # to keep REXML::Element#name accessible + # result:: [String] + def iname + attributes['name'] + end + + ## + # Set the item's name + # val:: [String] + def iname=(val) + attributes['name'] = val + end + + ## + # Set the item's name (chaining-friendly) + # val:: [String] + def set_iname(val) + self.iname = val + self + end + + ## + # Get the item's Service Discovery node or nil + # result:: [String] + def node + attributes['node'] + end + + ## + # Set the item's Service Discovery node + # val:: [String] + def node=(val) + attributes['node'] = val + end + + ## + # Set the item's Service Discovery node (chaining-friendly) + # val:: [String] + def set_node(val) + self.node = val + self + end + end + end +end + diff --git a/vendor/xmpp4r-0.3.2/lib/xmpp4r/error.rb b/vendor/xmpp4r-0.3.2/lib/xmpp4r/error.rb new file mode 100644 index 000000000..888a0a406 --- /dev/null +++ b/vendor/xmpp4r-0.3.2/lib/xmpp4r/error.rb @@ -0,0 +1,225 @@ +# =XMPP4R - XMPP Library for Ruby +# License:: Ruby's license (see the LICENSE file) or GNU GPL, at your option. +# Website::http://home.gna.org/xmpp4r/ + +module Jabber + ## + # A class used to build/parse elements. + # Look at JEP 0086 for explanation. + class Error < XMPPElement + name_xmlns 'error' + + ## + # errorcondition:: [nil] or [String] of the following: + # * "bad-request" + # * "conflict" + # * "feature-not-implemented" + # * "forbidden" + # * "gone" + # * "internal-server-error" + # * "item-not-found" + # * "jid-malformed" + # * "not-acceptable" + # * "not-allowed" + # * "not-authorized" + # * "payment-required" + # * "recipient-unavailable" + # * "redirect" + # * "registration-required" + # * "remote-server-not-found" + # * "remote-server-timeout" + # * "resource-constraint" + # * "service-unavailable" + # * "subscription-required" + # * "undefined-condition" + # * "unexpected-request" + # Will raise an [Exception] if not [nil] and none of the above + # + # Does also set type and code to appropriate values according to errorcondition + # + # text: [nil] or [String] Error text + def initialize(errorcondition=nil, text=nil) + if errorcondition.nil? + super() + set_text(text) unless text.nil? + else + errortype = nil + errorcode = nil + @@Errors.each { |cond,type,code| + if errorcondition == cond + errortype = type + errorcode = code + end + } + + if errortype.nil? || errorcode.nil? + raise("Unknown error condition when initializing Error") + end + + super() + set_error(errorcondition) + set_type(errortype) + set_code(errorcode) + set_text(text) unless text.nil? + end + end + + ## + # Get the 'Legacy error code' or nil + # result:: [Integer] Error code + def code + if attributes['code'] + attributes['code'].to_i + else + nil + end + end + + ## + # Set the 'Legacy error code' or nil + # i:: [Integer] Error code + def code=(i) + if i.nil? + attributes['code'] = nil + else + attributes['code'] = i.to_s + end + end + + ## + # Set the 'Legacy error code' (chaining-friendly) + def set_code(i) + self.code = i + self + end + + ## + # Get the 'XMPP error condition' + # + # This can be anything that possess the specific namespace, + # checks don't apply here + def error + name = nil + each_element { |e| name = e.name if (e.namespace == 'urn:ietf:params:xml:ns:xmpp-stanzas') && (e.name != 'text') } + name + end + + ## + # Set the 'XMPP error condition' + # + # One previous element with that namespace will be deleted before + # + # s:: [String] Name of the element to be added, + # namespace will be added automatically, checks don't apply here + def error=(s) + xe = nil + each_element { |e| xe = e if (e.namespace == 'urn:ietf:params:xml:ns:xmpp-stanzas') && (e.name != 'text') } + unless xe.nil? + delete_element(xe) + end + + add_element(s).add_namespace('urn:ietf:params:xml:ns:xmpp-stanzas') + end + + ## + # Set the 'XMPP error condition' (chaining-friendly) + def set_error(s) + self.error = s + self + end + + ## + # Get the errors element text + # result:: [String] or nil + def text + first_element_text('text') || super + end + + ## + # Set the errors element text + # (Previous elements will be deleted first) + # s:: [String] content or [nil] if no element + def text=(s) + delete_elements('text') + + unless s.nil? + e = add_element('text') + e.add_namespace('urn:ietf:params:xml:ns:xmpp-stanzas') + e.text = s + end + end + + ## + # Set the errors element text (chaining-friendly) + def set_text(s) + self.text = s + self + end + + ## + # Get the type of error + # (meaning how to proceed) + # result:: [Symbol] or [nil] as following: + # * :auth + # * :cancel + # * :continue + # * :modify + # * :wait + def type + case attributes['type'] + when 'auth' then :auth + when 'cancel' then :cancel + when 'continue' then :continue + when 'modify' then :modify + when 'wait' then :wait + else nil + end + end + + ## + # Set the type of error (see Error#type) + def type=(t) + case t + when :auth then attributes['type'] = 'auth' + when :cancel then attributes['type'] = 'cancel' + when :continue then attributes['type'] = 'continue' + when :modify then attributes['type'] = 'modify' + when :wait then attributes['type'] = 'wait' + else attributes['type'] = nil + end + end + + ## + # Set the type of error (chaining-friendly) + def set_type(t) + self.type = t + self + end + + ## + # Possible XMPP error conditions, types and codes + # (JEP 0086) + @@Errors = [['bad-request', :modify, 400], + ['conflict', :cancel, 409], + ['feature-not-implemented', :cancel, 501], + ['forbidden', :auth, 403], + ['gone', :modify, 302], + ['internal-server-error', :wait, 500], + ['item-not-found', :cancel, 404], + ['jid-malformed', :modify, 400], + ['not-acceptable', :modify, 406], + ['not-allowed', :cancel, 405], + ['not-authorized', :auth, 401], + ['payment-required', :auth, 402], + ['recipient-unavailable', :wait, 404], + ['redirect', :modify, 302], + ['registration-required', :auth, 407], + ['remote-server-not-found', :cancel, 404], + ['remote-server-timeout', :wait, 504], + ['resource-constraint', :wait, 500], + ['service-unavailable', :cancel, 503], + ['subscription-required', :auth, 407], + ['undefined-condition', nil, 500], + ['unexpected-request', :wait, 400]] + end +end diff --git a/vendor/xmpp4r-0.3.2/lib/xmpp4r/errorexception.rb b/vendor/xmpp4r-0.3.2/lib/xmpp4r/errorexception.rb new file mode 100644 index 000000000..34b9b1c56 --- /dev/null +++ b/vendor/xmpp4r-0.3.2/lib/xmpp4r/errorexception.rb @@ -0,0 +1,32 @@ +# =XMPP4R - XMPP Library for Ruby +# License:: Ruby's license (see the LICENSE file) or GNU GPL, at your option. +# Website::http://home.gna.org/xmpp4r/ + +module Jabber + ## + # This exception can be raised by Helpers when they + # receive answers with type='error' + # + # The ErrorException carries a Jabber::Error element + class ErrorException < RuntimeError + ## + # The error element which caused this exception + attr_reader :error + + ## + # Initialize an ErrorException + # error:: [Error] + def initialize(error) + @error = error + end + + ## + # Textual output + # + # Sample: + # subscription-required: Please subscribe first + def to_s + "#{@error.error}: #{@error.text}" + end + end +end diff --git a/vendor/xmpp4r-0.3.2/lib/xmpp4r/feature_negotiation.rb b/vendor/xmpp4r-0.3.2/lib/xmpp4r/feature_negotiation.rb new file mode 100644 index 000000000..9a6841197 --- /dev/null +++ b/vendor/xmpp4r-0.3.2/lib/xmpp4r/feature_negotiation.rb @@ -0,0 +1,5 @@ +# =XMPP4R - XMPP Library for Ruby +# License:: Ruby's license (see the LICENSE file) or GNU GPL, at your option. +# Website::http://home.gna.org/xmpp4r/ + +require 'xmpp4r/feature_negotiation/iq/feature.rb' diff --git a/vendor/xmpp4r-0.3.2/lib/xmpp4r/feature_negotiation/iq/feature.rb b/vendor/xmpp4r-0.3.2/lib/xmpp4r/feature_negotiation/iq/feature.rb new file mode 100644 index 000000000..5a20b4e0f --- /dev/null +++ b/vendor/xmpp4r-0.3.2/lib/xmpp4r/feature_negotiation/iq/feature.rb @@ -0,0 +1,28 @@ +# =XMPP4R - XMPP Library for Ruby +# License:: Ruby's license (see the LICENSE file) or GNU GPL, at your option. +# Website::http://home.gna.org/xmpp4r/ + +require 'xmpp4r/iq' +require 'xmpp4r/dataforms/x/data' + +module Jabber + module FeatureNegotiation + ## + # Feature negotiation, + # can appear as direct child to Iq + # or as child of IqSi + class IqFeature < XMPPElement + name_xmlns 'feature', 'http://jabber.org/protocol/feature-neg' + + ## + # First child with xmlns='jabber:x:data' + def x + res = nil + each_element('x') { |e| + res = e if e.namespace == 'jabber:x:data' + } + res + end + end + end +end diff --git a/vendor/xmpp4r-0.3.2/lib/xmpp4r/httpbinding.rb b/vendor/xmpp4r-0.3.2/lib/xmpp4r/httpbinding.rb new file mode 100644 index 000000000..a15e7a33a --- /dev/null +++ b/vendor/xmpp4r-0.3.2/lib/xmpp4r/httpbinding.rb @@ -0,0 +1,5 @@ +# =XMPP4R - XMPP Library for Ruby +# License:: Ruby's license (see the LICENSE file) or GNU GPL, at your option. +# Website::http://home.gna.org/xmpp4r/ + +require 'xmpp4r/httpbinding/client' diff --git a/vendor/xmpp4r-0.3.2/lib/xmpp4r/httpbinding/client.rb b/vendor/xmpp4r-0.3.2/lib/xmpp4r/httpbinding/client.rb new file mode 100644 index 000000000..a4d6a7056 --- /dev/null +++ b/vendor/xmpp4r-0.3.2/lib/xmpp4r/httpbinding/client.rb @@ -0,0 +1,285 @@ +# =XMPP4R - XMPP Library for Ruby +# License:: Ruby's license (see the LICENSE file) or GNU GPL, at your option. +# Website::http://home.gna.org/xmpp4r/ + + +require 'xmpp4r/client' +require 'net/http' + +module Jabber + module HTTPBinding + ## + # This class implements an alternative Client + # using HTTP Binding (JEP0124). + # + # This class is designed to be a drop-in replacement + # for Jabber::Client, except for the + # Jabber::HTTP::Client#connect method which takes an URI + # as argument. + # + # HTTP requests are buffered to not exceed the negotiated + # 'polling' and 'requests' parameters. + # + # Stanzas in HTTP resonses may be delayed to arrive in the + # order defined by 'rid' parameters. + # + # =Debugging + # Turning Jabber::debug to true will make debug output + # not only spit out stanzas but HTTP request/response + # bodies, too. + class Client < Jabber::Client + + # Content-Type to be used for communication + # (you can set this to "text/html") + attr_accessor :http_content_type + # The server should wait this value seconds if + # there is no stanza to be received + attr_accessor :http_wait + # The server may hold this amount of stanzas + # to reduce number of HTTP requests + attr_accessor :http_hold + + ## + # Initialize + # jid:: [JID or String] + def initialize(jid) + super + + @lock = Mutex.new + @pending_requests = 0 + @last_send = Time.at(0) + @send_buffer = '' + + @http_wait = 20 + @http_hold = 1 + @http_content_type = 'text/xml; charset=utf-8' + end + + ## + # Set up the stream using uri as the HTTP Binding URI + # + # You may optionally pass host and port parameters + # to make use of the JEP0124 'route' feature. + # + # uri:: [URI::Generic or String] + # host:: [String] Optional host to route to + # port:: [Fixnum] Port for route feature + def connect(uri, host=nil, port=5222) + uri = URI::parse(uri) unless uri.kind_of? URI::Generic + @uri = uri + + @allow_tls = false # Shall be done at HTTP level + @stream_mechanisms = [] + @stream_features = {} + @http_rid = IdGenerator.generate_id.to_i + @pending_rid = @http_rid + @pending_rid_lock = Mutex.new + + req_body = REXML::Element.new('body') + req_body.attributes['rid'] = @http_rid + req_body.attributes['content'] = @http_content_type + req_body.attributes['hold'] = @http_hold.to_s + req_body.attributes['wait'] = @http_wait.to_s + req_body.attributes['to'] = @jid.domain + if host + req_body.attributes['route'] = 'xmpp:#{host}:#{port}' + end + req_body.attributes['secure'] = 'true' + req_body.attributes['xmlns'] = 'http://jabber.org/protocol/httpbind' + res_body = post(req_body) + unless res_body.name == 'body' + raise 'Response body is no element' + end + + @streamid = res_body.attributes['authid'] + @status = CONNECTED + @http_sid = res_body.attributes['sid'] + @http_wait = res_body.attributes['wait'].to_i if res_body.attributes['wait'] + @http_hold = res_body.attributes['hold'].to_i if res_body.attributes['hold'] + @http_inactivity = res_body.attributes['inactivity'].to_i + @http_polling = res_body.attributes['polling'].to_i + @http_polling = 5 if @http_polling == 0 + @http_requests = res_body.attributes['requests'].to_i + @http_requests = 1 if @http_requests == 0 + + receive_elements_with_rid(@http_rid, res_body.children) + + @features_sem.run + end + + ## + # Send a stanza, additionally with block + # + # This method ensures a 'jabber:client' namespace for + # the stanza + def send(xml, &block) + if xml.kind_of? REXML::Element + xml.add_namespace('jabber:client') + end + + super + end + + ## + # Ensure that there is one pending request + # + # Will be automatically called if you've sent + # a stanza. + def ensure_one_pending_request + return if is_disconnected? + + if @lock.synchronize { @pending_requests } < 1 + send_data('') + end + end + + ## + # Close the session by sending + # + def close + @status = DISCONNECTED + send(Jabber::Presence.new.set_type(:unavailable)) + end + + private + + ## + # Receive stanzas ensuring that the 'rid' order is kept + # result:: [REXML::Element] + def receive_elements_with_rid(rid, elements) + while rid > @pending_rid + @pending_rid_lock.lock + end + @pending_rid = rid + 1 + + elements.each { |e| + receive(e) + } + + @pending_rid_lock.unlock + end + + ## + # Do a POST request + def post(body) + body = body.to_s + request = Net::HTTP::Post.new(@uri.path) + request.content_length = body.size + request.body = body + request['Content-Type'] = @http_content_type + Jabber::debuglog("HTTP REQUEST (#{@pending_requests}/#{@http_requests}):\n#{request.body}") + response = Net::HTTP.start(@uri.host, @uri.port) { |http| + http.use_ssl = true if @uri.kind_of? URI::HTTPS + http.request(request) + } + Jabber::debuglog("HTTP RESPONSE (#{@pending_requests}/#{@http_requests}):\n#{response.body}") + + unless response.kind_of? Net::HTTPSuccess + # Unfortunately, HTTPResponses aren't exceptions + # TODO: rescue'ing code should be able to distinguish + raise Net::HTTPBadResponse, "#{response.class}" + end + + body = REXML::Document.new(response.body).root + if body.name != 'body' and body.namespace != 'http://jabber.org/protocol/httpbind' + raise REXML::ParseException.new('Malformed body') + end + body + end + + ## + # Prepare data to POST and + # handle the result + def post_data(data) + req_body = nil + current_rid = nil + + begin + begin + @lock.synchronize { + # Do not send unneeded requests + if data.size < 1 and @pending_requests > 0 + return + end + + req_body = " e + Jabber::debuglog("POST error (will retry): #{e.class}: #{e}") + receive_elements_with_rid(current_rid, []) + # It's not good to resend on *any* exception, + # but there are too many cases (Timeout, 404, 502) + # where resending is appropriate + # TODO: recognize these conditions and act appropriate + send_data(data) + end + end + + ## + # Send data, + # buffered and obeying 'polling' and 'requests' limits + def send_data(data) + @lock.synchronize do + + @send_buffer += data + limited_by_polling = (@last_send + @http_polling >= Time.now) + limited_by_requests = (@pending_requests + 1 > @http_requests) + + # Can we send? + if !limited_by_polling and !limited_by_requests + data = @send_buffer + @send_buffer = '' + + Thread.new do + Thread.current.abort_on_exception = true + post_data(data) + end + + elsif !limited_by_requests + Thread.new do + Thread.current.abort_on_exception = true + # Defer until @http_polling has expired + wait = @last_send + @http_polling - Time.now + sleep(wait) if wait > 0 + # Ignore locking, it's already threaded ;-) + send_data('') + end + end + + end + end + end + end +end diff --git a/vendor/xmpp4r-0.3.2/lib/xmpp4r/idgenerator.rb b/vendor/xmpp4r-0.3.2/lib/xmpp4r/idgenerator.rb new file mode 100644 index 000000000..71b964d3f --- /dev/null +++ b/vendor/xmpp4r-0.3.2/lib/xmpp4r/idgenerator.rb @@ -0,0 +1,37 @@ +# =XMPP4R - XMPP Library for Ruby +# License:: Ruby's license (see the LICENSE file) or GNU GPL, at your option. +# Website::http://home.gna.org/xmpp4r/ + +require 'singleton' + +module Jabber + ## + # The Jabber::IdGenerator class generates unique IDs for use + # in XMMP stanzas. Jabber::IdGenerator includes the Singleton + # Mixin, usage as following: + # Jabber::IdGenerator.generate_id + # => "23" + class IdGenerator + include Singleton + + def initialize + @last_id = 0 + end + + ## + # Generate an unique ID. + # + # This is kind of boring this way, as it just counts up + # a number. Maybe something more random somewhen... + def IdGenerator.generate_id + IdGenerator.instance.generate_id + end + + def generate_id + @last_id += 1 + timefrac = Time.new.to_f.to_s.split(/\./, 2).last[-3..-1] + + "#{@last_id}#{timefrac}" + end + end +end diff --git a/vendor/xmpp4r-0.3.2/lib/xmpp4r/iq.rb b/vendor/xmpp4r-0.3.2/lib/xmpp4r/iq.rb new file mode 100644 index 000000000..d330752ee --- /dev/null +++ b/vendor/xmpp4r-0.3.2/lib/xmpp4r/iq.rb @@ -0,0 +1,210 @@ +# =XMPP4R - XMPP Library for Ruby +# License:: Ruby's license (see the LICENSE file) or GNU GPL, at your option. +# Website::http://home.gna.org/xmpp4r/ + +require 'xmpp4r/xmppstanza' +require 'xmpp4r/jid' +require 'digest/sha1' + +require 'xmpp4r/query' +require 'xmpp4r/vcard/iq/vcard' + +module Jabber + ## + # IQ: Information/Query + # (see RFC3920 - 9.2.3 + # + # A class used to build/parse IQ requests/responses + class Iq < XMPPStanza + name_xmlns 'iq', 'jabber:client' + force_xmlns true + + @@element_classes = {} + + ## + # Build a new stanza + # type:: [Symbol] or nil, see Iq#type + # to:: [JID] Recipient + def initialize(type = nil, to = nil) + super() + + if not to.nil? + set_to(to) + end + if not type.nil? + set_type(type) + end + end + + ## + # Get the type of the Iq stanza + # + # The following values are allowed: + # * :get + # * :set + # * :result + # * :error + # result:: [Symbol] or nil + def type + case super + when 'get' then :get + when 'set' then :set + when 'result' then :result + when 'error' then :error + else nil + end + end + + ## + # Set the type of the Iq stanza (see Iq#type) + # v:: [Symbol] or nil + def type=(v) + case v + when :get then super('get') + when :set then super('set') + when :result then super('result') + when :error then super('error') + else super(nil) + end + end + + ## + # Set the type of the Iq stanza (chaining-friendly) + # v:: [Symbol] or nil + def set_type(v) + self.type = v + self + end + + ## + # Returns the iq's query child, or nil + # result:: [IqQuery] + def query + first_element('query') + end + + ## + # Delete old elements named newquery.name + # + # newquery:: [REXML::Element] will be added + def query=(newquery) + delete_elements(newquery.name) + add(newquery) + end + + ## + # Returns the iq's query's namespace, or nil + # result:: [String] + def queryns + e = first_element('query') + if e + return e.namespace + else + return nil + end + end + + ## + # Returns the iq's child, or nil + # result:: [IqVcard] + def vcard + first_element('vCard') + end + + ## + # Returns the iq's child, or nil + # result:: [IqVcard] + def pubsub + first_element('pubsub') + end + + ## + # Returns the iq's child, or nil + # resulte:: [IqCommand] + def command + first_element("command") + end + + ## + # Create a new Iq stanza with an unspecified query child + # ( has no namespace) + def Iq.new_query(type = nil, to = nil) + iq = Iq::new(type, to) + query = IqQuery::new + iq.add(query) + iq + end + + ## + # Create a new jabber:iq:auth set Stanza. + def Iq.new_authset(jid, password) + iq = Iq::new(:set) + query = IqQuery::new + query.add_namespace('jabber:iq:auth') + query.add(REXML::Element::new('username').add_text(jid.node)) + query.add(REXML::Element::new('password').add_text(password)) + query.add(REXML::Element::new('resource').add_text(jid.resource)) if not jid.resource.nil? + iq.add(query) + iq + end + + ## + # Create a new jabber:iq:auth set Stanza for Digest authentication + def Iq.new_authset_digest(jid, session_id, password) + iq = Iq::new(:set) + query = IqQuery::new + query.add_namespace('jabber:iq:auth') + query.add(REXML::Element::new('username').add_text(jid.node)) + query.add(REXML::Element::new('digest').add_text(Digest::SHA1.hexdigest(session_id + password))) + query.add(REXML::Element::new('resource').add_text(jid.resource)) if not jid.resource.nil? + iq.add(query) + iq + end + + ## + # Create a new jabber:iq:register set stanza for service/server registration + # username:: [String] (Element will be ommited if unset) + # password:: [String] (Element will be ommited if unset) + def Iq.new_register(username=nil, password=nil) + iq = Iq::new(:set) + query = IqQuery::new + query.add_namespace('jabber:iq:register') + query.add(REXML::Element::new('username').add_text(username)) if username + query.add(REXML::Element::new('password').add_text(password)) if password + iq.add(query) + iq + end + + ## + # Create a new jabber:iq:roster get Stanza. + # + # IqQueryRoster is unused here because possibly not require'd + def Iq.new_rosterget + iq = Iq::new(:get) + query = IqQuery::new + query.add_namespace('jabber:iq:roster') + iq.add(query) + iq + end + + ## + # Create a new jabber:iq:roster get Stanza. + def Iq.new_browseget + iq = Iq::new(:get) + query = IqQuery::new + query.add_namespace('jabber:iq:browse') + iq.add(query) + iq + end + + ## + # Create a new jabber:iq:roster set Stanza. + def Iq.new_rosterset + iq = Iq::new(:set) + query = IqQuery::new + query.add_namespace('jabber:iq:roster') + iq.add(query) + iq + end + end +end diff --git a/vendor/xmpp4r-0.3.2/lib/xmpp4r/jid.rb b/vendor/xmpp4r-0.3.2/lib/xmpp4r/jid.rb new file mode 100755 index 000000000..809cd40ee --- /dev/null +++ b/vendor/xmpp4r-0.3.2/lib/xmpp4r/jid.rb @@ -0,0 +1,167 @@ +# =XMPP4R - XMPP Library for Ruby +# License:: Ruby's license (see the LICENSE file) or GNU GPL, at your option. +# Website::http://home.gna.org/xmpp4r/ + +module Jabber + ## + # The JID class represents a Jabber Identifier as described by + # RFC3920 section 3.1. + # + # Note that you can use JIDs also for Sorting, Hash keys, ... + class JID + include Comparable + + PATTERN = /^(?:([^@]*)@)??([^@\/]*)(?:\/(.*?))?$/ + + begin + require 'idn' + USE_STRINGPREP = true + rescue LoadError + USE_STRINGPREP = false + end + + ## + # Create a new JID. If called as new('a@b/c'), parse the string and + # split (node, domain, resource) + def initialize(node = "", domain = nil, resource = nil) + @resource = resource + @domain = domain + @node = node + if @domain.nil? and @resource.nil? and @node + @node, @domain, @resource = @node.to_s.scan(PATTERN).first + end + + if USE_STRINGPREP + @node = IDN::Stringprep.nodeprep(@node) if @node + @domain = IDN::Stringprep.nameprep(@domain) if @domain + @resource = IDN::Stringprep.resourceprep(@resource) if @resource + else + @node.downcase! if @node + @domain.downcase! if @domain + end + + raise ArgumentError, 'Node too long' if (@node || '').length > 1023 + raise ArgumentError, 'Domain too long' if (@domain || '').length > 1023 + raise ArgumentError, 'Resource too long' if (@resource || '').length > 1023 + end + + ## + # Returns a string representation of the JID + # * "" + # * "domain" + # * "node@domain" + # * "domain/resource" + # * "node@domain/resource" + def to_s + s = @domain + s = "#{@node}@#{s}" if @node + s += "/#{@resource}" if @resource + return s + end + + ## + # Returns a new JID with resource removed. + # return:: [JID] + def strip + JID::new(@node, @domain) + end + alias_method :bare, :strip + + ## + # Removes the resource (sets it to nil) + # return:: [JID] self + def strip! + @resource = nil + self + end + alias_method :bare!, :strip! + + ## + # Returns a hash value of the String representation + # (see JID#to_s) + def hash + return to_s.hash + end + + ## + # Ccompare to another JID + # + # String representations are compared, see JID#to_s + def eql?(o) + to_s.eql?(o.to_s) + end + + ## + # Ccompare to another JID + # + # String representations are compared, see JID#to_s + def ==(o) + to_s == o.to_s + end + + ## + # Compare two JIDs, + # helpful for sorting etc. + # + # String representations are compared, see JID#to_s + def <=>(o) + to_s <=> o.to_s + end + + # Get the JID's node + def node + @node + end + + # Set the JID's node + def node=(v) + @node = v.to_s + if USE_STRINGPREP + @node = IDN::Stringprep.nodeprep(@node) if @node + end + end + + # Get the JID's domain + def domain + return nil if @domain.empty? + @domain + end + + # Set the JID's domain + def domain=(v) + @domain = v.to_s + if USE_STRINGPREP + @domain = IDN::Stringprep.nodeprep(@domain) + end + end + + # Get the JID's resource + def resource + @resource + end + + # Set the JID's resource + def resource=(v) + @resource = v.to_s + if USE_STRINGPREP + @resource = IDN::Stringprep.nodeprep(@resource) + end + end + + # Escape JID + def JID::escape(jid) + return jid.to_s.gsub('@', '%') + end + + # Test if jid is empty + def empty? + to_s.empty? + end + + # Test id jid is strepped + def stripped? + @resource.nil? + end + alias_method :bared?, :stripped? + end +end diff --git a/vendor/xmpp4r-0.3.2/lib/xmpp4r/message.rb b/vendor/xmpp4r-0.3.2/lib/xmpp4r/message.rb new file mode 100644 index 000000000..357eb8faf --- /dev/null +++ b/vendor/xmpp4r-0.3.2/lib/xmpp4r/message.rb @@ -0,0 +1,148 @@ +# =XMPP4R - XMPP Library for Ruby +# License:: Ruby's license (see the LICENSE file) or GNU GPL, at your option. +# Website::http://home.gna.org/xmpp4r/ + +require 'xmpp4r/xmppstanza' +require 'xmpp4r/x' + +module Jabber + ## + # The Message class manages the stanzas, + # which is used for all messaging communication. + class Message < XMPPStanza + + name_xmlns 'message', 'jabber:client' + force_xmlns true + + include XParent + + ## + # Create a new message + # >to:: a JID or a String object to send the message to. + # >body:: the message's body + def initialize(to = nil, body = nil) + super() + if not to.nil? + set_to(to) + end + if !body.nil? + add_element(REXML::Element::new("body").add_text(body)) + end + end + + ## + # Get the type of the Message stanza + # + # The following Symbols are allowed: + # * :chat + # * :error + # * :groupchat + # * :headline + # * :normal + # result:: [Symbol] or nil + def type + case super + when 'chat' then :chat + when 'error' then :error + when 'groupchat' then :groupchat + when 'headline' then :headline + when 'normal' then :normal + else nil + end + end + + ## + # Set the type of the Message stanza (see Message#type for details) + # v:: [Symbol] or nil + def type=(v) + case v + when :chat then super('chat') + when :error then super('error') + when :groupchat then super('groupchat') + when :headline then super('headline') + when :normal then super('normal') + else super(nil) + end + end + + ## + # Set the type of the Message stanza (chaining-friendly) + # v:: [Symbol] or nil + def set_type(v) + self.type = v + self + end + + ## + # Returns the message's body, or nil. + # This is the message's plain-text content. + def body + first_element_text('body') + end + + ## + # Sets the message's body + # + # b:: [String] body to set + def body=(b) + replace_element_text('body', b) + end + + ## + # Sets the message's body + # + # b:: [String] body to set + # return:: [REXML::Element] self for chaining + def set_body(b) + self.body = b + self + end + + ## + # sets the message's subject + # + # s:: [String] subject to set + def subject=(s) + replace_element_text('subject', s) + end + + ## + # sets the message's subject + # + # s:: [String] subject to set + # return:: [REXML::Element] self for chaining + def set_subject(s) + self.subject = s + self + end + + ## + # Returns the message's subject, or nil + def subject + first_element_text('subject') + end + + ## + # sets the message's thread + # s:: [String] thread to set + def thread=(s) + delete_elements('thread') + replace_element_text('thread', s) unless s.nil? + end + + ## + # gets the message's thread (chaining-friendly) + # Please note that this are not [Thread] but a [String]-Identifier to track conversations + # s:: [String] thread to set + def set_thread(s) + self.thread = s + self + end + + ## + # Returns the message's thread, or nil + def thread + first_element_text('thread') + end + end +end diff --git a/vendor/xmpp4r-0.3.2/lib/xmpp4r/muc.rb b/vendor/xmpp4r-0.3.2/lib/xmpp4r/muc.rb new file mode 100644 index 000000000..e0d5fa400 --- /dev/null +++ b/vendor/xmpp4r-0.3.2/lib/xmpp4r/muc.rb @@ -0,0 +1,12 @@ +# =XMPP4R - XMPP Library for Ruby +# License:: Ruby's license (see the LICENSE file) or GNU GPL, at your option. +# Website::http://home.gna.org/xmpp4r/ + +require 'xmpp4r/dataforms' +require 'xmpp4r/muc/x/muc' +require 'xmpp4r/muc/x/mucuserinvite' +require 'xmpp4r/muc/x/mucuseritem' +require 'xmpp4r/muc/iq/mucowner' +require 'xmpp4r/muc/helper/mucbrowser' +require 'xmpp4r/muc/helper/mucclient' +require 'xmpp4r/muc/helper/simplemucclient' diff --git a/vendor/xmpp4r-0.3.2/lib/xmpp4r/muc/helper/mucbrowser.rb b/vendor/xmpp4r-0.3.2/lib/xmpp4r/muc/helper/mucbrowser.rb new file mode 100644 index 000000000..0e92490ef --- /dev/null +++ b/vendor/xmpp4r-0.3.2/lib/xmpp4r/muc/helper/mucbrowser.rb @@ -0,0 +1,107 @@ +# =XMPP4R - XMPP Library for Ruby +# License:: Ruby's license (see the LICENSE file) or GNU GPL, at your option. +# Website::http://home.gna.org/xmpp4r/ + +require 'xmpp4r/discovery' + +module Jabber + module MUC + ## + # The MUCBrowser helper can be used to discover + # Multi-User-Chat components via Service Discovery + # + # See JEP 0045 sections 6.1. and 6.2. + # + # Usage of its functions should be threaded as + # responses can take a while + class MUCBrowser + ## + # Initialize a new MUCBrowser helper + def initialize(stream) + @stream = stream + end + + ## + # Retrieve the name of a MUC component, + # depending upon whether the target entity supports + # the MUC protocol. + # + # A return-value of nil does *not* mean that the entity + # does not exist or does not support Service Discovery! + # nil just means that this is not a MUC-compliant service. + # + # Throws an ErrorException when receiving + # + # jid:: [JID] Target entity (set only domain!) + # return:: [String] or [nil] + def muc_name(jid) + iq = Iq.new(:get, jid) + iq.from = @stream.jid # Enable components to use this + iq.add(Discovery::IqQueryDiscoInfo.new) + + res = nil + + @stream.send_with_id(iq) do |answer| + if answer.type == :result + answer.query.each_element('feature') { |feature| + # Look if the component has a MUC or Groupchat feature + if feature.var == 'http://jabber.org/protocol/muc' or feature.var == 'gc-1.0' + # If so, get the identity + if answer.query.first_element('identity') + res = answer.query.first_element('identity').iname + end + end + } + true + else + false + end + end + + res + end + + ## + # Retrieve the existing rooms of a MUC component + # + # The resulting Hash contains pairs of room JID and room name + # + # Usage: + # my_mucbrowse_helper.muc_rooms('conference.jabber.org').each { |jid,name| ... } + # + # Throws an exception when receiving + # jid:: [JID] Target entity (set only domain!) + # return:: [Hash] + def muc_rooms(jid) + iq = Iq.new(:get, jid) + iq.from = @stream.jid # Enable components to use this + iq.add(Discovery::IqQueryDiscoItems.new) + + rooms = {} + err = nil + + @stream.send_with_id(iq) do |answer| + + if answer.type == :result + answer.query.each_element('item') { |item| + rooms[item.jid] = item.iname + } + true + elsif answer.type == :error + err = answer.error + true + else + false + end + end + + if err + raise "Error getting MUC rooms: #{err.error}, #{err.text}" + end + + rooms + end + end + end +end + diff --git a/vendor/xmpp4r-0.3.2/lib/xmpp4r/muc/helper/mucclient.rb b/vendor/xmpp4r-0.3.2/lib/xmpp4r/muc/helper/mucclient.rb new file mode 100644 index 000000000..858806782 --- /dev/null +++ b/vendor/xmpp4r-0.3.2/lib/xmpp4r/muc/helper/mucclient.rb @@ -0,0 +1,431 @@ +# =XMPP4R - XMPP Library for Ruby +# License:: Ruby's license (see the LICENSE file) or GNU GPL, at your option. +# Website::http://home.gna.org/xmpp4r/ + +require 'xmpp4r/muc/x/muc' + +module Jabber + module MUC + ## + # The MUCClient Helper handles low-level stuff of the + # Multi-User Chat (JEP 0045). + # + # Use one instance per room. + # + # Note that one client cannot join a single room multiple + # times. At least the clients' resources must be different. + # This is a protocol design issue. But don't consider it as + # a bug, it is just a clone-preventing feature. + class MUCClient + ## + # Sender JID, set this to use MUCClient from Components + # my_jid:: [JID] Defaults to nil + attr_accessor :my_jid + + ## + # MUC room roster + # roster:: [Hash] of [String] Nick => [Presence] + attr_reader :roster + + ## + # MUC JID + # jid:: [JID] room@component/nick + attr_reader :jid + + ## + # Initialize a MUCClient + # + # Call MUCClient#join *after* you have registered your + # callbacks to avoid reception of stanzas after joining + # and before registration of callbacks. + # stream:: [Stream] to operate on + def initialize(stream) + # Attributes initialization + @stream = stream + @my_jid = nil + @jid = nil + @roster = {} + @roster_lock = Mutex.new + + @active = false + + @join_cbs = CallbackList.new + @leave_cbs = CallbackList.new + @presence_cbs = CallbackList.new + @message_cbs = CallbackList.new + @private_message_cbs = CallbackList.new + end + + ## + # Join a room + # + # This registers its own callbacks on the stream + # provided to initialize and sends initial presence + # to the room. May throw ErrorException if joining + # fails. + # jid:: [JID] room@component/nick + # password:: [String] Optional password + # return:: [MUCClient] self (chain-able) + def join(jid, password=nil) + if active? + raise "MUCClient already active" + end + + @jid = (jid.kind_of?(JID) ? jid : JID.new(jid)) + activate + + # Joining + pres = Presence.new + pres.to = @jid + pres.from = @my_jid + xmuc = XMUC.new + xmuc.password = password + pres.add(xmuc) + + # We don't use Stream#send_with_id here as it's unknown + # if the MUC component *always* uses our stanza id. + error = nil + @stream.send(pres) { |r| + if from_room?(r.from) and r.kind_of?(Presence) and r.type == :error + # Error from room + error = r.error + true + # type='unavailable' may occur when the MUC kills our previous instance, + # but all join-failures should be type='error' + elsif r.from == jid and r.kind_of?(Presence) and r.type != :unavailable + # Our own presence reflected back - success + if r.x(XMUCUser) and (i = r.x(XMUCUser).items.first) + @affiliation = i.affiliation # we're interested in if it's :owner + @role = i.role # :moderator ? + end + + handle_presence(r, false) + true + else + # Everything else + false + end + } + + if error + deactivate + raise ErrorException.new(error) + end + + self + end + + ## + # Exit the room + # + # * Sends presence with type='unavailable' with an optional + # reason in , + # * then waits for a reply from the MUC component (will be + # processed by leave-callbacks), + # * then deletes callbacks from the stream. + # reason:: [String] Optional custom exit message + def exit(reason=nil) + unless active? + raise "MUCClient hasn't yet joined" + end + + pres = Presence.new + pres.type = :unavailable + pres.to = jid + pres.from = @my_jid + pres.status = reason if reason + @stream.send(pres) { |r| + Jabber::debuglog "exit: #{r.to_s.inspect}" + if r.kind_of?(Presence) and r.type == :unavailable and r.from == jid + @leave_cbs.process(r) + true + else + false + end + } + + deactivate + + self + end + + ## + # Is the MUC client active? + # + # This is false after initialization, + # true after joining and + # false after exit/kick + def active? + @active + end + + ## + # The MUCClient's own nick + # (= resource) + # result:: [String] Nickname + def nick + @jid ? @jid.resource : nil + end + + ## + # Change nick + # + # Threading is, again, suggested. This method waits for two + # stanzas, one indicating unavailabilty of the old + # transient JID, one indicating availability of the new + # transient JID. + # + # If the service denies nick-change, ErrorException will be raisen. + def nick=(new_nick) + unless active? + raise "MUCClient not active" + end + + new_jid = JID.new(@jid.node, @jid.domain, new_nick) + + # Joining + pres = Presence.new + pres.to = new_jid + pres.from = @my_jid + + error = nil + # Keeping track of the two stanzas enables us to process stanzas + # which don't arrive in the order specified by JEP-0045 + presence_unavailable = false + presence_available = false + # We don't use Stream#send_with_id here as it's unknown + # if the MUC component *always* uses our stanza id. + @stream.send(pres) { |r| + if from_room?(r.from) and r.kind_of?(Presence) and r.type == :error + # Error from room + error = r.error + elsif r.from == @jid and r.kind_of?(Presence) and r.type == :unavailable and + r.x and r.x.kind_of?(XMUCUser) and r.x.status_code == 303 + # Old JID is offline, but wait for the new JID and let stanza be handled + # by the standard callback + presence_unavailable = true + handle_presence(r) + elsif r.from == new_jid and r.kind_of?(Presence) and r.type != :unavailable + # Our own presence reflected back - success + presence_available = true + handle_presence(r) + end + + if error or (presence_available and presence_unavailable) + true + else + false + end + } + + if error + raise ErrorException.new(error) + end + + # Apply new JID + @jid = new_jid + end + + ## + # The room name + # (= node) + # result:: [String] Room name + def room + @jid ? @jid.node : nil + end + + ## + # Send a stanza to the room + # + # If stanza is a Jabber::Message, stanza.type will be + # automatically set to :groupchat if directed to room or :chat + # if directed to participant. + # stanza:: [XMPPStanza] to send + # to:: [String] Stanza destination recipient, or room if +nil+ + def send(stanza, to=nil) + if stanza.kind_of? Message + stanza.type = to ? :chat : :groupchat + end + stanza.from = @my_jid + stanza.to = JID::new(jid.node, jid.domain, to) + @stream.send(stanza) + end + + ## + # Add a callback for stanzas indicating availability + # of a MUC participant + # + # This callback will *not* be called for initial presences when + # a client joins a room, but only for the presences afterwards. + # + # The callback will be called from MUCClient#handle_presence with + # one argument: the stanza. + # Note that this stanza will have been already inserted into + # MUCClient#roster. + def add_join_callback(prio = 0, ref = nil, &block) + @join_cbs.add(prio, ref, block) + end + + ## + # Add a callback for stanzas indicating unavailability + # of a MUC participant + # + # The callback will be called with one argument: the stanza. + # + # Note that this is called just *before* the stanza is removed from + # MUCClient#roster, so it is still possible to see the last presence + # in the given block. + # + # If the presence's origin is your MUC JID, the MUCClient will be + # deactivated *afterwards*. + def add_leave_callback(prio = 0, ref = nil, &block) + @leave_cbs.add(prio, ref, block) + end + + ## + # Add a callback for a stanza which is neither a join + # nor a leave. This will be called when a room participant simply + # changes his status. + def add_presence_callback(prio = 0, ref = nil, &block) + @presence_cbs.add(prio, ref, block) + end + + ## + # Add a callback for stanza directed to the whole room. + # + # See MUCClient#add_private_message_callback for private messages + # between MUC participants. + def add_message_callback(prio = 0, ref = nil, &block) + @message_cbs.add(prio, ref, block) + end + + ## + # Add a callback for stanza with type='chat'. + # + # These stanza are normally not broadcasted to all room occupants + # but are some sort of private messaging. + def add_private_message_callback(prio = 0, ref = nil, &block) + @private_message_cbs.add(prio, ref, block) + end + + ## + # Does this JID belong to that room? + # jid:: [JID] + # result:: [true] or [false] + def from_room?(jid) + @jid.strip == jid.strip + end + + private + + ## + # call_join_cbs:: [Bool] Do not call them if we receive initial presences from room + def handle_presence(pres, call_join_cbs=true) # :nodoc: + if pres.type == :unavailable or pres.type == :error + @leave_cbs.process(pres) + @roster_lock.synchronize { + @roster.delete(pres.from.resource) + } + + if pres.from == jid and !(pres.x and pres.x.kind_of?(XMUCUser) and pres.x.status_code == 303) + deactivate + end + else + is_join = ! @roster.has_key?(pres.from.resource) + @roster_lock.synchronize { + @roster[pres.from.resource] = pres + } + if is_join + @join_cbs.process(pres) if call_join_cbs + else + @presence_cbs.process(pres) + end + end + end + + def handle_message(msg) # :nodoc: + if msg.type == :chat + @private_message_cbs.process(msg) + else # type == :groupchat or anything else + @message_cbs.process(msg) + end + end + + def activate # :nodoc: + @active = true + + # Callbacks + @stream.add_presence_callback(150, self) { |presence| + if from_room?(presence.from) + handle_presence(presence) + true + else + false + end + } + + @stream.add_message_callback(150, self) { |message| + if from_room?(message.from) + handle_message(message) + true + else + false + end + } + end + + def deactivate # :nodoc: + @active = false + @jid = nil + + # Callbacks + @stream.delete_presence_callback(self) + @stream.delete_message_callback(self) + end + + public + def owner? + @affiliation == :owner + end + + def configure(options={}) + raise 'You are not the owner' unless owner? + + iq = Iq.new(:get, jid) + iq.to = @jid + iq.from = @my_jid + iq.add(IqQueryMUCOwner.new) + + fields = [] + + answer = @stream.send_with_id(iq) + raise "Configuration not possible for this room" unless answer.query && answer.query.x(XData) + + answer.query.x(XData).fields.each { |field| + if (var = field.attributes['var']) + fields << var + end + } + + + # fill out the reply form + iq = Iq.new(:set, jid) + iq.to = @jid + iq.from = @my_jid + query = IqQueryMUCOwner.new + form = Dataforms::XData.new + form.type = :submit + options.each do |var, values| + field = Dataforms::XDataField.new + values = [values] unless values.is_a?(Array) + field.var, field.values = var, values + form.add(field) + end + query.add(form) + iq.add(query) + + @stream.send_with_id(iq) + end + end + end +end diff --git a/vendor/xmpp4r-0.3.2/lib/xmpp4r/muc/helper/simplemucclient.rb b/vendor/xmpp4r-0.3.2/lib/xmpp4r/muc/helper/simplemucclient.rb new file mode 100644 index 000000000..d79be945e --- /dev/null +++ b/vendor/xmpp4r-0.3.2/lib/xmpp4r/muc/helper/simplemucclient.rb @@ -0,0 +1,226 @@ +# =XMPP4R - XMPP Library for Ruby +# License:: Ruby's license (see the LICENSE file) or GNU GPL, at your option. +# Website::http://home.gna.org/xmpp4r/ + +require 'xmpp4r/delay/x/delay' +require 'xmpp4r/muc/helper/mucclient' + +module Jabber + module MUC + ## + # This class attempts to implement a lot of complexity of the + # Multi-User Chat protocol. If you want to implement JEP0045 + # yourself, use Jabber::MUC::MUCClient for some minor + # abstraction. + # + # Minor flexibility penalty: the on_* callbacks are no + # CallbackLists and may therefore only used once. A second + # invocation will overwrite the previous set up block. + # + # *Hint:* the parameter time may be nil if the server didn't + # send it. + # + # Example usage: + # my_muc = Jabber::MUC::SimpleMUCClient.new(my_client) + # my_muc.on_message { |time,nick,text| + # puts (time || Time.new).strftime('%I:%M') + " <#{nick}> #{text}" + # } + # my_muc.join(Jabber::JID.new('jdev@conference.jabber.org/XMPP4R-Bot')) + # + # Please take a look at Jabber::MUC::MUCClient for + # derived methods, such as MUCClient#join, MUCClient#exit, + # ... + class SimpleMUCClient < MUCClient + ## + # Initialize a SimpleMUCClient + # stream:: [Stream] to operate on + # jid:: [JID] room@component/nick + # password:: [String] Optional password + def initialize(stream) + super + + @room_message_block = nil + @message_block = nil + @private_message_block = nil + @subject_block = nil + + @subject = nil + + @join_block = nil + add_join_callback(999) { |pres| + # Presence time + time = nil + pres.each_element('x') { |x| + if x.kind_of?(Delay::XDelay) + time = x.stamp + end + } + + # Invoke... + @join_block.call(time, pres.from.resource) if @join_block + false + } + + @leave_block = nil + @self_leave_block = nil + add_leave_callback(999) { |pres| + # Presence time + time = nil + pres.each_element('x') { |x| + if x.kind_of?(Delay::XDelay) + time = x.stamp + end + } + + # Invoke... + if pres.from == jid + @self_leave_block.call(time) if @self_leave_block + else + @leave_block.call(time, pres.from.resource) if @leave_block + end + false + } + end + + private + + def handle_message(msg) + super + + # Message time (e.g. history) + time = nil + msg.each_element('x') { |x| + if x.kind_of?(Delay::XDelay) + time = x.stamp + end + } + sender_nick = msg.from.resource + + + if msg.subject + @subject = msg.subject + @subject_block.call(time, sender_nick, @subject) if @subject_block + end + + if msg.body + if sender_nick.nil? + @room_message_block.call(time, msg.body) if @room_message_block + else + if msg.type == :chat + @private_message_block.call(time, msg.from.resource, msg.body) if @private_message_block + elsif msg.type == :groupchat + @message_block.call(time, msg.from.resource, msg.body) if @message_block + else + # ...? + end + end + end + end + + public + + ## + # Room subject/topic + # result:: [String] The subject + def subject + @subject + end + + ## + # Change the room's subject/topic + # + # This will not be reflected by SimpleMUCClient#subject + # immediately, wait for SimpleMUCClient#on_subject + # s:: [String] New subject + def subject=(s) + msg = Message.new + msg.subject = s + send(msg) + end + + ## + # Send a simple text message + # text:: [String] Message body + # to:: [String] Optional nick if directed to specific user + def say(text, to=nil) + send(Message.new(nil, text), to) + end + + ## + # Request the MUC to invite users to this room + # + # Sample usage: + # my_muc.invite( {'wiccarocks@shakespeare.lit/laptop' => 'This coven needs both wiccarocks and hag66.', + # 'hag66@shakespeare.lit' => 'This coven needs both hag66 and wiccarocks.'} ) + # recipients:: [Hash] of [JID] => [String] Reason + def invite(recipients) + msg = Message.new + x = msg.add(XMUCUser.new) + recipients.each { |jid,reason| + x.add(XMUCUserInvite.new(jid, reason)) + } + send(msg) + end + + ## + # Block to be invoked when a message *from* the room arrives + # + # Example: + # Astro has joined this session + # block:: Takes two arguments: time, text + def on_room_message(&block) + @room_message_block = block + end + + ## + # Block to be invoked when a message from a participant to + # the whole room arrives + # block:: Takes three arguments: time, sender nickname, text + def on_message(&block) + @message_block = block + end + + ## + # Block to be invoked when a private message from a participant + # to you arrives. + # block:: Takes three arguments: time, sender nickname, text + def on_private_message(&block) + @private_message_block = block + end + + ## + # Block to be invoked when somebody sets a new room subject + # block:: Takes three arguments: time, nickname, new subject + def on_subject(&block) + @subject_block = block + end + + ## + # Block to be called when somebody enters the room + # + # If there is a non-nil time passed to the block, chances + # are great that this is initial presence from a participant + # after you have joined the room. + # block:: Takes two arguments: time, nickname + def on_join(&block) + @join_block = block + end + + ## + # Block to be called when somebody leaves the room + # block:: Takes two arguments: time, nickname + def on_leave(&block) + @leave_block = block + end + + ## + # Block to be called when *you* leave the room + # + # Deactivation occurs *afterwards*. + # block:: Takes one argument: time + def on_self_leave(&block) + @self_leave_block = block + end + end + end +end diff --git a/vendor/xmpp4r-0.3.2/lib/xmpp4r/muc/iq/mucowner.rb b/vendor/xmpp4r-0.3.2/lib/xmpp4r/muc/iq/mucowner.rb new file mode 100644 index 000000000..61e2b001e --- /dev/null +++ b/vendor/xmpp4r-0.3.2/lib/xmpp4r/muc/iq/mucowner.rb @@ -0,0 +1,11 @@ +require 'xmpp4r/x' + +module Jabber + module MUC + class IqQueryMUCOwner < IqQuery + name_xmlns 'query', 'http://jabber.org/protocol/muc#owner' + + include XParent + end + end +end diff --git a/vendor/xmpp4r-0.3.2/lib/xmpp4r/muc/x/muc.rb b/vendor/xmpp4r-0.3.2/lib/xmpp4r/muc/x/muc.rb new file mode 100644 index 000000000..a69702581 --- /dev/null +++ b/vendor/xmpp4r-0.3.2/lib/xmpp4r/muc/x/muc.rb @@ -0,0 +1,70 @@ +# =XMPP4R - XMPP Library for Ruby +# License:: Ruby's license (see the LICENSE file) or GNU GPL, at your option. +# Website::http://home.gna.org/xmpp4r/ + +require 'xmpp4r/muc/x/mucuseritem' +require 'xmpp4r/muc/x/mucuserinvite' + +module Jabber + module MUC + ## + # Class for elements + # with namespace http://jabber.org/protocol/muc + # + # See JEP-0045 for details + class XMUC < X + name_xmlns 'x', 'http://jabber.org/protocol/muc' + + ## + # Text content of the element + def password + first_element_text('password') + end + + ## + # Set the password for joining a room + # (text content of the element) + def password=(s) + if s + replace_element_text('password', s) + else + delete_elements('password') + end + end + end + + ## + # Class for elements + # with namespace http://jabber.org/protocol/muc#user + # + # See JEP-0058 for details + class XMUCUser < X + name_xmlns 'x', 'http://jabber.org/protocol/muc#user' + + ## + # Retrieve the three-digit code in + # + # result:: [Fixnum] or nil + def status_code + e = nil + each_element('status') { |xe| e = xe } + if e and e.attributes['code'].size == 3 and e.attributes['code'].to_i != 0 + e.attributes['code'].to_i + else + nil + end + end + + ## + # Get all elements + # result:: [Array] of [XMUCUserItem] + def items + res = [] + each_element('item') { |item| + res << item + } + res + end + end + end +end diff --git a/vendor/xmpp4r-0.3.2/lib/xmpp4r/muc/x/mucuserinvite.rb b/vendor/xmpp4r-0.3.2/lib/xmpp4r/muc/x/mucuserinvite.rb new file mode 100644 index 000000000..c5a037818 --- /dev/null +++ b/vendor/xmpp4r-0.3.2/lib/xmpp4r/muc/x/mucuserinvite.rb @@ -0,0 +1,60 @@ +# =XMPP4R - XMPP Library for Ruby +# License:: Ruby's license (see the LICENSE file) or GNU GPL, at your option. +# Website::http://home.gna.org/xmpp4r/ + +module Jabber + module MUC + class XMUCUserInvite < XMPPElement + name_xmlns 'invite', 'http://jabber.org/protocol/muc#user' + + def initialize(to=nil, reason=nil) + super() + set_to(to) + set_reason(reason) + end + + def to + attributes['to'].nil? ? nil : JID::new(attributes['to']) + end + + def to=(j) + attributes['to'] = j.nil? ? nil : j.to_s + end + + def set_to(j) + self.to = j + self + end + + def from + attributes['from'].nil? ? nil : JID::new(attributes['from']) + end + + def from=(j) + attributes['from'] = (j.nil? ? nil : j.to_s) + end + + def set_from(j) + self.from = j + self + end + + def reason + first_element_text('reason') + end + + def reason=(s) + if s + replace_element_text('reason', s) + else + delete_elements('reason') + end + end + + def set_reason(s) + self.reason = s + self + end + end + end +end diff --git a/vendor/xmpp4r-0.3.2/lib/xmpp4r/muc/x/mucuseritem.rb b/vendor/xmpp4r-0.3.2/lib/xmpp4r/muc/x/mucuseritem.rb new file mode 100644 index 000000000..d087b8976 --- /dev/null +++ b/vendor/xmpp4r-0.3.2/lib/xmpp4r/muc/x/mucuseritem.rb @@ -0,0 +1,150 @@ +# =XMPP4R - XMPP Library for Ruby +# License:: Ruby's license (see the LICENSE file) or GNU GPL, at your option. +# Website::http://home.gna.org/xmpp4r/ + +module Jabber + module MUC + class XMUCUserItem < XMPPElement + name_xmlns 'item', 'http://jabber.org/protocol/muc#user' + + def initialize(affiliation=nil, role=nil, jid=nil) + super() + set_affiliation(affiliation) + set_role(role) + set_jid(jid) + end + + def affiliation + case attributes['affiliation'] + when 'admin' then :admin + when 'member' then :member + when 'none' then :none + when 'outcast' then :outcast + when 'owner' then :owner + else nil + end + end + + def affiliation=(v) + case v + when :admin then attributes['affiliation'] = 'admin' + when :member then attributes['affiliation'] = 'member' + when :none then attributes['affiliation'] = 'none' + when :outcast then attributes['affiliation'] = 'outcast' + when :owner then attributes['affiliation'] = 'owner' + else attributes['affiliation'] = nil + end + end + + def set_affiliation(v) + self.affiliation = v + self + end + + def jid + attributes['jid'].nil? ? nil : JID::new(attributes['jid']) + end + + def jid=(j) + attributes['jid'] = j.nil? ? nil : j.to_s + end + + def set_jid(j) + self.jid = j + self + end + + def nick + attributes['nick'] + end + + def nick=(n) + attributes['nick'] = n + end + + def set_nick(n) + self.nick = n + self + end + + def role + case attributes['role'] + when 'moderator' then :moderator + when 'none' then :none + when 'participant' then :participant + when 'visitor' then :visitor + else nil + end + end + + def role=(r) + case r + when :moderator then attributes['role'] = 'moderator' + when :none then attributes['role'] = 'none' + when :participant then attributes['role'] = 'participant' + when :visitor then attributes['role'] = 'visitor' + else attributes['role'] = nil + end + end + + def set_role(r) + self.role = r + self + end + + def reason + text = nil + each_element('reason') { |xe| text = xe.text } + text + end + + def reason=(s) + delete_elements('reasion') + add_element('reason').text = s + end + + def set_reason(s) + self.reason = s + self + end + + def continue + c = nil + each_element('continue') { |xe| c = xe } + c.nil? + end + + def continue=(c) + delete_elements('continue') + add_element('continue') if c + end + + def set_continue(c) + self.continue = c + self + end + + def actors + a = [] + each_element('actor') { |xe| + a.push(JID::new(xe.attributes['jid'])) + } + a + end + + def actors=(a) + delete_elements('actor') + a.each { |jid| + e = add_element('actor') + e.attributes['jid'] = jid.to_s + } + end + + def set_actors(a) + self.actors = a + self + end + end + end +end + diff --git a/vendor/xmpp4r-0.3.2/lib/xmpp4r/presence.rb b/vendor/xmpp4r-0.3.2/lib/xmpp4r/presence.rb new file mode 100644 index 000000000..a660f8a96 --- /dev/null +++ b/vendor/xmpp4r-0.3.2/lib/xmpp4r/presence.rb @@ -0,0 +1,231 @@ +# =XMPP4R - XMPP Library for Ruby +# License:: Ruby's license (see the LICENSE file) or GNU GPL, at your option. +# Website::http://home.gna.org/xmpp4r/ + +require 'xmpp4r/xmppstanza' +require 'xmpp4r/x' + +module Jabber + ## + # The presence class is used to construct presence messages to + # send to the Jabber service. + class Presence < XMPPStanza + name_xmlns 'presence', 'jabber:client' + force_xmlns true + + include Comparable + include XParent + + ## + # Create presence stanza + # show:: [String] Initial Availability Status + # status:: [String] Initial status message + # priority:: [Fixnum] Initial priority value + def initialize(show=nil, status=nil, priority=nil) + super() + set_show(show) if show + set_status(status) if status + set_priority(priority) if priority + end + + ## + # Get type of presence + # + # result:: [Symbol] or [Nil] Possible values are: + # * :error + # * :probe (Servers send this to request presence information) + # * :subscribe (Subscription request) + # * :subscribed (Subscription approval) + # * :unavailable (User has gone offline) + # * :unsubscribe (Unsubscription request) + # * :unsubscribed (Unsubscription approval) + # * [nil] (available) + # See RFC3921 - 2.2.1. for explanation. + def type + case super + when 'error' then :error + when 'probe' then :probe + when 'subscribe' then :subscribe + when 'subscribed' then :subscribed + when 'unavailable' then :unavailable + when 'unsubscribe' then :unsubscribe + when 'unsubscribed' then :unsubscribed + else nil + end + end + + ## + # Set type of presence + # val:: [Symbol] See type for possible subscription types + def type=(val) + case val + when :error then super('error') + when :probe then super('probe') + when :subscribe then super('subscribe') + when :subscribed then super('subscribed') + when :unavailable then super('unavailable') + when :unsubscribe then super('unsubscribe') + when :unsubscribed then super('unsubscribed') + else super(nil) + end + end + + ## + # Set type of presence (chaining-friendly) + # val:: [Symbol] See type for possible subscription types + def set_type(val) + self.type = val + self + end + + ## + # Get Availability Status (RFC3921 - 5.2) + # result:: [Symbol] or [Nil] Valid values according to RFC3921: + # * nil (Available, no element) + # * :away + # * :chat (Free for chat) + # * :dnd (Do not disturb) + # * :xa (Extended away) + def show + e = first_element('show') + text = e ? e.text : nil + case text + when 'away' then :away + when 'chat' then :chat + when 'dnd' then :dnd + when 'xa' then :xa + else nil + end + end + + ## + # Set Availability Status + # val:: [Symbol] or [Nil] See show for explanation + def show=(val) + xe = first_element('show') + if xe.nil? + xe = add_element('show') + end + case val + when :away then text = 'away' + when :chat then text = 'chat' + when :dnd then text = 'dnd' + when :xa then text = 'xa' + when nil then text = nil + else raise "Invalid value for show." + end + + if text.nil? + delete_element(xe) + else + xe.text = text + end + end + + ## + # Set Availability Status (chaining-friendly) + # val:: [Symbol] or [Nil] See show for explanation + def set_show(val) + self.show = val + self + end + + ## + # Get status message + # result:: [String] or nil + def status + first_element_text('status') + end + + ## + # Set status message + # val:: [String] or nil + def status=(val) + if val.nil? + delete_element('status') + else + replace_element_text('status', val) + end + end + + ## + # Set status message (chaining-friendly) + # val:: [String] or nil + def set_status(val) + self.status = val + self + end + + ## + # Get presence priority, or nil if absent + # result:: [Integer] + def priority + e = first_element_text('priority') + if e + return e.to_i + else + return nil + end + end + + ## + # Set presence priority + # val:: [Integer] Priority value between -128 and +127 + # + # *Warning:* negative values make you receive no subscription requests etc. + # (RFC3921 - 2.2.2.3.) + def priority=(val) + if val.nil? + delete_element('priority') + else + replace_element_text('priority', val) + end + end + + ## + # Set presence priority (chaining-friendly) + # val:: [Integer] Priority value between -128 and +127 + def set_priority(val) + self.priority = val + self + end + + ## + # Compare two presences using priority + # (with cmp_interest as fall-back). + def <=>(o) + if priority.to_i == o.priority.to_i + cmp_interest(o) + else + priority.to_i <=> o.priority.to_i + end + end + + ## + # Compare two presences. The most suitable to talk with is the + # biggest. + PRESENCE_STATUS = { :chat => 4, + nil => 3, + :dnd => 2, + :away => 1, + :xa => 0, + :unavailable => -1, + :error => -2 } + def cmp_interest(o) + if type.nil? + if o.type.nil? + # both available. + PRESENCE_STATUS[show] <=> PRESENCE_STATUS[o.show] + else + return -1 + end + elsif o.type.nil? + return 1 + else + # both are non-nil. We consider this is equal. + return 0 + end + end + + end +end diff --git a/vendor/xmpp4r-0.3.2/lib/xmpp4r/pubsub.rb b/vendor/xmpp4r-0.3.2/lib/xmpp4r/pubsub.rb new file mode 100644 index 000000000..7909055a2 --- /dev/null +++ b/vendor/xmpp4r-0.3.2/lib/xmpp4r/pubsub.rb @@ -0,0 +1 @@ +require 'xmpp4r/pubsub/helper/servicehelper' diff --git a/vendor/xmpp4r-0.3.2/lib/xmpp4r/pubsub/helper/nodebrowser.rb b/vendor/xmpp4r-0.3.2/lib/xmpp4r/pubsub/helper/nodebrowser.rb new file mode 100644 index 000000000..4165b37e6 --- /dev/null +++ b/vendor/xmpp4r-0.3.2/lib/xmpp4r/pubsub/helper/nodebrowser.rb @@ -0,0 +1,174 @@ +# =XMPP4R - XMPP Library for Ruby +# License:: Ruby's license (see the LICENSE file) or GNU GPL, at your option. +# Website::http://home.gna.org/xmpp4r/ + +require 'xmpp4r/discovery' + +module Jabber + module PubSub + class NodeBrowser + ## + # Initialize a new NodeBrowser + # new(stream,pubsubservice) + # stream:: [Jabber::Stream] + def initialize(stream) + @stream = stream + end + + ## + # Retrive the nodes + # Throws an ErrorException when receiving + # + # jid:: [JID] Target entity (set only domain!) + # return:: [Array] of [String] or [nil] + def nodes(jid) + iq = Iq.new(:get,jid) + iq.from = @stream.jid + iq.add(Discovery::IqQueryDiscoItems.new) + nodes = [] + err = nil + @stream.send_with_id(iq) { |answer| + if answer.type == :result + answer.query.each_element('item') { |item| + nodes.push(item.node) + } + true + elsif answer.type == :error + err = answer.error + true + else + false + end + } + return nodes + end + + ## + # Retrive the nodes with names + # Throws an ErrorExeption when reciving + # + # jid:: [Jabber::JID] Target entity (set only domain!) + # return:: [Array] of [Hash] with keys 'node' => [String] and 'name' => [String] or [nil] + def nodes_names(jid) + iq = Iq.new(:get,jid) + iq.from = @stream.jid + iq.add(Discovery::IqQueryDiscoItems.new) + nodes = [] + err = nil + @stream.send_with_id(iq) { |answer| + if answer.type == :result + answer.query.each_element('item') { |item| + nodes.push( {'node' => item.node,'name' => item.iname } ) + } + true + elsif answer.type == :error + err = answer.error + true + else + false + end + } + return nodes + end + + + ## + # Retrive the items from a node + # Throws an ErrorExeption when reciving + # + # jid:: [Jabber::JID] Target entity (set only domain!) + # node:: [String] + # return:: [Array] of [Hash] with keys 'name' => [String] and 'jid' => [Jabber::JID] + def items(jid,node) + iq = Iq.new(:get,jid) + iq.from = @stream.jid + discoitems = Discovery::IqQueryDiscoItems.new + discoitems.node = node + iq.add(discoitems) + items = [] + err = nil + @stream.send_with_id(iq) { |answer| + if answer.type == :result + answer.query.each_element('item') { |item| + items.push( {'jid' => item.jid,'name' => item.iname } ) + } + true + elsif answer.type == :error + err = answer.error + true + else + false + end + } + return items + end + + ## + # get disco info for a node + # jid:: [Jabber::JID] + # node:: [String] + # return:: [Hash] with possible keys type:: [String] ,category:: [String],features:: [Array] of feature, nodeinformation:: [Jabber::XData] + # check http://www.xmpp.org/extensions/xep-0060.html#entity for more infos + + # this is only for a xep <-> nodebrowser.rb understanding + alias get_metadata get_info + + def get_info(jid,node) + iq = Iq.new(:get,jid) + iq.from = @stream.jid + discoinfo = Discovery::IqQueryDiscoInfo.new + discoinfo.node = node + iq.add(discoinfo) + info = {} + @stream.send_with_id(iq) { |answer| + if answer.type == :result + + identity = answer.query.identity + info['type'] = identity.type + info['category'] = identity.category + + info['features'] = answer.query.features + +# i think its not needed - if you think so then delete it +# answer.query.each_element('identity') { |identity| +# info['type'] = identity.type +# info['category'] = identity.category +# } +# +# features = [] +# answer.query.each_element('feature') { |feature| +# features.push(feature) +# } +# info['features'] = features +# + answer.query.each_element('x') { |x| + info['nodeinformation'] = x + } + end + } + return info + end + + ## + # get type of node + # jid:: [Jabber::JID] + # node:: [String] + # + def type(jid,node) + info = get_info(jid,node) + return info['type'] + end + + ## + # get category of node + # jid:: [Jabber::JID] + # node:: [String] + # + def category(jid,node) + info = get_info(jid,node) + return info['category'] + end + + end #class + end #module +end #module diff --git a/vendor/xmpp4r-0.3.2/lib/xmpp4r/pubsub/helper/nodehelper.rb b/vendor/xmpp4r-0.3.2/lib/xmpp4r/pubsub/helper/nodehelper.rb new file mode 100644 index 000000000..d7a0daf20 --- /dev/null +++ b/vendor/xmpp4r-0.3.2/lib/xmpp4r/pubsub/helper/nodehelper.rb @@ -0,0 +1,153 @@ +# =XMPP4R - XMPP Library for Ruby +# License:: Ruby's license (see the LICENSE file) or GNU GPL, at your option. +# Website::http://home.gna.org/xmpp4r/ +# +# For a documentation of the retunvalues please look into the +# documentation of [Jabber::PubSub::ServiceHelper] +# This class is only a wrapper around [Jabber::PubSub::ServiceHelper] +# + + +require 'xmpp4r/pubsub/helper/servicehelper' +require 'xmpp4r/pubsub/helper/nodebrowser' + +module Jabber + module PubSub + class NodeHelper < ServiceHelper + + attr_reader :nodename + attr_reader :name + attr_reader :jiod + attr_reader :my_subscriptions + ## + # creates a new node + # new(client,service,nodename) + # stream:: [Jabber::Stream] + # jid:: [String] (jid of the pubsub service) + # nodename:: [String] + def initialize(stream,jid,nodename=nil,create_if_not_exist=true) + super(stream,jid) + @nodename = nodename + @jid = jid + @stream = client + + get_subscriptions + + if create_if_not_exist and not node_exist? + # if no nodename is given a instant node will created + # (if the service supports instant nodes) + @nodename = create_node + end + end + + ## + # creates the node + # create(configuration=nil) + # configuration:: [Jabber::XData] + def create_node(configuration=nil) + create(@nodename,configuration) + end + + ## + # get the configuration of the node + # get_configuration(configuration=nil) + # configuration:: [Jabber::XData] + def get_configuration(subid=nil) + get_options(@nodename,subid) + end + + ## + # set the configuration of the node + # set_configuration(configuration=nil) + # configuration:: [Jabber::XData] + # subid:: [String] default is nil + def set_configuration(configuration,subid=nil) + set_options(@nodename,configuration,subid) + end + + ## + # deletes the node + # delete + def delete_node + delete(@nodename) + end + + ## + # publishing content on this node + # publish_content(items) + # items:: [REXML::Element] + def publish_content(items) + publish(@nodename,items) + end + + ## + # gets all items from the node + # get_all_items + def get_all_items + items(@nodename) + end + + ## + # get a count of items + # get_items(count) + # count:: [Fixnum] + def get_items(count) + items(@nodename,count) + end + + ## + # get all node affiliations + # get_affiliations + def get_affiliations + affiliations + end + + ## + # get all subscriptions on this node + # get_subscriptions + def get_subscriptions + subscriptions(@nodename) + end + + ## + # get all subscribers subscribed on this node + # get_subscribers + def get_subscribers + @subscriptions = subscribers(@nodename) + end + + ## + # subscribe to this node + # do_subscribe + def do_subscribe + subscribe(@nodename) + get_subscriptions + end + + ## + # unsubscribe from this node + # do_unsubscribe(subid = nil) + # subid:: [String] + def do_unsubscribe(subid) + unsubscribe(@nodename,subid) + end + + ## + # purge all items from this node + # purge_items + def purge_items + purge(@nodename) + end + + private + + def node_exist? + nodebrowser = PubSub::NodeBrowser.new(@stream) + nodebrowser.nodes.include?(nodename) + end + def disco_info + end + + end #class + end #module +end #module diff --git a/vendor/xmpp4r-0.3.2/lib/xmpp4r/pubsub/helper/servicehelper.rb b/vendor/xmpp4r-0.3.2/lib/xmpp4r/pubsub/helper/servicehelper.rb new file mode 100644 index 000000000..b3c0a9c54 --- /dev/null +++ b/vendor/xmpp4r-0.3.2/lib/xmpp4r/pubsub/helper/servicehelper.rb @@ -0,0 +1,326 @@ +# =XMPP4R - XMPP Library for Ruby +# License:: Ruby's license (see the LICENSE file) or GNU GPL, at your option. +# Website::http://home.gna.org/xmpp4r/ +# +# It's recommented to read the XEP-0066 before you use this Helper. +# + +require 'xmpp4r/pubsub/iq/pubsub' +require 'xmpp4r/pubsub/stanzas/event' +require 'xmpp4r/pubsub/stanzas/item' +require 'xmpp4r/pubsub/stanzas/items' +require 'xmpp4r/pubsub/stanzas/subscription' +require 'xmpp4r/dataforms' + +module Jabber + module PubSub + ## + # A Helper representing a PubSub Service + class ServiceHelper + + ## + # Creates a new representation of a pubsub service + # client:: [Jabber::Stream] + # pubsubjid:: [String] or [Jabber::JID] + def initialize(client, pubsubjid) + @client = client + @pubsubjid = pubsubjid + @event_cbs = CallbackList.new + @client.add_message_callback(200,self) { |message| + handle_message(message) + } + end + + ## + # Create a new node on the pubsub service + # node:: [String] you node name - otherwise you get a automaticly generated one (in most cases) + # configure:: [Jabber::XMLStanza] if you want to configure you node (default nil) + # return:: [String] + def create(node=nil, configure=nil) + rnode = nil + iq = basic_pubsub_query(:set) + iq.pubsub.add(REXML::Element.new('create')).attributes['node'] = node + if configure + confele = REXML::Element.new('configure') + + if configure.type_of?(XMLStanza) + confele << configure + end + iq.pubsub.add(confele) + end + + @client.send_with_id(iq) do |reply| + if (create = reply.first_element('pubsub/create')) + rnode = create.attributes['node'] + end + true + end + + rnode + end + + ## + # Delete a pubsub node + # node:: [String] + # return:: true + def delete(node) + iq = basic_pubsub_query(:set,true) + iq.pubsub.add(REXML::Element.new('delete')).attributes['node'] = node + @client.send_with_id(iq) { |reply| + true + } + end + + ## + # NOTE: this method sends only one item per publish request because some services may not + # allow batch processing + # maybe this will changed in the future + # node:: [String] + # item:: [Jabber::PubSub::Item] + # return:: true + def publish(node,item) + iq = basic_pubsub_query(:set) + publish = iq.pubsub.add(REXML::Element.new('publish')) + publish.attributes['node'] = node + if item.kind_of?(Jabber::PubSub::Item) + publish.add(item) + @client.send_with_id(iq) { |reply| true } + end + end + + ## + # node:: [String] + # item:: [REXML::Element] + # id:: [String] + # return:: true + def publish_with_id(node,item,id) + if item.kind_of?(REXML::Element) + xmlitem = Jabber::PubSub::Item.new + xmlitem.id = id + xmlitem.add(item) + publish(node,xmlitem) + else + raise "given item is not a proper xml document or Jabber::PubSub::Item" + end + end + + ## + # gets all items from a pubsub node + # node:: [String] + # count:: [Fixnum] + # return:: [Hash] { id => [Jabber::PubSub::Item] } + def items(node,count=nil) + iq = basic_pubsub_query(:get) + items = Jabber::PubSub::Items.new + items.node = node + iq.pubsub.add(items) + res = nil + @client.send_with_id(iq) { |reply| + if reply.kind_of?(Iq) and reply.pubsub and reply.pubsub.first_element('items') + res = {} + reply.pubsub.first_element('items').each_element('item') do |item| + res[item.attributes['id']] = item.children.first if item.children.first + end + end + true + } + res + end + + ## + # shows the affiliations on a pubsub service + # return:: [Hash] of { node => symbol } + def affiliations + iq = basic_pubsub_query(:get) + iq.pubsub.add(REXML::Element.new('affiliations')) + res = nil + @client.send_with_id(iq) { |reply| + if reply.pubsub.first_element('affiliations') + res = {} + reply.pubsub.first_element('affiliations').each_element('affiliation') do |affiliation| + # TODO: This should be handled by an affiliation element class + aff = case affiliation.attributes['affiliation'] + when 'owner' then :owner + when 'publisher' then :publisher + when 'none' then :none + when 'outcast' then :outcast + else nil + end + res[affiliation.attributes['node']] = aff + end + end + true + } + res + end + + ## + # shows all subscriptions on the given node + # node:: [String], or nil for all + # return:: [Array] of [REXML::Element] + def subscriptions(node=nil) + iq = basic_pubsub_query(:get) + entities = iq.pubsub.add(REXML::Element.new('subscriptions')) + entities.attributes['node'] = node + res = nil + @client.send_with_id(iq) { |reply| + if reply.pubsub.first_element('subscriptions') + res = [] + reply.pubsub.first_element('subscriptions').each_element('subscription') { |subscription| + res << REXML::Element.new(subscription) + } + end + true + } + res + end + + ## + # shows all jids of subscribers of a node + # node:: [String] + # return:: [Array] of [String] + def subscribers(node) + res = [] + subscriptions(node).each { |sub| + res << sub.attributes['jid'] + } + res + end + + ## + # subscribe to a node + # node:: [String] + # return:: [Hash] of { attributename => value } + def subscribe(node) + iq = basic_pubsub_query(:set) + sub = REXML::Element.new('subscribe') + sub.attributes['node'] = node + sub.attributes['jid'] = @client.jid.strip + iq.pubsub.add(sub) + res = {} + @client.send_with_id(iq) do |reply| + pubsubanswer = reply.pubsub + if pubsubanswer.first_element('subscription') + pubsubanswer.each_element('subscription') { |element| + element.attributes.each { |name,value| res[name] = value } + } + end + true + end # @client.send_with_id(iq) + res + end + + ## + # Unsubscibe from a node with an optional subscription id + # + # May raise ErrorException + # node:: [String] + # subid:: [String] or nil + # return:: true + def unsubscribe(node,subid=nil) + iq = basic_pubsub_query(:set) + unsub = REXML::Element.new('unsubscribe') + unsub.attributes['node'] = node + unsub.attributes['jid'] = @client.jid.strip + unsub.attributes['subid'] = subid + iq.pubsub.add(unsub) + @client.send_with_id(iq) { |reply| true } # @client.send_with_id(iq) + end + + ## + # get options of a node + # node:: [String] + # subid:: [String] or nil + # return:: [Jabber::XData] + def get_options(node,subid=nil) + iq = basic_pubsub_query(:get) + opt = REXML::Element.new('options') + opt.attributes['node'] = node + opt.attributes['jid'] = @client.jid.strip + opt.attributes['subid'] = subid + iq.pubsub.add(opt) + ret = nil + @client.send_with_id(iq) { |reply| + reply.pubsub.options.first_element('x') { |xdata| + + ret = xdata if xdata.kind_of?(Jabber::XData) + + } + true + } + return ret + end + + ## + # set options for a node + # node:: [String] + # options:: [Jabber::XData] + # subid:: [String] or nil + # return:: true + def set_options(node,options,subid=nil) + iq = basic_pubsub_query(:set) + opt = REXML::Element.new('options') + opt.attributes['node'] = node + opt.attributes['jid'] = @client.jid.strip + opt.attributes['subid'] = subid + iq.pubsub.add(opt) + iq.pubsub.options.add(options) + @client.send_with_id(iq) { |reply| true } + end + + ## + # purges all items on a persist node + # node:: [String] + # return:: true + def purge(node) + iq = basic_pubsub_query(:set) + purge = REXML::Element.new('purge') + purge.attributes['node'] = node + iq.pubsub.add(purge) + @client.send_with_id(iq) { |reply| true } + end + + ## + # String representation + # result:: [String] The PubSub service's JID + def to_s + @pubsubjid.to_s + end + + ## + # Register callbacks for incoming events + # (i.e. Message stanzas containing) PubSub notifications + def add_event_callback(prio = 200, ref = nil, &block) + @event_cbs.add(prio, ref, block) + end + + private + + ## + # creates a basic pubsub iq + # basic_pubsub_query(type) + # type:: [Symbol] + def basic_pubsub_query(type,ownerusecase = false) + iq = Jabber::Iq::new(type,@pubsubjid) + if ownerusecase + iq.add(IqPubSubOwner.new) + else + iq.add(IqPubSub.new) + end + iq + end + + ## + # handling incoming events + # handle_message(message) + # message:: [Jabber::Message] + def handle_message(message) + if message.from == @pubsubjid and message.first_element('event').kind_of?(Jabber::PubSub::Event) + event = message.first_element('event') + @event_cbs.process(event) + end + end + + end + end +end diff --git a/vendor/xmpp4r-0.3.2/lib/xmpp4r/pubsub/iq/pubsub.rb b/vendor/xmpp4r-0.3.2/lib/xmpp4r/pubsub/iq/pubsub.rb new file mode 100644 index 000000000..d3f35018e --- /dev/null +++ b/vendor/xmpp4r-0.3.2/lib/xmpp4r/pubsub/iq/pubsub.rb @@ -0,0 +1,19 @@ +# =XMPP4R - XMPP Library for Ruby +# License:: Ruby's license (see the LICENSE file) or GNU GPL, at your option. +# Website::http://home.gna.org/xmpp4r/ + +require 'xmpp4r/iq' + +module Jabber + module PubSub + NS_PUBSUB = 'http://jabber.org/protocol/pubsub' + class IqPubSub < XMPPElement + name_xmlns 'pubsub', NS_PUBSUB + force_xmlns true + end + class IqPubSubOwner < XMPPElement + name_xmlns 'pubsub', NS_PUBSUB + '#owner' + force_xmlns true + end + end +end diff --git a/vendor/xmpp4r-0.3.2/lib/xmpp4r/pubsub/stanzas/event.rb b/vendor/xmpp4r-0.3.2/lib/xmpp4r/pubsub/stanzas/event.rb new file mode 100644 index 000000000..51cf77a26 --- /dev/null +++ b/vendor/xmpp4r-0.3.2/lib/xmpp4r/pubsub/stanzas/event.rb @@ -0,0 +1,49 @@ +# =XMPP4R - XMPP Library for Ruby +# License:: Ruby's license (see the LICENSE file) or GNU GPL, at your option. +# Website::http://home.gna.org/xmpp4r/ + +require 'xmpp4r/iq' + +module Jabber + module PubSub + + ## + # Event + # a publishing event + class Event < XMPPElement + name_xmlns 'event', NS_PUBSUB + '#event' + force_xmlns true + + ## + # return payload + def payload + elements + end + + ## + # add payload + # payload:: [REXML::Element] + def payload=(pl) + add_element = pl + end + + ## + # return the payload type + def event_type? + # each child of event + # this should interate only one time + each_element('./event/*') { |plelement| + case plelement.name + when 'collection' then return :collection + when 'configuration' then return :configuration + when 'delete' then return :delete + when 'items' then return :items + when 'purge' then return :purge + when 'subscription' then return :subscription + else return nil + end + } + end + end + end +end diff --git a/vendor/xmpp4r-0.3.2/lib/xmpp4r/pubsub/stanzas/item.rb b/vendor/xmpp4r-0.3.2/lib/xmpp4r/pubsub/stanzas/item.rb new file mode 100644 index 000000000..e6445c2b5 --- /dev/null +++ b/vendor/xmpp4r-0.3.2/lib/xmpp4r/pubsub/stanzas/item.rb @@ -0,0 +1,27 @@ +# =XMPP4R - XMPP Library for Ruby +# License:: Ruby's license (see the LICENSE file) or GNU GPL, at your option. +# Website::http://home.gna.org/xmpp4r/ + +require 'xmpp4r/iq' + +module Jabber + module PubSub + ## + # Item + # One PubSub Item + class Item < XMPPElement + name_xmlns 'item', NS_PUBSUB + force_xmlns true + def initialize(id=nil) + super() + attributes['id'] = id + end + def id + attributes['id'] + end + def id=(myid) + attributes['id'] = myid + end + end + end +end diff --git a/vendor/xmpp4r-0.3.2/lib/xmpp4r/pubsub/stanzas/items.rb b/vendor/xmpp4r-0.3.2/lib/xmpp4r/pubsub/stanzas/items.rb new file mode 100644 index 000000000..2bbb8e816 --- /dev/null +++ b/vendor/xmpp4r-0.3.2/lib/xmpp4r/pubsub/stanzas/items.rb @@ -0,0 +1,35 @@ +# =XMPP4R - XMPP Library for Ruby +# License:: Ruby's license (see the LICENSE file) or GNU GPL, at your option. +# Website::http://home.gna.org/xmpp4r/ + +require 'xmpp4r/iq' + +module Jabber + module PubSub + ## + # Items + # a collection of Items + class Items < XMPPElement + name_xmlns 'items', NS_PUBSUB + force_xmlns true + def node + attributes['node'] + end + def node=(mynodename) + attributes['node'] = mynodename + end + def subid + attributes['subid'] + end + def subid=(mysubid) + attributes['subid'] = mysubid + end + def max_items + attributes['max_items'] + end + def max_items=(mymaxitems) + attributes['max_items'] = mymaxitems + end + end + end +end diff --git a/vendor/xmpp4r-0.3.2/lib/xmpp4r/pubsub/stanzas/subscription.rb b/vendor/xmpp4r-0.3.2/lib/xmpp4r/pubsub/stanzas/subscription.rb new file mode 100644 index 000000000..87f1af336 --- /dev/null +++ b/vendor/xmpp4r-0.3.2/lib/xmpp4r/pubsub/stanzas/subscription.rb @@ -0,0 +1,58 @@ +# =XMPP4R - XMPP Library for Ruby +# License:: Ruby's license (see the LICENSE file) or GNU GPL, at your option. +# Website::http://home.gna.org/xmpp4r/ + +require 'xmpp4r/iq' + +module Jabber + module PubSub + ## + # Subscription + class Subscription < XMPPElement + name_xmlns 'subscription', NS_PUBSUB + def initialize(myjid=nil,mynode=nil,mysubid=nil,mysubscription=nil) + super(true) + jid = myjid + node = mynode + subid = mysubid + state = mysubscription + end + def jid + attributes['jid'] + end + def jid=(myjid) + attributes['jid'] = myjid + end + + def node + attributes['node'] + end + def node=(mynode) + attributes['node'] = mynode + end + + def subid + attributes['subid'] + end + def subid=(mysubid) + attributes['subid'] = mysubid + end + + def state + # each child of event + # this should interate only one time + case attributes['subscription'] + when 'none' then return :none + when 'pending' then return :pending + when 'subscribed' then return :subscribed + when 'unconfigured' then return :items + else return nil + end + end + def state=(mystate) + attributes['subscription'] = mystate + end + alias subscription state + end + end +end \ No newline at end of file diff --git a/vendor/xmpp4r-0.3.2/lib/xmpp4r/query.rb b/vendor/xmpp4r-0.3.2/lib/xmpp4r/query.rb new file mode 100644 index 000000000..9f9c1ddff --- /dev/null +++ b/vendor/xmpp4r-0.3.2/lib/xmpp4r/query.rb @@ -0,0 +1,15 @@ +# =XMPP4R - XMPP Library for Ruby +# License:: Ruby's license (see the LICENSE file) or GNU GPL, at your option. +# Website::http://home.gna.org/xmpp4r/ + +require 'xmpp4r/xmppelement' + +module Jabber + ## + # A class used to build/parse IQ Query requests/responses + # + class IqQuery < XMPPElement + name_xmlns 'query' + force_xmlns true + end +end diff --git a/vendor/xmpp4r-0.3.2/lib/xmpp4r/rexmladdons.rb b/vendor/xmpp4r-0.3.2/lib/xmpp4r/rexmladdons.rb new file mode 100644 index 000000000..167c7587e --- /dev/null +++ b/vendor/xmpp4r-0.3.2/lib/xmpp4r/rexmladdons.rb @@ -0,0 +1,123 @@ +# =XMPP4R - XMPP Library for Ruby +# License:: Ruby's license (see the LICENSE file) or GNU GPL, at your option. +# Website::http://home.gna.org/xmpp4r/ + +require 'rexml/document' +require 'rexml/parsers/xpathparser' +require 'rexml/source' + +# Turn $VERBOSE off to suppress warnings about redefinition +oldverbose = $VERBOSE +$VERBOSE = false + +# REXML module. This file only adds the following methods to the REXML module, to +# ease the coding: +# * replace_element_text +# * first_element +# * first_element_text +# * typed_add +# * import +# * self.import +# * delete_elements +# +# Further definitions are just copied from REXML out of Ruby-1.8.4 to solve issues +# with REXML in Ruby-1.8.2. +# +# The redefinitions of Text::normalize and Attribute#initialize address an issue +# where entities in element texts and attributes were not escaped. This modifies +# the behavious of REXML a bit but Sean Russell intends a similar behaviour for +# the future of REXML. +module REXML + # this class adds a few helper methods to REXML::Element + class Element + ## + # Replaces or add a child element of name e with text t. + def replace_element_text(e, t) + el = first_element(e) + if el.nil? + el = REXML::Element::new(e) + add_element(el) + end + if t + el.text = t + end + self + end + + ## + # Returns first element of name e + def first_element(e) + each_element(e) { |el| return el } + return nil + end + + ## + # Returns text of first element of name e + def first_element_text(e) + el = first_element(e) + if el + return el.text + else + return nil + end + end + + # This method does exactly the same thing as add(), but it can be + # overriden by subclasses to provide on-the-fly object creations. + # For example, if you import a REXML::Element of name 'plop', and you + # have a Plop class that subclasses REXML::Element, with typed_add you + # can get your REXML::Element to be "magically" converted to Plop. + def typed_add(e) + add(e) + end + + ## + # import this element's children and attributes + def import(xmlelement) + if @name and @name != xmlelement.name + raise "Trying to import an #{xmlelement.name} to a #{@name} !" + end + add_attributes(xmlelement.attributes.clone) + @context = xmlelement.context + xmlelement.each do |e| + if e.kind_of? REXML::Element + typed_add(e.deep_clone) + else # text element, probably. + add(e.clone) + end + end + self + end + + def self.import(xmlelement) + self.new(xmlelement.name).import(xmlelement) + end + + ## + # Deletes one or more children elements, + # not just one like REXML::Element#delete_element + def delete_elements(element) + while(delete_element(element)) do end + end + end +end + +# very dirty fix for the :progress problem in REXML from Ruby 1.8.3 +# http://www.germane-software.com/projects/rexml/ticket/34 +# the fix proposed in REXML changeset 1145 only fixes this for pipes, not for +# TCP sockets, so we have to keep this. +module REXML + class IOSource + def position + 0 + end + + def current_line + [0, 0, ""] + end + end +end + +# Restore the old $VERBOSE setting +$VERBOSE = oldverbose + diff --git a/vendor/xmpp4r-0.3.2/lib/xmpp4r/roster.rb b/vendor/xmpp4r-0.3.2/lib/xmpp4r/roster.rb new file mode 100644 index 000000000..2169cab64 --- /dev/null +++ b/vendor/xmpp4r-0.3.2/lib/xmpp4r/roster.rb @@ -0,0 +1,8 @@ +# =XMPP4R - XMPP Library for Ruby +# License:: Ruby's license (see the LICENSE file) or GNU GPL, at your option. +# Website::http://home.gna.org/xmpp4r/ + +require 'xmpp4r/roster/iq/roster.rb' +require 'xmpp4r/roster/helper/roster.rb' +require 'xmpp4r/roster/x/roster.rb' + diff --git a/vendor/xmpp4r-0.3.2/lib/xmpp4r/roster/helper/roster.rb b/vendor/xmpp4r-0.3.2/lib/xmpp4r/roster/helper/roster.rb new file mode 100644 index 000000000..73e1a42e8 --- /dev/null +++ b/vendor/xmpp4r-0.3.2/lib/xmpp4r/roster/helper/roster.rb @@ -0,0 +1,508 @@ +# =XMPP4R - XMPP Library for Ruby +# License:: Ruby's license (see the LICENSE file) or GNU GPL, at your option. +# Website::http://home.gna.org/xmpp4r/ + +require 'xmpp4r/callbacks' +require 'thread' +require 'xmpp4r/roster/iq/roster' + +module Jabber + module Roster + ## + # The Roster helper intercepts stanzas with Jabber::IqQueryRoster + # and stanzas, but provides cbs which allow the programmer + # to keep track of updates. + # + # A thread for any received stanza is spawned, so the user can invoke + # accept_subscription et al in the callback blocks, without stopping + # the current (= parser) thread when waiting for a reply. + class Helper + ## + # All items in your roster + # items:: [Hash] ([JID] => [Roster::Helper::RosterItem]) + attr_reader :items + + ## + # Initialize a new Roster helper + # + # Registers its cbs (prio = 120, ref = self) + # + # Request a roster + # (Remember to send initial presence afterwards!) + # + # The initialization will not wait for the roster being received, + # use add_query_callback to get notifyed when Roster::Helper#items + # has been filled. + def initialize(stream) + @stream = stream + @items = {} + @items_lock = Mutex.new + @query_cbs = CallbackList.new + @update_cbs = CallbackList.new + @presence_cbs = CallbackList.new + @subscription_cbs = CallbackList.new + @subscription_request_cbs = CallbackList.new + + # Register cbs + stream.add_iq_callback(120, self) { |iq| + if iq.query.kind_of?(IqQueryRoster) + Thread.new do + Thread.current.abort_on_exception = true + handle_iq_query_roster(iq) + end + + true + else + false + end + } + stream.add_presence_callback(120, self) { |pres| + Thread.new do + Thread.current.abort_on_exception = true + handle_presence(pres) + end + } + + # Request the roster + rosterget = Iq.new_rosterget + stream.send(rosterget) + end + + ## + # Add a callback to be called when a query has been processed + # + # Because update callbacks are called for each roster item, + # this may be appropriate to notify that *anything* has updated. + # + # Arguments for callback block: The received stanza + def add_query_callback(prio = 0, ref = nil, &block) + @query_cbs.add(prio, ref, block) + end + + ## + # Add a callback for Jabber::Roster::Helper::RosterItem updates + # + # Note that this will be called much after initialization + # for the answer of the initial roster request + # + # The block receives two objects: + # * the old Jabber::Roster::Helper::RosterItem + # * the new Jabber::Roster::Helper::RosterItem + def add_update_callback(prio = 0, ref = nil, &block) + @update_cbs.add(prio, ref, block) + end + + ## + # Add a callback for Jabber::Presence updates + # + # This will be called for stanzas for known RosterItems. + # Unknown JIDs may still pass and can be caught via Jabber::Stream#add_presence_callback. + # + # The block receives three objects: + # * the Jabber::Roster::Helper::RosterItem + # * the old Jabber::Presence (or nil) + # * the new Jabber::Presence (or nil) + def add_presence_callback(prio = 0, ref = nil, &block) + @presence_cbs.add(prio, ref, block) + end + + ## + # Add a callback for subscription updates, + # which will be called upon receiving a stanza + # with type: + # * :subscribed + # * :unsubscribe + # * :unsubscribed + # + # The block receives two objects: + # * the Jabber::Roster::Helper::RosterItem (or nil) + # * the stanza + def add_subscription_callback(prio = 0, ref = nil, &block) + @subscription_cbs.add(prio, ref, block) + end + + ## + # Add a callback for subscription requests, + # which will be called upon receiving a stanza + # + # The block receives two objects: + # * the Jabber::Roster::Helper::RosterItem (or nil) + # * the stanza + # + # Response to this event can be taken with accept_subscription + # and decline_subscription. + # + # Example usage: + # my_roster.add_subscription_request_callback do |item,presence| + # if accept_subscription_requests + # my_roster.accept_subscription(presence.from) + # else + # my_roster.decline_subscription(presence.from) + # end + # end + def add_subscription_request_callback(prio = 0, ref = nil, &block) + @subscription_request_cbs.add(prio, ref, block) + end + + private + + ## + # Handle received stanzas, + # used internally + def handle_iq_query_roster(iq) + # If the contains we just ignore that + # and assume an empty roster + iq.query.each_element('item') do |item| + olditem, newitem = nil, nil + + @items_lock.synchronize { + olditem = @items[item.jid] + + # Handle deletion of item + if item.subscription == :remove + @items.delete(item.jid) + else + newitem = @items[item.jid] = RosterItem.new(@stream).import(item) + end + } + @update_cbs.process(olditem, newitem) + end + + @query_cbs.process(iq) + end + + ## + # Handle received stanzas, + # used internally + def handle_presence(pres) + item = self[pres.from] + + if [:subscribed, :unsubscribe, :unsubscribed].include?(pres.type) + @subscription_cbs.process(item, pres) + true + + elsif pres.type == :subscribe + @subscription_request_cbs.process(item, pres) + true + + else + unless item.nil? + update_presence(item, pres) + true # Callback consumed stanza + else + false # Callback did not consume stanza + end + end + end + + ## + # Update the presence of an item, + # used internally + # + # Callbacks are called here + def update_presence(item, pres) + + # This requires special handling, to announce all resources offline + if pres.from.resource.nil? and pres.type == :error + oldpresences = [] + item.each_presence do |oldpres| + oldpresences << oldpres + end + + item.add_presence(pres) + oldpresences.each { |oldpres| + @presence_cbs.process(item, oldpres, pres) + } + else + oldpres = item.presence(pres.from).nil? ? + nil : + Presence.new.import(item.presence(pres.from)) + + item.add_presence(pres) + @presence_cbs.process(item, oldpres, pres) + end + end + + public + + ## + # Get an item by jid + # + # If not available tries to look for it with the resource stripped + def [](jid) + jid = JID.new(jid) unless jid.kind_of? JID + + @items_lock.synchronize { + if @items.has_key?(jid) + @items[jid] + elsif @items.has_key?(jid.strip) + @items[jid.strip] + else + nil + end + } + end + + ## + # Returns the list of RosterItems which, stripped, are equal to the + # one you are looking for. + def find(jid) + jid = JID.new(jid) unless jid.kind_of? JID + + j = jid.strip + l = {} + @items_lock.synchronize { + @items.each_pair do |k, v| + l[k] = v if k.strip == j + end + } + l + end + + ## + # Groups in this Roster, + # sorted by name + # + # Contains +nil+ if there are ungrouped items + # result:: [Array] containing group names (String) + def groups + res = [] + @items_lock.synchronize { + @items.each_pair do |jid,item| + res += item.groups + res += [nil] if item.groups == [] + end + } + res.uniq.sort { |a,b| a.to_s <=> b.to_s } + end + + ## + # Get items in a group + # + # When group is nil, return ungrouped items + # group:: [String] Group name + # result:: Array of [RosterItem] + def find_by_group(group) + res = [] + @items_lock.synchronize { + @items.each_pair do |jid,item| + res.push(item) if item.groups.include?(group) + res.push(item) if item.groups == [] and group.nil? + end + } + res + end + + ## + # Add a user to your roster + # + # Threading is encouraged as the function waits for + # a result. ErrorException is thrown upon error. + # + # See Jabber::Roster::Helper::RosterItem#subscribe for details + # about subscribing. (This method isn't used here but the + # same functionality applies.) + # + # If the item is already in the local roster + # it will simply send itself + # jid:: [JID] to add + # iname:: [String] Optional item name + # subscribe:: [Boolean] Whether to subscribe to this jid + def add(jid, iname=nil, subscribe=false) + if self[jid] + self[jid].send + else + request = Iq.new_rosterset + request.query.add(Jabber::Roster::RosterItem.new(jid, iname)) + @stream.send_with_id(request) { true } + # Adding to list is handled by handle_iq_query_roster + end + + if subscribe + # Actually the item *should* already be known now, + # but we do it manually to exclude conditions. + pres = Presence.new.set_type(:subscribe).set_to(jid.strip) + @stream.send(pres) + end + end + + ## + # Accept a subscription request + # * Sends a stanza + # * Adds the contact to your roster + # jid:: [JID] of contact + # iname:: [String] Optional roster item name + def accept_subscription(jid, iname=nil) + pres = Presence.new.set_type(:subscribed).set_to(jid.strip) + @stream.send(pres) + + unless self[jid.strip] + request = Iq.new_rosterset + request.query.add(Jabber::Roster::RosterItem.new(jid.strip, iname)) + @stream.send_with_id(request) { true } + end + end + + ## + # Decline a subscription request + # * Sends a stanza + def decline_subscription(jid) + pres = Presence.new.set_type(:unsubscribed).set_to(jid.strip) + @stream.send(pres) + end + + ## + # These are extensions to RosterItem to carry presence information. + # This information is *not* stored in XML! + class RosterItem < Jabber::Roster::RosterItem + ## + # Tracked (online) presences of this RosterItem + attr_reader :presences + + ## + # Initialize an empty RosterItem + def initialize(stream) + super() + @stream = stream + @presences = [] + @presences_lock = Mutex.new + end + + ## + # Send the updated RosterItem to the server, + # i.e. if you modified iname, groups, ... + def send + request = Iq.new_rosterset + request.query.add(self) + @stream.send(request) + end + + ## + # Remove item + # + # This cancels both subscription *from* the contact to you + # and from you *to* the contact. + # + # The methods waits for a roster push from the server (success) + # or throws ErrorException upon failure. + def remove + request = Iq.new_rosterset + request.query.add(Jabber::Roster::RosterItem.new(jid, nil, :remove)) + @stream.send_with_id(request) { true } + # Removing from list is handled by Roster#handle_iq_query_roster + end + + ## + # Is any presence of this person on-line? + # + # (Or is there any presence? Unavailable presences are + # deleted.) + def online? + @presences_lock.synchronize { + @presences.select { |pres| + pres.type.nil? + }.size > 0 + } + end + + ## + # Iterate through all received stanzas + def each_presence(&block) + # Don't lock here, we don't know what block does... + @presences.each { |pres| + yield(pres) + } + end + + ## + # Get specific presence + # jid:: [JID] Full JID + def presence(jid) + @presences_lock.synchronize { + @presences.each { |pres| + return(pres) if pres.from == jid + } + } + nil + end + + ## + # Add presence and sort presences + # (unless type is :unavailable or :error) + # + # This overwrites previous stanzas with the same destination + # JID to keep track of resources. Presence stanzas with + # type == :unavailable or type == :error will + # be deleted as this indicates that this resource has gone + # offline. + # + # If type == :error and the presence's origin has no + # specific resource the contact is treated completely offline. + def add_presence(newpres) + @presences_lock.synchronize { + # Delete old presences with the same JID + @presences.delete_if do |pres| + pres.from == newpres.from or pres.from.resource.nil? + end + + if newpres.type == :error and newpres.from.resource.nil? + # Replace by single error presence + @presences = [newpres] + else + # Add new presence + @presences.push(newpres) + end + + @presences.sort! + } + end + + ## + # Send subscription request to the user + # + # The block given to Jabber::Roster::Roster#add_update_callback will + # be called, carrying the RosterItem with ask="subscribe" + # + # This function returns immediately after sending the subscription + # request and will not wait of approval or declination as it may + # take months for the contact to decide. ;-) + def subscribe + pres = Presence.new.set_type(:subscribe).set_to(jid.strip) + @stream.send(pres) + end + + ## + # Unsubscribe from a contact's presence + # + # This method waits for a presence with type='unsubscribed' + # from the contact. It may throw ErrorException upon failure. + # + # subscription attribute of the item is *from* or *none* + # afterwards. As long as you don't remove that item and + # subscription='from' the contact is subscribed to your + # presence. + def unsubscribe + pres = Presence.new.set_type(:unsubscribe).set_to(jid.strip) + @stream.send(pres) { |answer| + answer.type == :unsubscribed and + answer.from.strip == pres.to + } + end + + ## + # Deny the contact to see your presence. + # + # This method will not wait and returns immediately + # as you will need no confirmation for this action. + # + # Though, you will get a roster update for that item, + # carrying either subscription='to' or 'none'. + def cancel_subscription + pres = Presence.new.set_type(:unsubscribed).set_to(jid) + @stream.send(pres) + end + end + end #Class Roster + end #Module Roster +end #Module Jabber + diff --git a/vendor/xmpp4r-0.3.2/lib/xmpp4r/roster/iq/roster.rb b/vendor/xmpp4r-0.3.2/lib/xmpp4r/roster/iq/roster.rb new file mode 100755 index 000000000..10b220453 --- /dev/null +++ b/vendor/xmpp4r-0.3.2/lib/xmpp4r/roster/iq/roster.rb @@ -0,0 +1,215 @@ +# =XMPP4R - XMPP Library for Ruby +# License:: Ruby's license (see the LICENSE file) or GNU GPL, at your option. +# Website::http://home.gna.org/xmpp4r/ + +require 'xmpp4r/query' + +module Jabber + module Roster + ## + # Class for handling roster updates + # + # You must do 'client.send(Iq.new_rosterget)' or else you will + # have nothing to put in receive_iq() + # + # You must require 'xmpp4r/rosterquery' to use this class + # as its functionality is not needed for a working XMPP implementation. + # This will make [IqQuery] convert all Queries with namespace 'jabber:iq:roster' + # to [IqQueryRoster] + # + # This contains multiple children. See RosterItem. + class IqQueryRoster < IqQuery + name_xmlns 'query', 'jabber:iq:roster' + + ## + # Iterate through all items + # &block:: Yield for every [RosterItem] + def each(&block) + each_element { |item| + # XPath won't work here as it's missing a prefix... + yield(item) if item.kind_of?(RosterItem) + } + end + + ## + # Get roster item by JID + # jid:: [JID] or [Nil] + # result:: [RosterItem] + def [](jid) + each { |item| + return(item) if item.jid == jid + } + nil + end + + ## + # Get all items + # result:: [Array] of [RosterItem] + def to_a + a = [] + each { |item| + a.push(item) + } + a + end + + ## + # Update roster by stanza + # (to be fed by an iq_callback) + # iq:: [Iq] Containing new roster + # filter:: [Boolean] If false import non-roster-like results too + def receive_iq(iq, filter=true) + if filter && (((iq.type != :set) && (iq.type != :result)) || (iq.queryns != 'jabber:iq:roster')) + return + end + + import(iq.query) + end + + ## + # Output for "p" + # + # JIDs of all contained [RosterItem] elements are joined with a comma + # result:: [String] + def inspect + jids = to_a.collect { |item| item.jid.inspect } + jids.join(', ') + end + end + + ## + # Class containing the elements of the roster + # + # The 'name' attribute has been renamed to 'iname' here + # as 'name' is already used by REXML::Element for the + # element's name. It's still name='...' in XML. + class RosterItem < XMPPElement + name_xmlns 'item', 'jabber:iq:roster' + + ## + # Construct a new roster item + # jid:: [JID] Jabber ID + # iname:: [String] Name in the roster + # subscription:: [Symbol] Type of subscription (see RosterItem#subscription=) + # ask:: [Symbol] or [Nil] Can be :subscribe + def initialize(jid=nil, iname=nil, subscription=nil, ask=nil) + super() + self.jid = jid + self.iname = iname + self.subscription = subscription + self.ask = ask + end + + ## + # Get name of roster item + # + # names can be set by the roster's owner himself + # return:: [String] + def iname + attributes['name'] + end + + ## + # Set name of roster item + # val:: [String] Name for this item + def iname=(val) + attributes['name'] = val + end + + ## + # Get JID of roster item + # Resource of the JID will _not_ be stripped + # return:: [JID] + def jid + (a = attributes['jid']) ? JID::new(a) : nil + end + + ## + # Set JID of roster item + # val:: [JID] or nil + def jid=(val) + attributes['jid'] = val.nil? ? nil : val.to_s + end + + ## + # Get subscription type of roster item + # result:: [Symbol] or [Nil] The following values are valid according to RFC3921: + # * :both + # * :from + # * :none + # * :remove + # * :to + def subscription + case attributes['subscription'] + when 'both' then :both + when 'from' then :from + when 'none' then :none + when 'remove' then :remove + when 'to' then :to + else nil + end + end + + ## + # Set subscription type of roster item + # val:: [Symbol] or [Nil] See subscription for possible Symbols + def subscription=(val) + case val + when :both then attributes['subscription'] = 'both' + when :from then attributes['subscription'] = 'from' + when :none then attributes['subscription'] = 'none' + when :remove then attributes['subscription'] = 'remove' + when :to then attributes['subscription'] = 'to' + else attributes['subscription'] = nil + end + end + + ## + # Get if asking for subscription + # result:: [Symbol] nil or :subscribe + def ask + case attributes['ask'] + when 'subscribe' then :subscribe + else nil + end + end + + ## + # Set if asking for subscription + # val:: [Symbol] nil or :subscribe + def ask=(val) + case val + when :subscribe then attributes['ask'] = 'subscribe' + else attributes['ask'] = nil + end + end + + ## + # Get groups the item belongs to + # result:: [Array] of [String] The groups + def groups + result = [] + each_element('group') { |group| + result.push(group.text) + } + result.uniq + end + + ## + # Set groups the item belongs to, + # deletes old groups first. + # + # See JEP 0083 for nested groups + # ary:: [Array] New groups, duplicate values will be removed + def groups=(ary) + # Delete old group elements + delete_elements('group') + + # Add new group elements + ary.uniq.each { |group| + add_element('group').text = group + } + end + end + end #Module Roster +end #Module Jabber diff --git a/vendor/xmpp4r-0.3.2/lib/xmpp4r/roster/x/roster.rb b/vendor/xmpp4r-0.3.2/lib/xmpp4r/roster/x/roster.rb new file mode 100755 index 000000000..c0f60fff0 --- /dev/null +++ b/vendor/xmpp4r-0.3.2/lib/xmpp4r/roster/x/roster.rb @@ -0,0 +1,138 @@ +# =XMPP4R - XMPP Library for Ruby +# License:: Ruby's license (see the LICENSE file) or GNU GPL, at your option. +# Website::http://home.gna.org/xmpp4r/ + +require 'xmpp4r/x' +require 'xmpp4r/jid' + +module Jabber + module Roster + ## + # Implementation of JEP-0144 + # for + # attached to stanzas + # + # Should be backwards compatible to JEP-0093, + # as only action attribute of roster items are missing there. + # Pay attention to the namespace which is jabber:x:roster + # for JEP-0093! + class XRoster < X + name_xmlns 'x', 'jabber:x:roster' + end #Class XRoster + + class RosterX < XRoster + name_xmlns 'x', 'http://jabber.org/protocol/rosterx' + end + + ## + # Class containing an element + # + # The 'name' attribute has been renamed to 'iname' here + # as 'name' is already used by REXML::Element for the + # element's name. It's still name='...' in XML. + # + # This is all a bit analoguous to Jabber::RosterItem, used by + # Jabber::IqQueryRoster. But this class lacks the subscription and + # ask attributes. + class XRosterItem < XMPPElement + name_xmlns 'item', 'jabber:x:roster' + + ## + # Construct a new roster item + # jid:: [JID] Jabber ID + # iname:: [String] Name in the roster + def initialize(jid=nil, iname=nil) + super() + self.jid = jid + self.iname = iname + end + + ## + # Get name of roster item + # + # names can be set by the roster's owner himself + # return:: [String] + def iname + attributes['name'] + end + + ## + # Set name of roster item + # val:: [String] Name for this item + def iname=(val) + attributes['name'] = val + end + + ## + # Get JID of roster item + # Resource of the JID will _not_ be stripped + # return:: [JID] + def jid + JID::new(attributes['jid']) + end + + ## + # Set JID of roster item + # val:: [JID] or nil + def jid=(val) + attributes['jid'] = val.nil? ? nil : val.to_s + end + + ## + # Get action for this roster item + # * :add + # * :modify + # * :delete + # result:: [Symbol] (defaults to :add according to JEP-0144) + def action + case attributes['action'] + when 'modify' then :modify + when 'delete' then :delete + else :add + end + end + + ## + # Set action for this roster item + # (see action) + def action=(a) + case a + when :modify then attributes['action'] = 'modify' + when :delete then attributes['action'] = 'delete' + else attributes['action'] = 'add' + end + end + + ## + # Get groups the item belongs to + # result:: [Array] of [String] The groups + def groups + result = [] + each_element('group') { |group| + result.push(group.text) + } + result + end + + ## + # Set groups the item belongs to, + # deletes old groups first. + # + # See JEP 0083 for nested groups + # ary:: [Array] New groups, duplicate values will be removed + def groups=(ary) + # Delete old group elements + delete_elements('group') + + # Add new group elements + ary.uniq.each { |group| + add_element('group').text = group + } + end + end #Class XRosterItem + + class RosterXItem < XRosterItem + name_xmlns 'item', 'http://jabber.org/protocol/rosterx' + end + end #Module Roster +end #Module Jabber diff --git a/vendor/xmpp4r-0.3.2/lib/xmpp4r/rpc.rb b/vendor/xmpp4r-0.3.2/lib/xmpp4r/rpc.rb new file mode 100644 index 000000000..584680068 --- /dev/null +++ b/vendor/xmpp4r-0.3.2/lib/xmpp4r/rpc.rb @@ -0,0 +1,2 @@ +require 'xmpp4r/rpc/helper/client' +require 'xmpp4r/rpc/helper/server' diff --git a/vendor/xmpp4r-0.3.2/lib/xmpp4r/rpc/helper/client.rb b/vendor/xmpp4r-0.3.2/lib/xmpp4r/rpc/helper/client.rb new file mode 100644 index 000000000..6b31c0c0a --- /dev/null +++ b/vendor/xmpp4r-0.3.2/lib/xmpp4r/rpc/helper/client.rb @@ -0,0 +1,114 @@ +require 'xmlrpc/server' +require 'xmlrpc/parser' +require 'xmlrpc/create' +require 'xmlrpc/config' +require 'xmlrpc/utils' # ParserWriterChooseMixin + +require 'xmpp4r/dataforms/x/data' +require 'xmpp4r/rpc/iq/rpc' +require 'xmpp4r/rpc/helper/xmlrpcaddons' + +module Jabber + module RPC + + ## + # XMLRPC Client + class Client + + include XMLRPC::ParserWriterChooseMixin + include XMLRPC::ParseContentType + + attr_accessor :my_jid + + ## + # xmppstream + # stream:: [Stream] + # jid where you want to send the rpc requests to + # jid:: [JID] rpcserver@jabberserver/ressource + def initialize(stream,jid) + @jid = JID.new(jid) + @my_jid = stream.jid + @stream = stream + @parser = nil + @create = XMLRPC::Create.new + end + + def call(method, *args) + ok, param = call2(method, *args) + if ok + param + else + raise param + end + end + + def call2(method, *args) + request = @create.methodCall(method, *args) + data = do_rpc(request) + parser().parseMethodResponse(data) + end + + ## + # adds multi rpcalls to the query + # methods:: [Array] + def multicall(*methods) + ok, params = multicall2(*methods) + if ok + params + else + raise params + end + end + + ## + # generate a multicall + # methods:: [Array] + def multicall2(*methods) + gen_multicall(methods) + end + + def do_rpc(xmlrpc) + iq = Iq.new(:set, @jid) + iq.from = @my_jid + iq.id = IdGenerator::generate_id + rpcquery = iq.add(IqQueryRPC.new) + rpcquery.typed_add(xmlrpc) + + result = nil + @stream.send_with_id(iq) { |iqreply| + if iqreply.type == :result and iqreply.query.kind_of?(IqQueryRPC) + result = iqreply.query.to_s + true + else + false + end + } + + result + end + + private + + def gen_multicall(methods=[]) + ok, params = call2("system.multicall", + methods.collect { |m| {'methodName' => m[0], 'params' => m[1..-1]} } + ) + + if ok + params = params.collect{ |param| + if param.is_a? Array + param[0] + elsif param.is_a? Hash + XMLRPC::FaultException.new(param["faultCode"], param["faultString"]) + else + raise "Wrong multicall return value" + end + } + end + + return ok, params + end + end + end # Helpers +end # Jabber + diff --git a/vendor/xmpp4r-0.3.2/lib/xmpp4r/rpc/helper/server.rb b/vendor/xmpp4r-0.3.2/lib/xmpp4r/rpc/helper/server.rb new file mode 100644 index 000000000..45d342036 --- /dev/null +++ b/vendor/xmpp4r-0.3.2/lib/xmpp4r/rpc/helper/server.rb @@ -0,0 +1,75 @@ +require 'xmlrpc/server' +require 'xmlrpc/parser' +require 'xmlrpc/create' +require 'xmlrpc/config' +require 'xmlrpc/utils' # ParserWriterChooseMixin + +require 'xmpp4r/dataforms/x/data' +require 'xmpp4r/rpc/iq/rpc' +require 'xmpp4r/rpc/helper/xmlrpcaddons' + +module Jabber + module RPC + + ## + # XMLRPC Server + class Server < XMLRPC::BasicServer + + include XMLRPC::ParserWriterChooseMixin + include XMLRPC::ParseContentType + + ## + # new - creates a new server + # + def initialize(stream,class_delim=".") + super(class_delim) + @stream = stream + @stream.add_iq_callback(120,"Helpers::RPCServer") { |iq| + if iq.type == :set and iq.type != :result + handle_iq(iq) + true + else + false + end + } + end + + ## + # handles incoming iqs + # iq:: [Jabber::IQ] - the jabber iq + def handle_iq(iq) + if iq.type == :set + if iq.query.kind_of?(IqQueryRPC) + data = iq.query + response = IqQueryRPC.new + data.elements.each { |rpc| + if rpc + response.typed_add(handle_rpc_requests(rpc)) + end + } + + respiq = iq.answer(false) + respiq.type = :result + respiq.add(response) + @stream.send(respiq) + end + end + end + + private + + ## + # handles the rpc requests + # takes rpcdata:: [String] + def handle_rpc_requests(rpcdata) + resp = process(rpcdata.to_s) + if resp == nil or resp.size <= 0 + raise Jabber::Error.new(:forbidden) + else + return resp + end + end + end # RPCServer + end # Helpers +end # Jabber + diff --git a/vendor/xmpp4r-0.3.2/lib/xmpp4r/rpc/helper/xmlrpcaddons.rb b/vendor/xmpp4r-0.3.2/lib/xmpp4r/rpc/helper/xmlrpcaddons.rb new file mode 100644 index 000000000..7a52bb161 --- /dev/null +++ b/vendor/xmpp4r-0.3.2/lib/xmpp4r/rpc/helper/xmlrpcaddons.rb @@ -0,0 +1,57 @@ +require "xmlrpc/parser" +require "xmlrpc/create" +require "xmlrpc/config" +require "xmlrpc/utils" # ParserWriterChooseMixin + + +module XMLRPC + class Create + ## + # create a Method Call + # name:: [String] name of the method + # params:: [Array] params of the method as a array + def methodCall(name, *params) + name = name.to_s + + if name !~ /[a-zA-Z0-9_.:\/]+/ + raise ArgumentError, "Wrong XML-RPC method-name" + end + + parameter = params.collect { |param| + @writer.ele("param", conv2value(param)) + } + + tree = @writer.document( + @writer.ele("methodCall", + @writer.tag("methodName", name), + @writer.ele("params", *parameter) + ) + ) + + @writer.document_to_str(tree) + "\n" + end + ## + # create a response to a method call + # is_ret:: [TrueClass] is this a return (true) or a error (false) + # params:: [Array] a array of params + + def methodResponse(is_ret, *params) + + if is_ret + resp = params.collect do |param| + @writer.ele("param", conv2value(param)) + end + + resp = [@writer.ele("params", *resp)] + else + if params.size != 1 or params[0] === XMLRPC::FaultException + raise ArgumentError, "no valid fault-structure given" + end + resp = @writer.ele("fault", conv2value(params[0].to_h)) + end + + tree = @writer.document(@writer.ele("methodResponse", resp)) + @writer.document_to_str(tree) + "\n" + end + end +end diff --git a/vendor/xmpp4r-0.3.2/lib/xmpp4r/rpc/iq/rpc.rb b/vendor/xmpp4r-0.3.2/lib/xmpp4r/rpc/iq/rpc.rb new file mode 100644 index 000000000..fd373812d --- /dev/null +++ b/vendor/xmpp4r-0.3.2/lib/xmpp4r/rpc/iq/rpc.rb @@ -0,0 +1,24 @@ +# =XMPP4R - XMPP Library for Ruby +# License:: Ruby's license (see the LICENSE file) or GNU GPL, at your option. +# Website::http://home.gna.org/xmpp4r/ + +require 'xmpp4r/query' + +module Jabber + module RPC + class IqQueryRPC < IqQuery + NS_RPC = 'jabber:iq:rpc' + name_xmlns 'query', NS_RPC + + # TODO: Is typed_add with a String right here? + def typed_add(e) + if e.kind_of? String + typed_add(REXML::Document.new(e).root) + else + super + end + end + end + end +end + diff --git a/vendor/xmpp4r-0.3.2/lib/xmpp4r/sasl.rb b/vendor/xmpp4r-0.3.2/lib/xmpp4r/sasl.rb new file mode 100644 index 000000000..915400991 --- /dev/null +++ b/vendor/xmpp4r-0.3.2/lib/xmpp4r/sasl.rb @@ -0,0 +1,216 @@ +# =XMPP4R - XMPP Library for Ruby +# License:: Ruby's license (see the LICENSE file) or GNU GPL, at your option. +# Website::http://home.gna.org/xmpp4r/ + +require 'base64' +require 'digest/md5' + +module Jabber + ## + # Helpers for SASL authentication (RFC2222) + # + # You might not need to use them directly, they are + # invoked by Jabber::Client#auth + module SASL + NS_SASL = 'urn:ietf:params:xml:ns:xmpp-sasl' + + ## + # Factory function to obtain a SASL helper for the specified mechanism + def SASL::new(stream, mechanism) + case mechanism + when 'DIGEST-MD5' + DigestMD5.new(stream) + when 'PLAIN' + Plain.new(stream) + else + raise "Unknown SASL mechanism: #{mechanism}" + end + end + + ## + # SASL mechanism base class (stub) + class Base + def initialize(stream) + @stream = stream + end + + private + + def generate_auth(mechanism, text=nil) + auth = REXML::Element.new 'auth' + auth.add_namespace NS_SASL + auth.attributes['mechanism'] = mechanism + auth.text = text + auth + end + + def generate_nonce + Digest::MD5.hexdigest(Time.new.to_f.to_s) + end + end + + ## + # SASL PLAIN authentication helper (RFC2595) + class Plain < Base + ## + # Authenticate via sending password in clear-text + def auth(password) + auth_text = "#{@stream.jid.strip}\x00#{@stream.jid.node}\x00#{password}" + error = nil + @stream.send(generate_auth('PLAIN', Base64::encode64(auth_text).gsub(/\s/, ''))) { |reply| + if reply.name != 'success' + error = reply.first_element(nil).name + end + true + } + + raise error if error + end + end + + ## + # SASL DIGEST-MD5 authentication helper (RFC2831) + class DigestMD5 < Base + ## + # Sends the wished auth mechanism and wait for a challenge + # + # (proceed with DigestMD5#auth) + def initialize(stream) + super + + challenge = {} + error = nil + @stream.send(generate_auth('DIGEST-MD5')) { |reply| + if reply.name == 'challenge' and reply.namespace == NS_SASL + challenge = decode_challenge(reply.text) + else + error = reply.first_element(nil).name + end + true + } + raise error if error + + @nonce = challenge['nonce'] + @realm = challenge['realm'] + end + + def decode_challenge(challenge) + text = Base64::decode64(challenge) + res = {} + + state = :key + key = '' + value = '' + + text.scan(/./) do |ch| + if state == :key + if ch == '=' + state = :value + else + key += ch + end + + elsif state == :value + if ch == ',' + res[key] = value + key = '' + value = '' + state = :key + elsif ch == '"' and value == '' + state = :quote + else + value += ch + end + + elsif state == :quote + if ch == '"' + state = :value + else + value += ch + end + end + end + res[key] = value unless key == '' + + Jabber::debuglog("SASL DIGEST-MD5 challenge:\n#{text.inspect}\n#{res.inspect}") + + res + end + + ## + # * Send a response + # * Wait for the server's challenge (which aren't checked) + # * Send a blind response to the server's challenge + def auth(password) + response = {} + response['nonce'] = @nonce + response['charset'] = 'utf-8' + response['username'] = @stream.jid.node + response['realm'] = @realm || @stream.jid.domain + response['cnonce'] = generate_nonce + response['nc'] = '00000001' + response['qop'] = 'auth' + response['digest-uri'] = "xmpp/#{@stream.jid.domain}" + response['response'] = response_value(@stream.jid.node, @stream.jid.domain, response['digest-uri'], password, @nonce, response['cnonce'], response['qop']) + response.each { |key,value| + unless %w(nc qop response charset).include? key + response[key] = "\"#{value}\"" + end + } + + response_text = response.collect { |k,v| "#{k}=#{v}" }.join(',') + Jabber::debuglog("SASL DIGEST-MD5 response:\n#{response_text}") + + r = REXML::Element.new('response') + r.add_namespace NS_SASL + r.text = Base64::encode64(response_text).gsub(/\s/, '') + + success_already = false + error = nil + @stream.send(r) { |reply| + if reply.name == 'success' + success_already = true + elsif reply.name != 'challenge' + error = reply.first_element(nil).name + end + true + } + + return if success_already + raise error if error + + # TODO: check the challenge from the server + + r.text = nil + @stream.send(r) { |reply| + if reply.name != 'success' + error = reply.first_element(nil).name + end + true + } + + raise error if error + end + + private + + ## + # Function from RFC2831 + def h(s); Digest::MD5.digest(s); end + ## + # Function from RFC2831 + def hh(s); Digest::MD5.hexdigest(s); end + + ## + # Calculate the value for the response field + def response_value(username, realm, digest_uri, passwd, nonce, cnonce, qop) + a1_h = h("#{username}:#{realm}:#{passwd}") + a1 = "#{a1_h}:#{nonce}:#{cnonce}" + #a2 = "AUTHENTICATE:#{digest_uri}#{(qop == 'auth') ? '' : ':00000000000000000000000000000000'}" + a2 = "AUTHENTICATE:#{digest_uri}" + + hh("#{hh(a1)}:#{nonce}:00000001:#{cnonce}:#{qop}:#{hh(a2)}") + end + end + end +end diff --git a/vendor/xmpp4r-0.3.2/lib/xmpp4r/semaphore.rb b/vendor/xmpp4r-0.3.2/lib/xmpp4r/semaphore.rb new file mode 100644 index 000000000..23e7e4e41 --- /dev/null +++ b/vendor/xmpp4r-0.3.2/lib/xmpp4r/semaphore.rb @@ -0,0 +1,38 @@ +# =XMPP4R - XMPP Library for Ruby +# License:: Ruby's license (see the LICENSE file) or GNU GPL, at your option. +# Website::http://home.gna.org/xmpp4r/ + +module Jabber + ## + # This class implements semaphore for threads synchronization. + class Semaphore + + ## + # Initialize new semaphore + # + # val:: [Integer] number of threads, that can enter to section + def initialize(val=0) + @tickets = val + @lock = Mutex.new + @cond = ConditionVariable.new + end + + ## + # Waits until are available some free tickets + def wait + @lock.synchronize { + @cond.wait(@lock) while !(@tickets > 0) + @tickets -= 1 + } + end + + ## + # Unlocks guarded section, increments number of free tickets + def run + @lock.synchronize { + @tickets += 1 + @cond.signal + } + end + end +end diff --git a/vendor/xmpp4r-0.3.2/lib/xmpp4r/stream.rb b/vendor/xmpp4r-0.3.2/lib/xmpp4r/stream.rb new file mode 100644 index 000000000..a3b6e27a9 --- /dev/null +++ b/vendor/xmpp4r-0.3.2/lib/xmpp4r/stream.rb @@ -0,0 +1,502 @@ +# =XMPP4R - XMPP Library for Ruby +# License:: Ruby's license (see the LICENSE file) or GNU GPL, at your option. +# Website::http://home.gna.org/xmpp4r/ + +require 'xmpp4r/callbacks' +require 'socket' +require 'thread' +require 'xmpp4r/semaphore' +require 'xmpp4r/streamparser' +require 'xmpp4r/presence' +require 'xmpp4r/message' +require 'xmpp4r/iq' +require 'xmpp4r/errorexception' +require 'xmpp4r/debuglog' +require 'xmpp4r/idgenerator' + +module Jabber + ## + # The stream class manages a connection stream (a file descriptor using which + # XML messages are read and sent) + # + # You may register callbacks for the three Jabber stanzas + # (message, presence and iq) and use the send and send_with_id + # methods. + # + # To ensure the order of received stanzas, callback blocks are + # launched in the parser thread. If further blocking operations + # are intended in those callbacks, run your own thread there. + class Stream + DISCONNECTED = 1 + CONNECTED = 2 + + # file descriptor used + attr_reader :fd + + # connection status + attr_reader :status + + ## + # Create a new stream + # (just initializes) + def initialize(threaded = true) + unless threaded + raise "Non-threaded mode was removed from XMPP4R." + end + @fd = nil + @status = DISCONNECTED + @xmlcbs = CallbackList::new + @stanzacbs = CallbackList::new + @messagecbs = CallbackList::new + @iqcbs = CallbackList::new + @presencecbs = CallbackList::new + @send_lock = Mutex.new + @last_send = Time.now + @exception_block = nil + @threadblocks = [] + @wakeup_thread = nil + @streamid = nil + @streamns = 'jabber:client' + @features_sem = Semaphore.new + @parser_thread = nil + end + + ## + # Start the XML parser on the fd + def start(fd) + @stream_mechanisms = [] + @stream_features = {} + + @fd = fd + @parser = StreamParser.new(@fd, self) + @parser_thread = Thread.new do + Thread.current.abort_on_exception = true + begin + @parser.parse + Jabber::debuglog("DISCONNECTED\n") + + if @exception_block + Thread.new { close!; @exception_block.call(nil, self, :disconnected) } + else + close! + end + rescue Exception => e + Jabber::debuglog("EXCEPTION:\n#{e.class}\n#{e.message}\n#{e.backtrace.join("\n")}") + + if @exception_block + Thread.new do + Thread.current.abort_on_exception = true + close + @exception_block.call(e, self, :start) + end + else + if Jabber::debug + puts "Exception caught in Parser thread! (#{e.class})" + puts e.backtrace + end + close! + raise + end + end + end + + @status = CONNECTED + end + + def stop + @parser_thread.kill + @parser = nil + end + + ## + # Mounts a block to handle exceptions if they occur during the + # poll send. This will likely be the first indication that + # the socket dropped in a Jabber Session. + # + # The block has to take three arguments: + # * the Exception + # * the Jabber::Stream object (self) + # * a symbol where it happened, namely :start, :parser, :sending and :end + def on_exception(&block) + @exception_block = block + end + + ## + # This method is called by the parser when a failure occurs + def parse_failure(e) + Jabber::debuglog("EXCEPTION:\n#{e.class}\n#{e.message}\n#{e.backtrace.join("\n")}") + + # A new thread has to be created because close will cause the thread + # to commit suicide(???) + if @exception_block + # New thread, because close will kill the current thread + Thread.new do + Thread.current.abort_on_exception = true + close + @exception_block.call(e, self, :parser) + end + else + puts "Stream#parse_failure was called by XML parser. Dumping " + + "backtrace...\n" + e.exception + "\n" + puts e.backtrace + close + raise + end + end + + ## + # This method is called by the parser upon receiving + def parser_end + if @exception_block + Thread.new do + Thread.current.abort_on_exception = true + close + @exception_block.call(nil, self, :close) + end + else + close + end + end + + ## + # Returns if this connection is connected to a Jabber service + # return:: [Boolean] Connection status + def is_connected? + return @status == CONNECTED + end + + ## + # Returns if this connection is NOT connected to a Jabber service + # + # return:: [Boolean] Connection status + def is_disconnected? + return @status == DISCONNECTED + end + + ## + # Processes a received REXML::Element and executes + # registered thread blocks and filters against it. + # + # element:: [REXML::Element] The received element + def receive(element) + Jabber::debuglog("RECEIVED:\n#{element.to_s}") + + if element.namespace('').to_s == '' # REXML namespaces are always strings + element.add_namespace(@streamns) + end + + case element.prefix + when 'stream' + case element.name + when 'stream' + stanza = element + @streamid = element.attributes['id'] + @streamns = element.namespace('') if element.namespace('') + + # Hack: component streams are basically client streams. + # Someday we may want to create special stanza classes + # for components/s2s deriving from normal stanzas but + # posessing these namespaces + @streamns = 'jabber:client' if @streamns == 'jabber:component:accept' + + unless element.attributes['version'] # isn't XMPP compliant, so + Jabber::debuglog("FEATURES: server not XMPP compliant, will not wait for features") + @features_sem.run # don't wait for + end + when 'features' + stanza = element + element.each { |e| + if e.name == 'mechanisms' and e.namespace == 'urn:ietf:params:xml:ns:xmpp-sasl' + e.each_element('mechanism') { |mech| + @stream_mechanisms.push(mech.text) + } + else + @stream_features[e.name] = e.namespace + end + } + Jabber::debuglog("FEATURES: received") + @features_sem.run + else + stanza = element + end + else + # Any stanza, classes are registered by XMPPElement::name_xmlns + begin + stanza = XMPPStanza::import(element) + rescue NoNameXmlnsRegistered + stanza = element + end + end + + # Iterate through blocked threads (= waiting for an answer) + # + # We're dup'ping the @threadblocks here, so that we won't end up in an + # endless loop if Stream#send is being nested. That means, the nested + # threadblock won't receive the stanza currently processed, but the next + # one. + threadblocks = @threadblocks.dup + threadblocks.each { |threadblock| + exception = nil + r = false + begin + r = threadblock.call(stanza) + rescue Exception => e + exception = e + end + + if r == true + @threadblocks.delete(threadblock) + threadblock.wakeup + return + elsif exception + @threadblocks.delete(threadblock) + threadblock.raise(exception) + end + } + + Jabber::debuglog("PROCESSING:\n#{stanza.to_s} (#{stanza.class})") + return true if @xmlcbs.process(stanza) + return true if @stanzacbs.process(stanza) + case stanza + when Message + return true if @messagecbs.process(stanza) + when Iq + return true if @iqcbs.process(stanza) + when Presence + return true if @presencecbs.process(stanza) + end + end + + ## + # This is used by Jabber::Stream internally to + # keep track of any blocks which were passed to + # Stream#send. + class ThreadBlock + def initialize(block) + @block = block + @waiter = Semaphore.new + @exception = nil + end + def call(*args) + @block.call(*args) + end + def wait + @waiter.wait + raise @exception if @exception + end + def wakeup + # TODO: Handle threadblock removal if !alive? + @waiter.run + end + def raise(exception) + @exception = exception + @waiter.run + end + end + + def send_data(data) + @send_lock.synchronize do + @last_send = Time.now + @fd << data + @fd.flush + end + end + + ## + # Sends XML data to the socket and (optionally) waits + # to process received data. + # + # Do not invoke this in a callback but in a seperate thread + # because we may not suspend the parser-thread (in whose + # context callbacks are executed). + # + # xml:: [String] The xml data to send + # &block:: [Block] The optional block + def send(xml, &block) + Jabber::debuglog("SENDING:\n#{xml}") + @threadblocks.unshift(threadblock = ThreadBlock.new(block)) if block + begin + # Temporarily remove stanza's namespace to + # reduce bandwidth consumption + if xml.kind_of? XMPPStanza and xml.namespace == 'jabber:client' + xml.delete_namespace + send_data(xml.to_s) + xml.add_namespace(@streamns) + else + send_data(xml.to_s) + end + rescue Exception => e + Jabber::debuglog("EXCEPTION:\n#{e.class}\n#{e.message}\n#{e.backtrace.join("\n")}") + + if @exception_block + Thread.new do + Thread.current.abort_on_exception = true + close! + @exception_block.call(e, self, :sending) + end + else + if Jabber::debug + puts "Exception caught while sending! (#{e.class})" + puts e.backtrace + end + close! + raise + end + end + # The parser thread might be running this (think of a callback running send()) + # If this is the case, we mustn't stop (or we would cause a deadlock) + if block and Thread.current != @parser_thread + threadblock.wait + elsif block + Jabber::debuglog("WARNING:\nCannot stop current thread in Jabber::Stream#send because it is the parser thread!") + end + end + + ## + # Send an XMMP stanza with an Jabber::XMPPStanza#id. The id will be + # generated by Jabber::IdGenerator if not already set. + # + # The block will be called once: when receiving a stanza with the + # same Jabber::XMPPStanza#id. There is no need to return true to + # complete this! Instead the return value of the block will be + # returned. + # + # Be aware that if a stanza with type='error' is received + # the function does not yield but raises an ErrorException with + # the corresponding error element. + # + # Please see Stream#send for some implementational details. + # + # Please read the note about nesting at Stream#send + # xml:: [XMPPStanza] + def send_with_id(xml, &block) + if xml.id.nil? + xml.id = Jabber::IdGenerator.instance.generate_id + end + + res = nil + error = nil + send(xml) do |received| + if received.kind_of? XMPPStanza and received.id == xml.id + if received.type == :error + error = (received.error ? received.error : Error.new) + true + else + res = yield(received) + true + end + else + false + end + end + + unless error.nil? + raise ErrorException.new(error) + end + + res + end + + ## + # Adds a callback block to process received XML messages + # + # priority:: [Integer] The callback's priority, the higher, the sooner + # ref:: [String] The callback's reference + # &block:: [Block] The optional block + def add_xml_callback(priority = 0, ref = nil, &block) + @xmlcbs.add(priority, ref, block) + end + + ## + # Delete an XML-messages callback + # + # ref:: [String] The reference of the callback to delete + def delete_xml_callback(ref) + @xmlcbs.delete(ref) + end + + ## + # Adds a callback block to process received Messages + # + # priority:: [Integer] The callback's priority, the higher, the sooner + # ref:: [String] The callback's reference + # &block:: [Block] The optional block + def add_message_callback(priority = 0, ref = nil, &block) + @messagecbs.add(priority, ref, block) + end + + ## + # Delete an Message callback + # + # ref:: [String] The reference of the callback to delete + def delete_message_callback(ref) + @messagecbs.delete(ref) + end + + ## + # Adds a callback block to process received Stanzas + # + # priority:: [Integer] The callback's priority, the higher, the sooner + # ref:: [String] The callback's reference + # &block:: [Block] The optional block + def add_stanza_callback(priority = 0, ref = nil, &block) + @stanzacbs.add(priority, ref, block) + end + + ## + # Delete a Stanza callback + # + # ref:: [String] The reference of the callback to delete + def delete_stanza_callback(ref) + @stanzacbs.delete(ref) + end + + ## + # Adds a callback block to process received Presences + # + # priority:: [Integer] The callback's priority, the higher, the sooner + # ref:: [String] The callback's reference + # &block:: [Block] The optional block + def add_presence_callback(priority = 0, ref = nil, &block) + @presencecbs.add(priority, ref, block) + end + + ## + # Delete a Presence callback + # + # ref:: [String] The reference of the callback to delete + def delete_presence_callback(ref) + @presencecbs.delete(ref) + end + + ## + # Adds a callback block to process received Iqs + # + # priority:: [Integer] The callback's priority, the higher, the sooner + # ref:: [String] The callback's reference + # &block:: [Block] The optional block + def add_iq_callback(priority = 0, ref = nil, &block) + @iqcbs.add(priority, ref, block) + end + + ## + # Delete an Iq callback + # + # ref:: [String] The reference of the callback to delete + # + def delete_iq_callback(ref) + @iqcbs.delete(ref) + end + ## + # Closes the connection to the Jabber service + def close + close! + end + + def close! + @parser_thread.kill if @parser_thread + @fd.close if @fd and !@fd.closed? + @status = DISCONNECTED + end + end +end diff --git a/vendor/xmpp4r-0.3.2/lib/xmpp4r/streamparser.rb b/vendor/xmpp4r-0.3.2/lib/xmpp4r/streamparser.rb new file mode 100644 index 000000000..495446e07 --- /dev/null +++ b/vendor/xmpp4r-0.3.2/lib/xmpp4r/streamparser.rb @@ -0,0 +1,77 @@ +# =XMPP4R - XMPP Library for Ruby +# License:: Ruby's license (see the LICENSE file) or GNU GPL, at your option. +# Website::http://home.gna.org/xmpp4r/ + +require 'rexml/parsers/sax2parser' +require 'rexml/source' +require 'xmpp4r/rexmladdons' + +module Jabber + + ## + # The StreamParser uses REXML to parse the incoming XML stream + # of the Jabber protocol and fires XMPPStanza at the Connection + # instance. + # + class StreamParser + # status if the parser is started + attr_reader :started + + ## + # Constructs a parser for the supplied stream (socket input) + # + # stream:: [IO] Socket input stream + # listener:: [Object.receive(XMPPStanza)] The listener (usually a Jabber::Protocol::Connection instance) + # + def initialize(stream, listener) + @stream = stream + @listener = listener + @current = nil + end + + ## + # Begins parsing the XML stream and does not return until + # the stream closes. + # + def parse + @started = false + begin + parser = REXML::Parsers::SAX2Parser.new @stream + + parser.listen( :start_element ) do |uri, localname, qname, attributes| + e = REXML::Element::new(qname) + e.add_attributes attributes + @current = @current.nil? ? e : @current.add_element(e) + + if @current.name == 'stream' and !@started + @started = true + @listener.receive(@current) + @current = nil + end + end + + parser.listen( :end_element ) do |uri, localname, qname| + if qname == 'stream:stream' and @current.nil? + @started = false + @listener.parser_end + else + @listener.receive(@current) unless @current.parent + @current = @current.parent + end + end + + parser.listen( :characters ) do | text | + @current.text = @current.text.to_s + text if @current + end + + parser.listen( :cdata ) do | text | + raise "Not implemented !" + end + + parser.parse + rescue REXML::ParseException => e + @listener.parse_failure(e) + end + end + end +end diff --git a/vendor/xmpp4r-0.3.2/lib/xmpp4r/vcard.rb b/vendor/xmpp4r-0.3.2/lib/xmpp4r/vcard.rb new file mode 100644 index 000000000..6fc6c6198 --- /dev/null +++ b/vendor/xmpp4r-0.3.2/lib/xmpp4r/vcard.rb @@ -0,0 +1,7 @@ +# =XMPP4R - XMPP Library for Ruby +# License:: Ruby's license (see the LICENSE file) or GNU GPL, at your option. +# Website::http://home.gna.org/xmpp4r/ + +require 'xmpp4r/vcard/helper/vcard.rb' +require 'xmpp4r/vcard/iq/vcard.rb' + diff --git a/vendor/xmpp4r-0.3.2/lib/xmpp4r/vcard/helper/vcard.rb b/vendor/xmpp4r-0.3.2/lib/xmpp4r/vcard/helper/vcard.rb new file mode 100644 index 000000000..23a35406a --- /dev/null +++ b/vendor/xmpp4r-0.3.2/lib/xmpp4r/vcard/helper/vcard.rb @@ -0,0 +1,86 @@ +# =XMPP4R - XMPP Library for Ruby +# License:: Ruby's license (see the LICENSE file) or GNU GPL, at your option. +# Website::http://home.gna.org/xmpp4r/ + +require 'xmpp4r/iq' +require 'xmpp4r/errorexception' + +module Jabber + module Vcard + ## + # The Vcard helper retrieves vCards + class Helper + ## + # Initialize a new Vcard helper + def initialize(stream) + @stream = stream + end + + ## + # Retrieve vCard of an entity + # + # Raises exception upon retrieval error, please catch that! + # (The exception is ErrorException and is raisen by + # Stream#send_with_id. + # + # Usage of Threads is suggested here as vCards can be very + # big (see /iq/vCard/PHOTO/BINVAL). + # + # jid:: [Jabber::JID] or nil (should be stripped, nil for the client's own vCard) + # result:: [Jabber::IqVcard] or nil (nil results may be handled as empty vCards) + def get(jid=nil) + res = nil + request = Iq.new(:get, jid) + request.from = @stream.jid # Enable components to use this + request.add(IqVcard.new) + @stream.send_with_id(request) { |answer| + # No check for sender or queryns needed (see send_with_id) + if answer.type == :result + res = answer.vcard + true + else + false + end + } + res + end + + ## + # Set your own vCard (Clients only) + # + # Raises exception when setting fails + # + # Usage of Threads suggested here, too. The function + # waits for approval from the server. + # + # iqvcard:: [Jabber::IqVcard] + def set(iqvcard) + iq = Iq.new(:set) + iq.add(iqvcard) + + @stream.send_with_id(iq) { |answer| + if answer.type == :result + true + else + false + end + } + end + + ## + # Quickly initialize a Vcard helper and get + # a vCard. See Vcard#get + def self.get(stream, jid=nil) + new(stream).get(jid) + end + + ## + # Quickly initialize a Vcard helper and set + # your vCard. See Vcard#set + def self.set(stream, iqvcard) + new(stream).set(iqvcard) + end + end + end +end + diff --git a/vendor/xmpp4r-0.3.2/lib/xmpp4r/vcard/iq/vcard.rb b/vendor/xmpp4r-0.3.2/lib/xmpp4r/vcard/iq/vcard.rb new file mode 100644 index 000000000..0b99a915d --- /dev/null +++ b/vendor/xmpp4r-0.3.2/lib/xmpp4r/vcard/iq/vcard.rb @@ -0,0 +1,95 @@ +# =XMPP4R - XMPP Library for Ruby +# License:: Ruby's license (see the LICENSE file) or GNU GPL, at your option. +# Website::http://home.gna.org/xmpp4r/ + +require 'xmpp4r/iq' + +module Jabber + module Vcard + ## + # vCard container for User Information + # (can be specified by users themselves, mostly kept on servers) + # (JEP 0054) + class IqVcard < XMPPElement + name_xmlns 'vCard', 'vcard-temp' + force_xmlns true + + ## + # Initialize a element + # fields:: [Hash] Initialize with keys as XPath element names and values for element texts + def initialize(fields=nil) + super() + + unless fields.nil? + fields.each { |name,value| + self[name] = value + } + end + end + + ## + # Get an elements/fields text + # + # vCards have too much possible children, so ask for them here + # and extract the result with iqvcard.element('...').text + # name:: [String] XPath + def [](name) + text = nil + each_element(name) { |child| text = child.text } + text + end + + ## + # Set an elements/fields text + # name:: [String] XPath + # text:: [String] Value + def []=(name, text) + xe = self + name.split(/\//).each do |elementname| + # Does the children already exist? + newxe = nil + xe.each_element(elementname) { |child| newxe = child } + + if newxe.nil? + # Create a new + xe = xe.add_element(elementname) + else + # Or take existing + xe = newxe + end + end + xe.text = text + end + + ## + # Get vCard field names + # + # Example: + # ["NICKNAME", "BDAY", "ORG/ORGUNIT", "PHOTO/TYPE", "PHOTO/BINVAL"] + # + # result:: [Array] of [String] + def fields + element_names(self).uniq + end + + ## + # Recursive helper function, + # returns all element names in an array, concatenated + # to their parent's name with a slash + def element_names(xe, prefix='') # :nodoc: + res = [] + xe.each_element { |child| + if child.kind_of?(REXML::Element) + children = element_names(child, "#{prefix}#{child.name}/") + if children == [] + res.push("#{prefix}#{child.name}") + else + res += children + end + end + } + res + end + end + end +end diff --git a/vendor/xmpp4r-0.3.2/lib/xmpp4r/version.rb b/vendor/xmpp4r-0.3.2/lib/xmpp4r/version.rb new file mode 100644 index 000000000..9ca09d1d6 --- /dev/null +++ b/vendor/xmpp4r-0.3.2/lib/xmpp4r/version.rb @@ -0,0 +1,7 @@ +# =XMPP4R - XMPP Library for Ruby +# License:: Ruby's license (see the LICENSE file) or GNU GPL, at your option. +# Website::http://home.gna.org/xmpp4r/ + +require 'xmpp4r/version/helper/responder.rb' +require 'xmpp4r/version/helper/simpleresponder.rb' +require 'xmpp4r/version/iq/version.rb' diff --git a/vendor/xmpp4r-0.3.2/lib/xmpp4r/version/helper/responder.rb b/vendor/xmpp4r-0.3.2/lib/xmpp4r/version/helper/responder.rb new file mode 100644 index 000000000..74d4a7a4e --- /dev/null +++ b/vendor/xmpp4r-0.3.2/lib/xmpp4r/version/helper/responder.rb @@ -0,0 +1,72 @@ +# =XMPP4R - XMPP Library for Ruby +# License:: Ruby's license (see the LICENSE file) or GNU GPL, at your option. +# Website::http://home.gna.org/xmpp4r/ + +require 'xmpp4r/callbacks' +require 'xmpp4r/version/iq/version' + +module Jabber + module Version + ## + # A class to answer version requests using IqQueryVersion + # + # If you don't need the flexibility of dynamic responses with + # the callback you can register with add_version_callback, + # take a look at SimpleResponder + class Responder + ## + # Initialize a new version responder + # + # Registers it's callback (prio = 180, ref = self) + # stream:: [Stream] Where to register callback handlers + def initialize(stream) + @stream = stream + @versioncbs = CallbackList.new + + stream.add_iq_callback(180, self) { |iq| + iq_callback(iq) + } + end + + ## + # Add a callback for Iq stanzas with IqQueryVersion + # + # First argument passed to block is the Iq stanza, + # second argument is a block, which can be called with + # software name, version and os + # + # Example: + # my_version_helper.add_version_callback { |iq,block| + # block.call('Cool client', '6.0', 'Cool OS') + # } + def add_version_callback(priority = 0, ref = nil, &block) + @versioncbs.add(priority, ref, block) + end + + ## + # callback handler to answer Software Version queries + # (registered by constructor and used internally only) + # + # Used internally + def iq_callback(iq) + if iq.type == :get + if iq.query.kind_of?(IqQueryVersion) + replyblock = lambda { |name,version,os| + answer = iq.answer + answer.type = :result + answer.query.set_iname(name).set_version(version).set_os(os) + + @stream.send(answer) + true + } + @versioncbs.process(iq, replyblock) + else + false + end + else + false + end + end + end + end +end diff --git a/vendor/xmpp4r-0.3.2/lib/xmpp4r/version/helper/simpleresponder.rb b/vendor/xmpp4r-0.3.2/lib/xmpp4r/version/helper/simpleresponder.rb new file mode 100644 index 000000000..724050884 --- /dev/null +++ b/vendor/xmpp4r-0.3.2/lib/xmpp4r/version/helper/simpleresponder.rb @@ -0,0 +1,44 @@ +# =XMPP4R - XMPP Library for Ruby +# License:: Ruby's license (see the LICENSE file) or GNU GPL, at your option. +# Website::http://home.gna.org/xmpp4r/ + +require 'xmpp4r/iq' +require 'xmpp4r/version/helper/responder' + +module Jabber + module Version + ## + # A class to answer version requests using IqQueryVersion + # + # This is simplification as one doesn't need dynamic + # version answering normally. + # + # Example usage: + # Jabber::Version::SimpleResponder.new(my_client, "My cool XMPP4R script", "1.0", "Younicks") + class SimpleResponder < Responder + attr_accessor :name + attr_accessor :version + attr_accessor :os + + ## + # Initialize a new version responder + # + # Registers it's callback (prio = 180, ref = self) + # stream:: [Stream] Where to register callback handlers + # name:: [String] Software name for answers + # version:: [String] Software versio for answers + # os:: [String] Optional operating system name for answers + def initialize(stream, name, version, os=nil) + super stream + + @name = name + @version = version + @os = os + + add_version_callback(180, self) { |iq,block| + block.call(@name, @version, @os) + } + end + end + end +end diff --git a/vendor/xmpp4r-0.3.2/lib/xmpp4r/version/iq/version.rb b/vendor/xmpp4r-0.3.2/lib/xmpp4r/version/iq/version.rb new file mode 100755 index 000000000..c9c94f592 --- /dev/null +++ b/vendor/xmpp4r-0.3.2/lib/xmpp4r/version/iq/version.rb @@ -0,0 +1,106 @@ +# =XMPP4R - XMPP Library for Ruby +# License:: Ruby's license (see the LICENSE file) or GNU GPL, at your option. +# Website::http://home.gna.org/xmpp4r/ + +require 'xmpp4r/query' + +module Jabber + module Version + ## + # Class for handling queries for 'Software Version' + # (JEP 0092) + # + # Notice that according to JEP 0092 only the element can be omitted, + # (iname) and must be present + class IqQueryVersion < IqQuery + name_xmlns 'query', 'jabber:iq:version' + + ## + # Create a new element + def initialize(iname=nil, version=nil, os=nil) + super() + set_iname(iname) if iname + set_version(version) if version + set_os(os) if os + end + + ## + # Get the name of the software + # + # This has been renamed to 'iname' here to keep + # REXML::Element#name accessible + def iname + first_element_text('name') + end + + ## + # Set the name of the software + # + # The element won't be deleted if text is nil as + # it must occur in a version query, but its text will + # be empty. + def iname=(text) + replace_element_text('name', text.nil? ? '' : text) + end + + ## + # Set the name of the software (chaining-friendly) + # result:: [String] or nil + def set_iname(text) + self.iname = text + self + end + + ## + # Get the version of the software + # result:: [String] or nil + def version + first_element_text('version') + end + + ## + # Set the version of the software + # + # The element won't be deleted if text is nil as + # it must occur in a version query + def version=(text) + replace_element_text('version', text.nil? ? '' : text) + end + + ## + # Set the version of the software (chaining-friendly) + # text:: [String] + def set_version(text) + self.version = text + self + end + + ## + # Get the operating system or nil + # (os is not mandatory for Version Query) + def os + first_element_text('os') + end + + ## + # Set the os of the software + # text:: [String] or nil + def os=(text) + if text + replace_element_text('os', text) + else + delete_elements('os') + end + end + + ## + # Set the os of the software (chaining-friendly) + # text:: [String] or nil + def set_os(text) + self.os = text + self + end + end + end +end + diff --git a/vendor/xmpp4r-0.3.2/lib/xmpp4r/x.rb b/vendor/xmpp4r-0.3.2/lib/xmpp4r/x.rb new file mode 100644 index 000000000..53e0d03a2 --- /dev/null +++ b/vendor/xmpp4r-0.3.2/lib/xmpp4r/x.rb @@ -0,0 +1,37 @@ +# =XMPP4R - XMPP Library for Ruby +# License:: Ruby's license (see the LICENSE file) or GNU GPL, at your option. +# Website::http://home.gna.org/xmpp4r/ + +require 'xmpp4r/xmppelement' + +module Jabber + ## + # A class used to build/parse elements + # + # These elements may occur as "attachments" + # in [Message] and [Presence] stanzas + class X < XMPPElement + name_xmlns 'x' + force_xmlns true + end + + module XParent + ## + # Get the first element in this stanza, or nil if none found. + # wanted_xmlns:: [String] Optional, find the first element having this xmlns, + # wanted_xmlns can also be a derivate of XMPPElement from which the namespace will be taken + # result:: [REXML::Element] or nil + def x(wanted_xmlns=nil) + if wanted_xmlns.kind_of? Class and wanted_xmlns.ancestors.include? XMPPElement + wanted_xmlns = wanted_xmlns.new.namespace + end + + each_element('x') { |x| + if wanted_xmlns.nil? or wanted_xmlns == x.namespace + return x + end + } + nil + end + end +end diff --git a/vendor/xmpp4r-0.3.2/lib/xmpp4r/xmpp4r.rb b/vendor/xmpp4r-0.3.2/lib/xmpp4r/xmpp4r.rb new file mode 100644 index 000000000..f47a85a6a --- /dev/null +++ b/vendor/xmpp4r-0.3.2/lib/xmpp4r/xmpp4r.rb @@ -0,0 +1,16 @@ +# =XMPP4R - XMPP Library for Ruby +# License:: Ruby's license (see the LICENSE file) or GNU GPL, at your option. +# Website::http://home.gna.org/xmpp4r/ + +## +# The Jabber module is the root namespace of the library. You might want +# to Include it in your script to ease your coding. It provides +# a simple debug logging support. +module Jabber + # XMPP4R Version number + XMPP4R_VERSION = '0.3.2' +end + +require 'xmpp4r/client' +require 'xmpp4r/component' +require 'xmpp4r/debuglog' diff --git a/vendor/xmpp4r-0.3.2/lib/xmpp4r/xmppelement.rb b/vendor/xmpp4r-0.3.2/lib/xmpp4r/xmppelement.rb new file mode 100644 index 000000000..670ddf90c --- /dev/null +++ b/vendor/xmpp4r-0.3.2/lib/xmpp4r/xmppelement.rb @@ -0,0 +1,152 @@ +# =XMPP4R - XMPP Library for Ruby +# License:: Ruby's license (see the LICENSE file) or GNU GPL, at your option. +# Website::http://home.gna.org/xmpp4r/ + +require 'xmpp4r/rexmladdons' + +module Jabber + class NoNameXmlnsRegistered < RuntimeError + def initialize(klass) + super "Class #{klass} has not set name and xmlns" + end + end + + ## + # This class represents an XML element and provides functionality + # for automatic casting of XML element classes according to their + # element name and namespace. + # + # Deriving classes must met these criteria: + # * The element name and namespace must be specified by calling + # the name_xmlns class method + # * The class constructor must be callable with no mandatory parameter + class XMPPElement < REXML::Element + @@name_xmlns_classes = {} + @@force_xmlns = false + + ## + # Specify XML element name and xmlns for a deriving class, + # this pair and the class will be added to a global pool + # + # If the namespace is nil the class is a "wildcard class" + # matching elements with any xmlns if no other class with + # that namespace was defined + def self.name_xmlns(name, xmlns=nil) + @@name_xmlns_classes[[name, xmlns]] = self + end + + ## + # Set whether this element is always built with an xmlns attribute + def self.force_xmlns(force) + @@force_xmlns = force + end + + ## + # Whether this element is always built with an xmlns attribute + def self.force_xmlns? + @@force_xmlns + end + + ## + # Find the name and namespace for a given class. + # This class must have registered these two values + # by calling name_xmlns at definition time. + # + # Raises an exception if none was found + # klass:: [Class] + # result:: [String, String] name and namespace + def self.name_xmlns_for_class(klass) + klass.ancestors.each do |klass1| + @@name_xmlns_classes.each do |name_xmlns,k| + if klass1 == k + return name_xmlns + end + end + end + + raise NoNameXmlnsRegistered.new(klass) + end + + ## + # Find a class for given name and namespace + # name:: [String] + # xmlns:: [String] + # result:: A descendant of XMPPElement or REXML::Element + def self.class_for_name_xmlns(name, xmlns) + if @@name_xmlns_classes.has_key? [name, xmlns] + @@name_xmlns_classes[[name, xmlns]] + elsif @@name_xmlns_classes.has_key? [name, nil] + @@name_xmlns_classes[[name, nil]] + else + REXML::Element + end + end + + ## + # Import another REXML::Element descendant to: + # * Either an element class that registered with + # name and xmlns before + # * Or if none was found to the class itself + # (you may call this class method on a deriving class) + def self.import(element) + klass = class_for_name_xmlns(element.name, element.namespace) + if klass != self and klass.ancestors.include?(self) + klass.new.import(element) + else + self.new.import(element) + end + end + + ## + # Initialize this element, which will then be initialized + # with the name registered with name_xmlns. + def initialize(*arg) + if arg.empty? + name, xmlns = self.class::name_xmlns_for_class(self.class) + super(name) + if self.class::force_xmlns? + add_namespace(xmlns) + end + else + super + end + end + + ## + # Add a child element which will be imported according to the + # child's name and xmlns + # element:: [REXML::Element] Child + # result:: [REXML::Element or descendant of XMPPElement] New child + def typed_add(element) + if element.kind_of? REXML::Element + element_ns = (element.namespace.to_s == '') ? namespace : element.namespace + + klass = XMPPElement::class_for_name_xmlns(element.name, element_ns) + if klass != element.class + element = klass.import(element) + end + end + + super(element) + end + + def parent=(new_parent) + if parent and parent.namespace('') == namespace('') and attributes['xmlns'].nil? + add_namespace parent.namespace('') + end + + super + + if new_parent and new_parent.namespace('') == namespace('') + delete_namespace + end + end + + def clone + cloned = self.class.new + cloned.add_attributes self.attributes.clone + cloned.context = @context + cloned + end + end +end diff --git a/vendor/xmpp4r-0.3.2/lib/xmpp4r/xmppstanza.rb b/vendor/xmpp4r-0.3.2/lib/xmpp4r/xmppstanza.rb new file mode 100644 index 000000000..084183f08 --- /dev/null +++ b/vendor/xmpp4r-0.3.2/lib/xmpp4r/xmppstanza.rb @@ -0,0 +1,162 @@ +# =XMPP4R - XMPP Library for Ruby +# License:: Ruby's license (see the LICENSE file) or GNU GPL, at your option. +# Website::http://home.gna.org/xmpp4r/ + +require 'xmpp4r/xmppelement' +require 'xmpp4r/error' +require 'xmpp4r/jid' + +module Jabber + ## + # root class of all Jabber XML elements + class XMPPStanza < XMPPElement + ## + # Compose a response by doing the following: + # * Create a new XMPPStanza of the same subclass + # with the same element-name + # * Import xmppstanza if import is true + # * Swap 'to' and 'from' + # * Copy 'id' + # * Does not take care about the type + # + # *Attention*: Be careful when answering to stanzas with + # type == :error - answering to an error may generate + # another error on the other side, which could be leading to a + # ping-pong effect quickly! + # + # xmppstanza:: [XMPPStanza] source + # import:: [true or false] Copy attributes and children of source + # result:: [XMPPStanza] answer stanza + def XMPPStanza.answer(xmppstanza, import=true) + x = xmppstanza.class::new + if import + x.import(xmppstanza) + end + x.from = xmppstanza.to + x.to = xmppstanza.from + x.id = xmppstanza.id + x + end + + ## + # Return the first child + def error + first_element('error') + end + + ## + # Compose a response of this XMPPStanza + # (see XMPPStanza.answer) + # result:: [XMPPStanza] New constructed stanza + def answer(import=true) + XMPPStanza.answer(self, import) + end + + ## + # Makes some changes to the structure of an XML element to help + # it respect the specification. For example, in a message, we should + # have < < { rest of tags } + def normalize + end + + ## + # get the to attribute + # + # return:: [String] the element's to attribute + def to + (a = attribute('to')).nil? ? a : JID::new(a.value) + end + + ## + # set the to attribute + # + # v:: [String] the value to set + def to= (v) + add_attribute('to', v ? v.to_s : nil) + end + + ## + # set the to attribute (chaining-friendly) + # + # v:: [String] the value to set + def set_to(v) + self.to = v + self + end + + ## + # get the from attribute + # + # return:: [String] the element's from attribute + def from + (a = attribute('from')).nil? ? a : JID::new(a.value) + end + + ## + # set the from attribute + # + # v:: [String] the value from set + def from= (v) + add_attribute('from', v ? v.to_s : nil) + end + + ## + # set the from attribute (chaining-friendly) + # + # v:: [String] the value from set + def set_from(v) + add_attribute('from', v ? v.to_s : nil) + self + end + + ## + # get the id attribute + # + # return:: [String] the element's id attribute + def id + (a = attribute('id')).nil? ? a : a.value + end + + ## + # set the id attribute + # + # v:: [String] the value id set + def id= (v) + add_attribute('id', v) + end + + ## + # set the id attribute (chaining-friendly) + # + # v:: [String] the value id set + def set_id(v) + add_attribute('id', v) + self + end + + ## + # get the type attribute + # + # return:: [String] the element's type attribute + def type + (a = attribute('type')).nil? ? a : a.value + end + + ## + # set the type attribute + # + # v:: [String] the value type set + def type= (v) + add_attribute('type', v) + end + + ## + # set the type attribute (chaining-friendly) + # + # v:: [String] the value type set + def set_type(v) + add_attribute('type', v) + self + end + end +end diff --git a/vendor/xmpp4r-0.3.2/setup.rb b/vendor/xmpp4r-0.3.2/setup.rb new file mode 100755 index 000000000..03de9bf1a --- /dev/null +++ b/vendor/xmpp4r-0.3.2/setup.rb @@ -0,0 +1,1586 @@ +#!/usr/bin/env ruby +# +# setup.rb +# +# Copyright (c) 2000-2005 Minero Aoki +# +# This program is free software. +# You can distribute/modify this program under the terms of +# the GNU LGPL, Lesser General Public License version 2.1. +# + +unless Enumerable.method_defined?(:map) # Ruby 1.4.6 + module Enumerable + alias map collect + end +end + +unless File.respond_to?(:read) # Ruby 1.6 + def File.read(fname) + open(fname) {|f| + return f.read + } + end +end + +unless Errno.const_defined?(:ENOTEMPTY) # Windows? + module Errno + class ENOTEMPTY + # We do not raise this exception, implementation is not needed. + end + end +end + +def File.binread(fname) + open(fname, 'rb') {|f| + return f.read + } +end + +# for corrupted Windows' stat(2) +def File.dir?(path) + File.directory?((path[-1,1] == '/') ? path : path + '/') +end + + +class ConfigTable + + include Enumerable + + def initialize(rbconfig) + @rbconfig = rbconfig + @items = [] + @table = {} + # options + @install_prefix = nil + @config_opt = nil + @verbose = true + @no_harm = false + end + + attr_accessor :install_prefix + attr_accessor :config_opt + + attr_writer :verbose + + def verbose? + @verbose + end + + attr_writer :no_harm + + def no_harm? + @no_harm + end + + def [](key) + lookup(key).resolve(self) + end + + def []=(key, val) + lookup(key).set val + end + + def names + @items.map {|i| i.name } + end + + def each(&block) + @items.each(&block) + end + + def key?(name) + @table.key?(name) + end + + def lookup(name) + @table[name] or setup_rb_error "no such config item: #{name}" + end + + def add(item) + @items.push item + @table[item.name] = item + end + + def remove(name) + item = lookup(name) + @items.delete_if {|i| i.name == name } + @table.delete_if {|name, i| i.name == name } + item + end + + def load_script(path, inst = nil) + if File.file?(path) + MetaConfigEnvironment.new(self, inst).instance_eval File.read(path), path + end + end + + def savefile + '.config' + end + + def load_savefile + begin + File.foreach(savefile()) do |line| + k, v = *line.split(/=/, 2) + self[k] = v.strip + end + rescue Errno::ENOENT + setup_rb_error $!.message + "\n#{File.basename($0)} config first" + end + end + + def save + @items.each {|i| i.value } + File.open(savefile(), 'w') {|f| + @items.each do |i| + f.printf "%s=%s\n", i.name, i.value if i.value? and i.value + end + } + end + + def load_standard_entries + standard_entries(@rbconfig).each do |ent| + add ent + end + end + + def standard_entries(rbconfig) + c = rbconfig + + rubypath = File.join(c['bindir'], c['ruby_install_name'] + c['EXEEXT']) + + major = c['MAJOR'].to_i + minor = c['MINOR'].to_i + teeny = c['TEENY'].to_i + version = "#{major}.#{minor}" + + # ruby ver. >= 1.4.4? + newpath_p = ((major >= 2) or + ((major == 1) and + ((minor >= 5) or + ((minor == 4) and (teeny >= 4))))) + + if c['rubylibdir'] + # V > 1.6.3 + libruby = "#{c['prefix']}/lib/ruby" + librubyver = c['rubylibdir'] + librubyverarch = c['archdir'] + siteruby = c['sitedir'] + siterubyver = c['sitelibdir'] + siterubyverarch = c['sitearchdir'] + elsif newpath_p + # 1.4.4 <= V <= 1.6.3 + libruby = "#{c['prefix']}/lib/ruby" + librubyver = "#{c['prefix']}/lib/ruby/#{version}" + librubyverarch = "#{c['prefix']}/lib/ruby/#{version}/#{c['arch']}" + siteruby = c['sitedir'] + siterubyver = "$siteruby/#{version}" + siterubyverarch = "$siterubyver/#{c['arch']}" + else + # V < 1.4.4 + libruby = "#{c['prefix']}/lib/ruby" + librubyver = "#{c['prefix']}/lib/ruby/#{version}" + librubyverarch = "#{c['prefix']}/lib/ruby/#{version}/#{c['arch']}" + siteruby = "#{c['prefix']}/lib/ruby/#{version}/site_ruby" + siterubyver = siteruby + siterubyverarch = "$siterubyver/#{c['arch']}" + end + parameterize = lambda {|path| + path.sub(/\A#{Regexp.quote(c['prefix'])}/, '$prefix') + } + + if arg = c['configure_args'].split.detect {|arg| /--with-make-prog=/ =~ arg } + makeprog = arg.sub(/'/, '').split(/=/, 2)[1] + else + makeprog = 'make' + end + + [ + ExecItem.new('installdirs', 'std/site/home', + 'std: install under libruby; site: install under site_ruby; home: install under $HOME')\ + {|val, table| + case val + when 'std' + table['rbdir'] = '$librubyver' + table['sodir'] = '$librubyverarch' + when 'site' + table['rbdir'] = '$siterubyver' + table['sodir'] = '$siterubyverarch' + when 'home' + setup_rb_error '$HOME was not set' unless ENV['HOME'] + table['prefix'] = ENV['HOME'] + table['rbdir'] = '$libdir/ruby' + table['sodir'] = '$libdir/ruby' + end + }, + PathItem.new('prefix', 'path', c['prefix'], + 'path prefix of target environment'), + PathItem.new('bindir', 'path', parameterize.call(c['bindir']), + 'the directory for commands'), + PathItem.new('libdir', 'path', parameterize.call(c['libdir']), + 'the directory for libraries'), + PathItem.new('datadir', 'path', parameterize.call(c['datadir']), + 'the directory for shared data'), + PathItem.new('mandir', 'path', parameterize.call(c['mandir']), + 'the directory for man pages'), + PathItem.new('sysconfdir', 'path', parameterize.call(c['sysconfdir']), + 'the directory for system configuration files'), + PathItem.new('localstatedir', 'path', parameterize.call(c['localstatedir']), + 'the directory for local state data'), + PathItem.new('libruby', 'path', libruby, + 'the directory for ruby libraries'), + PathItem.new('librubyver', 'path', librubyver, + 'the directory for standard ruby libraries'), + PathItem.new('librubyverarch', 'path', librubyverarch, + 'the directory for standard ruby extensions'), + PathItem.new('siteruby', 'path', siteruby, + 'the directory for version-independent aux ruby libraries'), + PathItem.new('siterubyver', 'path', siterubyver, + 'the directory for aux ruby libraries'), + PathItem.new('siterubyverarch', 'path', siterubyverarch, + 'the directory for aux ruby binaries'), + PathItem.new('rbdir', 'path', '$siterubyver', + 'the directory for ruby scripts'), + PathItem.new('sodir', 'path', '$siterubyverarch', + 'the directory for ruby extentions'), + PathItem.new('rubypath', 'path', rubypath, + 'the path to set to #! line'), + ProgramItem.new('rubyprog', 'name', rubypath, + 'the ruby program using for installation'), + ProgramItem.new('makeprog', 'name', makeprog, + 'the make program to compile ruby extentions'), + SelectItem.new('shebang', 'all/ruby/never', 'ruby', + 'shebang line (#!) editing mode'), + BoolItem.new('without-ext', 'yes/no', 'no', + 'does not compile/install ruby extentions') + ] + end + private :standard_entries + + def load_multipackage_entries + multipackage_entries().each do |ent| + add ent + end + end + + def multipackage_entries + [ + PackageSelectionItem.new('with', 'name,name...', '', 'ALL', + 'package names that you want to install'), + PackageSelectionItem.new('without', 'name,name...', '', 'NONE', + 'package names that you do not want to install') + ] + end + private :multipackage_entries + + ALIASES = { + 'std-ruby' => 'librubyver', + 'stdruby' => 'librubyver', + 'rubylibdir' => 'librubyver', + 'archdir' => 'librubyverarch', + 'site-ruby-common' => 'siteruby', # For backward compatibility + 'site-ruby' => 'siterubyver', # For backward compatibility + 'bin-dir' => 'bindir', + 'bin-dir' => 'bindir', + 'rb-dir' => 'rbdir', + 'so-dir' => 'sodir', + 'data-dir' => 'datadir', + 'ruby-path' => 'rubypath', + 'ruby-prog' => 'rubyprog', + 'ruby' => 'rubyprog', + 'make-prog' => 'makeprog', + 'make' => 'makeprog' + } + + def fixup + ALIASES.each do |ali, name| + @table[ali] = @table[name] + end + @items.freeze + @table.freeze + @options_re = /\A--(#{@table.keys.join('|')})(?:=(.*))?\z/ + end + + def parse_opt(opt) + m = @options_re.match(opt) or setup_rb_error "config: unknown option #{opt}" + m.to_a[1,2] + end + + def dllext + @rbconfig['DLEXT'] + end + + def value_config?(name) + lookup(name).value? + end + + class Item + def initialize(name, template, default, desc) + @name = name.freeze + @template = template + @value = default + @default = default + @description = desc + end + + attr_reader :name + attr_reader :description + + attr_accessor :default + alias help_default default + + def help_opt + "--#{@name}=#{@template}" + end + + def value? + true + end + + def value + @value + end + + def resolve(table) + @value.gsub(%r<\$([^/]+)>) { table[$1] } + end + + def set(val) + @value = check(val) + end + + private + + def check(val) + setup_rb_error "config: --#{name} requires argument" unless val + val + end + end + + class BoolItem < Item + def config_type + 'bool' + end + + def help_opt + "--#{@name}" + end + + private + + def check(val) + return 'yes' unless val + case val + when /\Ay(es)?\z/i, /\At(rue)?\z/i then 'yes' + when /\An(o)?\z/i, /\Af(alse)\z/i then 'no' + else + setup_rb_error "config: --#{@name} accepts only yes/no for argument" + end + end + end + + class PathItem < Item + def config_type + 'path' + end + + private + + def check(path) + setup_rb_error "config: --#{@name} requires argument" unless path + path[0,1] == '$' ? path : File.expand_path(path) + end + end + + class ProgramItem < Item + def config_type + 'program' + end + end + + class SelectItem < Item + def initialize(name, selection, default, desc) + super + @ok = selection.split('/') + end + + def config_type + 'select' + end + + private + + def check(val) + unless @ok.include?(val.strip) + setup_rb_error "config: use --#{@name}=#{@template} (#{val})" + end + val.strip + end + end + + class ExecItem < Item + def initialize(name, selection, desc, &block) + super name, selection, nil, desc + @ok = selection.split('/') + @action = block + end + + def config_type + 'exec' + end + + def value? + false + end + + def resolve(table) + setup_rb_error "$#{name()} wrongly used as option value" + end + + undef set + + def evaluate(val, table) + v = val.strip.downcase + unless @ok.include?(v) + setup_rb_error "invalid option --#{@name}=#{val} (use #{@template})" + end + @action.call v, table + end + end + + class PackageSelectionItem < Item + def initialize(name, template, default, help_default, desc) + super name, template, default, desc + @help_default = help_default + end + + attr_reader :help_default + + def config_type + 'package' + end + + private + + def check(val) + unless File.dir?("packages/#{val}") + setup_rb_error "config: no such package: #{val}" + end + val + end + end + + class MetaConfigEnvironment + def initialize(config, installer) + @config = config + @installer = installer + end + + def config_names + @config.names + end + + def config?(name) + @config.key?(name) + end + + def bool_config?(name) + @config.lookup(name).config_type == 'bool' + end + + def path_config?(name) + @config.lookup(name).config_type == 'path' + end + + def value_config?(name) + @config.lookup(name).config_type != 'exec' + end + + def add_config(item) + @config.add item + end + + def add_bool_config(name, default, desc) + @config.add BoolItem.new(name, 'yes/no', default ? 'yes' : 'no', desc) + end + + def add_path_config(name, default, desc) + @config.add PathItem.new(name, 'path', default, desc) + end + + def set_config_default(name, default) + @config.lookup(name).default = default + end + + def remove_config(name) + @config.remove(name) + end + + # For only multipackage + def packages + raise '[setup.rb fatal] multi-package metaconfig API packages() called for single-package; contact application package vendor' unless @installer + @installer.packages + end + + # For only multipackage + def declare_packages(list) + raise '[setup.rb fatal] multi-package metaconfig API declare_packages() called for single-package; contact application package vendor' unless @installer + @installer.packages = list + end + end + +end # class ConfigTable + + +# This module requires: #verbose?, #no_harm? +module FileOperations + + def mkdir_p(dirname, prefix = nil) + dirname = prefix + File.expand_path(dirname) if prefix + $stderr.puts "mkdir -p #{dirname}" if verbose? + return if no_harm? + + # Does not check '/', it's too abnormal. + dirs = File.expand_path(dirname).split(%r<(?=/)>) + if /\A[a-z]:\z/i =~ dirs[0] + disk = dirs.shift + dirs[0] = disk + dirs[0] + end + dirs.each_index do |idx| + path = dirs[0..idx].join('') + Dir.mkdir path unless File.dir?(path) + end + end + + def rm_f(path) + $stderr.puts "rm -f #{path}" if verbose? + return if no_harm? + force_remove_file path + end + + def rm_rf(path) + $stderr.puts "rm -rf #{path}" if verbose? + return if no_harm? + remove_tree path + end + + def remove_tree(path) + if File.symlink?(path) + remove_file path + elsif File.dir?(path) + remove_tree0 path + else + force_remove_file path + end + end + + def remove_tree0(path) + Dir.foreach(path) do |ent| + next if ent == '.' + next if ent == '..' + entpath = "#{path}/#{ent}" + if File.symlink?(entpath) + remove_file entpath + elsif File.dir?(entpath) + remove_tree0 entpath + else + force_remove_file entpath + end + end + begin + Dir.rmdir path + rescue Errno::ENOTEMPTY + # directory may not be empty + end + end + + def move_file(src, dest) + force_remove_file dest + begin + File.rename src, dest + rescue + File.open(dest, 'wb') {|f| + f.write File.binread(src) + } + File.chmod File.stat(src).mode, dest + File.unlink src + end + end + + def force_remove_file(path) + begin + remove_file path + rescue + end + end + + def remove_file(path) + File.chmod 0777, path + File.unlink path + end + + def install(from, dest, mode, prefix = nil) + $stderr.puts "install #{from} #{dest}" if verbose? + return if no_harm? + + realdest = prefix ? prefix + File.expand_path(dest) : dest + realdest = File.join(realdest, File.basename(from)) if File.dir?(realdest) + str = File.binread(from) + if diff?(str, realdest) + verbose_off { + rm_f realdest if File.exist?(realdest) + } + File.open(realdest, 'wb') {|f| + f.write str + } + File.chmod mode, realdest + + File.open("#{objdir_root()}/InstalledFiles", 'a') {|f| + if prefix + f.puts realdest.sub(prefix, '') + else + f.puts realdest + end + } + end + end + + def diff?(new_content, path) + return true unless File.exist?(path) + new_content != File.binread(path) + end + + def command(*args) + $stderr.puts args.join(' ') if verbose? + system(*args) or raise RuntimeError, + "system(#{args.map{|a| a.inspect }.join(' ')}) failed" + end + + def ruby(*args) + command config('rubyprog'), *args + end + + def make(task = nil) + command(*[config('makeprog'), task].compact) + end + + def extdir?(dir) + File.exist?("#{dir}/MANIFEST") or File.exist?("#{dir}/extconf.rb") + end + + def files_of(dir) + Dir.open(dir) {|d| + return d.select {|ent| File.file?("#{dir}/#{ent}") } + } + end + + DIR_REJECT = %w( . .. CVS SCCS RCS CVS.adm .svn ) + + def directories_of(dir) + Dir.open(dir) {|d| + return d.select {|ent| File.dir?("#{dir}/#{ent}") } - DIR_REJECT + } + end + +end + + +# This module requires: #srcdir_root, #objdir_root, #relpath +module HookScriptAPI + + def get_config(key) + @config[key] + end + + alias config get_config + + # obsolete: use metaconfig to change configuration + def set_config(key, val) + @config[key] = val + end + + # + # srcdir/objdir (works only in the package directory) + # + + def curr_srcdir + "#{srcdir_root()}/#{relpath()}" + end + + def curr_objdir + "#{objdir_root()}/#{relpath()}" + end + + def srcfile(path) + "#{curr_srcdir()}/#{path}" + end + + def srcexist?(path) + File.exist?(srcfile(path)) + end + + def srcdirectory?(path) + File.dir?(srcfile(path)) + end + + def srcfile?(path) + File.file?(srcfile(path)) + end + + def srcentries(path = '.') + Dir.open("#{curr_srcdir()}/#{path}") {|d| + return d.to_a - %w(. ..) + } + end + + def srcfiles(path = '.') + srcentries(path).select {|fname| + File.file?(File.join(curr_srcdir(), path, fname)) + } + end + + def srcdirectories(path = '.') + srcentries(path).select {|fname| + File.dir?(File.join(curr_srcdir(), path, fname)) + } + end + +end + + +class ToplevelInstaller + + Version = '3.4.1' + Copyright = 'Copyright (c) 2000-2005 Minero Aoki' + + TASKS = [ + [ 'all', 'do config, setup, then install' ], + [ 'config', 'saves your configurations' ], + [ 'show', 'shows current configuration' ], + [ 'setup', 'compiles ruby extentions and others' ], + [ 'install', 'installs files' ], + [ 'test', 'run all tests in test/' ], + [ 'clean', "does `make clean' for each extention" ], + [ 'distclean',"does `make distclean' for each extention" ] + ] + + def ToplevelInstaller.invoke + config = ConfigTable.new(load_rbconfig()) + config.load_standard_entries + config.load_multipackage_entries if multipackage? + config.fixup + klass = (multipackage?() ? ToplevelInstallerMulti : ToplevelInstaller) + klass.new(File.dirname($0), config).invoke + end + + def ToplevelInstaller.multipackage? + File.dir?(File.dirname($0) + '/packages') + end + + def ToplevelInstaller.load_rbconfig + if arg = ARGV.detect {|arg| /\A--rbconfig=/ =~ arg } + ARGV.delete(arg) + load File.expand_path(arg.split(/=/, 2)[1]) + $".push 'rbconfig.rb' + else + require 'rbconfig' + end + ::Config::CONFIG + end + + def initialize(ardir_root, config) + @ardir = File.expand_path(ardir_root) + @config = config + # cache + @valid_task_re = nil + end + + def config(key) + @config[key] + end + + def inspect + "#<#{self.class} #{__id__()}>" + end + + def invoke + run_metaconfigs + case task = parsearg_global() + when nil, 'all' + parsearg_config + init_installers + exec_config + exec_setup + exec_install + else + case task + when 'config', 'test' + ; + when 'clean', 'distclean' + @config.load_savefile if File.exist?(@config.savefile) + else + @config.load_savefile + end + __send__ "parsearg_#{task}" + init_installers + __send__ "exec_#{task}" + end + end + + def run_metaconfigs + @config.load_script "#{@ardir}/metaconfig" + end + + def init_installers + @installer = Installer.new(@config, @ardir, File.expand_path('.')) + end + + # + # Hook Script API bases + # + + def srcdir_root + @ardir + end + + def objdir_root + '.' + end + + def relpath + '.' + end + + # + # Option Parsing + # + + def parsearg_global + while arg = ARGV.shift + case arg + when /\A\w+\z/ + setup_rb_error "invalid task: #{arg}" unless valid_task?(arg) + return arg + when '-q', '--quiet' + @config.verbose = false + when '--verbose' + @config.verbose = true + when '--help' + print_usage $stdout + exit 0 + when '--version' + puts "#{File.basename($0)} version #{Version}" + exit 0 + when '--copyright' + puts Copyright + exit 0 + else + setup_rb_error "unknown global option '#{arg}'" + end + end + nil + end + + def valid_task?(t) + valid_task_re() =~ t + end + + def valid_task_re + @valid_task_re ||= /\A(?:#{TASKS.map {|task,desc| task }.join('|')})\z/ + end + + def parsearg_no_options + unless ARGV.empty? + task = caller(0).first.slice(%r<`parsearg_(\w+)'>, 1) + setup_rb_error "#{task}: unknown options: #{ARGV.join(' ')}" + end + end + + alias parsearg_show parsearg_no_options + alias parsearg_setup parsearg_no_options + alias parsearg_test parsearg_no_options + alias parsearg_clean parsearg_no_options + alias parsearg_distclean parsearg_no_options + + def parsearg_config + evalopt = [] + set = [] + @config.config_opt = [] + while i = ARGV.shift + if /\A--?\z/ =~ i + @config.config_opt = ARGV.dup + break + end + name, value = *@config.parse_opt(i) + if @config.value_config?(name) + @config[name] = value + else + evalopt.push [name, value] + end + set.push name + end + evalopt.each do |name, value| + @config.lookup(name).evaluate value, @config + end + # Check if configuration is valid + set.each do |n| + @config[n] if @config.value_config?(n) + end + end + + def parsearg_install + @config.no_harm = false + @config.install_prefix = '' + while a = ARGV.shift + case a + when '--no-harm' + @config.no_harm = true + when /\A--prefix=/ + path = a.split(/=/, 2)[1] + path = File.expand_path(path) unless path[0,1] == '/' + @config.install_prefix = path + else + setup_rb_error "install: unknown option #{a}" + end + end + end + + def print_usage(out) + out.puts 'Typical Installation Procedure:' + out.puts " $ ruby #{File.basename $0} config" + out.puts " $ ruby #{File.basename $0} setup" + out.puts " # ruby #{File.basename $0} install (may require root privilege)" + out.puts + out.puts 'Detailed Usage:' + out.puts " ruby #{File.basename $0} " + out.puts " ruby #{File.basename $0} [] []" + + fmt = " %-24s %s\n" + out.puts + out.puts 'Global options:' + out.printf fmt, '-q,--quiet', 'suppress message outputs' + out.printf fmt, ' --verbose', 'output messages verbosely' + out.printf fmt, ' --help', 'print this message' + out.printf fmt, ' --version', 'print version and quit' + out.printf fmt, ' --copyright', 'print copyright and quit' + out.puts + out.puts 'Tasks:' + TASKS.each do |name, desc| + out.printf fmt, name, desc + end + + fmt = " %-24s %s [%s]\n" + out.puts + out.puts 'Options for CONFIG or ALL:' + @config.each do |item| + out.printf fmt, item.help_opt, item.description, item.help_default + end + out.printf fmt, '--rbconfig=path', 'rbconfig.rb to load',"running ruby's" + out.puts + out.puts 'Options for INSTALL:' + out.printf fmt, '--no-harm', 'only display what to do if given', 'off' + out.printf fmt, '--prefix=path', 'install path prefix', '' + out.puts + end + + # + # Task Handlers + # + + def exec_config + @installer.exec_config + @config.save # must be final + end + + def exec_setup + @installer.exec_setup + end + + def exec_install + @installer.exec_install + end + + def exec_test + @installer.exec_test + end + + def exec_show + @config.each do |i| + printf "%-20s %s\n", i.name, i.value if i.value? + end + end + + def exec_clean + @installer.exec_clean + end + + def exec_distclean + @installer.exec_distclean + end + +end # class ToplevelInstaller + + +class ToplevelInstallerMulti < ToplevelInstaller + + include FileOperations + + def initialize(ardir_root, config) + super + @packages = directories_of("#{@ardir}/packages") + raise 'no package exists' if @packages.empty? + @root_installer = Installer.new(@config, @ardir, File.expand_path('.')) + end + + def run_metaconfigs + @config.load_script "#{@ardir}/metaconfig", self + @packages.each do |name| + @config.load_script "#{@ardir}/packages/#{name}/metaconfig" + end + end + + attr_reader :packages + + def packages=(list) + raise 'package list is empty' if list.empty? + list.each do |name| + raise "directory packages/#{name} does not exist"\ + unless File.dir?("#{@ardir}/packages/#{name}") + end + @packages = list + end + + def init_installers + @installers = {} + @packages.each do |pack| + @installers[pack] = Installer.new(@config, + "#{@ardir}/packages/#{pack}", + "packages/#{pack}") + end + with = extract_selection(config('with')) + without = extract_selection(config('without')) + @selected = @installers.keys.select {|name| + (with.empty? or with.include?(name)) \ + and not without.include?(name) + } + end + + def extract_selection(list) + a = list.split(/,/) + a.each do |name| + setup_rb_error "no such package: #{name}" unless @installers.key?(name) + end + a + end + + def print_usage(f) + super + f.puts 'Inluded packages:' + f.puts ' ' + @packages.sort.join(' ') + f.puts + end + + # + # Task Handlers + # + + def exec_config + run_hook 'pre-config' + each_selected_installers {|inst| inst.exec_config } + run_hook 'post-config' + @config.save # must be final + end + + def exec_setup + run_hook 'pre-setup' + each_selected_installers {|inst| inst.exec_setup } + run_hook 'post-setup' + end + + def exec_install + run_hook 'pre-install' + each_selected_installers {|inst| inst.exec_install } + run_hook 'post-install' + end + + def exec_test + run_hook 'pre-test' + each_selected_installers {|inst| inst.exec_test } + run_hook 'post-test' + end + + def exec_clean + rm_f @config.savefile + run_hook 'pre-clean' + each_selected_installers {|inst| inst.exec_clean } + run_hook 'post-clean' + end + + def exec_distclean + rm_f @config.savefile + run_hook 'pre-distclean' + each_selected_installers {|inst| inst.exec_distclean } + run_hook 'post-distclean' + end + + # + # lib + # + + def each_selected_installers + Dir.mkdir 'packages' unless File.dir?('packages') + @selected.each do |pack| + $stderr.puts "Processing the package `#{pack}' ..." if verbose? + Dir.mkdir "packages/#{pack}" unless File.dir?("packages/#{pack}") + Dir.chdir "packages/#{pack}" + yield @installers[pack] + Dir.chdir '../..' + end + end + + def run_hook(id) + @root_installer.run_hook id + end + + # module FileOperations requires this + def verbose? + @config.verbose? + end + + # module FileOperations requires this + def no_harm? + @config.no_harm? + end + +end # class ToplevelInstallerMulti + + +class Installer + + FILETYPES = %w( bin lib ext data conf man ) + + include FileOperations + include HookScriptAPI + + def initialize(config, srcroot, objroot) + @config = config + @srcdir = File.expand_path(srcroot) + @objdir = File.expand_path(objroot) + @currdir = '.' + end + + def inspect + "#<#{self.class} #{File.basename(@srcdir)}>" + end + + def noop(rel) + end + + # + # Hook Script API base methods + # + + def srcdir_root + @srcdir + end + + def objdir_root + @objdir + end + + def relpath + @currdir + end + + # + # Config Access + # + + # module FileOperations requires this + def verbose? + @config.verbose? + end + + # module FileOperations requires this + def no_harm? + @config.no_harm? + end + + def verbose_off + begin + save, @config.verbose = @config.verbose?, false + yield + ensure + @config.verbose = save + end + end + + # + # TASK config + # + + def exec_config + exec_task_traverse 'config' + end + + alias config_dir_bin noop + alias config_dir_lib noop + + def config_dir_ext(rel) + extconf if extdir?(curr_srcdir()) + end + + alias config_dir_data noop + alias config_dir_conf noop + alias config_dir_man noop + + def extconf + ruby "#{curr_srcdir()}/extconf.rb", *@config.config_opt + end + + # + # TASK setup + # + + def exec_setup + exec_task_traverse 'setup' + end + + def setup_dir_bin(rel) + files_of(curr_srcdir()).each do |fname| + update_shebang_line "#{curr_srcdir()}/#{fname}" + end + end + + alias setup_dir_lib noop + + def setup_dir_ext(rel) + make if extdir?(curr_srcdir()) + end + + alias setup_dir_data noop + alias setup_dir_conf noop + alias setup_dir_man noop + + def update_shebang_line(path) + return if no_harm? + return if config('shebang') == 'never' + old = Shebang.load(path) + if old + $stderr.puts "warning: #{path}: Shebang line includes too many args. It is not portable and your program may not work." if old.args.size > 1 + new = new_shebang(old) + return if new.to_s == old.to_s + else + return unless config('shebang') == 'all' + new = Shebang.new(config('rubypath')) + end + $stderr.puts "updating shebang: #{File.basename(path)}" if verbose? + open_atomic_writer(path) {|output| + File.open(path, 'rb') {|f| + f.gets if old # discard + output.puts new.to_s + output.print f.read + } + } + end + + def new_shebang(old) + if /\Aruby/ =~ File.basename(old.cmd) + Shebang.new(config('rubypath'), old.args) + elsif File.basename(old.cmd) == 'env' and old.args.first == 'ruby' + Shebang.new(config('rubypath'), old.args[1..-1]) + else + return old unless config('shebang') == 'all' + Shebang.new(config('rubypath')) + end + end + + def open_atomic_writer(path, &block) + tmpfile = File.basename(path) + '.tmp' + begin + File.open(tmpfile, 'wb', &block) + File.rename tmpfile, File.basename(path) + ensure + File.unlink tmpfile if File.exist?(tmpfile) + end + end + + class Shebang + def Shebang.load(path) + line = nil + File.open(path) {|f| + line = f.gets + } + return nil unless /\A#!/ =~ line + parse(line) + end + + def Shebang.parse(line) + cmd, *args = *line.strip.sub(/\A\#!/, '').split(' ') + new(cmd, args) + end + + def initialize(cmd, args = []) + @cmd = cmd + @args = args + end + + attr_reader :cmd + attr_reader :args + + def to_s + "#! #{@cmd}" + (@args.empty? ? '' : " #{@args.join(' ')}") + end + end + + # + # TASK install + # + + def exec_install + rm_f 'InstalledFiles' + exec_task_traverse 'install' + end + + def install_dir_bin(rel) + install_files targetfiles(), "#{config('bindir')}/#{rel}", 0755 + end + + def install_dir_lib(rel) + install_files libfiles(), "#{config('rbdir')}/#{rel}", 0644 + end + + def install_dir_ext(rel) + return unless extdir?(curr_srcdir()) + install_files rubyextentions('.'), + "#{config('sodir')}/#{File.dirname(rel)}", + 0555 + end + + def install_dir_data(rel) + install_files targetfiles(), "#{config('datadir')}/#{rel}", 0644 + end + + def install_dir_conf(rel) + # FIXME: should not remove current config files + # (rename previous file to .old/.org) + install_files targetfiles(), "#{config('sysconfdir')}/#{rel}", 0644 + end + + def install_dir_man(rel) + install_files targetfiles(), "#{config('mandir')}/#{rel}", 0644 + end + + def install_files(list, dest, mode) + mkdir_p dest, @config.install_prefix + list.each do |fname| + install fname, dest, mode, @config.install_prefix + end + end + + def libfiles + glob_reject(%w(*.y *.output), targetfiles()) + end + + def rubyextentions(dir) + ents = glob_select("*.#{@config.dllext}", targetfiles()) + if ents.empty? + setup_rb_error "no ruby extention exists: 'ruby #{$0} setup' first" + end + ents + end + + def targetfiles + mapdir(existfiles() - hookfiles()) + end + + def mapdir(ents) + ents.map {|ent| + if File.exist?(ent) + then ent # objdir + else "#{curr_srcdir()}/#{ent}" # srcdir + end + } + end + + # picked up many entries from cvs-1.11.1/src/ignore.c + JUNK_FILES = %w( + core RCSLOG tags TAGS .make.state + .nse_depinfo #* .#* cvslog.* ,* .del-* *.olb + *~ *.old *.bak *.BAK *.orig *.rej _$* *$ + + *.org *.in .* + ) + + def existfiles + glob_reject(JUNK_FILES, (files_of(curr_srcdir()) | files_of('.'))) + end + + def hookfiles + %w( pre-%s post-%s pre-%s.rb post-%s.rb ).map {|fmt| + %w( config setup install clean ).map {|t| sprintf(fmt, t) } + }.flatten + end + + def glob_select(pat, ents) + re = globs2re([pat]) + ents.select {|ent| re =~ ent } + end + + def glob_reject(pats, ents) + re = globs2re(pats) + ents.reject {|ent| re =~ ent } + end + + GLOB2REGEX = { + '.' => '\.', + '$' => '\$', + '#' => '\#', + '*' => '.*' + } + + def globs2re(pats) + /\A(?:#{ + pats.map {|pat| pat.gsub(/[\.\$\#\*]/) {|ch| GLOB2REGEX[ch] } }.join('|') + })\z/ + end + + # + # TASK test + # + + TESTDIR = 'test' + + def exec_test + unless File.directory?('test') + $stderr.puts 'no test in this package' if verbose? + return + end + $stderr.puts 'Running tests...' if verbose? + begin + require 'test/unit' + rescue LoadError + setup_rb_error 'test/unit cannot loaded. You need Ruby 1.8 or later to invoke this task.' + end + runner = Test::Unit::AutoRunner.new(true) + runner.to_run << TESTDIR + runner.run + end + + # + # TASK clean + # + + def exec_clean + exec_task_traverse 'clean' + rm_f @config.savefile + rm_f 'InstalledFiles' + end + + alias clean_dir_bin noop + alias clean_dir_lib noop + alias clean_dir_data noop + alias clean_dir_conf noop + alias clean_dir_man noop + + def clean_dir_ext(rel) + return unless extdir?(curr_srcdir()) + make 'clean' if File.file?('Makefile') + end + + # + # TASK distclean + # + + def exec_distclean + exec_task_traverse 'distclean' + rm_f @config.savefile + rm_f 'InstalledFiles' + end + + alias distclean_dir_bin noop + alias distclean_dir_lib noop + + def distclean_dir_ext(rel) + return unless extdir?(curr_srcdir()) + make 'distclean' if File.file?('Makefile') + end + + alias distclean_dir_data noop + alias distclean_dir_conf noop + alias distclean_dir_man noop + + # + # Traversing + # + + def exec_task_traverse(task) + run_hook "pre-#{task}" + FILETYPES.each do |type| + if type == 'ext' and config('without-ext') == 'yes' + $stderr.puts 'skipping ext/* by user option' if verbose? + next + end + traverse task, type, "#{task}_dir_#{type}" + end + run_hook "post-#{task}" + end + + def traverse(task, rel, mid) + dive_into(rel) { + run_hook "pre-#{task}" + __send__ mid, rel.sub(%r[\A.*?(?:/|\z)], '') + directories_of(curr_srcdir()).each do |d| + traverse task, "#{rel}/#{d}", mid + end + run_hook "post-#{task}" + } + end + + def dive_into(rel) + return unless File.dir?("#{@srcdir}/#{rel}") + + dir = File.basename(rel) + Dir.mkdir dir unless File.dir?(dir) + prevdir = Dir.pwd + Dir.chdir dir + $stderr.puts '---> ' + rel if verbose? + @currdir = rel + yield + Dir.chdir prevdir + $stderr.puts '<--- ' + rel if verbose? + @currdir = File.dirname(rel) + end + + def run_hook(id) + path = [ "#{curr_srcdir()}/#{id}", + "#{curr_srcdir()}/#{id}.rb" ].detect {|cand| File.file?(cand) } + return unless path + begin + instance_eval File.read(path), path, 1 + rescue + raise if $DEBUG + setup_rb_error "hook #{path} failed:\n" + $!.message + end + end + +end # class Installer + + +class SetupError < StandardError; end + +def setup_rb_error(msg) + raise SetupError, msg +end + +if $0 == __FILE__ + begin + ToplevelInstaller.invoke + rescue SetupError + raise if $DEBUG + $stderr.puts $!.message + $stderr.puts "Try 'ruby #{$0} --help' for detailed usage." + exit 1 + end +end diff --git a/vendor/xmpp4r-0.3.2/test/bytestreams/tc_ibb.rb b/vendor/xmpp4r-0.3.2/test/bytestreams/tc_ibb.rb new file mode 100755 index 000000000..7124e5e3d --- /dev/null +++ b/vendor/xmpp4r-0.3.2/test/bytestreams/tc_ibb.rb @@ -0,0 +1,186 @@ +#!/usr/bin/ruby + +$:.unshift File::dirname(__FILE__) + '/../../lib' + +require 'test/unit' +require File::dirname(__FILE__) + '/../lib/clienttester' + +require 'xmpp4r' +require 'xmpp4r/bytestreams' +require 'xmpp4r/semaphore' +include Jabber + +class IBBTest < Test::Unit::TestCase + include ClientTester + + def create_buffer(size) + ([nil] * size).collect { rand(256).chr }.join + end + + def test_ibb_target2initiator + target = Bytestreams::IBBTarget.new(@server, '1', nil, '1@a.com/1') + initiator = Bytestreams::IBBInitiator.new(@client, '1', nil, '1@a.com/1') + + buffer = create_buffer(9999) + + Thread.new do + target.accept + target.write(buffer) + Thread.pass + target.close + end + + + initiator.open + + received = '' + while buf = initiator.read + received += buf + end + + initiator.close + + assert_equal(buffer, received) + end + + def test_ibb_initiator2target + target = Bytestreams::IBBTarget.new(@server, '1', nil, '1@a.com/1') + initiator = Bytestreams::IBBInitiator.new(@client, '1', nil, '1@a.com/1') + + buffer = create_buffer(9999) + + Thread.new do + Thread.pass + initiator.open + initiator.write(buffer) + Thread.pass + initiator.close + end + + + target.accept + + received = '' + while buf = target.read + received += buf + end + + target.close + + assert_equal(buffer, received) + end + + def test_ibb_pingpong + ignored_stanzas = 0 + wait = Semaphore.new + @server.add_message_callback { ignored_stanzas += 1; wait.run } + @server.add_iq_callback { ignored_stanzas += 1; wait.run } + + + target = Bytestreams::IBBTarget.new(@server, '1', nil, '1@a.com/1') + initiator = Bytestreams::IBBInitiator.new(@client, '1', nil, '1@a.com/1') + + Thread.new do + target.accept + + while buf = target.read + target.write(buf) + target.flush + end + + target.close + end + + + assert_equal(0, ignored_stanzas) + @client.send(" + + ") + wait.wait + assert_equal(1, ignored_stanzas) + + + initiator.open + + + assert_equal(1, ignored_stanzas) + @client.send(" + + ") + wait.wait + assert_equal(2, ignored_stanzas) + @client.send(" + + ") + wait.wait + assert_equal(3, ignored_stanzas) + + + 10.times do + buf = create_buffer(9999) + initiator.write(buf) + initiator.flush + + bufr = '' + begin + bufr += initiator.read + end while bufr.size < buf.size + assert_equal(buf, bufr) + end + + initiator.close + + + assert_equal(3, ignored_stanzas) + end + + def test_ibb_error + target = Bytestreams::IBBTarget.new(@server, '1', nil, '1@a.com/1') + initiator = Bytestreams::IBBInitiator.new(@client, '1', nil, '1@a.com/1') + + Thread.new do + target.accept + + @server.send(" + + ") + end + + + initiator.open + + assert_nil(initiator.read) + + initiator.close + end + + def test_ibb_inactive + target = Bytestreams::IBBTarget.new(@server, '1', nil, '1@a.com/1') + initiator = Bytestreams::IBBInitiator.new(@client, '1', nil, '1@a.com/1') + + assert_nil(target.read) + assert_nil(initiator.read) + + assert_raise(RuntimeError) { + target.write('a' * target.block_size) + } + assert_raise(RuntimeError) { + initiator.write('a' * initiator.block_size) + } + end + + def test_ibb_queueitem + i1 = Bytestreams::IBBQueueItem.new(:close) + assert_equal(:close, i1.type) + assert_nil(i1.seq) + + i2 = Bytestreams::IBBQueueItem.new(:data, 1, Base64::encode64('blah')) + assert_equal(:data, i2.type) + assert_equal(1, i2.seq) + assert_equal('blah', i2.data) + + assert_raise(RuntimeError) { + i3 = Bytestreams::IBBQueueItem.new(:invalid) + } + end +end diff --git a/vendor/xmpp4r-0.3.2/test/bytestreams/tc_socks5bytestreams.rb b/vendor/xmpp4r-0.3.2/test/bytestreams/tc_socks5bytestreams.rb new file mode 100755 index 000000000..076cc6495 --- /dev/null +++ b/vendor/xmpp4r-0.3.2/test/bytestreams/tc_socks5bytestreams.rb @@ -0,0 +1,103 @@ +#!/usr/bin/ruby + +$:.unshift File::dirname(__FILE__) + '/../../lib' + +require 'test/unit' +require File::dirname(__FILE__) + '/../lib/clienttester' + +require 'xmpp4r' +require 'xmpp4r/bytestreams' +include Jabber + +class SOCKS5BytestreamsTest < Test::Unit::TestCase + include ClientTester + + @@server = Bytestreams::SOCKS5BytestreamsServer.new(65005) + @@server.add_address('localhost') + + def create_buffer(size) + ([nil] * size).collect { rand(256).chr }.join + end + + def test_server2multi + target1 = Bytestreams::SOCKS5BytestreamsTarget.new(@server, '1', '1@a.com/1', '1@a.com/2') + target2 = Bytestreams::SOCKS5BytestreamsTarget.new(@server, '2', '2@a.com/1', '2@a.com/2') + initiator1 = Bytestreams::SOCKS5BytestreamsInitiator.new(@client, '1', '1@a.com/1', '1@a.com/2') + initiator2 = Bytestreams::SOCKS5BytestreamsInitiator.new(@client, '2', '2@a.com/1', '2@a.com/2') + initiator1.add_streamhost(@@server) + initiator2.add_streamhost(@@server) + + buf1 = create_buffer(8192) + buf2 = create_buffer(8192) + + Thread.new do + target1.accept + target1.write(buf1) + target1.flush + target1.close + end + + Thread.new do + target2.accept + target2.write(buf2) + target2.flush + target2.close + end + + initiator1.open + initiator2.open + + recv1 = '' + recv2 = '' + + while buf = initiator2.read(256) + recv2 += buf + end + + while buf = initiator1.read(256) + recv1 += buf + end + + initiator1.close + initiator2.close + + assert_equal(buf1, recv1) + assert_equal(buf2, recv2) + end + + def test_pingpong + target = Bytestreams::SOCKS5BytestreamsTarget.new(@server, '1', '1@a.com/1', '1@a.com/2') + initiator = Bytestreams::SOCKS5BytestreamsInitiator.new(@client, '1', '1@a.com/1', '1@a.com/2') + initiator.add_streamhost(@@server) + + + Thread.new do + target.accept + + while buf = target.read(256) + target.write(buf) + target.flush + end + + target.close + end + + + initiator.open + + 10.times do + buf = create_buffer(8192) + initiator.write(buf) + initiator.flush + + bufr = '' + begin + bufr += initiator.read(256) + end while bufr.size < buf.size + assert_equal(buf, bufr) + end + + initiator.close + end + +end diff --git a/vendor/xmpp4r-0.3.2/test/dataforms/tc_data.rb b/vendor/xmpp4r-0.3.2/test/dataforms/tc_data.rb new file mode 100755 index 000000000..a11934daf --- /dev/null +++ b/vendor/xmpp4r-0.3.2/test/dataforms/tc_data.rb @@ -0,0 +1,81 @@ +#!/usr/bin/ruby + +$:.unshift File::dirname(__FILE__) + '/../../lib' + +require 'test/unit' +require 'xmpp4r/dataforms' +include Jabber + +class DataFormsTest < Test::Unit::TestCase + + def test_create_defaults + v = Dataforms::XDataTitle.new + assert_nil(v.title) + assert_equal("", v.to_s) + + v = Dataforms::XDataInstructions.new + assert_nil(v.instructions) + assert_equal("", v.to_s) + + v = Dataforms::XDataField.new + assert_nil(v.label) + assert_nil(v.var) + assert_nil(v.type) + assert_equal(false, v.required?) + assert_equal([], v.values) + assert_equal({}, v.options) + + v = Dataforms::XData.new + assert_equal([], v.fields) + assert_nil(v.type) + end + + def test_create + v = Dataforms::XDataTitle.new "This is the title" + assert_equal("This is the title",v.title) + assert_equal("This is the title", v.to_s) + + v = Dataforms::XDataInstructions.new "Instructions" + assert_equal("Instructions",v.instructions) + assert_equal("Instructions", v.to_s) + + f = Dataforms::XDataField.new "botname", :text_single + assert_nil(f.label) + assert_equal("botname", f.var) + assert_equal(:text_single, f.type) + assert_equal(false, f.required?) + assert_equal([], f.values) + assert_equal({}, f.options) + f.label = "The name of your bot" + assert_equal("The name of your bot", f.label) + [:boolean, :fixed, :hidden, :jid_multi, :jid_single, + :list_multi, :list_single, :text_multi, :text_private, + :text_single].each do |type| + f.type = type + assert_equal(type, f.type) + end + f.type = :wrong_type + assert_nil(f.type) + f.required= true + assert_equal(true, f.required?) + f.values = ["the value"] + assert_equal(["the value"], f.values) + f.options = { "option 1" => "Label 1", "option 2" => "Label 2", "option 3" => nil } + assert_equal({ "option 1" => "Label 1", "option 2" => "Label 2", "option 3" => nil }, f.options) + + + f = Dataforms::XDataField.new "test", :text_single + v = Dataforms::XData.new :form + assert_equal([], v.fields) + assert_equal(:form, v.type) + [:form, :result, :submit, :cancel].each do |type| + v.type = type + assert_equal(type, v.type) + end + v.add f + assert_equal(f, v.field('test')) + assert_nil(v.field('wrong field')) + assert_equal([f], v.fields) + end + +end diff --git a/vendor/xmpp4r-0.3.2/test/delay/tc_xdelay.rb b/vendor/xmpp4r-0.3.2/test/delay/tc_xdelay.rb new file mode 100755 index 000000000..64b3dc302 --- /dev/null +++ b/vendor/xmpp4r-0.3.2/test/delay/tc_xdelay.rb @@ -0,0 +1,51 @@ +#!/usr/bin/ruby + +$:.unshift File::dirname(__FILE__) + '/../../lib' + +require 'test/unit' +require 'xmpp4r/rexmladdons' +require 'xmpp4r/delay/x/delay' +include Jabber + +class XDelayTest < Test::Unit::TestCase + def test_create1 + d = Delay::XDelay.new(false) + assert_equal(nil, d.stamp) + assert_equal(nil, d.from) + assert_equal('jabber:x:delay', d.namespace) + end + + def test_create2 + d = Delay::XDelay.new + # Hopefully the seconds don't change here... + assert_equal(Time.now.to_s, d.stamp.to_s) + assert_equal(nil, d.from) + assert_equal('jabber:x:delay', d.namespace) + end + + def test_from + d = Delay::XDelay.new + assert_equal(nil, d.from) + d.from = JID::new('astro@spaceboyz.net') + assert_equal(JID::new('astro@spaceboyz.net'), d.from) + assert_equal(d, d.set_from(nil)) + assert_equal(nil, d.from) + end + + def test_stamp + d = Delay::XDelay.new(false) + assert_equal(nil, d.stamp) + now = Time.now + d.stamp = now + assert_equal(now.to_s, d.stamp.to_s) + assert_equal(d, d.set_stamp(nil)) + assert_equal(nil, d.stamp) + end + + def test_import + x1 = X.new + x1.add_namespace('jabber:x:delay') + x2 = X::import(x1) + assert_equal(Delay::XDelay, x2.class) + end +end diff --git a/vendor/xmpp4r-0.3.2/test/lib/clienttester.rb b/vendor/xmpp4r-0.3.2/test/lib/clienttester.rb new file mode 100644 index 000000000..d2e4f6651 --- /dev/null +++ b/vendor/xmpp4r-0.3.2/test/lib/clienttester.rb @@ -0,0 +1,113 @@ +$:.unshift '../lib' +require 'xmpp4r' +require 'test/unit' +require 'socket' +require 'xmpp4r/semaphore' + +# Turn $VERBOSE off to suppress warnings about redefinition +oldverbose = $VERBOSE +$VERBOSE = false + +module Jabber + ## + # The ClientTester is a mix-in which provides a setup and teardown + # method to prepare a Stream object (@client) and two methods + # interfacing as the "server side": + # * send(xml):: Send a stanza to @client + # * receive:: (Wait and) retrieve a stanza sent by the client (in order) + # + # The server side is a stream, too: add your callbacks to @server + # + # ClientTester is written to test complex helper classes. + module ClientTester + @@SOCKET_PORT = 65223 + + def setup + servlisten = TCPServer.new(@@SOCKET_PORT) + serverwait = Semaphore.new + Thread.new do + Thread.current.abort_on_exception = true + serversock = servlisten.accept + servlisten.close + serversock.sync = true + @server = Stream.new(true) + @server.add_xml_callback do |xml| + if xml.prefix == 'stream' and xml.name == 'stream' + send('') + true + else + false + end + end + @server.start(serversock) + + serverwait.run + end + + clientsock = TCPSocket.new('localhost', @@SOCKET_PORT) + clientsock.sync = true + @client = Stream.new(true) + @client.start(clientsock) + + @client.send('') { |reply| true } + + @state = 0 + @states = [] + @state_wait = Semaphore.new + @state_wait2 = Semaphore.new + @server.add_stanza_callback { |stanza| + if @state < @states.size + @states[@state].call(stanza) + @state += 1 + @state_wait2.wait + @state_wait.run + end + + false + } + + serverwait.wait + end + + def teardown + @client.close + @server.close + end + + def send(xml) + @server.send(xml) + end + + def receive + @receive_lock.lock + + loop { + @stanzas_lock.synchronize { + if @stanzas.size > 0 + @receive_lock.unlock + return @stanzas.shift + end + } + + @receive_lock.lock + @receive_lock.unlock + } + end + + def state(&block) + @states << block + end + + def wait_state + @state_wait2.run + @state_wait.wait + end + + def skip_state + @state_wait2.run + end + end +end + +# Restore the old $VERBOSE setting +$VERBOSE = oldverbose diff --git a/vendor/xmpp4r-0.3.2/test/muc/tc_muc_mucclient.rb b/vendor/xmpp4r-0.3.2/test/muc/tc_muc_mucclient.rb new file mode 100755 index 000000000..482fea808 --- /dev/null +++ b/vendor/xmpp4r-0.3.2/test/muc/tc_muc_mucclient.rb @@ -0,0 +1,594 @@ +#!/usr/bin/ruby + + +$:.unshift File::dirname(__FILE__) + '/../../lib' + +require 'test/unit' +require File::dirname(__FILE__) + '/../lib/clienttester' +require 'xmpp4r/muc' +require 'xmpp4r/semaphore' +include Jabber + +class MUCClientTest < Test::Unit::TestCase + include ClientTester + + def test_new1 + m = MUC::MUCClient.new(@client) + assert_equal(nil, m.jid) + assert_equal(nil, m.my_jid) + assert_equal({}, m.roster) + assert(!m.active?) + end + + # JEP-0045: 6.3 Entering a Room + def test_enter_room + state { |pres| + assert_kind_of(Presence, pres) + assert_equal(JID.new('hag66@shakespeare.lit/pda'), pres.from) + assert_equal(JID.new('darkcave@macbeth.shakespeare.lit/thirdwitch'), pres.to) + send("" + + "" + + "" + + "") + } + state { |pres| + assert_kind_of(Presence, pres) + assert_equal(JID.new('hag66@shakespeare.lit/pda'), pres.from) + assert_equal(JID.new('darkcave@macbeth.shakespeare.lit/thirdwitch'), pres.to) + send("" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "") + } + + + m = MUC::MUCClient.new(@client) + m.my_jid = 'hag66@shakespeare.lit/pda' + assert(!m.active?) + assert_nil(m.room) + + assert_raises(ErrorException) { + m.join('darkcave@macbeth.shakespeare.lit/thirdwitch') + } + wait_state + assert(!m.active?) + assert_nil(m.room) + + assert_equal(m, m.join('darkcave@macbeth.shakespeare.lit/thirdwitch')) + wait_state + assert(m.active?) + assert_equal('darkcave', m.room) + assert_equal(3, m.roster.size) + m.roster.each { |resource,pres| + assert_equal(resource, pres.from.resource) + assert_equal('darkcave', pres.from.node) + assert_equal('macbeth.shakespeare.lit', pres.from.domain) + assert_kind_of(String, resource) + assert_kind_of(Presence, pres) + assert(%w(firstwitch secondwitch thirdwitch).include?(resource)) + assert_kind_of(MUC::XMUCUser, pres.x) + assert_kind_of(Array, pres.x.items) + assert_equal(1, pres.x.items.size) + } + assert_equal(:owner, m.roster['firstwitch'].x.items[0].affiliation) + assert_equal(:moderator, m.roster['firstwitch'].x.items[0].role) + assert_equal(:admin, m.roster['secondwitch'].x.items[0].affiliation) + assert_equal(:moderator, m.roster['secondwitch'].x.items[0].role) + assert_equal(:member, m.roster['thirdwitch'].x.items[0].affiliation) + assert_equal(:participant, m.roster['thirdwitch'].x.items[0].role) + assert_nil(m.roster['thirdwitch'].x.items[0].jid) + + send("" + + "" + + "") + sleep 0.1 + assert_equal(3, m.roster.size) + assert_equal(:none, m.roster['thirdwitch'].x.items[0].affiliation) + assert_equal(:participant, m.roster['thirdwitch'].x.items[0].role) + assert_equal(JID.new('hag66@shakespeare.lit/pda'), m.roster['thirdwitch'].x.items[0].jid) + end + + def test_enter_room_password + state { |pres| + assert_kind_of(Presence, pres) + send("" + + "" + + "") + } + state { |pres| + assert_kind_of(Presence, pres) + assert_equal('cauldron', pres.x.password) + send("" + + "" + + "") + } + + m = MUC::MUCClient.new(@client) + m.my_jid = 'hag66@shakespeare.lit/pda' + assert_raises(ErrorException) { + m.join('darkcave@macbeth.shakespeare.lit/thirdwitch') + } + wait_state + assert(!m.active?) + + assert_equal(m, m.join('darkcave@macbeth.shakespeare.lit/thirdwitch', 'cauldron')) + wait_state + assert(m.active?) + end + + def test_members_only_room + state { |pres| + assert_kind_of(Presence, pres) + send("" + + "" + + "") + } + + m = MUC::MUCClient.new(@client) + m.my_jid = 'hag66@shakespeare.lit/pda' + assert_raises(ErrorException) { + m.join('darkcave@macbeth.shakespeare.lit/thirdwitch') + } + assert(!m.active?) + + wait_state + end + + def test_banned_users + state { |pres| + assert_kind_of(Presence, pres) + send("" + + "" + + "") + } + + m = MUC::MUCClient.new(@client) + m.my_jid = 'hag66@shakespeare.lit/pda' + assert_raises(ErrorException) { + m.join('darkcave@macbeth.shakespeare.lit/thirdwitch') + } + assert(!m.active?) + + wait_state + end + + def test_nickname_conflict + state { |pres| + assert_kind_of(Presence, pres) + send("" + + "" + + "") + } + + m = MUC::MUCClient.new(@client) + m.my_jid = 'hag66@shakespeare.lit/pda' + assert_raises(ErrorException) { + m.join('darkcave@macbeth.shakespeare.lit/thirdwitch') + } + assert(!m.active?) + + wait_state + end + + def test_max_users + state { |pres| + assert_kind_of(Presence, pres) + send("" + + "" + + "") + } + + m = MUC::MUCClient.new(@client) + m.my_jid = 'hag66@shakespeare.lit/pda' + assert_raises(ErrorException) { + m.join('darkcave@macbeth.shakespeare.lit/thirdwitch') + } + assert(!m.active?) + + wait_state + end + + def test_locked_room + state { |pres| + send("" + + "" + + "") + } + + m = MUC::MUCClient.new(@client) + m.my_jid = 'hag66@shakespeare.lit/pda' + assert_raises(ErrorException) { + m.join('darkcave@macbeth.shakespeare.lit/thirdwitch') + } + assert(!m.active?) + wait_state + end + + def test_exit_room + state { |pres| + assert_kind_of(Presence, pres) + assert_nil(pres.type) + send("" + + "" + + "") + } + state { |pres| + assert_kind_of(Presence, pres) + assert_equal(:unavailable, pres.type) + assert_equal(JID.new('hag66@shakespeare.lit/pda'), pres.from) + assert_nil(pres.status) + send("" + + "" + + "") + send("" + + "" + + "") + } + + ignored_stanzas = 0 + @client.add_stanza_callback { |stanza| + ignored_stanzas += 1 + } + + m = MUC::MUCClient.new(@client) + m.my_jid = 'hag66@shakespeare.lit/pda' + assert_equal(0, ignored_stanzas) + assert_equal(m, m.join('darkcave@macbeth.shakespeare.lit/thirdwitch')) + wait_state + assert(m.active?) + + assert_equal(0, ignored_stanzas) + assert_equal(m, m.exit) + wait_state + assert(!m.active?) + assert_equal(1, ignored_stanzas) + end + + def test_custom_exit_message + state { |pres| + assert_kind_of(Presence, pres) + assert_nil(pres.type) + send("" + + "" + + "") + } + state { |pres| + assert_kind_of(Presence, pres) + assert_equal(:unavailable, pres.type) + assert_equal(JID.new('hag66@shakespeare.lit/pda'), pres.from) + assert_equal('gone where the goblins go', pres.status) + send("" + + "" + + "") + } + + m = MUC::MUCClient.new(@client) + m.my_jid = 'hag66@shakespeare.lit/pda' + assert_equal(m, m.join('darkcave@macbeth.shakespeare.lit/thirdwitch')) + assert(m.active?) + wait_state + + assert_equal(m, m.exit('gone where the goblins go')) + assert(!m.active?) + wait_state + end + + def test_joins + state { |pres| + assert_kind_of(Presence, pres) + assert_nil(pres.type) + assert_equal(JID.new('hag66@shakespeare.lit/pda'), pres.from) + assert_equal(JID.new('darkcave@macbeth.shakespeare.lit/thirdwitch'), pres.to) + send("" + + "" + + "") + } + state { |pres| + assert_kind_of(Presence, pres) + assert_equal(:unavailable, pres.type) + assert_equal(JID.new('hag66@shakespeare.lit/pda'), pres.from) + assert_equal(JID.new('darkcave@macbeth.shakespeare.lit/thirdwitch'), pres.to) + assert_nil(pres.status) + send("" + + "" + + "") + } + state { |pres| + assert_kind_of(Presence, pres) + assert_nil(pres.type) + assert_equal(JID.new('hag66@shakespeare.lit/pda'), pres.from) + assert_equal(JID.new('darkcave@macbeth.shakespeare.lit/fourthwitch'), pres.to) + send("" + + "" + + "") + } + state { |pres| + assert_kind_of(Presence, pres) + assert_equal(:unavailable, pres.type) + assert_equal(JID.new('hag66@shakespeare.lit/pda'), pres.from) + assert_equal(JID.new('darkcave@macbeth.shakespeare.lit/fourthwitch'), pres.to) + assert_equal(pres.status, 'Exiting one last time') + send("" + + "" + + "") + } + + m = MUC::MUCClient.new(@client) + m.my_jid = 'hag66@shakespeare.lit/pda' + assert_equal(m, m.join('darkcave@macbeth.shakespeare.lit/thirdwitch')) + wait_state + assert(m.active?) + + assert_raises(RuntimeError) { m.join('darkcave@macbeth.shakespeare.lit/thirdwitch') } + assert_raises(RuntimeError) { m.join('darkcave@macbeth.shakespeare.lit/fourthwitch') } + assert(m.active?) + + assert_equal(m, m.exit) + wait_state + assert(!m.active?) + assert_raises(RuntimeError) { m.exit } + assert(!m.active?) + + assert_equal(m, m.join('darkcave@macbeth.shakespeare.lit/fourthwitch')) + wait_state + assert(m.active?) + + assert_raises(RuntimeError) { m.join('darkcave@macbeth.shakespeare.lit/thirdwitch') } + assert_raises(RuntimeError) { m.join('darkcave@macbeth.shakespeare.lit/fourthwitch') } + assert(m.active?) + + assert_equal(m, m.exit('Exiting one last time')) + wait_state + assert(!m.active?) + assert_raises(RuntimeError) { m.exit } + assert(!m.active?) + end + + def test_message_callback + state { |pres| + assert_kind_of(Presence, pres) + assert_equal('cauldron', pres.x.password) + send("" + + "" + + "") + } + + message_lock = Semaphore.new + + messages_client = 0 + @client.add_message_callback { |msg| + messages_client += 1 + message_lock.run + } + + m = MUC::MUCClient.new(@client) + m.my_jid = 'hag66@shakespeare.lit/pda' + messages_muc = 0 + m.add_message_callback { |msg| + messages_muc += 1 + message_lock.run + } + messages_muc_private = 0 + m.add_private_message_callback { |msg| + messages_muc_private += 1 + message_lock.run + } + + assert_equal(m, m.join('darkcave@macbeth.shakespeare.lit/thirdwitch', 'cauldron')) + assert(m.active?) + + assert_equal(0, messages_client) + assert_equal(0, messages_muc) + assert_equal(0, messages_muc_private) + + send("Hello") + message_lock.wait + + assert_equal(0, messages_client) + assert_equal(1, messages_muc) + assert_equal(0, messages_muc_private) + + send("Hello") + message_lock.wait + + assert_equal(1, messages_client) + assert_equal(1, messages_muc) + assert_equal(0, messages_muc_private) + + send("Hello") + message_lock.wait + + assert_equal(1, messages_client) + assert_equal(1, messages_muc) + assert_equal(1, messages_muc_private) + + wait_state + end + + def test_presence_callbacks + state { |pres| + assert_kind_of(Presence, pres) + assert_nil(pres.x.password) + send("" + + "" + + "") + } + + presence_lock = Semaphore.new + + presences_client = 0 + @client.add_presence_callback { |pres| + presences_client += 1 + presence_lock.run + } + m = MUC::MUCClient.new(@client) + m.my_jid = 'hag66@shakespeare.lit/pda' + presences_join = 0 + m.add_join_callback { |pres| + presences_join += 1 + presence_lock.run + } + presences_leave = 0 + m.add_leave_callback { |pres| + presences_leave += 1 + presence_lock.run + } + presences_muc = 0 + m.add_presence_callback { |pres| + presences_muc += 1 + presence_lock.run + } + + assert_equal(0, presences_client) + assert_equal(0, presences_join) + assert_equal(0, presences_leave) + assert_equal(0, presences_muc) + + assert_equal(m, m.join('darkcave@macbeth.shakespeare.lit/thirdwitch')) + assert(m.active?) + + assert_equal(0, presences_client) + assert_equal(0, presences_join) # Joins before own join won't be called back + assert_equal(0, presences_leave) + assert_equal(0, presences_muc) + + send("" + + "" + + "") + presence_lock.wait + assert_equal(0, presences_client) + assert_equal(1, presences_join) + assert_equal(0, presences_leave) + assert_equal(0, presences_muc) + + send("" + + "chat" + + "") + presence_lock.wait + assert_equal(1, presences_client) + assert_equal(1, presences_join) + assert_equal(0, presences_leave) + assert_equal(0, presences_muc) + + send("" + + "" + + "away") + presence_lock.wait + assert_equal(1, presences_client) + assert_equal(1, presences_join) + assert_equal(0, presences_leave) + assert_equal(1, presences_muc) + + send("") + presence_lock.wait + assert_equal(1, presences_client) + assert_equal(1, presences_join) + assert_equal(1, presences_leave) + assert_equal(1, presences_muc) + wait_state + end + + def test_send + state { |pres| + assert_kind_of(Presence, pres) + assert_nil(pres.x.password) + send("" + + "" + + "") + } + state { |stanza| + assert_kind_of(Message, stanza) + assert(:groupchat, stanza.type) + assert_equal(JID.new('hag66@shakespeare.lit/pda'), stanza.from) + assert_equal(JID.new('darkcave@macbeth.shakespeare.lit'), stanza.to) + assert_equal('First message', stanza.body) + } + state { |stanza| + assert_kind_of(Message, stanza) + assert(:chat, stanza.type) + assert_equal(JID.new('hag66@shakespeare.lit/pda'), stanza.from) + assert_equal(JID.new('darkcave@macbeth.shakespeare.lit/secondwitch'), stanza.to) + assert_equal('Second message', stanza.body) + } + state { |stanza| + assert_kind_of(Message, stanza) + assert(:chat, stanza.type) + assert_equal(JID.new('hag66@shakespeare.lit/pda'), stanza.from) + assert_equal(JID.new('darkcave@macbeth.shakespeare.lit/firstwitch'), stanza.to) + assert_equal('Third message', stanza.body) + } + + m = MUC::MUCClient.new(@client) + m.my_jid = 'hag66@shakespeare.lit/pda' + + assert_equal(m, m.join('darkcave@macbeth.shakespeare.lit/thirdwitch')) + wait_state + assert(m.active?) + + m.send(Jabber::Message.new(nil, 'First message')) + wait_state + m.send(Jabber::Message.new(nil, 'Second message'), 'secondwitch') + wait_state + m.send(Jabber::Message.new('secondwitch', 'Third message'), 'firstwitch') + wait_state + end + + def test_nick + state { |pres| + assert_kind_of(Presence, pres) + assert_nil(pres.x.password) + send("" + + "" + + "") + } + state { |pres| + assert_kind_of(Presence, pres) + assert_equal(JID.new('hag66@shakespeare.lit/pda'), pres.from) + assert_equal(JID.new('darkcave@macbeth.shakespeare.lit/secondwitch'), pres.to) + assert_nil(pres.type) + send("" + + "" + + "") + } + state { |pres| + assert_kind_of(Presence, pres) + assert_equal(JID.new('hag66@shakespeare.lit/pda'), pres.from) + assert_equal(JID.new('darkcave@macbeth.shakespeare.lit/oldhag'), pres.to) + assert_nil(pres.type) + send("" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "") + } + + m = MUC::MUCClient.new(@client) + m.my_jid = 'hag66@shakespeare.lit/pda' + + assert_equal(m, m.join('darkcave@macbeth.shakespeare.lit/thirdwitch')) + wait_state + assert(m.active?) + assert_equal('thirdwitch', m.nick) + + assert_raises(ErrorException) { + m.nick = 'secondwitch' + } + wait_state + assert(m.active?) + assert_equal('thirdwitch', m.nick) + + m.nick = 'oldhag' + wait_state + assert(m.active?) + assert_equal('oldhag', m.nick) + end +end diff --git a/vendor/xmpp4r-0.3.2/test/muc/tc_muc_simplemucclient.rb b/vendor/xmpp4r-0.3.2/test/muc/tc_muc_simplemucclient.rb new file mode 100755 index 000000000..f8b48e3a7 --- /dev/null +++ b/vendor/xmpp4r-0.3.2/test/muc/tc_muc_simplemucclient.rb @@ -0,0 +1,75 @@ +#!/usr/bin/ruby + + +$:.unshift File::dirname(__FILE__) + '/../../lib' + +require 'test/unit' +require File::dirname(__FILE__) + '/../lib/clienttester' +require 'xmpp4r/muc' +require 'xmpp4r/semaphore' +include Jabber + +class SimpleMUCClientTest < Test::Unit::TestCase + include ClientTester + + def test_new1 + m = MUC::SimpleMUCClient.new(@client) + assert_equal(nil, m.jid) + assert_equal(nil, m.my_jid) + assert_equal({}, m.roster) + assert(!m.active?) + end + + def test_complex + m = MUC::SimpleMUCClient.new(@client) + + block_args = [] + wait = Semaphore.new + block = lambda { |*a| block_args = a; wait.run } + m.on_room_message(&block) + m.on_message(&block) + m.on_private_message(&block) + m.on_subject(&block) + m.on_join(&block) + m.on_leave(&block) + m.on_self_leave(&block) + + state { |pres| + assert_kind_of(Presence, pres) + assert_equal(JID.new('hag66@shakespeare.lit/pda'), pres.from) + assert_equal(JID.new('darkcave@macbeth.shakespeare.lit/thirdwitch'), pres.to) + send("" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "") + } + m.my_jid = 'hag66@shakespeare.lit/pda' + assert_equal(m, m.join('darkcave@macbeth.shakespeare.lit/thirdwitch')) + wait_state + assert(m.active?) + assert_equal(3, m.roster.size) + + state { |msg| + assert_kind_of(Message, msg) + assert_equal(:groupchat, msg.type) + assert_equal(JID.new('hag66@shakespeare.lit/pda'), msg.from) + assert_equal(JID.new('darkcave@macbeth.shakespeare.lit'), msg.to) + assert_equal('TestCasing room', msg.subject) + assert_nil(msg.body) + send(msg.set_from('darkcave@macbeth.shakespeare.lit/thirdwitch').set_to('hag66@shakespeare.lit/pda')) + } + assert_nil(m.subject) + wait.wait + m.subject = 'TestCasing room' + wait_state + wait.wait + assert_equal([nil, 'thirdwitch', 'TestCasing room'], block_args) + assert_equal('TestCasing room', m.subject) + end + +end diff --git a/vendor/xmpp4r-0.3.2/test/muc/tc_mucowner.rb b/vendor/xmpp4r-0.3.2/test/muc/tc_mucowner.rb new file mode 100644 index 000000000..4f9015a90 --- /dev/null +++ b/vendor/xmpp4r-0.3.2/test/muc/tc_mucowner.rb @@ -0,0 +1,50 @@ +#!/usr/bin/ruby + +$:.unshift '../lib' + +require 'test/unit' +require 'xmpp4r' +require 'xmpp4r/muc' +include Jabber + +class MUCOwnerTest < Test::Unit::TestCase + def test_parse + s = < + + + Configuration for "darkcave" Room + + Complete this form to make changes to + the configuration of your room. + + + http://jabber.org/protocol/muc#roomconfig + + + A Dark Cave + + + + +EOF + iq = Iq::import(REXML::Document.new(s).root) + + assert_kind_of(Iq, iq) + assert_kind_of(MUC::IqQueryMUCOwner, iq.query) + assert_kind_of(Dataforms::XData, iq.query.x) + assert_kind_of(Dataforms::XData, iq.query.x('jabber:x:data')) + assert_kind_of(Dataforms::XData, iq.query.x(Dataforms::XData)) + + assert_equal(1, iq.query.x.fields.size) + assert_equal('Natural-Language Room Name', iq.query.x.fields[0].label) + end +end diff --git a/vendor/xmpp4r-0.3.2/test/pubsub/tc_helper.rb b/vendor/xmpp4r-0.3.2/test/pubsub/tc_helper.rb new file mode 100644 index 000000000..7f812663f --- /dev/null +++ b/vendor/xmpp4r-0.3.2/test/pubsub/tc_helper.rb @@ -0,0 +1,227 @@ +#!/usr/bin/ruby + +$:.unshift File::dirname(__FILE__) + '/../../lib' + +require 'test/unit' +require File::dirname(__FILE__) + '/../lib/clienttester' + +require 'xmpp4r' +require 'xmpp4r/pubsub/helper/servicehelper' +include Jabber + + +class PubSub::ServiceHelperTest < Test::Unit::TestCase + include ClientTester + + def test_create + h = PubSub::ServiceHelper.new(@client,'pubsub.example.org') + assert_kind_of(PubSub::ServiceHelper, h) + + state { |iq| + assert_kind_of(Jabber::Iq, iq) + assert_equal(:set, iq.type) + assert_equal(1, iq.children.size) + assert_equal('http://jabber.org/protocol/pubsub', iq.pubsub.namespace) + assert_equal(1, iq.pubsub.children.size) + assert_equal('create', iq.pubsub.children.first.name) + assert_equal('mynode', iq.pubsub.children.first.attributes['node']) + send(" + + + + ") + } + assert_equal('mynode', h.create('mynode')) + wait_state + end + + def test_delete + h = PubSub::ServiceHelper.new(@client,'pubsub.example.org') + + state { |iq| + assert_kind_of(Jabber::Iq, iq) + assert_equal(:set, iq.type) + assert_equal(1, iq.children.size) + assert_equal(1, iq.pubsub.children.size) + assert_equal('delete', iq.pubsub.children.first.name) + assert_equal('mynode', iq.pubsub.children.first.attributes['node']) + send("") + } + h.delete('mynode') + wait_state + end + + def test_publish + item1 = Jabber::PubSub::Item.new + item1.text = 'foobar' + h = PubSub::ServiceHelper.new(@client,'pubsub.example.org') + + state { |iq| + assert_kind_of(Jabber::Iq, iq) + assert_equal(:set, iq.type) + assert_equal(1, iq.children.size) + assert_equal(1, iq.pubsub.children.size) + assert_equal('publish', iq.pubsub.children[0].name) + assert_equal(1, iq.pubsub.children[0].children.size) + assert_equal('item', iq.pubsub.children[0].children[0].name) + assert_nil(iq.pubsub.children[0].children[0].attributes['id']) + assert_equal(1, iq.pubsub.children[0].children[0].children.size) + assert_equal(item1.children.to_s, iq.pubsub.children[0].children[0].children[0].to_s) + send("") + } + h.publish('mynode', item1) + wait_state + end + + def test_items + item1 = Jabber::PubSub::Item.new("1") + item1.text = 'foobar' + item2 = Jabber::PubSub::Item.new("2") + item2.text = 'barfoo' + + h = PubSub::ServiceHelper.new(@client,'pubsub.example.org') + + state { |iq| + assert_kind_of(Jabber::Iq, iq) + assert_equal(:get, iq.type) + assert_equal(1, iq.pubsub.children.size) + assert_equal('items', iq.pubsub.children.first.name) + assert_equal('mynode', iq.pubsub.children.first.attributes['node']) + send(" + + + #{item1.to_s} + #{item2.to_s} + + + ") + } + + items = h.items('mynode') + assert_equal(2, items.size) + assert_kind_of(REXML::Text, items['1']) + assert_kind_of(REXML::Text, items['2']) + assert_equal(item1.children.to_s, items['1'].to_s) + assert_equal(item2.children.to_s, items['2'].to_s) + wait_state + end + + def test_affiliations + h = PubSub::ServiceHelper.new(@client,'pubsub.example.org') + + state { |iq| + assert_kind_of(Jabber::Iq, iq) + assert_equal(:get, iq.type) + assert_equal(1, iq.pubsub.children.size) + assert_equal('affiliations', iq.pubsub.children.first.name) + send(" + + + + + + + + + ") + } + + a = h.affiliations + assert_kind_of(Hash, a) + assert_equal(4, a.size) + assert_equal(:owner, a['node1']) + assert_equal(:publisher, a['node2']) + assert_equal(:outcast, a['node5']) + assert_equal(:owner, a['node6']) + wait_state + end + + def test_subscriptions + h = PubSub::ServiceHelper.new(@client,'pubsub.example.org') + + state { |iq| + assert_kind_of(Jabber::Iq, iq) + assert_equal(:get, iq.type) + assert_equal(1, iq.pubsub.children.size) + assert_equal('subscriptions', iq.pubsub.children.first.name) + send(" + + + + + + + + + ") + } + + s = h.subscriptions('mynode') + assert_kind_of(Array,s) + assert_equal(4,s.size) + assert_kind_of(REXML::Element,s[0]) + assert_kind_of(REXML::Element,s[1]) + assert_kind_of(REXML::Element,s[2]) + assert_kind_of(REXML::Element,s[3]) + wait_state + end + + def test_get_all_subscriptions + h = PubSub::ServiceHelper.new(@client,'pubsub.example.org') + + state { |iq| + assert_kind_of(Jabber::Iq, iq) + assert_equal(:get, iq.type) + assert_equal(1, iq.pubsub.children.size) + assert_equal('subscriptions', iq.pubsub.children.first.name) + send(" + + + + + + + + + ") + } + + s = h.subscriptions + assert_kind_of(Array,s) + assert_equal(4,s.size) + assert_kind_of(REXML::Element,s[0]) + assert_kind_of(REXML::Element,s[1]) + assert_kind_of(REXML::Element,s[2]) + assert_kind_of(REXML::Element,s[3]) + wait_state + end + + def test_subscribers + h = PubSub::ServiceHelper.new(@client,'pubsub.example.org') + + state { |iq| + assert_kind_of(Jabber::Iq, iq) + assert_equal(:get, iq.type) + assert_equal(1, iq.pubsub.children.size) + assert_equal('subscriptions', iq.pubsub.children.first.name) + send(" + + + + + + + + + ") + } + + s = h.subscribers('princely_musings') + assert_equal(4,s.size) + assert_kind_of(String,s[0]) + assert_kind_of(String,s[1]) + assert_kind_of(String,s[2]) + assert_kind_of(String,s[3]) + wait_state + end +end diff --git a/vendor/xmpp4r-0.3.2/test/roster/tc_helper.rb b/vendor/xmpp4r-0.3.2/test/roster/tc_helper.rb new file mode 100644 index 000000000..b24956977 --- /dev/null +++ b/vendor/xmpp4r-0.3.2/test/roster/tc_helper.rb @@ -0,0 +1,515 @@ +#!/usr/bin/ruby + +$:.unshift File::dirname(__FILE__) + '/../../lib' + +require 'test/unit' +require File::dirname(__FILE__) + '/../lib/clienttester' + +require 'xmpp4r' +require 'xmpp4r/roster/helper/roster' +require 'xmpp4r/semaphore' +include Jabber + +class Roster::HelperTest < Test::Unit::TestCase + include ClientTester + + def test_simple + state { |iq| + assert_kind_of(Iq, iq) + assert_equal(:get, iq.type) + assert_nil(iq.to) + assert_equal('jabber:iq:roster', iq.queryns) + + send("") + send(" + + + + + + ") + } + + query_waiter = Semaphore.new + h = Roster::Helper::new(@client) + h.add_query_callback { |iq| + query_waiter.run + } + wait_state + query_waiter.wait + + assert_equal([nil], h.groups) + jids = h.find_by_group(nil).collect { |item| item.jid }.sort + assert_equal([JID.new('123@xyz'), JID.new('a@b.c'), JID.new('b@b.c')], jids) + assert_equal(1, h.find('123@xyz/res').size) + + assert_kind_of(Roster::Helper::RosterItem, h['a@b.c']) + assert_equal(JID.new('a@b.c'), h['a@b.c'].jid) + assert_nil(h['a@b.c'].iname) + assert_equal(:both, h['a@b.c'].subscription) + assert_nil(h['a@b.c'].ask) + + assert_kind_of(Roster::Helper::RosterItem, h[JID.new('b@b.c')]) + assert_equal(JID.new('b@b.c'), h['b@b.c'].jid) + assert_equal('b guy', h['b@b.c'].iname) + assert_equal(:from, h['b@b.c'].subscription) + assert_equal(:subscribe, h['b@b.c'].ask) + + assert_kind_of(Roster::Helper::RosterItem, h['123@xyz']) + assert_equal(JID.new('123@xyz'), h['123@xyz'].jid) + assert_equal('123', h['123@xyz'].iname) + assert_equal(:to, h['123@xyz'].subscription) + assert_nil(h['123@xyz'].ask) + + assert_nil(h['c@b.c']) + assert_nil(h[JID.new('c@b.c')]) + end + + def test_rosterpush + state { |iq| + send(" + + + ") + } + + query_waiter = Semaphore.new + h = Roster::Helper::new(@client) + h.add_query_callback { |iq| query_waiter.run } + wait_state + query_waiter.wait + + assert_equal([], h.groups) + assert_nil(h['a@b.c']) + + send(" + + + + ") + query_waiter.wait + + assert_kind_of(Roster::Helper::RosterItem, h['a@b.c']) + assert_equal(JID.new('a@b.c'), h['a@b.c'].jid) + assert_nil(h['a@b.c'].iname) + assert_nil(h['a@b.c'].subscription) + assert_nil(h['a@b.c'].ask) + + send(" + + + + ") + query_waiter.wait + + assert_kind_of(Roster::Helper::RosterItem, h['a@b.c']) + assert_equal(JID.new('a@b.c'), h['a@b.c'].jid) + assert_equal('ABC', h['a@b.c'].iname) + assert_equal(:from, h['a@b.c'].subscription) + assert_equal(:subscribe, h['a@b.c'].ask) + + send(" + + + + ") + query_waiter.wait + + assert_kind_of(Roster::Helper::RosterItem, h['a@b.c']) + assert_equal(JID.new('a@b.c'), h['a@b.c'].jid) + assert_nil(h['a@b.c'].iname) + assert_nil(h['a@b.c'].subscription) + assert_nil(h['a@b.c'].ask) + + send(" + + + + ") + query_waiter.wait + + assert_nil(h['a@b.c']) + end + + def test_presence + state { |iq| + send(" + + + + ") + } + + query_waiter = Semaphore.new + presence_waiter = Semaphore.new + h = Roster::Helper::new(@client) + h.add_query_callback { |iq| + query_waiter.run + } + cb_item, cb_op, cb_p = nil, nil, nil + h.add_presence_callback { |item,oldpres,pres| + # HACK: + # if two stanzas are expected for one sent stanza, + # race conditions may appear here + Thread.pass + + cb_item, cb_op, cb_p = item, oldpres, pres + presence_waiter.run + } + + wait_state + query_waiter.wait + + assert_equal(false, h['a@b.c'].online?) + presences = 0 + h['a@b.c'].each_presence { presences += 1 } + assert_equal(0, presences) + + send("") + presence_waiter.wait + + assert_kind_of(Roster::Helper::RosterItem, cb_item) + assert_nil(cb_op) + assert_kind_of(Presence, cb_p) + assert_nil(cb_p.type) + assert_equal(true, h['a@b.c'].online?) + presences = 0 + h['a@b.c'].each_presence { presences += 1 } + assert_equal(1, presences) + + send("") + presence_waiter.wait + + assert_kind_of(Roster::Helper::RosterItem, cb_item) + assert_nil(cb_op) + assert_kind_of(Presence, cb_p) + assert_nil(cb_p.type) + assert_equal(true, h['a@b.c'].online?) + presences = 0 + h['a@b.c'].each_presence { presences += 1 } + assert_equal(2, presences) + + send("") + presence_waiter.wait + + assert_kind_of(Roster::Helper::RosterItem, cb_item) + assert_kind_of(Presence, cb_op) + assert_nil(cb_op.type) + assert_kind_of(Presence, cb_p) + assert_nil(cb_p.type) + assert_equal(true, h['a@b.c'].online?) + presences = 0 + h['a@b.c'].each_presence { presences += 1 } + assert_equal(2, presences) + + send("") + presence_waiter.wait + + assert_kind_of(Roster::Helper::RosterItem, cb_item) + assert_kind_of(Presence, cb_op) + assert_nil(cb_op.type) + assert_kind_of(Presence, cb_p) + assert_equal(:error, cb_p.type) + assert_equal(true, h['a@b.c'].online?) + presences = 0 + h['a@b.c'].each_presence { presences += 1 } + assert_equal(2, presences) + + send("") + presence_waiter.wait + + assert_kind_of(Roster::Helper::RosterItem, cb_item) + assert_kind_of(Presence, cb_op) + assert_nil(cb_op.type) + assert_kind_of(Presence, cb_p) + assert_equal(:unavailable, cb_p.type) + assert_equal(false, h['a@b.c'].online?) + presences = 0 + h['a@b.c'].each_presence { presences += 1 } + assert_equal(2, presences) + + send("") + presence_waiter.wait + + assert_kind_of(Roster::Helper::RosterItem, cb_item) + assert_kind_of(Presence, cb_op) + assert_equal(:error, cb_op.type) + assert_kind_of(Presence, cb_p) + assert_nil(cb_p.type) + assert_equal(true, h['a@b.c'].online?) + presences = 0 + h['a@b.c'].each_presence { presences += 1 } + assert_equal(2, presences) + + send("") + presence_waiter.wait + + assert_kind_of(Roster::Helper::RosterItem, cb_item) + assert_kind_of(Presence, cb_op) + assert_equal(:unavailable, cb_op.type) + assert_kind_of(Presence, cb_p) + assert_nil(cb_p.type) + assert_equal(true, h['a@b.c'].online?) + presences = 0 + h['a@b.c'].each_presence { presences += 1 } + assert_equal(2, presences) + + send("") + 2.times { + presence_waiter.wait + + assert_kind_of(Roster::Helper::RosterItem, cb_item) + assert_kind_of(Presence, cb_op) + assert_kind_of(Presence, cb_p) + assert_equal(:error, cb_p.type) + } + assert_equal(false, h['a@b.c'].online?) + presences = 0 + h['a@b.c'].each_presence { presences += 1 } + assert_equal(1, presences) + + send("") + presence_waiter.wait + + assert_kind_of(Roster::Helper::RosterItem, cb_item) + assert_nil(cb_op) + assert_kind_of(Presence, cb_p) + assert_nil(cb_p.type) + assert_equal(true, h['a@b.c'].online?) + presences = 0 + h['a@b.c'].each_presence { presences += 1 } + assert_equal(1, presences) + end + + def test_subscribe + state { |iq| + send(" + + ") + } + + query_waiter = Semaphore.new + h = Roster::Helper::new(@client) + h.add_query_callback { |iq| query_waiter.run } + wait_state + query_waiter.wait + + state { |iq| + assert_kind_of(Iq, iq) + assert_equal('jabber:iq:roster', iq.queryns) + assert_equal(JID.new('contact@example.org'), iq.query.first_element('item').jid) + assert_equal('MyContact', iq.query.first_element('item').iname) + send(" + + + + + ") + } + state { |pres| + assert_kind_of(Presence, pres) + assert_equal(:subscribe, pres.type) + assert_equal(JID.new('contact@example.org'), pres.to) + Thread.pass + } + h.add('contact@example.org', 'MyContact', true) + wait_state + query_waiter.wait + wait_state + end + + def test_accept_subscription + state { |iq| + send(" + + ") + } + + query_waiter = Semaphore.new + h = Roster::Helper::new(@client) + h.add_query_callback { |iq| query_waiter.run } + wait_state + query_waiter.wait + + state { |pres| + assert_kind_of(Presence, pres) + assert_equal(:subscribed, pres.type) + } + state { |iq| + assert_kind_of(Iq, iq) + assert_equal(:set, iq.type) + send("") + } + + cb_lock = Semaphore.new + h.add_subscription_request_callback { |item,pres| + assert_nil(item) + assert_kind_of(Presence, pres) + h.accept_subscription(pres.from) + cb_lock.run + } + send("") + skip_state + wait_state + cb_lock.wait + + state { |pres| + assert_kind_of(Presence, pres) + assert_equal(:subscribed, pres.type) + assert_equal(JID.new('contact@example.org'), pres.to) + } + wait_state + end + + def test_decline_subscription + state { |iq| + send(" + + ") + } + + query_waiter = Semaphore.new + h = Roster::Helper::new(@client) + h.add_query_callback { |iq| query_waiter.run } + wait_state + query_waiter.wait + + state { |pres| + assert_kind_of(Presence, pres) + assert_equal(:unsubscribed, pres.type) + assert_equal(JID.new('contact@example.org'), pres.to) + } + cb_lock = Semaphore.new + h.add_subscription_request_callback { |item,pres| + assert_nil(item) + assert_kind_of(Presence, pres) + h.decline_subscription(pres.from) + + cb_lock.run + } + + send("") + wait_state + cb_lock.wait + end + + def test_groupset + state { |iq| + send(" + + + One + Two + + + ") + } + + query_waiter = Semaphore.new + h = Roster::Helper.new(@client) + h.add_query_callback { query_waiter.run } + wait_state + query_waiter.wait + + assert_equal(1, h.items.size) + assert_equal(%w(One Two).sort, h['test@test'].groups.sort) + + state { |iq| + send("#{iq.query}") + } + h['test@test'].groups = %w(One Two Three) + h['test@test'].send + wait_state + query_waiter.wait + assert_equal(%w(One Two Three).sort, h['test@test'].groups.sort) + end + + def test_nameset + state { |iq| + send(" + + + + ") + } + + query_waiter = Semaphore.new + h = Roster::Helper.new(@client) + h.add_query_callback { query_waiter.run } + wait_state + query_waiter.wait + + assert_equal(1, h.items.size) + assert_nil(h['test@test'].iname) + + state { |iq| + send("#{iq.query}") + } + h['test@test'].iname = 'Unknown' + h['test@test'].send + wait_state + query_waiter.wait + assert_equal('Unknown', h['test@test'].iname) + + state { |iq| + send("#{iq.query}") + } + h['test@test'].iname = 'Known' + h['test@test'].send + wait_state + query_waiter.wait + assert_equal('Known', h['test@test'].iname) + + state { |iq| + send("#{iq.query}") + } + h['test@test'].iname = nil + h['test@test'].send + wait_state + query_waiter.wait + assert_nil(h['test@test'].iname) + end + + def test_update_callback + update_args = nil + update_waiter = Semaphore.new + + h = Roster::Helper.new(@client) + h.add_update_callback { |*a| + update_args = a + update_waiter.run + } + + send(" + + + + ") + update_waiter.wait + assert_nil(update_args[0]) + assert_equal(JID.new('foo@bar'), update_args[1].jid) + assert_equal('Foo', update_args[1].iname) + + send(" + + + + ") + update_waiter.wait + assert_equal(JID.new('foo@bar'), update_args[0].jid) + assert_equal('Foo', update_args[0].iname) + assert_equal(JID.new('foo@bar'), update_args[1].jid) + assert_nil(update_args[1].iname) + + send(" + + + + ") + update_waiter.wait + assert_equal(JID.new('foo@bar'), update_args[0].jid) + assert_nil(update_args[0].iname) + assert_nil(update_args[1]) + end +end + diff --git a/vendor/xmpp4r-0.3.2/test/roster/tc_iqqueryroster.rb b/vendor/xmpp4r-0.3.2/test/roster/tc_iqqueryroster.rb new file mode 100755 index 000000000..f6162a9c6 --- /dev/null +++ b/vendor/xmpp4r-0.3.2/test/roster/tc_iqqueryroster.rb @@ -0,0 +1,173 @@ +#!/usr/bin/ruby + +$:.unshift File::dirname(__FILE__) + '/../../lib' + +require 'test/unit' +require 'xmpp4r/rexmladdons' +require 'xmpp4r/roster/iq/roster' +require 'xmpp4r/jid' +require 'xmpp4r/iq' +include Jabber + +class Roster::IqQueryRosterTest < Test::Unit::TestCase + def test_create + r = Roster::IqQueryRoster::new + assert_equal('jabber:iq:roster', r.namespace) + assert_equal(r.to_a.size, 0) + assert_equal(r.to_a, []) + assert_equal(r.to_s, "") + end + + def test_import + iq = Iq::new + q = REXML::Element::new('query') + q.add_namespace('jabber:iq:roster') + iq.add(q) + iq2 = Iq::new.import(iq) + assert_equal(Roster::IqQueryRoster, iq2.query.class) + end + + def test_answer + iq = Iq::new_rosterget + assert_equal(:get, iq.type) + assert_nil(iq.to) + assert_equal('jabber:client', iq.namespace) + assert_equal('jabber:iq:roster', iq.queryns) + assert_equal(0, iq.query.children.size) + + iq2 = iq.answer(true) + assert_equal(:get, iq2.type) + assert_nil(iq2.from) + assert_equal('jabber:client', iq2.namespace) + assert_equal('jabber:iq:roster', iq2.queryns) + assert_equal(0, iq2.query.children.size) + end + + def test_xmlns + ri = Roster::RosterItem.new + assert_equal('jabber:iq:roster', ri.namespace) + assert_equal('jabber:iq:roster', ri.attributes['xmlns']) + + r = Roster::IqQueryRoster.new + assert_equal('jabber:iq:roster', r.namespace) + assert_equal('jabber:iq:roster', r.attributes['xmlns']) + + r.add(ri) + + assert_equal('jabber:iq:roster', ri.namespace) + assert_nil(ri.attributes['xmlns']) + end + + def test_items + r = Roster::IqQueryRoster::new + r.add(Roster::RosterItem.new) + r.add(Roster::RosterItem.new(JID.new('a@b/d'), 'ABC', :none, :subscribe)).groups = ['a'] + itemstr = "" \ + + "SpaceBoyZxmpp4r" + r.typed_add(REXML::Document.new(itemstr).root) + + r.each { |item| + assert_equal(item, r[item.jid]) + } + + r.to_a.each { |item| + assert_equal(item, r[item.jid]) + } + + assert_equal(JID.new, r.to_a[0].jid) + assert_equal(nil, r.to_a[0].iname) + assert_equal(nil, r.to_a[0].subscription) + assert_equal(nil, r.to_a[0].ask) + + assert_equal(JID.new('a@b/d'), r.to_a[1].jid) + assert_equal('ABC', r.to_a[1].iname) + assert_equal(:none, r.to_a[1].subscription) + assert_equal(:subscribe, r.to_a[1].ask) + + assert_equal(REXML::Document.new(itemstr).root.to_s, r.to_a[2].to_s) + end + + def test_dupitems + r = Roster::IqQueryRoster::new + jid = JID::new('a@b') + jid2 = JID::new('c@d') + ri = Roster::RosterItem::new(jid, 'ab') + r.add(ri) + assert_equal('ab', ri.iname) + assert_equal('ab', r[jid].iname) + ri.iname = 'cd' + assert_equal('cd', ri.iname) + # There are no shallow copies - everything is alright. + assert_equal('cd', r[jid].iname) + + r.add(ri) + assert_equal('cd', r[jid].iname) + assert_equal(ri, r[jid]) + + ri.jid = jid2 + assert_equal(nil, r[jid]) + assert_equal(ri, r[jid2]) + assert_equal(2, r.to_a.size) + + r.each_element('item') { |item| + assert_equal(ri, item) + assert_equal(ri.jid, item.jid) + assert_equal(ri.iname, item.iname) + assert_equal(jid2, item.jid) + assert_equal('cd', item.iname) + } + end +end + +class Roster::RosterItemTest < Test::Unit::TestCase + def test_create + ri = Roster::RosterItem::new + assert_equal(JID.new, ri.jid) + assert_equal(nil, ri.iname) + assert_equal(nil, ri.subscription) + assert_equal(nil, ri.ask) + + ri = Roster::RosterItem::new(JID.new('a@b/c'), 'xyz', :both, nil) + assert_equal(JID.new('a@b/c'), ri.jid) + assert_equal('xyz', ri.iname) + assert_equal(:both, ri.subscription) + assert_equal(nil, ri.ask) + end + + def test_modify + ri = Roster::RosterItem::new(JID.new('a@b/c'), 'xyz', :both, :subscribe) + + assert_equal(JID.new('a@b/c'), ri.jid) + ri.jid = nil + assert_equal(JID::new, ri.jid) + + assert_equal('xyz', ri.iname) + ri.iname = nil + assert_equal(nil, ri.iname) + + assert_equal(:both, ri.subscription) + ri.subscription = nil + assert_equal(nil, ri.subscription) + + assert_equal(:subscribe, ri.ask) + ri.ask = nil + assert_equal(nil, ri.ask) + end + + def test_groupdeletion + ri = Roster::RosterItem::new + g1 = ['a', 'b', 'c'] + ri.groups = g1 + assert_equal(g1, ri.groups.sort) + g2 = ['c', 'd', 'e'] + ri.groups = g2 + assert_equal(g2, ri.groups.sort) + end + + def test_dupgroups + ri = Roster::RosterItem::new + mygroups = ['a', 'a', 'b'] + ri.groups = mygroups + assert_equal(mygroups.uniq, ri.groups) + end +end diff --git a/vendor/xmpp4r-0.3.2/test/roster/tc_xroster.rb b/vendor/xmpp4r-0.3.2/test/roster/tc_xroster.rb new file mode 100755 index 000000000..5665a844c --- /dev/null +++ b/vendor/xmpp4r-0.3.2/test/roster/tc_xroster.rb @@ -0,0 +1,73 @@ +#!/usr/bin/ruby + +$:.unshift File::dirname(__FILE__) + '/../../lib' + +require 'test/unit' +require 'xmpp4r/rexmladdons' +require 'xmpp4r/roster/x/roster' +require 'xmpp4r/jid' +include Jabber + +class Roster::XRosterTest < Test::Unit::TestCase + def test_create + r1 = Roster::XRoster.new + assert_equal('x', r1.name) + assert_equal('jabber:x:roster', r1.namespace) + r2 = Roster::RosterX.new + assert_equal('x', r2.name) + assert_equal('http://jabber.org/protocol/rosterx', r2.namespace) + end + + def test_import + x1 = X.new + x1.add_namespace('jabber:x:roster') + x2 = X::import(x1) + assert_equal(Roster::XRoster, x2.class) + assert_equal('jabber:x:roster', x2.namespace) + end + + def test_typed_add + x = REXML::Element.new('x') + x.add(REXML::Element.new('item')) + r = Roster::XRoster.new.import(x) + assert_kind_of(Roster::XRosterItem, r.first_element('item')) + assert_kind_of(Roster::XRosterItem, r.typed_add(REXML::Element.new('item'))) + end + + def test_items + j1 = Roster::XRosterItem.new + assert_equal(JID.new(nil), j1.jid) + assert_equal(nil, j1.iname) + + j2 = Roster::XRosterItem.new(JID.new('a@b/c')) + assert_equal(JID.new('a@b/c'), j2.jid) + assert_equal(nil, j2.iname) + j3 = Roster::XRosterItem.new(JID.new('a@b/c'), 'Mr. Abc') + assert_equal(JID.new('a@b/c'), j3.jid) + assert_equal('Mr. Abc', j3.iname) + assert_equal([], j3.groups) + + j3.groups = ['X', 'Y', 'Z'] + assert_equal(['X', 'Y', 'Z'], j3.groups) + end + + def test_actions + j = Roster::XRosterItem.new + assert_equal(:add, j.action) + + j.action = :modify + assert_equal(:modify, j.action) + + j.action = :delete + assert_equal(:delete, j.action) + + j.action = :invalid + assert_equal(:add, j.action) + + j.attributes['action'] = 'modify' + assert_equal(:modify, j.action) + + j.attributes['action'] = 'invalid' + assert_equal(:add, j.action) + end +end diff --git a/vendor/xmpp4r-0.3.2/test/rpc/tc_helper.rb b/vendor/xmpp4r-0.3.2/test/rpc/tc_helper.rb new file mode 100644 index 000000000..7dc8520c6 --- /dev/null +++ b/vendor/xmpp4r-0.3.2/test/rpc/tc_helper.rb @@ -0,0 +1,84 @@ +#!/usr/bin/ruby + +$:.unshift File::dirname(__FILE__) + '/../../lib' + +require 'test/unit' +require File::dirname(__FILE__) + '/../lib/clienttester' + +require 'xmpp4r' +require 'xmpp4r/rpc/helper/client' +require 'xmpp4r/rpc/helper/server' +include Jabber + +class RPC::HelperTest < Test::Unit::TestCase + include ClientTester + + def give_client_jid! + class << @client + def jid; Jabber::JID.new('client@test.com/clienttester'); end + end + end + + def test_create + give_client_jid! + + cl = RPC::Client.new(@client, 'a@b/c') + assert_kind_of(RPC::Client, cl) + sv = RPC::Server.new(@server) + assert_kind_of(RPC::Server, sv) + end + + def test_simple + give_client_jid! + + sv = RPC::Server.new(@server) + sv.add_handler("echo") do |s| s end + + cl = RPC::Client.new(@client, 'a@b/c') + assert_equal('Test string', cl.call("echo", 'Test string')) + end + + def test_introspection + give_client_jid! + + sv = RPC::Server.new(@server) + sv.add_introspection + + cl = RPC::Client.new(@client, 'a@b/c') + cl_methods = cl.call("system.listMethods") + assert(cl_methods.size > 0) + cl_methods.each { |method| + assert_kind_of(String, method) + assert(method =~ /^system\./) + } + end + + def test_multicall + give_client_jid! + + sv = RPC::Server.new(@server) + sv.add_multicall + sv.add_handler("reverse") do |s| s.reverse end + sv.add_handler("upcase") do |s| s.upcase end + + cl = RPC::Client.new(@client, 'a@b/c') + assert_equal(['tseT', 'TEST'], cl.multicall(['reverse', 'Test'], ['upcase', 'Test'])) + end + + def test_100calls + give_client_jid! + + sv = RPC::Server.new(@server) + sv.add_handler("add") do |a,b| a+b end + + cl = RPC::Client.new(@client, 'a@b/c') + correct = true + 100.times { + a, b = rand(1000), rand(1000) + correct &&= (cl.call('add', a, b) == a + b) + } + + assert(correct) + end +end + diff --git a/vendor/xmpp4r-0.3.2/test/tc_callbacks.rb b/vendor/xmpp4r-0.3.2/test/tc_callbacks.rb new file mode 100755 index 000000000..034d413a1 --- /dev/null +++ b/vendor/xmpp4r-0.3.2/test/tc_callbacks.rb @@ -0,0 +1,129 @@ +#!/usr/bin/ruby + +$:.unshift '../lib' + +require 'test/unit' +require 'xmpp4r/callbacks' +include Jabber + +class CallbacksTest < Test::Unit::TestCase + def test_test1 + called = 0 + cb = Callback::new(5, "toto", Proc::new { called += 1 }) + assert_equal(5, cb.priority) + assert_equal("toto", cb.ref) + cb.block.call + assert_equal(1, called) + cb.block.call + assert_equal(2, called) + end + + def test_callbacklist1 + cbl = CallbackList::new + called1 = false + called2 = false + called3 = false + called4 = false + cbl.add(5, "ref1") { called1 = true ; true } + cbl.add(7, "ref1") { |e| called2 = true ; false} + cbl.add(9, "ref1") { called3 = true ;false } + cbl.add(11, "ref1") { called4 = true ; false } + o = "aaaa" + assert(cbl.process(o)) + assert(called1) + assert(called2) + assert(called3) + assert(called4) + end + + def test_callbacklist2 + cbl = CallbackList::new + assert(0, cbl.length) + cbl.add(5, "ref1") { called1 = true } + assert(1, cbl.length) + cbl.add(7, "ref2") { |e| called2 = true ; e.consume } + assert(2, cbl.length) + cbl.delete("ref2") + assert(1, cbl.length) + cbl.add(9, "ref3") { called3 = true } + assert(2, cbl.length) + end + + def test_callbacklist4 + cbl = CallbackList::new + cbl.add(5, "ref1") { false } + cbl.add(7, "ref1") { false } + o = "o" + assert(!cbl.process(o)) + end + + def test_callbacklist5 + cbl = CallbackList::new + cbl.add(5, "ref1") { true } + cbl.add(7, "ref1") { false } + o = "o" + assert(cbl.process(o)) + end + + def test_callbacklist6 + cbl = CallbackList::new + ok = false + c = 'a' + d = 'b' + cbl.add(5, "ref1") { |a, b| + if a == 'a' and b == 'b' + ok = true + end + false + } + assert(!cbl.process(c, d)) + assert(ok) + end + + def test_callbacklist7 + cbl = CallbackList::new + called1 = false + called2 = false + called3 = false + called4 = false + cbl.add(3, "ref1") { called4 = true ; true } + cbl.add(5, "ref1") { called1 = true ; true } + cbl.add(7, "ref1") { called2 = true ; 'a'} + cbl.add(9, "ref1") { called3 = true ;1 } + o = "aaaa" + assert(cbl.process(o)) + assert(called1) + assert(called2) + assert(called3) + assert(!called4) + end + + def test_nested + cbl = CallbackList.new + called_outer = 0 + called_inner = 0 + + cbl.add(100, nil) { + called_outer += 1 + + if called_outer == 1 + cbl.add(200, nil) { + called_inner += 1 + } + end + } + + assert_equal(0, called_inner) + assert_equal(0, called_outer) + + cbl.process + + assert_equal(0, called_inner) + assert_equal(1, called_outer) + + cbl.process + + assert_equal(1, called_inner) + assert_equal(2, called_outer) + end +end diff --git a/vendor/xmpp4r-0.3.2/test/tc_class_names.rb b/vendor/xmpp4r-0.3.2/test/tc_class_names.rb new file mode 100755 index 000000000..169b2cd65 --- /dev/null +++ b/vendor/xmpp4r-0.3.2/test/tc_class_names.rb @@ -0,0 +1,137 @@ +#!/usr/bin/ruby + +$:.unshift '../lib' + +require 'test/unit' +require 'xmpp4r' +# No include Jabber, test full namespace + +class JIDTest < Test::Unit::TestCase + def test_base + assert_kind_of(Module, Jabber) + assert_kind_of(Class, Jabber::AuthenticationFailure) + assert_kind_of(Class, Jabber::Client) + assert_kind_of(Class, Jabber::Component) + assert_kind_of(Class, Jabber::Connection) + assert_kind_of(Class, Jabber::Error) + assert_kind_of(Class, Jabber::ErrorException) + assert_kind_of(Class, Jabber::IdGenerator) + assert_kind_of(Class, Jabber::Iq) + assert_kind_of(Class, Jabber::IqQuery) + assert_kind_of(Class, Jabber::JID) + assert_kind_of(Class, Jabber::Message) + assert_kind_of(Class, Jabber::Presence) + assert_kind_of(Module, Jabber::SASL) + assert_respond_to(Jabber::SASL, :new) + assert_kind_of(Class, Jabber::SASL::Base) + assert_kind_of(Class, Jabber::SASL::Plain) + assert_kind_of(Class, Jabber::SASL::DigestMD5) + assert_kind_of(Class, Jabber::Stream) + assert_kind_of(Class, Jabber::StreamParser) + assert_kind_of(Class, Jabber::X) + assert_kind_of(Class, Jabber::XMPPElement) + assert_kind_of(Class, Jabber::XMPPStanza) + end + + def test_roster + require 'xmpp4r/roster' + assert_kind_of(Module, Jabber::Roster) + assert_kind_of(Class, Jabber::Roster::Helper) + assert_kind_of(Class, Jabber::Roster::Helper::RosterItem) + assert_kind_of(Class, Jabber::Roster::RosterItem) + assert_kind_of(Class, Jabber::Roster::IqQueryRoster) + assert_kind_of(Class, Jabber::Roster::XRoster) + assert_kind_of(Class, Jabber::Roster::XRosterItem) + end + + def test_muc + require 'xmpp4r/muc' + assert_kind_of(Module, Jabber::MUC) + assert_kind_of(Class, Jabber::MUC::MUCBrowser) + assert_kind_of(Class, Jabber::MUC::MUCClient) + assert_kind_of(Class, Jabber::MUC::SimpleMUCClient) + assert_kind_of(Class, Jabber::MUC::XMUC) + assert_kind_of(Class, Jabber::MUC::XMUCUser) + assert_kind_of(Class, Jabber::MUC::XMUCUserInvite) + end + + def test_bytestreams + require 'xmpp4r/bytestreams' + assert_kind_of(Module, Jabber::FileTransfer) + assert_kind_of(Module, Jabber::FileTransfer::TransferSource) + assert_kind_of(Class, Jabber::FileTransfer::FileSource) + assert_kind_of(Class, Jabber::FileTransfer::Helper) + assert_kind_of(Class, Jabber::Bytestreams::SOCKS5BytestreamsServer) + assert_kind_of(Class, Jabber::Bytestreams::SOCKS5BytestreamsServerStreamHost) + assert_kind_of(Class, Jabber::Bytestreams::SOCKS5BytestreamsPeer) + assert_kind_of(Class, Jabber::Bytestreams::IqQueryBytestreams) + assert_kind_of(Class, Jabber::Bytestreams::StreamHost) + assert_kind_of(Class, Jabber::Bytestreams::StreamHostUsed) + assert_kind_of(Class, Jabber::Bytestreams::IqSi) + assert_kind_of(Class, Jabber::Bytestreams::IqSiFile) + assert_kind_of(Class, Jabber::Bytestreams::IqSiFileRange) + assert_kind_of(Class, Jabber::Bytestreams::IBB) + assert_kind_of(Class, Jabber::Bytestreams::IBBQueueItem) + assert_kind_of(Class, Jabber::Bytestreams::IBBInitiator) + assert_kind_of(Class, Jabber::Bytestreams::IBBTarget) + assert_kind_of(Class, Jabber::Bytestreams::SOCKS5Bytestreams) + assert_kind_of(Class, Jabber::Bytestreams::SOCKS5BytestreamsInitiator) + assert_kind_of(Class, Jabber::Bytestreams::SOCKS5BytestreamsTarget) + assert_kind_of(Class, Jabber::Bytestreams::SOCKS5Error) + assert_kind_of(Class, Jabber::Bytestreams::SOCKS5Socket) + end + + def test_dataforms + require 'xmpp4r/dataforms' + assert_kind_of(Module, Jabber::Dataforms) + assert_kind_of(Class, Jabber::Dataforms::XData) + assert_kind_of(Class, Jabber::Dataforms::XDataTitle) + assert_kind_of(Class, Jabber::Dataforms::XDataInstructions) + assert_kind_of(Class, Jabber::Dataforms::XDataField) + assert_kind_of(Class, Jabber::Dataforms::XDataReported) + end + + def test_delay + require 'xmpp4r/delay' + assert_kind_of(Module, Jabber::Delay) + assert_kind_of(Class, Jabber::Delay::XDelay) + end + + def test_discovery + require 'xmpp4r/discovery' + assert_kind_of(Module, Jabber::Discovery) + assert_kind_of(Class, Jabber::Discovery::IqQueryDiscoInfo) + assert_kind_of(Class, Jabber::Discovery::Identity) + assert_kind_of(Class, Jabber::Discovery::Feature) + assert_kind_of(Class, Jabber::Discovery::IqQueryDiscoItems) + assert_kind_of(Class, Jabber::Discovery::Item) + end + + def test_feature_negotiation + require 'xmpp4r/feature_negotiation' + assert_kind_of(Module, Jabber::FeatureNegotiation) + assert_kind_of(Class, Jabber::FeatureNegotiation::IqFeature) + end + + def test_vcard + require 'xmpp4r/vcard' + assert_kind_of(Module, Jabber::Vcard) + assert_kind_of(Class, Jabber::Vcard::Helper) + assert_kind_of(Class, Jabber::Vcard::IqVcard) + end + + def test_version + require 'xmpp4r/version' + assert_kind_of(Module, Jabber::Version) + assert_kind_of(Class, Jabber::Version::Responder) + assert_kind_of(Class, Jabber::Version::SimpleResponder) + assert_kind_of(Class, Jabber::Version::IqQueryVersion) + end + + def test_rpc + require 'xmpp4r/rpc' + assert_kind_of(Module, Jabber::RPC) + assert_kind_of(Class, Jabber::RPC::Server) + assert_kind_of(Class, Jabber::RPC::Client) + end +end diff --git a/vendor/xmpp4r-0.3.2/test/tc_client.rb b/vendor/xmpp4r-0.3.2/test/tc_client.rb new file mode 100755 index 000000000..50bce0957 --- /dev/null +++ b/vendor/xmpp4r-0.3.2/test/tc_client.rb @@ -0,0 +1,30 @@ +#!/usr/bin/ruby + +$:.unshift '../lib' + +require 'test/unit' +require 'xmpp4r/client' +include Jabber + +class ClientTest < Test::Unit::TestCase + def test_client1 +=begin + c = Client::new(JID::new('client1@localhost/res')) + assert_nothing_raised("Couldn't connect") { + c.connect + } + assert_nothing_raised("Couldn't authenticate") { + c.auth('pw') + } +=end + end + + def test_jid_is_jid + c1 = Client::new(JID::new('user@host/resource')) + assert_kind_of(JID, c1.jid) + assert_equal('user@host/resource', c1.jid.to_s) + c2 = Client::new('user@host/resource') + assert_kind_of(JID, c2.jid) + assert_equal('user@host/resource', c2.jid.to_s) + end +end diff --git a/vendor/xmpp4r-0.3.2/test/tc_error.rb b/vendor/xmpp4r-0.3.2/test/tc_error.rb new file mode 100755 index 000000000..c61de8db2 --- /dev/null +++ b/vendor/xmpp4r-0.3.2/test/tc_error.rb @@ -0,0 +1,104 @@ +#!/usr/bin/ruby + +$:.unshift '../lib' + +require 'test/unit' +require 'xmpp4r' +require 'xmpp4r/rexmladdons' +require 'xmpp4r/error' +require 'xmpp4r/message' +include Jabber + +class ErrorTest < Test::Unit::TestCase + def test_create + e = Error::new + assert_equal(nil, e.error) + assert_equal(nil, e.code) + assert_equal(nil, e.type) + assert_equal(nil, e.text) + end + + def test_create2 + e = Error::new('payment-required') + assert_equal('payment-required', e.error) + assert_equal(402, e.code) + assert_equal(:auth, e.type) + assert_equal(nil, e.text) + end + + def test_create3 + e = Error::new('gone', 'User moved to afterlife.gov') + assert_equal('gone', e.error) + assert_equal(302, e.code) + assert_equal(:modify, e.type) + assert_equal('User moved to afterlife.gov', e.text) + end + + def test_create_invalid + assert_raise(RuntimeError) { + e = Error::new('invalid error') + } + end + + def test_type + e = Error::new + assert_nil(e.type) + e.type = :auth + assert_equal(:auth, e.type) + e.type = :cancel + assert_equal(:cancel, e.type) + e.type = :continue + assert_equal(:continue, e.type) + e.type = :modify + assert_equal(:modify, e.type) + e.type = :wait + assert_equal(:wait, e.type) + e.type = nil + assert_nil(e.type) + end + + def test_code + e = Error::new + assert_nil(e.code) + e.code = 404 + assert_equal(404, e.code) + assert_equal("", e.to_s) + e.code = nil + assert_nil(e.code) + end + + def test_error + e = Error::new + assert_nil(e.error) + e.error = 'gone' + assert_equal('gone', e.error) + assert_raise(RuntimeError) { + e.error = nil + } + end + + def test_stanzas + m = Message.new + assert_equal(nil, m.error) + m.typed_add(Error.new) + assert_equal('', m.error.to_s) + end + + def test_sample_normal + src = '...' + e = Error.new.import(REXML::Document.new(src).root) + assert_equal(:modify, e.type) + assert_equal(302, e.code) + assert_equal('gone', e.error) + assert_equal('...', e.text) + end + + def test_sample_muc + src = 'Please choose a different nickname.' + e = Error.new.import(REXML::Document.new(src).root) + assert_equal(nil, e.type) + assert_equal(409, e.code) + assert_equal(nil, e.error) + assert_equal('Please choose a different nickname.', e.text) + end +end diff --git a/vendor/xmpp4r-0.3.2/test/tc_idgenerator.rb b/vendor/xmpp4r-0.3.2/test/tc_idgenerator.rb new file mode 100755 index 000000000..2b80fca40 --- /dev/null +++ b/vendor/xmpp4r-0.3.2/test/tc_idgenerator.rb @@ -0,0 +1,30 @@ +#!/usr/bin/ruby + +$:.unshift '../lib' + +require 'test/unit' +require 'xmpp4r/idgenerator' +include Jabber + +class IdGeneratorTest < Test::Unit::TestCase + def test_instances + assert_equal(Jabber::IdGenerator.instance, Jabber::IdGenerator.instance) + end + + def test_unique + ids = [] + 100.times { ids.push(Jabber::IdGenerator.generate_id) } + + ok = true + ids.each_index { |a| + ids.each_index { |b| + if a == b + ok = false if ids[a] != ids[b] + else + ok = false if ids[a] == ids[b] + end + } + } + assert(ok) + end +end diff --git a/vendor/xmpp4r-0.3.2/test/tc_iq.rb b/vendor/xmpp4r-0.3.2/test/tc_iq.rb new file mode 100755 index 000000000..3fcdfe3be --- /dev/null +++ b/vendor/xmpp4r-0.3.2/test/tc_iq.rb @@ -0,0 +1,110 @@ +#!/usr/bin/ruby + +$:.unshift '../lib' + +require 'test/unit' +require 'socket' +require 'xmpp4r/rexmladdons' +require 'xmpp4r/iq' +include Jabber + +class IqTest < Test::Unit::TestCase + def test_create + x = Iq::new() + assert_equal("iq", x.name) + assert_equal("jabber:client", x.namespace) + assert_equal("", x.to_s) + end + + def test_iqauth + x = Iq::new_authset(JID::new('node@domain/resource'), 'password') + assert_equal("nodepasswordresource", x.to_s) + end + + def test_iqauth_digest + x = Iq::new_authset_digest(JID::new('node@domain/resource'), '', 'password') + assert_equal("node#{Digest::SHA1.hexdigest('password')}resource", x.to_s) + end + + def test_register + x1 = Iq::new_register + assert_equal("", x1.to_s) + x2 = Iq::new_register('node') + assert_equal("node", x2.to_s) + x3 = Iq::new_register('node', 'password') + assert_equal("nodepassword", x3.to_s) + end + + def test_rosterget + x = Iq::new_rosterget + assert_equal("", x.to_s) + end + + def test_rosterset + x = Iq::new_rosterset + assert_equal("", x.to_s) + end + + def test_browseget + x = Iq::new_browseget + assert_equal("", x.to_s) + end + + def test_types + iq = Iq::new + assert_equal(nil, iq.type) + iq.type = :get + assert_equal(:get, iq.type) + iq.type = :set + assert_equal(:set, iq.type) + iq.type = :result + assert_equal(:result, iq.type) + iq.type = :error + assert_equal(:error, iq.type) + iq.type = :invalid + assert_equal(nil, iq.type) + end + + def test_query + x = Iq::new(:set) + assert_equal(nil, x.queryns) + query = REXML::Element::new('query') + x.add(query) + assert_equal('jabber:client', x.queryns) + query.add_namespace('jabber:iq:auth') + assert_equal(query.to_s, x.query.to_s) + assert_equal('jabber:iq:auth', x.queryns) + + query2 = REXML::Element::new('query') + x.query = query2 + assert_equal('jabber:client', x.queryns) + query2.add_namespace('jabber:iq:register') + assert_equal('jabber:iq:register', x.queryns) + end + + def test_vcard + x = Iq::new + assert_equal(nil, x.vcard) + x.add(vcard = REXML::Element.new('vCard')) + assert_equal(vcard, x.vcard) + end + + def test_error + x = Iq::new(:set) + e = REXML::Element::new('error') + x.add(e) + # test if, after an import, the error element is successfully changed + # into an Error object. + x2 = Iq::new.import(x) + assert_equal(Error, x2.first_element('error').class) + end + + def test_new_query + x = Iq::new_query(:get, JID.new('a@b/c')) + assert_equal(:get, x.type) + assert_equal(nil, x.from) + assert_equal(JID.new('a@b/c'), x.to) + assert_kind_of(IqQuery, x.query) + assert_equal('jabber:client', x.queryns) + end +end diff --git a/vendor/xmpp4r-0.3.2/test/tc_iqquery.rb b/vendor/xmpp4r-0.3.2/test/tc_iqquery.rb new file mode 100755 index 000000000..41a22ace5 --- /dev/null +++ b/vendor/xmpp4r-0.3.2/test/tc_iqquery.rb @@ -0,0 +1,31 @@ +#!/usr/bin/ruby + +$:.unshift '../lib' + +require 'test/unit' +require 'xmpp4r/rexmladdons' +require 'xmpp4r/query' +include Jabber + +class IqQueryTest < Test::Unit::TestCase + def test_create + x = IqQuery::new() + assert_equal("query", x.name) + assert_equal("", x.to_s) + end + + def test_import + q = IqQuery::new + assert_equal(IqQuery, q.class) + + e = REXML::Element.new('query') + e.add_namespace('jabber:iq:roster') + # kind_of? only checks that the class belongs to the IqQuery class + # hierarchy. See IqQueryRosterTest#test_import for a more strict + # check. + assert_kind_of(IqQuery, IqQuery.import(e)) + + # Importing specific derivates is to be tested in the test case of the derivate + # (e.g. tc_iqqueryroster.rb) + end +end diff --git a/vendor/xmpp4r-0.3.2/test/tc_jid.rb b/vendor/xmpp4r-0.3.2/test/tc_jid.rb new file mode 100755 index 000000000..07c2abea7 --- /dev/null +++ b/vendor/xmpp4r-0.3.2/test/tc_jid.rb @@ -0,0 +1,202 @@ +#!/usr/bin/ruby + +$:.unshift '../lib' + +require 'test/unit' +require 'xmpp4r/jid' +include Jabber + +class JIDTest < Test::Unit::TestCase + def test_create1 + j = JID::new('a', 'b', 'c') + assert_equal('a', j.node) + assert_equal('b', j.domain) + assert_equal('c', j.resource) + end + + def test_create2 + j = JID::new('a', 'b', 'c') + j2 = JID::new(j) + assert_equal('a', j2.node) + assert_equal('b', j2.domain) + assert_equal('c', j2.resource) + assert_equal('a@b/c', j.to_s) + end + + def test_create3 + j = JID::new('a@b/c') + assert_equal('a', j.node) + assert_equal('b', j.domain) + assert_equal('c', j.resource) + assert_equal('a@b/c', j.to_s) + end + + def test_create4 + j = JID::new('a@b') + assert_equal('a', j.node) + assert_equal('b', j.domain) + assert_equal(nil, j.resource) + assert_equal('a@b', j.to_s) + end + + def test_create5 + j = JID::new + assert_equal(nil, j.node) + assert_equal(nil, j.domain) + assert_equal(nil, j.resource) + assert_equal('', j.to_s) + end + + def test_create6 + j = JID::new('dom') + assert_equal(nil, j.node) + assert_equal('dom', j.domain) + assert_equal(nil, j.resource) + assert_equal('dom', j.to_s) + end + + def test_create7 + j = JID::new('dom/res') + assert_equal(nil, j.node) + assert_equal('dom', j.domain) + assert_equal('res', j.resource) + assert_equal('dom/res', j.to_s) + end + + def test_create8 + j = JID::new('dom/a@b') + assert_equal(nil, j.node) + assert_equal('dom', j.domain) + assert_equal('a@b', j.resource) + assert_equal('dom/a@b', j.to_s) + end + + def test_create9 + assert_nothing_raised { JID::new("#{'n'*1023}@#{'d'*1023}/#{'r'*1023}") } + assert_raises(ArgumentError) { JID::new("#{'n'*1024}@#{'d'*1023}/#{'r'*1023}") } + assert_raises(ArgumentError) { JID::new("#{'n'*1023}@#{'d'*1024}/#{'r'*1023}") } + assert_raises(ArgumentError) { JID::new("#{'n'*1023}@#{'d'*1023}/#{'r'*1024}") } + end + + def test_create10 + j = JID::new('@b/c') + assert_equal('', j.node) + assert_equal('b', j.domain) + assert_equal('c', j.resource) + assert_equal('@b/c', j.to_s) + end + + def test_create11 + j = JID::new('@b') + assert_equal('', j.node) + assert_equal('b', j.domain) + assert_equal(nil, j.resource) + assert_equal('@b', j.to_s) + end + + def test_create12 + j = JID::new('@b/') + assert_equal('', j.node) + assert_equal('b', j.domain) + assert_equal('', j.resource) + assert_equal('@b/', j.to_s) + end + + def test_create13 + j = JID::new('a@b/') + assert_equal('a', j.node) + assert_equal('b', j.domain) + assert_equal('', j.resource) + assert_equal('a@b/', j.to_s) + end + + def test_create14 + j = JID::new('nOdE@dOmAiN/rEsOuRcE') + assert_equal('node', j.node) + assert_equal('domain', j.domain) + assert_equal('rEsOuRcE', j.resource) + assert_equal('node@domain/rEsOuRcE', j.to_s) + end + + def test_tos + assert_equal('', JID::new.to_s) + assert_equal('domain.fr', JID::new('domain.fr').to_s) + assert_equal('l@domain.fr', JID::new('l','domain.fr').to_s) + assert_equal('l@domain.fr/res', JID::new('l','domain.fr','res').to_s) + assert_equal('domain.fr/res', JID::new(nil,'domain.fr','res').to_s) + end + + def test_equal + assert_equal(JID::new('domain.fr'), JID::new('domain.fr')) + assert_equal(JID::new('domain.fr'), JID::new(nil, 'domain.fr')) + assert_equal(JID::new('l@domain.fr'), JID::new('l@domain.fr')) + assert_equal(JID::new('l@domain.fr'), JID::new('l', 'domain.fr')) + assert_equal(JID::new('l@domain.fr/res'), JID::new('l@domain.fr/res')) + assert_equal(JID::new('l@domain.fr/res'), JID::new('l', 'domain.fr', 'res')) + end + + def test_hash + h = {} + j = JID::new('l@domain.fr/res') + h[j] = 'a' + assert_equal(h[j], h[JID::new('l@domain.fr/res')]) + end + + def test_strip + assert_equal(JID::new('l@domain.fr'), JID::new('l@domain.fr/res').strip) + assert_equal(JID::new('l@domain.fr'), JID::new('l@domain.fr').strip) + assert_equal(JID::new('l@domain.fr'), JID::new('l@domain.fr/res').bare) + jid = JID::new('l@domain.fr/res') + jid.strip! + assert_equal(JID::new('l@domain.fr'), jid) + + jid = JID::new('l@domain.fr/res') + jid.bare! + assert_equal(JID::new('l@domain.fr'), jid) + end + + def test_change1 + j = JID::new('a@b/c') + j.node = 'd' + assert_equal('d@b/c', j.to_s) + j.domain = 'e' + assert_equal('d@e/c', j.to_s) + j.resource = 'f' + assert_equal('d@e/f', j.to_s) + end + + def test_escaping + j = JID::new('user1@server1') + j2 = JID::new(JID::escape(j), 'server2', 'res2') + assert_equal('user1%server1@server2/res2', j2.to_s) + end + +if defined?(libidnbug) # this crashes the interpreter + def test_invalidnode +# assert_raises(IDN::Stringprep::StringprepError) { JID::new('toto@a/a', 'server', 'res') } + assert_raises(IDN::Stringprep::StringprepError) { IDN::Stringprep.nodeprep('toto@a/a') } + end +end + + def test_empty + assert(JID.new.empty?) + assert(!JID.new("test").empty?) + end + + def test_stripped + assert(JID.new("node@domain").stripped?) + assert(!JID.new("node@domain/res").stripped?) + assert(JID.new("node@domain").bared?) + assert(!JID.new("node@domain/res").bared?) + end + + def test_sort + assert_equal(-1, JID.new('a@b') <=> JID.new('b@b')) + assert_equal(0, JID.new('a@b') <=> JID.new('a@b')) + assert_equal(1, JID.new('a@b/r') <=> JID.new('a@b')) + + jids = [JID.new('b@b'), JID.new('a@b/r'), JID.new('a@b')] + jids.sort! + assert_equal([JID.new('a@b'), JID.new('a@b/r'), JID.new('b@b')], jids) + end +end diff --git a/vendor/xmpp4r-0.3.2/test/tc_message.rb b/vendor/xmpp4r-0.3.2/test/tc_message.rb new file mode 100755 index 000000000..c612d1c3f --- /dev/null +++ b/vendor/xmpp4r-0.3.2/test/tc_message.rb @@ -0,0 +1,115 @@ +#!/usr/bin/ruby + +$:.unshift '../lib' + +require 'test/unit' +require 'socket' +require 'xmpp4r/rexmladdons' +require 'xmpp4r/message' +include Jabber + +class MessageTest < Test::Unit::TestCase + def test_create + x = Message::new() + assert_equal("message", x.name) + assert_equal("jabber:client", x.namespace) + assert_equal(nil, x.to) + assert_equal(nil, x.body) + + x = Message::new("lucas@linux.ensimag.fr", "coucou") + assert_equal("message", x.name) + assert_equal("lucas@linux.ensimag.fr", x.to.to_s) + assert_equal("coucou", x.body) + end + + def test_import + x = Message::new + assert_kind_of(REXML::Element, x.typed_add(REXML::Element.new('thread'))) + assert_kind_of(X, x.typed_add(REXML::Element.new('x'))) + assert_kind_of(X, x.x) + end + + def test_type + x = Message.new + assert_equal(nil, x.type) + x.type = :chat + assert_equal(:chat, x.type) + assert_equal(x, x.set_type(:error)) + assert_equal(:error, x.type) + x.type = :groupchat + assert_equal(:groupchat, x.type) + x.type = :headline + assert_equal(:headline, x.type) + x.type = :normal + assert_equal(:normal, x.type) + x.type = :invalid + assert_equal(nil, x.type) + end + + def test_body + x = Message::new() + assert_equal(nil, x.body) + assert_equal(x, x.set_body("trezrze ezfrezr ezr zer ezr ezrezrez ezr z")) + assert_equal("trezrze ezfrezr ezr zer ezr ezrezrez ezr z", x.body) + x.body = "2" + assert_equal("2", x.body) + end + + def test_subject + x = Message::new + assert_equal(nil, x.subject) + subject = REXML::Element.new('subject') + subject.text = 'A' + x.add(subject) + assert_equal('A', x.subject) + x.subject = 'Test message' + assert_equal('Test message', x.subject) + x.each_element('subject') { |s| assert_equal('Test message', s.text) } + assert_equal(x, x.set_subject('Breaking news')) + assert_equal('Breaking news', x.subject) + end + + def test_thread + x = Message::new + assert_equal(nil, x.thread) + thread = REXML::Element.new('thread') + thread.text = '123' + x.add(thread) + assert_equal('123', x.thread) + x.thread = '321' + assert_equal('321', x.thread) + x.each_element('thread') { |s| assert_equal('321', s.text) } + assert_equal(x, x.set_thread('abc')) + assert_equal('abc', x.thread) + end + + def test_error + x = Message::new() + assert_equal(nil, x.error) + e = REXML::Element::new('error') + x.add(e) + # test if, after an import, the error element is successfully changed + # into an Error object. + x2 = Message::new.import(x) + assert_equal(Error, x2.first_element('error').class) + end + + def test_answer + orig = Message::new + orig.from = 'a@b' + orig.to = 'b@a' + orig.id = '123' + orig.type = :chat + orig.add(REXML::Element.new('x')) + + answer = orig.answer + assert_equal(JID.new('b@a'), answer.from) + assert_equal(JID.new('a@b'), answer.to) + assert_equal('123', answer.id) + assert_equal(:chat, answer.type) + answer.each_element { |e| + assert_equal('x', e.name) + assert_kind_of(X, e) + } + end +end diff --git a/vendor/xmpp4r-0.3.2/test/tc_presence.rb b/vendor/xmpp4r-0.3.2/test/tc_presence.rb new file mode 100755 index 000000000..f7691bc6b --- /dev/null +++ b/vendor/xmpp4r-0.3.2/test/tc_presence.rb @@ -0,0 +1,149 @@ +#!/usr/bin/ruby + +$:.unshift '../lib' + +require 'test/unit' +require 'socket' +require 'xmpp4r/rexmladdons' +require 'xmpp4r/presence' +include Jabber + +class PresenceTest < Test::Unit::TestCase + def test_create + x = Presence::new() + assert_equal("presence", x.name) + assert_equal("jabber:client", x.namespace) + assert_equal(nil, x.to) + assert_equal(nil, x.show) + assert_equal(nil, x.status) + assert_equal(nil, x.priority) + + x = Presence::new(:away, "I am away", 23) + assert_equal("presence", x.name) + assert_equal(:away, x.show) + assert_equal("away", x.show.to_s) + assert_equal("I am away", x.status) + assert_equal(23, x.priority) + end + + def test_show + x = Presence::new() + assert_equal(nil, x.show) + assert_raise(RuntimeError) { x.show = "a" } + assert_equal(nil, x.show) + assert_raise(RuntimeError) { x.show = 'away' } + assert_equal(nil, x.show) + x.show = :away + assert_equal(:away, x.show) + x.each_element('show') { |e| assert(e.class == REXML::Element, " is not REXML::Element") } + x.show = nil + assert_equal(nil, x.show) + x.each_element('show') { |e| assert(true, " exists after 'show=nil'") } + x.show = nil + assert_equal(nil, x.show) + + showelement = REXML::Element.new('show') + showelement.text = 'chat' + x.add(showelement) + assert_equal(:chat, x.show) + end + + def test_status + x = Presence::new() + assert_equal(nil, x.status) + x.status = "b" + assert_equal("b", x.status) + x.each_element('status') { |e| assert(e.class == REXML::Element, " is not REXML::Element") } + x.status = nil + assert_equal(nil, x.status) + x.each_element('status') { |e| assert(true, " exists after 'status=nil'") } + x.status = nil + assert_equal(nil, x.status) + end + + def test_priority + x = Presence::new() + assert_equal(nil, x.priority) + x.priority = 5 + assert_equal(5, x.priority) + x.each_element('priority') { |e| assert(e.class == REXML::Element, " is not REXML::Element") } + x.priority = "5" + assert_equal(5, x.priority) + x.priority = nil + assert_equal(nil, x.priority) + x.each_element('priority') { |e| assert(true, " exists after 'priority=nil'") } + end + + def test_type + x = Presence::new() + assert_equal(nil, x.type) + x.type = :delete + assert_equal(nil, x.type) + x.type = nil + assert_equal(nil, x.type) + x.type = nil + assert_equal(nil, x.type) + [:error, :probe, :subscribe, :subscribed, :unavailable, :unsubscribe, :unsubscribed, nil].each { |type| + x.type = type + assert_equal(type, x.type) + } + end + + def test_chaining + x = Presence::new() + x.set_show(:xa).set_status("Plundering the fridge.").set_priority(0) + assert_equal(:xa, x.show) + assert_equal("Plundering the fridge.", x.status) + assert_equal(0, x.priority) + end + + def test_error + x = Presence::new() + e = REXML::Element::new('error') + x.add(e) + x2 = Presence::new.import(x) + # test if, after an import, the error element is successfully changed + # into an Error object. + assert_equal(Error, x2.first_element('error').class) + end + + def test_sample + x = Presence::new + require 'rexml/document' + d = REXML::Document.new("\n xa\n I am the evil fingerprinting robot\n ") + x.import(d.root) + num = 0 + x.each_element('show') { num += 1 } + assert_equal(1, num) + assert_equal(:xa, x.show) + assert_equal('I am the evil fingerprinting robot', x.status) + end + + def test_xpathbug + require 'rexml/document' + d = REXML::Document.new("xa") + x = d.root + num = 0 + x.each_element('tada') { num += 1 } + assert_equal(1, num) + end + + def test_compare_prio + assert_equal(0, Presence::new(:chat, '', 5) <=> Presence::new(:chat, '', 5)) + assert_equal(-1, Presence::new(:chat, '', 4) <=> Presence::new(:chat, '', 5)) + assert_equal(1, Presence::new(:chat, '', 4) <=> Presence::new(:chat, '', 3)) + assert_equal(-1, Presence::new(:chat, '', nil) <=> Presence::new(:chat, '', 3)) + assert_equal(1, Presence::new(:chat, '', 10) <=> Presence::new(:chat, '', nil)) + assert_equal(0, Presence::new(:chat, '', nil) <=> Presence::new(:chat, '', nil)) + end + + def test_compare_interest + unav = Presence::new.set_type(:unavailable) + assert_equal(0, unav.cmp_interest(unav)) + assert_equal(1, unav.cmp_interest(Presence::new)) + assert_equal(-1, Presence::new.cmp_interest(unav)) + assert_equal(1, Presence::new(:chat).cmp_interest(Presence::new)) + assert_equal(-1, Presence::new(:away).cmp_interest(Presence::new(:dnd))) + end + +end diff --git a/vendor/xmpp4r-0.3.2/test/tc_rexml.rb b/vendor/xmpp4r-0.3.2/test/tc_rexml.rb new file mode 100755 index 000000000..b947817a6 --- /dev/null +++ b/vendor/xmpp4r-0.3.2/test/tc_rexml.rb @@ -0,0 +1,60 @@ +#!/usr/bin/ruby + +$:.unshift '../lib' + +require 'test/unit' +require 'xmpp4r/rexmladdons' + +class REXMLTest < Test::Unit::TestCase + def test_simple + e = REXML::Element.new('e') + assert_kind_of(REXML::Element, e) + assert_nil(e.text) + assert_nil(e.attributes['x']) + end + + def test_normalize + assert_equal('&', REXML::Text::normalize('&')) + assert_equal('&amp;', REXML::Text::normalize('&')) + assert_equal('&amp;amp;', REXML::Text::normalize('&amp;')) + assert_equal('&nbsp;', REXML::Text::normalize(' ')) + end + + def test_unnormalize + assert_equal('&', REXML::Text::unnormalize('&')) + assert_equal('&', REXML::Text::unnormalize('&amp;')) + assert_equal('&amp;', REXML::Text::unnormalize('&amp;amp;')) + assert_equal(' ', REXML::Text::unnormalize('&nbsp;')) + assert_equal(' ', REXML::Text::unnormalize(' ')) # ? + end + + def test_text_entities + e = REXML::Element.new('e') + e.text = '&' + assert_equal('&', e.to_s) + e.text = '&' + assert_equal('&amp;', e.to_s) + e.text = ' ' + assert_equal('&nbsp', e.to_s) + e.text = ' ' + assert_equal('&nbsp;', e.to_s) + e.text = '&<;' + assert_equal('&<;', e.to_s) + e.text = '<>"\'' + assert_equal('<>"'', e.to_s) + e.text = '&' + assert_equal('<x>&amp;</x>', e.to_s) + end + + def test_attribute_entites + e = REXML::Element.new('e') + e.attributes['x'] = '&' + assert_equal('&', e.attributes['x']) + e.attributes['x'] = '&' + assert_equal('&', e.attributes['x']) # this one should not be escaped + e.attributes['x'] = ' ' + assert_equal(' ', e.attributes['x']) + e.attributes['x'] = ' ' + assert_equal(' ', e.attributes['x']) + end +end diff --git a/vendor/xmpp4r-0.3.2/test/tc_stream.rb b/vendor/xmpp4r-0.3.2/test/tc_stream.rb new file mode 100755 index 000000000..2d45d0b06 --- /dev/null +++ b/vendor/xmpp4r-0.3.2/test/tc_stream.rb @@ -0,0 +1,227 @@ +#!/usr/bin/ruby + +$:.unshift '../lib' + +require 'tempfile' +require 'test/unit' +require 'socket' +require 'xmpp4r/stream' +require 'xmpp4r/semaphore' +include Jabber + +class StreamTest < Test::Unit::TestCase + def setup + @tmpfile = Tempfile::new("StreamSendTest") + @tmpfilepath = @tmpfile.path() + @tmpfile.unlink + @servlisten = UNIXServer::new(@tmpfilepath) + thServer = Thread.new { @server = @servlisten.accept } + @iostream = UNIXSocket::new(@tmpfilepath) + n = 0 + while not defined? @server and n < 10 + sleep 0.1 + n += 1 + end + @stream = Stream::new + @stream.start(@iostream) + end + + def teardown + @stream.close + @server.close + end + + ## + # tests that connection really waits the call to process() to dispatch + # stanzas to filters + def test_process + called = false + @stream.add_xml_callback { called = true } + assert(!called) + @server.puts('') + @server.flush + assert(called) + end + + def test_process100 + @server.puts('') + @server.flush + + done = Semaphore.new + n = 0 + @stream.add_message_callback { + n += 1 + done.run if n % 100 == 0 + } + + 100.times { + @server.puts('') + @server.flush + } + + done.wait + assert_equal(100, n) + + @server.puts('' * 100) + @server.flush + + done.wait + assert_equal(200, n) + end + + def test_send + @server.puts('') + @server.flush + + Thread.new { + assert_equal(Iq.new(:get).delete_namespace.to_s, @server.gets('>')) + @stream.receive(Iq.new(:result)) + } + + called = 0 + @stream.send(Iq.new(:get)) { |reply| + called += 1 + if reply.kind_of? Iq and reply.type == :result + true + else + false + end + } + + assert_equal(1, called) + end + + def test_send_nested + @server.puts('') + @server.flush + finished = Semaphore.new + + Thread.new { + assert_equal(Iq.new(:get).delete_namespace.to_s, @server.gets('>')) + @server.puts(Iq.new(:result).set_id('1').delete_namespace.to_s) + @server.flush + assert_equal(Iq.new(:set).delete_namespace.to_s, @server.gets('>')) + @server.puts(Iq.new(:result).set_id('2').delete_namespace.to_s) + @server.flush + assert_equal(Iq.new(:get).delete_namespace.to_s, @server.gets('>')) + @server.puts(Iq.new(:result).set_id('3').delete_namespace.to_s) + @server.flush + + finished.run + } + + called_outer = 0 + called_inner = 0 + + @stream.send(Iq.new(:get)) do |reply| + called_outer += 1 + assert_kind_of(Iq, reply) + assert_equal(:result, reply.type) + + if reply.id == '1' + @stream.send(Iq.new(:set)) do |reply2| + called_inner += 1 + assert_kind_of(Iq, reply2) + assert_equal(:result, reply2.type) + assert_equal('2', reply2.id) + + @stream.send(Iq.new(:get)) + + true + end + false + elsif reply.id == '3' + true + else + false + end + end + + assert_equal(2, called_outer) + assert_equal(1, called_inner) + + finished.wait + end + + def test_send_in_callback + @server.puts('') + @server.flush + finished = Semaphore.new + + @stream.add_message_callback { + @stream.send_with_id(Iq.new(:get)) { |reply| + assert_equal(:result, reply.type) + } + } + + Thread.new { + @server.gets('>') + @server.puts(Iq.new(:result)) + finished.run + } + + @server.puts(Message.new) + finished.wait + end + + def test_bidi + @server.puts('') + @server.flush + finished = Semaphore.new + ok = true + n = 100 + + Thread.new { + n.times { |i| + ok &&= (Iq.new(:get).set_id(i).delete_namespace.to_s == @server.gets('>')) + @server.puts(Iq.new(:result).set_id(i).to_s) + @server.flush + } + + finished.run + } + + n.times { |i| + @stream.send(Iq.new(:get).set_id(i)) { |reply| + ok &&= reply.kind_of? Iq + ok &&= (:result == reply.type) + ok &&= (i.to_s == reply.id) + true + } + } + + finished.wait + assert(ok) + end + + def test_similar_children + delay = 0.1 + n = 0 + @stream.add_message_callback { n += 1 } + assert_equal(0, n) + @server.puts('') + @server.flush + sleep delay + assert_equal(1, n) + @server.puts('') + @server.flush + sleep delay + assert_equal(1, n) + @server.puts('') + @server.flush + sleep delay + assert_equal(1, n) + @server.puts('') + @server.flush + sleep delay + assert_equal(2, n) + @server.puts('') + @server.flush + sleep delay + assert_equal(2, n) + @server.puts('') + @server.flush + sleep delay + assert_equal(3, n) + end +end diff --git a/vendor/xmpp4r-0.3.2/test/tc_streamComponent.rb b/vendor/xmpp4r-0.3.2/test/tc_streamComponent.rb new file mode 100755 index 000000000..59cbb2c5c --- /dev/null +++ b/vendor/xmpp4r-0.3.2/test/tc_streamComponent.rb @@ -0,0 +1,94 @@ +#!/usr/bin/ruby + +$:.unshift '../lib' + +require 'tempfile' +require 'test/unit' +require 'socket' +require 'xmpp4r/component' +require 'xmpp4r/bytestreams' +require 'xmpp4r/semaphore' +require 'xmpp4r' +include Jabber + +class StreamComponentTest < Test::Unit::TestCase + @@SOCKET_PORT = 65224 + + def setup + servlisten = TCPServer.new(@@SOCKET_PORT) + serverwait = Semaphore.new + Thread.new do + Thread.current.abort_on_exception = true + serversock = servlisten.accept + servlisten.close + serversock.sync = true + @server = Stream.new(true) + @server.add_xml_callback do |xml| + if xml.prefix == 'stream' and xml.name == 'stream' + @server.send('') + true + else + false + end + end + @server.start(serversock) + + serverwait.run + end + + @stream = Component::new('test') + @stream.connect('localhost', @@SOCKET_PORT) + + serverwait.wait + end + + def teardown + @stream.close + @server.close + end + + def test_process + stanzas = 0 + message_lock = Semaphore.new + iq_lock = Semaphore.new + presence_lock = Semaphore.new + + @stream.add_message_callback { |msg| + assert_kind_of(Message, msg) + stanzas += 1 + message_lock.run + } + @stream.add_iq_callback { |iq| + assert_kind_of(Iq, iq) + stanzas += 1 + iq_lock.run + } + @stream.add_presence_callback { |pres| + assert_kind_of(Presence, pres) + stanzas += 1 + presence_lock.run + } + + @server.send('') + @server.send('') + @server.send('') + + message_lock.wait + iq_lock.wait + presence_lock.wait + + assert_equal(3, stanzas) + end + + def test_outgoing + received_wait = Semaphore.new + + @server.add_message_callback { |msg| + assert_kind_of(Message, msg) + received_wait.run + } + + @stream.send(Message.new) + received_wait.wait + end +end diff --git a/vendor/xmpp4r-0.3.2/test/tc_streamError.rb b/vendor/xmpp4r-0.3.2/test/tc_streamError.rb new file mode 100755 index 000000000..1ad5ee329 --- /dev/null +++ b/vendor/xmpp4r-0.3.2/test/tc_streamError.rb @@ -0,0 +1,125 @@ +#!/usr/bin/ruby + +$:.unshift '../lib' + +require 'test/unit' +require 'socket' +require 'xmpp4r/client' +include Jabber + +class ConnectionErrorTest < Test::Unit::TestCase + @@SOCKET_PORT = 65225 + + def setup + servlisten = TCPServer.new(@@SOCKET_PORT) + serverwait = Semaphore.new + @server = nil + Thread.new do + Thread.current.abort_on_exception = true + @server = servlisten.accept + servlisten.close + @server.sync = true + + serverwait.run + end + + @conn = TCPSocket::new('localhost', @@SOCKET_PORT) + + serverwait.wait + end + + def teardown + @conn.close if not @conn.closed? + @server.close if not @conn.closed? + end + + def test_connectionError_start_withexcblock + @stream = Stream::new + error = false + @stream.on_exception do |e, o, w| + # strange exception, it's caused by REXML, actually + assert_equal(NameError, e.class) + assert_equal(Jabber::Stream, o.class) + assert_equal(:start, w) + error = true + end + assert(!error) + begin + # wrong port on purpose + conn = TCPSocket::new('localhost', 1) + rescue + end + @stream.start(conn) + sleep 0.2 + assert(error) + @server.close + @stream.close + end + + def test_connectionError_parse_withexcblock + @stream = Stream::new + error = false + @stream.start(@conn) + @stream.on_exception do |e, o, w| + assert_equal(REXML::ParseException, e.class) + assert_equal(Jabber::Stream, o.class) + assert_equal(:parser, w) + error = true + end + @server.puts('') + @server.flush + assert(!error) + @server.puts('') + @server.flush + sleep 0.2 + assert(error) + @server.close + @stream.close + end + + def test_connectionError_send_withexcblock + @stream = Stream::new + error = 0 + @stream.start(@conn) + @stream.on_exception do |exc, o, w| + case w + when :sending + assert_equal(IOError, exc.class) + assert_equal(Jabber::Stream, o.class) + when :disconnected + assert_equal(nil, exc) + assert_equal(Jabber::Stream, o.class) + else + assert(false) + end + error += 1 + end + @server.puts('') + @server.flush + assert_equal(0, error) + @server.close + sleep 0.1 + assert_equal(1, error) + @stream.send('') + sleep 0.1 + @stream.send('') + sleep 0.1 + assert_equal(3, error) + @stream.close + end + + def test_connectionError_send_withoutexcblock + @stream = Stream::new + @stream.start(@conn) + @server.puts('') + @server.flush + assert_raise(Errno::EPIPE) do + @server.close + sleep 0.1 + @stream.send('') + sleep 0.1 + @stream.send('') + sleep 0.1 + end + end +end diff --git a/vendor/xmpp4r-0.3.2/test/tc_streamSend.rb b/vendor/xmpp4r-0.3.2/test/tc_streamSend.rb new file mode 100755 index 000000000..8acd86009 --- /dev/null +++ b/vendor/xmpp4r-0.3.2/test/tc_streamSend.rb @@ -0,0 +1,59 @@ +#!/usr/bin/ruby + +$:.unshift '../lib' + +require 'test/unit' +require 'socket' +require 'tempfile' +require 'io/wait' +require 'xmpp4r' +include Jabber + +class StreamSendTest < Test::Unit::TestCase + def setup + @tmpfile = Tempfile::new("StreamSendTest") + @tmpfilepath = @tmpfile.path() + @tmpfile.unlink + @servlisten = UNIXServer::new(@tmpfilepath) + thServer = Thread.new { @server = @servlisten.accept } + @iostream = UNIXSocket::new(@tmpfilepath) + @stream = Stream::new + @stream.start(@iostream) + + thServer.join + end + + def teardown + @stream.close + @server.close + @servlisten.close + end + + def mysend(s) + @stream.send(s) + @stream.send("\n") #needed for easy test writing + end + + ## + # Tries to send a basic message + def test_sendbasic + mysend(Message::new) + assert_equal("\n", @server.gets) + end + + def test_sendmessage + mysend(Message::new('lucas@linux.ensimag.fr', 'coucou')) + assert_equal("coucou\n", @server.gets) + end + + def test_sendpresence + mysend(Presence::new) + assert_equal("\n", @server.gets) + end + + def test_sendiq + mysend(Iq::new) + assert_equal("\n", @server.gets) + end + +end diff --git a/vendor/xmpp4r-0.3.2/test/tc_xmppstanza.rb b/vendor/xmpp4r-0.3.2/test/tc_xmppstanza.rb new file mode 100755 index 000000000..f127173bd --- /dev/null +++ b/vendor/xmpp4r-0.3.2/test/tc_xmppstanza.rb @@ -0,0 +1,125 @@ +#!/usr/bin/ruby + +$:.unshift '../lib' + +require 'test/unit' +require 'socket' +require 'xmpp4r/rexmladdons' +require 'xmpp4r/xmppstanza' +require 'xmpp4r/iq' +require 'xmpp4r/feature_negotiation' +require 'xmpp4r/dataforms' +include Jabber + +class XMPPStanzaTest < Test::Unit::TestCase + + ## + # Hack: XMPPStanza derives from XMPPElement + # which enforces element classes to be named at declaration time + class MyXMPPStanza < XMPPStanza + name_xmlns 'stanza', 'http://home.gna.org/xmpp4r' + end + + class MyStanza < XMPPStanza + end + + def test_from + x = MyXMPPStanza::new + assert_equal(nil, x.from) + assert_equal(x, x.set_from("blop")) + assert_equal("blop", x.from.to_s) + x.from = "tada" + assert_equal("tada", x.from.to_s) + end + + def test_to + x = MyXMPPStanza::new + assert_equal(nil, x.to) + assert_equal(x, x.set_to("blop")) + assert_equal("blop", x.to.to_s) + x.to = "tada" + assert_equal("tada", x.to.to_s) + end + + def test_id + x = MyXMPPStanza::new + assert_equal(nil, x.id) + assert_equal(x, x.set_id("blop")) + assert_equal("blop", x.id) + x.id = "tada" + assert_equal("tada", x.id) + end + + def test_type + x = MyXMPPStanza::new + assert_equal(nil, x.type) + assert_equal(x, x.set_type("blop")) + assert_equal("blop", x.type) + x.type = "tada" + assert_equal("tada", x.type) + end + + def test_import + x = MyXMPPStanza::new + x.id = "heya" + q = x.add_element("query") + q.add_namespace("about:blank") + q.add_element("b").text = "I am b" + q.add_text("I am text") + q.add_element("a").add_attribute("href", "http://home.gna.org/xmpp4r/") + x.add_text("yow") + x.add_element("query") + + assert_raise(RuntimeError) { iq = Iq.import(x) } + x.name = 'iq' + iq = Iq.import(x) + + assert_equal(x.id, iq.id) + assert_equal(q.to_s, iq.query.to_s) + assert_equal(x.to_s, iq.to_s) + assert_equal(q.namespace, iq.queryns) + end + + def test_import2 + feature = FeatureNegotiation::IqFeature.new + xdata = feature.add(Dataforms::XData.new(:form)) + field = xdata.add(Dataforms::XDataField.new('stream-method', :list_single)) + + feature2 = FeatureNegotiation::IqFeature.new.import(feature) + assert_equal(field.var, feature2.x.fields.first.var) + assert_equal(field.type, feature2.x.fields.first.type) + end + + def test_error + x = MyXMPPStanza::new + assert_equal(nil, x.error) + x.typed_add(REXML::Element.new('error')) + assert_equal('', x.error.to_s) + assert_equal(Error, x.error.class) + end + + def test_clone_and_dup + x = MyXMPPStanza::new + x.attributes['xyz'] = '123' + x.text = 'abc' + + assert_equal(x.attributes['xyz'], '123') + assert_equal(x.text, 'abc') + + x2 = x.clone + assert_kind_of(MyXMPPStanza, x2) + assert_equal('123', x2.attributes['xyz']) + assert_nil(x2.text) + + x3 = x.dup + assert_kind_of(MyXMPPStanza, x3) + assert_equal('123', x3.attributes['xyz']) + assert_equal('abc', x3.text) + end + + def test_raise + assert_raises(NoNameXmlnsRegistered) { + XMPPStanza.name_xmlns_for_class(MyStanza) + } + end +end diff --git a/vendor/xmpp4r-0.3.2/test/ts_xmpp4r.rb b/vendor/xmpp4r-0.3.2/test/ts_xmpp4r.rb new file mode 100755 index 000000000..7b7f9124f --- /dev/null +++ b/vendor/xmpp4r-0.3.2/test/ts_xmpp4r.rb @@ -0,0 +1,44 @@ +#!/usr/bin/ruby -w + +$:.unshift File.join(File.dirname(__FILE__), '..', 'lib') +$:.unshift File.join(File.dirname(__FILE__), '..', 'test') +$:.unshift File.join(File.dirname(__FILE__), 'lib') +$:.unshift File.join(File.dirname(__FILE__), 'test') + +# This is allowed here, to make sure it's enabled in all test. +Thread::abort_on_exception = true + +require 'xmpp4r' +require 'find' + +# List files' basenames, not full path! +# EXCLUDED_FILES = [ 'tc_muc_simplemucclient.rb' ] +EXCLUDED_FILES = [] + +tc_files = [] +tc_subdirs = [] +Find.find(File.dirname(__FILE__)) do |f| + if File::directory?(f) + if f == '.' + # do nothing + elsif File::basename(f) != '.svn' + tc_subdirs << f + Find.prune + end + elsif File::basename(f) =~ /^tc.*\.rb$/ + tc_files << f + end +end + +tc_subdirs.each do |dir| + Find.find(dir) do |f| + if File::file?(f) and File::basename(f) =~ /^tc.*\.rb$/ + tc_files << f + end + end +end + +tc_files.each do |f| + next if EXCLUDED_FILES.include?(File::basename(f)) + require f +end diff --git a/vendor/xmpp4r-0.3.2/test/vcard/tc_helper.rb b/vendor/xmpp4r-0.3.2/test/vcard/tc_helper.rb new file mode 100644 index 000000000..691e03708 --- /dev/null +++ b/vendor/xmpp4r-0.3.2/test/vcard/tc_helper.rb @@ -0,0 +1,49 @@ +#!/usr/bin/ruby + +$:.unshift File::dirname(__FILE__) + '/../../lib' + +require 'test/unit' +require File::dirname(__FILE__) + '/../lib/clienttester' + +require 'xmpp4r' +require 'xmpp4r/vcard/helper/vcard' +include Jabber + +class Vcard::HelperTest < Test::Unit::TestCase + include ClientTester + + def test_create + h = Vcard::Helper::new(@client) + assert_kind_of(Vcard::Helper, h) + end + + def test_callback + @server.on_exception{|*e| p e} + class << @client + def jid + JID.new('b@b.com/b') + end + end + + state { |iq| + assert_kind_of(Iq, iq) + assert_equal(JID.new('a@b.com'), iq.to) + assert_equal(:get, iq.type) + assert_nil(iq.queryns) + assert_kind_of(Vcard::IqVcard, iq.vcard) + children = 0 + iq.vcard.each_child { children += 1 } + assert_equal(0, children) + + send("Mr. Bimage/png====") + } + + res = Vcard::Helper::get(@client, 'a@b.com') + wait_state + assert_kind_of(Vcard::IqVcard, res) + assert_equal('Mr. B', res['NICKNAME']) + assert_equal('image/png', res['PHOTO/TYPE']) + assert_equal('====', res['PHOTO/BINVAL']) + end +end + diff --git a/vendor/xmpp4r-0.3.2/test/vcard/tc_iqvcard.rb b/vendor/xmpp4r-0.3.2/test/vcard/tc_iqvcard.rb new file mode 100755 index 000000000..f4dda1545 --- /dev/null +++ b/vendor/xmpp4r-0.3.2/test/vcard/tc_iqvcard.rb @@ -0,0 +1,52 @@ +#!/usr/bin/ruby + +$:.unshift File::dirname(__FILE__) + '/../../lib' + +require 'test/unit' +require 'xmpp4r/rexmladdons' +require 'xmpp4r/vcard/iq/vcard' +include Jabber + +class IqVcardTest < Test::Unit::TestCase + def test_create + v = Vcard::IqVcard.new + assert_equal([], v.fields) + end + + def test_create_with_fields + v = Vcard::IqVcard.new({'FN' => 'B C', 'NICKNAME' => 'D'}) + assert_equal(['FN', 'NICKNAME'], v.fields.sort) + assert_equal('B C', v['FN']) + assert_equal('D', v['NICKNAME']) + assert_equal(nil, v['x']) + end + + def test_fields + v = Vcard::IqVcard.new + f = ['a', 'b', 'c', 'd', 'e'] + f.each { |s| + v[s.downcase] = s.upcase + } + + assert_equal(f, v.fields.sort) + + f.each { |s| + assert_equal(s.upcase, v[s.downcase]) + assert_equal(nil, v[s.upcase]) + } + end + + def test_deep + v = Vcard::IqVcard.new({ + 'FN' => 'John D. Random', + 'PHOTO/TYPE' => 'image/png', + 'PHOTO/BINVAL' => '===='}) + + assert_equal(['FN', 'PHOTO/BINVAL', 'PHOTO/TYPE'], v.fields.sort) + assert_equal('John D. Random', v['FN']) + assert_equal('image/png', v['PHOTO/TYPE']) + assert_equal('====', v['PHOTO/BINVAL']) + assert_equal(nil, v['PHOTO']) + assert_equal(nil, v['NICKNAME']) + end +end diff --git a/vendor/xmpp4r-0.3.2/test/version/tc_helper.rb b/vendor/xmpp4r-0.3.2/test/version/tc_helper.rb new file mode 100755 index 000000000..181954d6a --- /dev/null +++ b/vendor/xmpp4r-0.3.2/test/version/tc_helper.rb @@ -0,0 +1,60 @@ +#!/usr/bin/ruby + +$:.unshift File::dirname(__FILE__) + '/../../lib' + +require 'test/unit' +require File::dirname(__FILE__) + '/../lib/clienttester' + +require 'xmpp4r' +require 'xmpp4r/version/helper/responder' +require 'xmpp4r/version/helper/simpleresponder' +include Jabber + +class Version::HelperTest < Test::Unit::TestCase + include ClientTester + + def test_create + h = Version::Responder::new(@client) + assert_kind_of(Version::Responder, h) + assert_respond_to(h, :add_version_callback) + end + + def test_callback + # Prepare helper + h = Version::Responder::new(@client) + + calls = 0 + h.add_version_callback { |iq,responder| + calls += 1 + assert('jabber:iq:version', iq.queryns) + responder.call('Test program', '1.0', 'Ruby Test::Unit') + } + + # Send stanzas which shouldn't match + @server.send("") + @server.send("") + assert_equal(0, calls) + + # Send a query + @server.send("") { |reply| + assert_equal('Test program', reply.query.iname) + assert_equal('1.0', reply.query.version) + assert_equal('Ruby Test::Unit', reply.query.os) + true + } + assert_equal(1, calls) + end + + def test_simple + h = Version::SimpleResponder.new(@client, 'Test program', '1.0', 'Ruby Test::Unit') + + # Send a query + @server.send("") { |reply| + assert_equal('Test program', reply.query.iname) + assert_equal('1.0', reply.query.version) + assert_equal('Ruby Test::Unit', reply.query.os) + true + } + + end +end diff --git a/vendor/xmpp4r-0.3.2/test/version/tc_iqqueryversion.rb b/vendor/xmpp4r-0.3.2/test/version/tc_iqqueryversion.rb new file mode 100755 index 000000000..e2a88fd13 --- /dev/null +++ b/vendor/xmpp4r-0.3.2/test/version/tc_iqqueryversion.rb @@ -0,0 +1,97 @@ +#!/usr/bin/ruby + +$:.unshift File::dirname(__FILE__) + '/../../lib' + +require 'test/unit' +require 'xmpp4r/rexmladdons' +require 'xmpp4r/iq' +require 'xmpp4r/version/iq/version' +include Jabber + +class Version::IqQueryVersionTest < Test::Unit::TestCase + def test_create_empty + x = Version::IqQueryVersion::new + assert_equal('jabber:iq:version', x.namespace) + assert_nil(x.iname) + assert_nil(x.version) + assert_nil(x.os) + end + + def test_create + x = Version::IqQueryVersion::new('my test', 'XP') + assert_equal('jabber:iq:version', x.namespace) + assert_equal('my test', x.iname) + assert_equal('XP', x.version) + assert_equal(nil, x.os) + end + + def test_create_with_os + x = Version::IqQueryVersion::new('superbot', '1.0-final', 'FreeBSD 5.4-RELEASE-p4') + assert_equal('jabber:iq:version', x.namespace) + assert_equal('superbot', x.iname) + assert_equal('1.0-final', x.version) + assert_equal('FreeBSD 5.4-RELEASE-p4', x.os) + end + + def test_import1 + iq = Iq::new + q = REXML::Element::new('query') + q.add_namespace('jabber:iq:version') + iq.add(q) + iq2 = Iq::new.import(iq) + assert_equal(Version::IqQueryVersion, iq2.query.class) + end + + def test_import2 + iq = Iq::new + q = REXML::Element::new('query') + q.add_namespace('jabber:iq:version') + q.add_element('name').text = 'AstroBot' + q.add_element('version').text = 'XP' + q.add_element('os').text = 'FreeDOS' + iq.add(q) + iq = Iq::new.import(iq) + assert_equal(Version::IqQueryVersion, iq.query.class) + assert_equal('AstroBot', iq.query.iname) + assert_equal('XP', iq.query.version) + assert_equal('FreeDOS', iq.query.os) + end + + def test_replace + x = Version::IqQueryVersion::new('name', 'version', 'os') + + num = 0 + x.each_element('name') { |e| num += 1 } + assert_equal(1, num) + num = 0 + x.each_element('version') { |e| num += 1 } + assert_equal(1, num) + num = 0 + x.each_element('os') { |e| num += 1 } + assert_equal(1, num) + + x.set_iname('N').set_version('V').set_os('O') + + num = 0 + x.each_element('name') { |e| num += 1 } + assert_equal(1, num) + num = 0 + x.each_element('version') { |e| num += 1 } + assert_equal(1, num) + num = 0 + x.each_element('os') { |e| num += 1 } + assert_equal(1, num) + + x.set_iname(nil).set_version(nil).set_os(nil) + + num = 0 + x.each_element('name') { |e| num += 1 } + assert_equal(1, num) + num = 0 + x.each_element('version') { |e| num += 1 } + assert_equal(1, num) + num = 0 + x.each_element('os') { |e| num += 1 } + assert_equal(0, num) + end +end diff --git a/vendor/xmpp4r-0.3.2/tools/doctoweb.bash b/vendor/xmpp4r-0.3.2/tools/doctoweb.bash new file mode 100755 index 000000000..1a914111e --- /dev/null +++ b/vendor/xmpp4r-0.3.2/tools/doctoweb.bash @@ -0,0 +1,30 @@ +#!/bin/bash + +if [ -z $CVSDIR ]; then + CVSDIR=$HOME/dev/xmpp4r-web +fi + +TARGET=$CVSDIR/rdoc + +echo "Copying rdoc documentation to $TARGET." + +if [ ! -d $TARGET ]; then + echo "$TARGET doesn't exist, exiting." + exit 1 +fi +rsync -a rdoc/ $TARGET/ + +echo "###########################################################" +echo "CVS status :" +cd $TARGET +cvs -q up +echo "CVS Adding files." +while [ $(cvs -q up | grep "^? " | wc -l) -gt 0 ]; do + cvs add $(cvs -q up | grep "^? " | awk '{print $2}') +done +echo "###########################################################" +echo "CVS status after adding missing files:" +cvs -q up +echo "Commit changes now with" +echo "# (cd $TARGET && cvs commit -m \"rdoc update\")" +exit 0 diff --git a/vendor/xmpp4r-0.3.2/tools/gen_requires.bash b/vendor/xmpp4r-0.3.2/tools/gen_requires.bash new file mode 100644 index 000000000..40af1b2f8 --- /dev/null +++ b/vendor/xmpp4r-0.3.2/tools/gen_requires.bash @@ -0,0 +1,10 @@ +#!/bin/bash + +cd lib +f=$(mktemp) +echo "strict digraph requirestree { " > $f +grep -r "^require " * |grep -v svn |grep -v swp | sed "s/^\(.*\).rb:require '\(.*\)'/\1 -> \2;/" | sed 's/\//_/g' >> $f +echo "}" >> $f +cd .. +dot -Tpng $f -o gen_requires.png +rm -f $f diff --git a/vendor/xmpp4r-simple-0.8.4/CHANGELOG b/vendor/xmpp4r-simple-0.8.4/CHANGELOG new file mode 100644 index 000000000..9269f0bd8 --- /dev/null +++ b/vendor/xmpp4r-simple-0.8.4/CHANGELOG @@ -0,0 +1,40 @@ +xmpp4r-simple (0.8.3) + + [ Blaine Cook ] + * Catch broken connections and attempt to reconnect 3 times. + +-- Blaine Cook Fri, 23 Dec 2006 00:12:09 -0800 + + +xmpp4r-simple (0.8.3) + + [ Blaine Cook ] + * Update presence_updates to only store one presence_update per user. + Changes methods, will break code that uses presence_updates if not updated + correspondingly, check the documenation for the new semantics. + +-- Blaine Cook Thu, 07 Dec 2006 12:45:52 -0800 + + +xmpp4r-simple (0.8.2) + + [ Blaine Cook ] + * Add presence_updates?, received_messages?, and new_subscriptions? methods. + +-- Blaine Cook Wed, 06 Dec 2006 09:40:28 -0800 + + +xmpp4r-simple (0.8.1) + + [ Blaine Cook ] + * Make the deferred_delivery method public + +-- Blaine Cook Tue, 05 Dec 2006 17:39:14 -0800 + + +xmpp4r-simple (0.8.0) + + [ Blaine Cook ] + * initial import + +-- Blaine Cook Wed, 08 Nov 2006 20:42:42 -0800 diff --git a/vendor/xmpp4r-simple-0.8.4/COPYING b/vendor/xmpp4r-simple-0.8.4/COPYING new file mode 100644 index 000000000..84e494885 --- /dev/null +++ b/vendor/xmpp4r-simple-0.8.4/COPYING @@ -0,0 +1,281 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + diff --git a/vendor/xmpp4r-simple-0.8.4/README b/vendor/xmpp4r-simple-0.8.4/README new file mode 100644 index 000000000..67f01eeac --- /dev/null +++ b/vendor/xmpp4r-simple-0.8.4/README @@ -0,0 +1,59 @@ += Name + +Jabber::Simple - An extremely easy-to-use Jabber client library. + += Synopsis + + # Send a message to a friend, asking for authorization if necessary: + im = Jabber::Simple.new("user@example.com", "password") + im.deliver("friend@example.com", "Hey there friend!") + + # Get received messages and print them out to the console: + im.received_messages { |msg| puts msg.body if msg.type == :chat } + + # Send an authorization request to a user: + im.add("friend@example.com") + + # Get presence updates from your friends, and print them out to the console: + # (admittedly, this one needs some work) + im.presence_updates { |update| + from = update[0].jid.strip.to_s + status = update[2].status + presence = update[2].show + puts "#{from} went #{presence}: #{status}" + end + + # Remove a user from your contact list: + im.remove("unfriendly@example.com") + + # See the Jabber::Simple documentation for more information. + += Description + +Jabber::Simple is intended to make Jabber client programming dead simple. XMPP, +the Jabber protocol, is extremely powerful but also carries a steep learning +curve. This library exposes only the most common tasks, and does so in a way +that is familiar to users of traditional instant messenger clients. + += Known Issues + +* None. If you'd like additional functionality, please contact the developer! + += Copyright + +Jabber::Simple - An extremely easy-to-use Jabber client library. +Copyright 2006 Blaine Cook , Obvious Corp. + +Jabber::Simple is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +Jabber::Simple is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Jabber::Simple; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA diff --git a/vendor/xmpp4r-simple-0.8.4/lib/xmpp4r-simple.rb b/vendor/xmpp4r-simple-0.8.4/lib/xmpp4r-simple.rb new file mode 100644 index 000000000..8d9f8bd71 --- /dev/null +++ b/vendor/xmpp4r-simple-0.8.4/lib/xmpp4r-simple.rb @@ -0,0 +1,464 @@ +# Jabber::Simple - An extremely easy-to-use Jabber client library. +# Copyright 2006 Blaine Cook , Obvious Corp. +# +# Jabber::Simple is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# Jabber::Simple is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Jabber::Simple; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +require 'rubygems' +require 'xmpp4r' +require 'xmpp4r/roster' + +module Jabber + + class ConnectionError < StandardError #:nodoc: + end + + class Contact #:nodoc: + + def initialize(client, jid) + @jid = jid.respond_to?(:resource) ? jid : JID.new(jid) + @client = client + end + + def inspect + "Jabber::Contact #{jid.to_s}" + end + + def subscribed? + [:to, :both].include?(subscription) + end + + def subscription + roster_item && roster_item.subscription + end + + def ask_for_authorization! + subscription_request = Presence.new.set_type(:subscribe) + subscription_request.to = jid + client.send!(subscription_request) + end + + def unsubscribe! + unsubscription_request = Presence.new.set_type(:unsubscribe) + unsubscription_request.to = jid + client.send!(unsubscription_request) + client.send!(unsubscription_request.set_type(:unsubscribed)) + end + + def jid(bare=true) + bare ? @jid.strip : @jid + end + + private + + def roster_item + client.roster.items[jid] + end + + def client + @client + end + end + + class Simple + + # Create a new Jabber::Simple client. You will be automatically connected + # to the Jabber server and your status message will be set to the string + # passed in as the status_message argument. + # + # jabber = Jabber::Simple.new("me@example.com", "password", "Chat with me - Please!") + def initialize(jid, password, status = nil, status_message = "Available") + @jid = jid + @password = password + @disconnected = false + status(status, status_message) + start_deferred_delivery_thread + end + + def inspect #:nodoc: + "Jabber::Simple #{@jid}" + end + + # Send a message to jabber user jid. + # + # Valid message types are: + # + # * :normal (default): a normal message. + # * :chat: a one-to-one chat message. + # * :groupchat: a group-chat message. + # * :headline: a "headline" message. + # * :error: an error message. + # + # If the recipient is not in your contacts list, the message will be queued + # for later delivery, and the Contact will be automatically asked for + # authorization (see Jabber::Simple#add). + def deliver(jid, message, type=:chat) + contacts(jid) do |friend| + unless subscribed_to? friend + add(friend.jid) + return deliver_deferred(friend.jid, message, type) + end + msg = Message.new(friend.jid) + msg.type = type + msg.body = message + send!(msg) + end + end + + # Set your presence, with a message. + # + # Available values for presence are: + # + # * nil: online. + # * :chat: free for chat. + # * :away: away from the computer. + # * :dnd: do not disturb. + # * :xa: extended away. + # + # It's not possible to set an offline status - to do that, disconnect! :-) + def status(presence, message) + @presence = presence + @status_message = message + stat_msg = Presence.new(@presence, @status_message) + send!(stat_msg) + end + + # Ask the users specified by jids for authorization (i.e., ask them to add + # you to their contact list). If you are already in the user's contact list, + # add() will not attempt to re-request authorization. In order to force + # re-authorization, first remove() the user, then re-add them. + # + # Example usage: + # + # jabber_simple.add("friend@friendosaurus.com") + # + # Because the authorization process might take a few seconds, or might + # never happen depending on when (and if) the user accepts your + # request, results are placed in the Jabber::Simple#new_subscriptions queue. + def add(*jids) + contacts(*jids) do |friend| + next if subscribed_to? friend + friend.ask_for_authorization! + end + end + + # Remove the jabber users specified by jids from the contact list. + def remove(*jids) + contacts(*jids) do |unfriend| + unfriend.unsubscribe! + end + end + + # Returns true if this Jabber account is subscribed to status updates for + # the jabber user jid, false otherwise. + def subscribed_to?(jid) + contacts(jid) do |contact| + return contact.subscribed? + end + end + + # If contacts is a single contact, returns a Jabber::Contact object + # representing that user; if contacts is an array, returns an array of + # Jabber::Contact objects. + # + # When called with a block, contacts will yield each Jabber::Contact object + # in turn. This is mainly used internally, but exposed as an utility + # function. + def contacts(*contacts, &block) + @contacts ||= {} + contakts = [] + contacts.each do |contact| + jid = contact.to_s + unless @contacts[jid] + @contacts[jid] = contact.respond_to?(:ask_for_authorization!) ? contact : Contact.new(self, contact) + end + yield @contacts[jid] if block_given? + contakts << @contacts[jid] + end + contakts.size > 1 ? contakts : contakts.first + end + + # Returns true if the Jabber client is connected to the Jabber server, + # false otherwise. + def connected? + @client ||= nil + connected = @client.respond_to?(:is_connected?) && @client.is_connected? + return connected + end + + # Returns an array of messages received since the last time + # received_messages was called. Passing a block will yield each message in + # turn, allowing you to break part-way through processing (especially + # useful when your message handling code is not thread-safe (e.g., + # ActiveRecord). + # + # e.g.: + # + # jabber.received_messages do |message| + # puts "Received message from #{message.from}: #{message.body}" + # end + def received_messages(&block) + dequeue(:received_messages, &block) + end + + # Returns true if there are unprocessed received messages waiting in the + # queue, false otherwise. + def received_messages? + !queue(:received_messages).empty? + end + + # Returns an array of presence updates received since the last time + # presence_updates was called. Passing a block will yield each update in + # turn, allowing you to break part-way through processing (especially + # useful when your presence handling code is not thread-safe (e.g., + # ActiveRecord). + # + # e.g.: + # + # jabber.presence_updates do |friend, new_presence| + # puts "Received presence update from #{friend}: #{new_presence}" + # end + def presence_updates(&block) + updates = [] + @presence_mutex.synchronize do + dequeue(:presence_updates) do |friend| + presence = @presence_updates[friend] + next unless presence + new_update = [friend, presence[0], presence[1]] + yield new_update if block_given? + updates << new_update + @presence_updates.delete(friend) + end + end + return updates + end + + # Returns true if there are unprocessed presence updates waiting in the + # queue, false otherwise. + def presence_updates? + !queue(:presence_updates).empty? + end + + # Returns an array of subscription notifications received since the last + # time new_subscriptions was called. Passing a block will yield each update + # in turn, allowing you to break part-way through processing (especially + # useful when your subscription handling code is not thread-safe (e.g., + # ActiveRecord). + # + # e.g.: + # + # jabber.new_subscriptions do |friend, presence| + # puts "Received presence update from #{friend.to_s}: #{presence}" + # end + def new_subscriptions(&block) + dequeue(:new_subscriptions, &block) + end + + # Returns true if there are unprocessed presence updates waiting in the + # queue, false otherwise. + def new_subscriptions? + !queue(:new_subscriptions).empty? + end + + # Returns an array of subscription notifications received since the last + # time subscription_requests was called. Passing a block will yield each update + # in turn, allowing you to break part-way through processing (especially + # useful when your subscription handling code is not thread-safe (e.g., + # ActiveRecord). + # + # e.g.: + # + # jabber.subscription_requests do |friend, presence| + # puts "Received presence update from #{friend.to_s}: #{presence}" + # end + def subscription_requests(&block) + dequeue(:subscription_requests, &block) + end + + # Returns true if auto-accept subscriptions (friend requests) is enabled + # (default), false otherwise. + def accept_subscriptions? + @accept_subscriptions = true if @accept_subscriptions.nil? + @accept_subscriptions + end + + # Change whether or not subscriptions (friend requests) are automatically accepted. + def accept_subscriptions=(accept_status) + @accept_subscriptions = accept_status + end + + # Direct access to the underlying Roster helper. + def roster + return @roster if @roster + self.roster = Roster::Helper.new(client) + end + + # Direct access to the underlying Jabber client. + def client + connect!() unless connected? + @client + end + + # Send a Jabber stanza over-the-wire. + def send!(msg) + attempts = 0 + begin + attempts += 1 + client.send(msg) + rescue Errno::EPIPE, IOError => e + sleep 0.33 + disconnect + reconnect + retry unless attempts > 3 + raise e + end + end + + # Use this to force the client to reconnect after a force_disconnect. + def reconnect + @disconnected = false + connect! + end + + # Use this to force the client to disconnect and not automatically + # reconnect. + def disconnect + disconnect! + end + + # Queue messages for delivery once a user has accepted our authorization + # request. Works in conjunction with the deferred delivery thread. + # + # You can use this method if you want to manually add friends and still + # have the message queued for later delivery. + def deliver_deferred(jid, message, type) + msg = {:to => jid, :message => message, :type => type} + queue(:pending_messages) << [msg] + end + + private + + def client=(client) + self.roster = nil # ensure we clear the roster, since that's now associated with a different client. + @client = client + end + + def roster=(new_roster) + @roster = new_roster + end + + def connect! + raise ConnectionError, "Connections are disabled - use Jabber::Simple::force_connect() to reconnect." if @disconnected + # Pre-connect + @connect_mutex ||= Mutex.new + @connect_mutex.lock + disconnect!(false) if connected? + + # Connect + jid = JID.new(@jid) + my_client = Client.new(@jid) + my_client.connect + my_client.auth(@password) + self.client = my_client + + # Post-connect + register_default_callbacks + status(@presence, @status_message) + @connect_mutex.unlock + end + + def disconnect!(auto_reconnect = true) + if client.respond_to?(:is_connected?) && client.is_connected? + client.close + end + client = nil + @disconnected = auto_reconnect + end + + def register_default_callbacks + client.add_message_callback do |message| + queue(:received_messages) << message unless message.body.nil? + end + + roster.add_subscription_callback do |roster_item, presence| + if presence.type == :subscribed + queue(:new_subscriptions) << [roster_item, presence] + end + end + + roster.add_subscription_request_callback do |roster_item, presence| + if accept_subscriptions? + roster.accept_subscription(presence.from) + else + queue(:subscription_requests) << [roster_item, presence] + end + end + + @presence_updates = {} + @presence_mutex = Mutex.new + roster.add_presence_callback do |roster_item, old_presence, new_presence| + simple_jid = roster_item.jid.strip.to_s + presence = case new_presence.type + when nil: new_presence.show || :online + when :unavailable: :unavailable + else + nil + end + + if presence && @presence_updates[simple_jid] != presence + queue(:presence_updates) << simple_jid + @presence_mutex.synchronize { @presence_updates[simple_jid] = [presence, new_presence.status] } + end + end + end + + # This thread facilitates the delivery of messages to users who haven't yet + # accepted an invitation from us. When we attempt to deliver a message, if + # the user hasn't subscribed, we place the message in a queue for later + # delivery. Once a user has accepted our authorization request, we deliver + # any messages that have been queued up in the meantime. + def start_deferred_delivery_thread #:nodoc: + Thread.new { + loop { + messages = [queue(:pending_messages).pop].flatten + messages.each do |message| + if subscribed_to?(message[:to]) + deliver(message[:to], message[:message], message[:type]) + else + queue(:pending_messages) << message + end + end + } + } + end + + def queue(queue) + @queues ||= Hash.new { |h,k| h[k] = Queue.new } + @queues[queue] + end + + def dequeue(queue, non_blocking = true, max_items = 100, &block) + queue_items = [] + max_items.times do + queue_item = queue(queue).pop(non_blocking) rescue nil + break if queue_item.nil? + queue_items << queue_item + yield queue_item if block_given? + end + queue_items + end + end +end + +true diff --git a/vendor/xmpp4r-simple-0.8.4/test/test_xmpp4r_simple.rb b/vendor/xmpp4r-simple-0.8.4/test/test_xmpp4r_simple.rb new file mode 100644 index 000000000..d8fd29ed3 --- /dev/null +++ b/vendor/xmpp4r-simple-0.8.4/test/test_xmpp4r_simple.rb @@ -0,0 +1,242 @@ +# Jabber::Simple - An extremely easy-to-use Jabber client library. +# Copyright 2006 Blaine Cook , Obvious Corp. +# +# Jabber::Simple is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# Jabber::Simple is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Jabber::Simple; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +# +$:.unshift "#{File.dirname(__FILE__)}/../lib" + +require 'test/unit' +require 'timeout' +require 'xmpp4r-simple' + +class JabberSimpleTest < Test::Unit::TestCase + + def setup + @@connections ||= {} + + if @@connections.include?(:client1) + @client1 = @@connections[:client1] + @client2 = @@connections[:client2] + @client1.accept_subscriptions = true + @client2.accept_subscriptions = true + @jid1_raw = @@connections[:jid1_raw] + @jid2_raw = @@connections[:jid2_raw] + @jid1 = @jid1_raw.strip.to_s + @jid2 = @jid2_raw.strip.to_s + return true + end + + logins = [] + begin + logins = File.readlines(File.expand_path("~/.xmpp4r-simple-test-config")).map! { |login| login.split(" ") } + raise StandardError unless logins.size == 2 + rescue => e + puts "\nConfiguration Error!\n\nYou must make available two unique Jabber accounts in order for the tests to pass." + puts "Place them in ~/.xmpp4r-simple-test-config, one per line like so:\n\n" + puts "user1@example.com/res password" + puts "user2@example.com/res password\n\n" + raise e + end + + @@connections[:client1] = Jabber::Simple.new(*logins[0]) + @@connections[:client2] = Jabber::Simple.new(*logins[1]) + + @@connections[:jid1_raw] = Jabber::JID.new(logins[0][0]) + @@connections[:jid2_raw] = Jabber::JID.new(logins[1][0]) + + # Force load the client and roster, just to be safe. + @@connections[:client1].roster + @@connections[:client2].roster + + # Re-run this method to setup the local instance variables the first time. + setup + end + + def test_ensure_the_jabber_clients_are_connected_after_setup + assert @client1.client.is_connected? + assert @client2.client.is_connected? + end + + def test_inspect_should_be_custom + assert_equal "Jabber::Simple #{@jid1_raw}", @client1.inspect + end + + def test_inspect_contact_should_be_custom + assert_equal "Jabber::Contact #{@jid2}", @client1.contacts(@jid2).inspect + end + + def test_remove_users_from_our_roster_should_succeed + @client2.remove(@jid1) + @client1.remove(@jid2) + + sleep 3 + + assert_equal false, @client1.subscribed_to?(@jid2) + assert_equal false, @client2.subscribed_to?(@jid1) + end + + def test_add_users_to_our_roster_should_succeed_with_automatic_approval + @client1.remove(@jid2) + @client2.remove(@jid1) + + assert_before 60 do + assert_equal false, @client1.subscribed_to?(@jid2) + assert_equal false, @client2.subscribed_to?(@jid1) + end + + @client1.new_subscriptions + @client1.add(@jid2) + + assert_before 60 do + assert @client1.subscribed_to?(@jid2) + assert @client2.subscribed_to?(@jid1) + end + + new_subscriptions = @client1.new_subscriptions + assert_equal 1, new_subscriptions.size + assert_equal @jid2, new_subscriptions[0][0].jid.strip.to_s + end + + def test_sent_message_should_be_received + # First clear the client's message queue, just in case. + assert_kind_of Array, @client2.received_messages + + # Next ensure that we're not subscribed, so that we can test the deferred message queue. + @client1.remove(@jid2) + @client2.remove(@jid1) + sleep 2 + + # Deliver the messages; this should be received by the other client. + @client1.deliver(@jid2, "test message") + + sleep 2 + + # Fetch the message; allow up to ten seconds for the delivery to occur. + messages = [] + begin + Timeout::timeout(20) { + loop do + messages = @client2.received_messages + break unless messages.empty? + sleep 1 + end + } + rescue Timeout::Error + flunk "Timeout waiting for message" + end + + # Ensure that the message was received intact. + assert_equal @jid1, messages.first.from.strip.to_s + assert_equal "test message", messages.first.body + end + + def test_presence_updates_should_be_received + + @client2.add(@client1) + @client1.add(@client2) + + assert_before(60) { assert @client2.subscribed_to?(@jid1) } + assert_before(60) { assert_equal 0, @client2.presence_updates.size } + + @client1.status(:away, "Doing something else.") + + new_statuses = [] + assert_before(60) do + new_statuses = @client2.presence_updates + assert_equal 1, new_statuses.size + end + + new_status = new_statuses.first + assert_equal @jid1, new_status[0] + assert_equal "Doing something else.", new_status[2] + assert_equal :away, new_status[1] + end + + def test_disable_auto_accept_subscription_requests + @client1.remove(@jid2) + @client2.remove(@jid1) + + assert_before(60) do + assert_equal false, @client1.subscribed_to?(@jid2) + assert_equal false, @client2.subscribed_to?(@jid1) + end + + @client1.accept_subscriptions = false + assert_equal false, @client1.accept_subscriptions? + + assert_before(60) { assert_equal 0, @client1.subscription_requests.size } + + @client2.add(@jid1) + + new_subscription_requests = [] + assert_before(60) do + new_subscription_requests = @client1.subscription_requests + assert_equal 1, new_subscription_requests.size + end + + new_subscription = new_subscription_requests.first + assert_equal @jid2, new_subscription[0].jid.strip.to_s + assert_equal :subscribe, new_subscription[1].type + end + + def test_automatically_reconnect + @client1.client.close + + assert_before 60 do + assert_equal false, @client1.connected? + end + + # empty client 2's received message queue. + @client2.received_messages + + @client1.deliver(@jid2, "Testing") + + assert_before(60) { assert @client1.connected? } + assert @client1.roster.instance_variable_get('@stream').is_connected? + assert_before(60) { assert_equal 1, @client2.received_messages.size } + end + + def test_disconnect_doesnt_allow_auto_reconnects + @client1.disconnect + + assert_equal false, @client1.connected? + + assert_raises Jabber::ConnectionError do + @client1.deliver(@jid2, "testing") + end + + @client1.reconnect + end + + private + + def assert_before(seconds, &block) + error = nil + begin + Timeout::timeout(seconds) { + begin + yield + rescue => e + error = e + sleep 0.5 + retry + end + } + rescue Timeout::Error + raise error + end + end + +end