aboutsummaryrefslogtreecommitdiff
path: root/src/client
diff options
context:
space:
mode:
authorJeremy Lainé <jeremy.laine@m4x.org>2012-02-08 12:33:41 +0000
committerJeremy Lainé <jeremy.laine@m4x.org>2012-02-08 12:33:41 +0000
commit21acd67e9b65bea87902032b12709675905aa922 (patch)
treeed5ae9066b10400c4fe6e67dfaf2f4c37a09c32e /src/client
parentcea7ae1e702b82d2d0d0a851de1aae58270b14f6 (diff)
downloadqxmpp-21acd67e9b65bea87902032b12709675905aa922.tar.gz
start moving client-specific code
Diffstat (limited to 'src/client')
-rw-r--r--src/client/QXmppArchiveManager.cpp117
-rw-r--r--src/client/QXmppArchiveManager.h75
-rw-r--r--src/client/QXmppBookmarkManager.cpp207
-rw-r--r--src/client/QXmppBookmarkManager.h71
-rw-r--r--src/client/QXmppCallManager.cpp1005
-rw-r--r--src/client/QXmppCallManager.h210
-rw-r--r--src/client/QXmppClient.cpp585
-rw-r--r--src/client/QXmppClient.h244
-rw-r--r--src/client/QXmppClientExtension.cpp83
-rw-r--r--src/client/QXmppClientExtension.h76
-rw-r--r--src/client/QXmppConfiguration.cpp485
-rw-r--r--src/client/QXmppConfiguration.h189
-rw-r--r--src/client/QXmppDiscoveryManager.cpp235
-rw-r--r--src/client/QXmppDiscoveryManager.h79
-rw-r--r--src/client/QXmppEntityTimeManager.cpp85
-rw-r--r--src/client/QXmppEntityTimeManager.h54
-rw-r--r--src/client/QXmppMessageReceiptManager.cpp72
-rw-r--r--src/client/QXmppMessageReceiptManager.h55
-rw-r--r--src/client/QXmppMucManager.cpp661
-rw-r--r--src/client/QXmppMucManager.h214
-rw-r--r--src/client/QXmppOutgoingClient.cpp773
-rw-r--r--src/client/QXmppOutgoingClient.h107
-rw-r--r--src/client/QXmppReconnectionManager.cpp101
-rw-r--r--src/client/QXmppReconnectionManager.h61
-rw-r--r--src/client/QXmppRemoteMethod.cpp75
-rw-r--r--src/client/QXmppRemoteMethod.h63
-rw-r--r--src/client/QXmppRosterManager.cpp421
-rw-r--r--src/client/QXmppRosterManager.h145
-rw-r--r--src/client/QXmppRpcManager.cpp180
-rw-r--r--src/client/QXmppRpcManager.h93
-rw-r--r--src/client/QXmppTransferManager.cpp1567
-rw-r--r--src/client/QXmppTransferManager.h293
-rw-r--r--src/client/QXmppVCardManager.cpp116
-rw-r--r--src/client/QXmppVCardManager.h88
-rw-r--r--src/client/QXmppVersionManager.cpp159
-rw-r--r--src/client/QXmppVersionManager.h67
36 files changed, 9111 insertions, 0 deletions
diff --git a/src/client/QXmppArchiveManager.cpp b/src/client/QXmppArchiveManager.cpp
new file mode 100644
index 00000000..75d3fbc7
--- /dev/null
+++ b/src/client/QXmppArchiveManager.cpp
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2008-2011 The QXmpp developers
+ *
+ * Author:
+ * Jeremy Lainé
+ *
+ * Source:
+ * http://code.google.com/p/qxmpp
+ *
+ * This file is a part of QXmpp library.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ */
+
+#include <QDomElement>
+
+#include "QXmppArchiveIq.h"
+#include "QXmppArchiveManager.h"
+#include "QXmppClient.h"
+
+bool QXmppArchiveManager::handleStanza(const QDomElement &element)
+{
+ if (element.tagName() != "iq")
+ return false;
+
+ // XEP-0136: Message Archiving
+ if(QXmppArchiveChatIq::isArchiveChatIq(element))
+ {
+ QXmppArchiveChatIq archiveIq;
+ archiveIq.parse(element);
+ emit archiveChatReceived(archiveIq.chat());
+ return true;
+ }
+ else if(QXmppArchiveListIq::isArchiveListIq(element))
+ {
+ QXmppArchiveListIq archiveIq;
+ archiveIq.parse(element);
+ emit archiveListReceived(archiveIq.chats());
+ return true;
+ }
+ else if(QXmppArchivePrefIq::isArchivePrefIq(element))
+ {
+ // TODO: handle preference iq
+ QXmppArchivePrefIq archiveIq;
+ archiveIq.parse(element);
+ return true;
+ }
+
+ return false;
+}
+
+/// Retrieves the list of available collections. Once the results are
+/// received, the archiveListReceived() signal will be emitted.
+///
+/// \param jid Optional JID if you only want conversations with a specific JID.
+/// \param start Optional start time.
+/// \param end Optional end time.
+/// \param max Optional maximum number of collections to list.
+///
+void QXmppArchiveManager::listCollections(const QString &jid, const QDateTime &start, const QDateTime &end, int max)
+{
+ QXmppArchiveListIq packet;
+ packet.setMax(max);
+ packet.setWith(jid);
+ packet.setStart(start);
+ packet.setEnd(end);
+ client()->sendPacket(packet);
+}
+
+/// Removes the specified collection(s).
+///
+/// \param jid The JID of the collection
+/// \param start Optional start time.
+/// \param end Optional end time.
+///
+void QXmppArchiveManager::removeCollections(const QString &jid, const QDateTime &start, const QDateTime &end)
+{
+ QXmppArchiveRemoveIq packet;
+ packet.setType(QXmppIq::Set);
+ packet.setWith(jid);
+ packet.setStart(start);
+ packet.setEnd(end);
+ client()->sendPacket(packet);
+}
+
+/// Retrieves the specified collection. Once the results are received,
+/// the archiveChatReceived() will be emitted.
+///
+/// \param jid The JID of the collection
+/// \param start The start time of the collection.
+/// \param max Optional maximum number of messages to retrieve.
+///
+void QXmppArchiveManager::retrieveCollection(const QString &jid, const QDateTime &start, int max)
+{
+ QXmppArchiveRetrieveIq packet;
+ packet.setMax(max);
+ packet.setStart(start);
+ packet.setWith(jid);
+ client()->sendPacket(packet);
+}
+
+#if 0
+void QXmppArchiveManager::getPreferences()
+{
+ QXmppArchivePrefIq packet;
+ client()->sendPacket(packet);
+}
+#endif
diff --git a/src/client/QXmppArchiveManager.h b/src/client/QXmppArchiveManager.h
new file mode 100644
index 00000000..aa1444df
--- /dev/null
+++ b/src/client/QXmppArchiveManager.h
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2008-2011 The QXmpp developers
+ *
+ * Author:
+ * Jeremy Lainé
+ *
+ * Source:
+ * http://code.google.com/p/qxmpp
+ *
+ * This file is a part of QXmpp library.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ */
+
+#ifndef QXMPPARCHIVEMANAGER_H
+#define QXMPPARCHIVEMANAGER_H
+
+#include <QDateTime>
+
+#include "QXmppClientExtension.h"
+
+class QXmppArchiveChat;
+class QXmppArchiveChatIq;
+class QXmppArchiveListIq;
+class QXmppArchivePrefIq;
+
+/// \brief The QXmppArchiveManager class makes it possible to access message
+/// archives as defined by XEP-0136: Message Archiving.
+///
+/// To make use of this manager, you need to instantiate it and load it into
+/// the QXmppClient instance as follows:
+///
+/// \code
+/// QXmppArchiveManager *manager = new QXmppArchiveManager;
+/// client->addExtension(manager);
+/// \endcode
+///
+/// \note Few servers support message archiving. Check if the server in use supports
+/// this XEP.
+///
+/// \ingroup Managers
+
+class QXmppArchiveManager : public QXmppClientExtension
+{
+ Q_OBJECT
+
+public:
+ void listCollections(const QString &jid, const QDateTime &start = QDateTime(), const QDateTime &end = QDateTime(), int max = 0);
+ void removeCollections(const QString &jid, const QDateTime &start = QDateTime(), const QDateTime &end = QDateTime());
+ void retrieveCollection(const QString &jid, const QDateTime &start, int max = 0);
+
+ /// \cond
+ bool handleStanza(const QDomElement &element);
+ /// \endcond
+
+signals:
+ /// This signal is emitted when archive list is received
+ /// after calling listCollections()
+ void archiveListReceived(const QList<QXmppArchiveChat>&);
+
+ /// This signal is emitted when archive chat is received
+ /// after calling retrieveCollection()
+ void archiveChatReceived(const QXmppArchiveChat&);
+};
+
+#endif
diff --git a/src/client/QXmppBookmarkManager.cpp b/src/client/QXmppBookmarkManager.cpp
new file mode 100644
index 00000000..367b9384
--- /dev/null
+++ b/src/client/QXmppBookmarkManager.cpp
@@ -0,0 +1,207 @@
+/*
+ * Copyright (C) 2008-2011 The QXmpp developers
+ *
+ * Author:
+ * Jeremy Lainé
+ *
+ * Source:
+ * http://code.google.com/p/qxmpp
+ *
+ * This file is a part of QXmpp library.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ */
+
+#include <QDomElement>
+
+#include "QXmppBookmarkManager.h"
+#include "QXmppBookmarkSet.h"
+#include "QXmppClient.h"
+#include "QXmppIq.h"
+#include "QXmppUtils.h"
+
+static const char * ns_private_storage = "jabber:iq:private";
+
+// The QXmppPrivateStorageIq class represents an XML private storage IQ
+// as defined by XEP-0049: Private XML Storage.
+//
+// FIXME: currently, we only handle bookmarks
+
+class QXmppPrivateStorageIq : public QXmppIq
+{
+public:
+ QXmppBookmarkSet bookmarks() const;
+ void setBookmarks(const QXmppBookmarkSet &bookmark);
+
+ static bool isPrivateStorageIq(const QDomElement &element);
+
+protected:
+ void parseElementFromChild(const QDomElement &element);
+ void toXmlElementFromChild(QXmlStreamWriter *writer) const;
+
+private:
+ QXmppBookmarkSet m_bookmarks;
+};
+
+QXmppBookmarkSet QXmppPrivateStorageIq::bookmarks() const
+{
+ return m_bookmarks;
+}
+
+void QXmppPrivateStorageIq::setBookmarks(const QXmppBookmarkSet &bookmarks)
+{
+ m_bookmarks = bookmarks;
+}
+
+bool QXmppPrivateStorageIq::isPrivateStorageIq(const QDomElement &element)
+{
+ const QDomElement queryElement = element.firstChildElement("query");
+ return queryElement.namespaceURI() == ns_private_storage &&
+ QXmppBookmarkSet::isBookmarkSet(queryElement.firstChildElement());
+}
+
+void QXmppPrivateStorageIq::parseElementFromChild(const QDomElement &element)
+{
+ const QDomElement queryElement = element.firstChildElement("query");
+ m_bookmarks.parse(queryElement.firstChildElement());
+}
+
+void QXmppPrivateStorageIq::toXmlElementFromChild(QXmlStreamWriter *writer) const
+{
+ writer->writeStartElement("query");
+ writer->writeAttribute("xmlns", ns_private_storage);
+ m_bookmarks.toXml(writer);
+ writer->writeEndElement();
+}
+
+class QXmppBookmarkManagerPrivate
+{
+public:
+ QXmppBookmarkSet bookmarks;
+ QXmppBookmarkSet pendingBookmarks;
+ QString pendingId;
+ bool bookmarksReceived;
+};
+
+/// Constructs a new bookmark manager.
+///
+QXmppBookmarkManager::QXmppBookmarkManager()
+ : d(new QXmppBookmarkManagerPrivate)
+{
+ d->bookmarksReceived = false;
+}
+
+/// Destroys a bookmark manager.
+///
+QXmppBookmarkManager::~QXmppBookmarkManager()
+{
+ delete d;
+}
+
+/// Returns true if the bookmarks have been received from the server,
+/// false otherwise.
+///
+bool QXmppBookmarkManager::areBookmarksReceived() const
+{
+ return d->bookmarksReceived;
+}
+
+/// Returns the bookmarks stored on the server.
+///
+/// Before calling this method, check that the bookmarks
+/// have indeed been received by calling areBookmarksReceived().
+///
+
+QXmppBookmarkSet QXmppBookmarkManager::bookmarks() const
+{
+ return d->bookmarks;
+}
+
+/// Stores the bookmarks on the server.
+///
+/// \param bookmarks
+
+bool QXmppBookmarkManager::setBookmarks(const QXmppBookmarkSet &bookmarks)
+{
+ QXmppPrivateStorageIq iq;
+ iq.setType(QXmppIq::Set);
+ iq.setBookmarks(bookmarks);
+ if (!client()->sendPacket(iq))
+ return false;
+
+ d->pendingBookmarks = bookmarks;
+ d->pendingId = iq.id();
+ return true;
+}
+
+void QXmppBookmarkManager::setClient(QXmppClient *client)
+{
+ bool check;
+ Q_UNUSED(check);
+
+ QXmppClientExtension::setClient(client);
+
+ check = connect(client, SIGNAL(connected()),
+ this, SLOT(slotConnected()));
+ Q_ASSERT(check);
+
+ check = connect(client, SIGNAL(disconnected()),
+ this, SLOT(slotDisconnected()));
+ Q_ASSERT(check);
+}
+
+bool QXmppBookmarkManager::handleStanza(const QDomElement &stanza)
+{
+ if (stanza.tagName() == "iq")
+ {
+ if (QXmppPrivateStorageIq::isPrivateStorageIq(stanza))
+ {
+ QXmppPrivateStorageIq iq;
+ iq.parse(stanza);
+
+ if (iq.type() == QXmppIq::Result)
+ {
+ d->bookmarks = iq.bookmarks();
+ d->bookmarksReceived = true;
+ emit bookmarksReceived(d->bookmarks);
+ }
+ return true;
+ }
+ else if (!d->pendingId.isEmpty() && stanza.attribute("id") == d->pendingId)
+ {
+ QXmppIq iq;
+ iq.parse(stanza);
+ if (iq.type() == QXmppIq::Result)
+ {
+ d->bookmarks = d->pendingBookmarks;
+ emit bookmarksReceived(d->bookmarks);
+ }
+ d->pendingId = QString();
+ return true;
+ }
+ }
+ return false;
+}
+
+void QXmppBookmarkManager::slotConnected()
+{
+ QXmppPrivateStorageIq iq;
+ iq.setType(QXmppIq::Get);
+ client()->sendPacket(iq);
+}
+
+void QXmppBookmarkManager::slotDisconnected()
+{
+ d->bookmarks = QXmppBookmarkSet();
+ d->bookmarksReceived = false;
+}
+
diff --git a/src/client/QXmppBookmarkManager.h b/src/client/QXmppBookmarkManager.h
new file mode 100644
index 00000000..40c4be0b
--- /dev/null
+++ b/src/client/QXmppBookmarkManager.h
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2008-2011 The QXmpp developers
+ *
+ * Author:
+ * Jeremy Lainé
+ *
+ * Source:
+ * http://code.google.com/p/qxmpp
+ *
+ * This file is a part of QXmpp library.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ */
+
+#ifndef QXMPPBOOKMARKMANAGER_H
+#define QXMPPBOOKMARKMANAGER_H
+
+#include <QUrl>
+
+#include "QXmppClientExtension.h"
+
+class QXmppBookmarkManagerPrivate;
+class QXmppBookmarkSet;
+
+/// \brief The QXmppBookmarkManager class allows you to store and retrieve
+/// bookmarks as defined by XEP-0048: Bookmarks.
+///
+
+class QXmppBookmarkManager : public QXmppClientExtension
+{
+ Q_OBJECT
+
+public:
+ QXmppBookmarkManager();
+ ~QXmppBookmarkManager();
+
+ bool areBookmarksReceived() const;
+ QXmppBookmarkSet bookmarks() const;
+ bool setBookmarks(const QXmppBookmarkSet &bookmarks);
+
+ /// \cond
+ bool handleStanza(const QDomElement &stanza);
+ /// \endcond
+
+signals:
+ /// This signal is emitted when bookmarks are received.
+ void bookmarksReceived(const QXmppBookmarkSet &bookmarks);
+
+protected:
+ /// \cond
+ void setClient(QXmppClient* client);
+ /// \endcond
+
+private slots:
+ void slotConnected();
+ void slotDisconnected();
+
+private:
+ QXmppBookmarkManagerPrivate * const d;
+};
+
+#endif
diff --git a/src/client/QXmppCallManager.cpp b/src/client/QXmppCallManager.cpp
new file mode 100644
index 00000000..363881af
--- /dev/null
+++ b/src/client/QXmppCallManager.cpp
@@ -0,0 +1,1005 @@
+/*
+ * Copyright (C) 2008-2011 The QXmpp developers
+ *
+ * Author:
+ * Jeremy Lainé
+ *
+ * Source:
+ * http://code.google.com/p/qxmpp
+ *
+ * This file is a part of QXmpp library.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ */
+
+#include <QDomElement>
+#include <QTimer>
+
+#include "QXmppCallManager.h"
+#include "QXmppClient.h"
+#include "QXmppCodec.h"
+#include "QXmppConstants.h"
+#include "QXmppJingleIq.h"
+#include "QXmppRtpChannel.h"
+#include "QXmppStun.h"
+#include "QXmppUtils.h"
+
+static int typeId = qRegisterMetaType<QXmppCall::State>();
+
+static const int RTP_COMPONENT = 1;
+static const int RTCP_COMPONENT = 2;
+
+static const QLatin1String AUDIO_MEDIA("audio");
+static const QLatin1String VIDEO_MEDIA("video");
+
+class QXmppCallPrivate
+{
+public:
+ class Stream {
+ public:
+ QXmppRtpChannel *channel;
+ QXmppIceConnection *connection;
+ QString creator;
+ QString media;
+ QString name;
+ };
+
+ QXmppCallPrivate(QXmppCall *qq);
+ Stream *createStream(const QString &media);
+ Stream *findStreamByMedia(const QString &media);
+ Stream *findStreamByName(const QString &name);
+ void handleAck(const QXmppIq &iq);
+ bool handleDescription(QXmppCallPrivate::Stream *stream, const QXmppJingleIq::Content &content);
+ void handleRequest(const QXmppJingleIq &iq);
+ bool handleTransport(QXmppCallPrivate::Stream *stream, const QXmppJingleIq::Content &content);
+ void setState(QXmppCall::State state);
+ bool sendAck(const QXmppJingleIq &iq);
+ bool sendInvite();
+ bool sendRequest(const QXmppJingleIq &iq);
+ void terminate(QXmppJingleIq::Reason::Type reasonType);
+
+ QXmppCall::Direction direction;
+ QString jid;
+ QString ownJid;
+ QXmppCallManager *manager;
+ QList<QXmppJingleIq> requests;
+ QString sid;
+ QXmppCall::State state;
+
+ // Media streams
+ bool sendVideo;
+ QList<Stream*> streams;
+ QIODevice::OpenMode audioMode;
+ QIODevice::OpenMode videoMode;
+
+private:
+ QXmppCall *q;
+};
+
+class QXmppCallManagerPrivate
+{
+public:
+ QXmppCallManagerPrivate(QXmppCallManager *qq);
+ QXmppCall *findCall(const QString &sid) const;
+ QXmppCall *findCall(const QString &sid, QXmppCall::Direction direction) const;
+
+ QList<QXmppCall*> calls;
+ QHostAddress stunHost;
+ quint16 stunPort;
+ QHostAddress turnHost;
+ quint16 turnPort;
+ QString turnUser;
+ QString turnPassword;
+
+private:
+ QXmppCallManager *q;
+};
+
+QXmppCallPrivate::QXmppCallPrivate(QXmppCall *qq)
+ : state(QXmppCall::ConnectingState),
+ sendVideo(false),
+ audioMode(QIODevice::NotOpen),
+ videoMode(QIODevice::NotOpen),
+ q(qq)
+{
+}
+
+QXmppCallPrivate::Stream *QXmppCallPrivate::findStreamByMedia(const QString &media)
+{
+ foreach (Stream *stream, streams)
+ if (stream->media == media)
+ return stream;
+ return 0;
+}
+
+QXmppCallPrivate::Stream *QXmppCallPrivate::findStreamByName(const QString &name)
+{
+ foreach (Stream *stream, streams)
+ if (stream->name == name)
+ return stream;
+ return 0;
+}
+
+void QXmppCallPrivate::handleAck(const QXmppIq &ack)
+{
+ const QString id = ack.id();
+ for (int i = 0; i < requests.size(); ++i) {
+ if (id == requests[i].id()) {
+ // process acknowledgement
+ const QXmppJingleIq request = requests.takeAt(i);
+ q->debug(QString("Received ACK for packet %1").arg(id));
+
+ // handle termination
+ if (request.action() == QXmppJingleIq::SessionTerminate)
+ q->terminated();
+ return;
+ }
+ }
+}
+
+bool QXmppCallPrivate::handleDescription(QXmppCallPrivate::Stream *stream, const QXmppJingleIq::Content &content)
+{
+ stream->channel->setRemotePayloadTypes(content.payloadTypes());
+ if (!(stream->channel->openMode() & QIODevice::ReadWrite)) {
+ q->warning(QString("Remote party %1 did not provide any known %2 payloads for call %3").arg(jid, stream->media, sid));
+ return false;
+ }
+ q->updateOpenMode();
+ return true;
+}
+
+bool QXmppCallPrivate::handleTransport(QXmppCallPrivate::Stream *stream, const QXmppJingleIq::Content &content)
+{
+ stream->connection->setRemoteUser(content.transportUser());
+ stream->connection->setRemotePassword(content.transportPassword());
+ foreach (const QXmppJingleCandidate &candidate, content.transportCandidates())
+ stream->connection->addRemoteCandidate(candidate);
+
+ // perform ICE negotiation
+ if (!content.transportCandidates().isEmpty())
+ stream->connection->connectToHost();
+ return true;
+}
+
+void QXmppCallPrivate::handleRequest(const QXmppJingleIq &iq)
+{
+ if (iq.action() == QXmppJingleIq::SessionAccept) {
+
+ if (direction == QXmppCall::IncomingDirection) {
+ q->warning("Ignoring Session-Accept for an incoming call");
+ return;
+ }
+
+ // send ack
+ sendAck(iq);
+
+ // check content description and transport
+ QXmppCallPrivate::Stream *stream = findStreamByName(iq.content().name());
+ if (!stream ||
+ !handleDescription(stream, iq.content()) ||
+ !handleTransport(stream, iq.content())) {
+
+ // terminate call
+ terminate(QXmppJingleIq::Reason::FailedApplication);
+ return;
+ }
+
+ // check for call establishment
+ setState(QXmppCall::ActiveState);
+
+ } else if (iq.action() == QXmppJingleIq::SessionInfo) {
+
+ // notify user
+ QTimer::singleShot(0, q, SIGNAL(ringing()));
+
+ } else if (iq.action() == QXmppJingleIq::SessionTerminate) {
+
+ // send ack
+ sendAck(iq);
+
+ // terminate
+ q->info(QString("Remote party %1 terminated call %2").arg(iq.from(), iq.sid()));
+ q->terminated();
+
+ } else if (iq.action() == QXmppJingleIq::ContentAccept) {
+
+ // send ack
+ sendAck(iq);
+
+ // check content description and transport
+ QXmppCallPrivate::Stream *stream = findStreamByName(iq.content().name());
+ if (!stream ||
+ !handleDescription(stream, iq.content()) ||
+ !handleTransport(stream, iq.content())) {
+
+ // FIXME: what action?
+ return;
+ }
+
+ } else if (iq.action() == QXmppJingleIq::ContentAdd) {
+
+ // send ack
+ sendAck(iq);
+
+ // check media stream does not exist yet
+ QXmppCallPrivate::Stream *stream = findStreamByName(iq.content().name());
+ if (stream)
+ return;
+
+ // create media stream
+ stream = createStream(iq.content().descriptionMedia());
+ if (!stream)
+ return;
+ stream->creator = iq.content().creator();
+ stream->name = iq.content().name();
+
+ // check content description
+ if (!handleDescription(stream, iq.content()) ||
+ !handleTransport(stream, iq.content())) {
+
+ QXmppJingleIq iq;
+ iq.setTo(q->jid());
+ iq.setType(QXmppIq::Set);
+ iq.setAction(QXmppJingleIq::ContentReject);
+ iq.setSid(q->sid());
+ iq.reason().setType(QXmppJingleIq::Reason::FailedApplication);
+ sendRequest(iq);
+ delete stream;
+ return;
+ }
+ streams << stream;
+
+ // accept content
+ QXmppJingleIq iq;
+ iq.setTo(q->jid());
+ iq.setType(QXmppIq::Set);
+ iq.setAction(QXmppJingleIq::ContentAccept);
+ iq.setSid(q->sid());
+ iq.content().setCreator(stream->creator);
+ iq.content().setName(stream->name);
+
+ // description
+ iq.content().setDescriptionMedia(stream->media);
+ foreach (const QXmppJinglePayloadType &payload, stream->channel->localPayloadTypes())
+ iq.content().addPayloadType(payload);
+
+ // transport
+ iq.content().setTransportUser(stream->connection->localUser());
+ iq.content().setTransportPassword(stream->connection->localPassword());
+ foreach (const QXmppJingleCandidate &candidate, stream->connection->localCandidates())
+ iq.content().addTransportCandidate(candidate);
+
+ sendRequest(iq);
+
+ } else if (iq.action() == QXmppJingleIq::TransportInfo) {
+
+ // send ack
+ sendAck(iq);
+
+ // check content transport
+ QXmppCallPrivate::Stream *stream = findStreamByName(iq.content().name());
+ if (!stream ||
+ !handleTransport(stream, iq.content())) {
+ // FIXME: what action?
+ return;
+ }
+
+ }
+}
+
+QXmppCallPrivate::Stream *QXmppCallPrivate::createStream(const QString &media)
+{
+ bool check;
+ Q_UNUSED(check);
+ Q_ASSERT(manager);
+
+ Stream *stream = new Stream;
+ stream->media = media;
+
+ // RTP channel
+ QObject *channelObject = 0;
+ if (media == AUDIO_MEDIA) {
+ QXmppRtpAudioChannel *audioChannel = new QXmppRtpAudioChannel(q);
+ stream->channel = audioChannel;
+ channelObject = audioChannel;
+ } else if (media == VIDEO_MEDIA) {
+ QXmppRtpVideoChannel *videoChannel = new QXmppRtpVideoChannel(q);
+ stream->channel = videoChannel;
+ channelObject = videoChannel;
+ } else {
+ q->warning(QString("Unsupported media type %1").arg(media));
+ delete stream;
+ return 0;
+ }
+
+ // ICE connection
+ stream->connection = new QXmppIceConnection(q);
+ stream->connection->setIceControlling(direction == QXmppCall::OutgoingDirection);
+ stream->connection->setStunServer(manager->d->stunHost, manager->d->stunPort);
+ stream->connection->setTurnServer(manager->d->turnHost, manager->d->turnPort);
+ stream->connection->setTurnUser(manager->d->turnUser);
+ stream->connection->setTurnPassword(manager->d->turnPassword);
+ stream->connection->addComponent(RTP_COMPONENT);
+ stream->connection->addComponent(RTCP_COMPONENT);
+ stream->connection->bind(QXmppIceComponent::discoverAddresses());
+
+ // connect signals
+ check = QObject::connect(stream->connection, SIGNAL(localCandidatesChanged()),
+ q, SLOT(localCandidatesChanged()));
+ Q_ASSERT(check);
+
+ check = QObject::connect(stream->connection, SIGNAL(connected()),
+ q, SLOT(updateOpenMode()));
+ Q_ASSERT(check);
+
+ check = QObject::connect(q, SIGNAL(stateChanged(QXmppCall::State)),
+ q, SLOT(updateOpenMode()));
+ Q_ASSERT(check);
+
+ check = QObject::connect(stream->connection, SIGNAL(disconnected()),
+ q, SLOT(hangup()));
+ Q_ASSERT(check);
+
+ if (channelObject) {
+ QXmppIceComponent *rtpComponent = stream->connection->component(RTP_COMPONENT);
+
+ check = QObject::connect(rtpComponent, SIGNAL(datagramReceived(QByteArray)),
+ channelObject, SLOT(datagramReceived(QByteArray)));
+ Q_ASSERT(check);
+
+ check = QObject::connect(channelObject, SIGNAL(sendDatagram(QByteArray)),
+ rtpComponent, SLOT(sendDatagram(QByteArray)));
+ Q_ASSERT(check);
+ }
+ return stream;
+}
+
+/// Sends an acknowledgement for a Jingle IQ.
+///
+
+bool QXmppCallPrivate::sendAck(const QXmppJingleIq &iq)
+{
+ QXmppIq ack;
+ ack.setId(iq.id());
+ ack.setTo(iq.from());
+ ack.setType(QXmppIq::Result);
+ return manager->client()->sendPacket(ack);
+}
+
+bool QXmppCallPrivate::sendInvite()
+{
+ QXmppJingleIq iq;
+ iq.setTo(jid);
+ iq.setType(QXmppIq::Set);
+ iq.setAction(QXmppJingleIq::SessionInitiate);
+ iq.setInitiator(ownJid);
+ iq.setSid(sid);
+
+ // create audio stream
+ QXmppCallPrivate::Stream *stream = findStreamByMedia(AUDIO_MEDIA);
+ Q_ASSERT(stream);
+ iq.content().setCreator(stream->creator);
+ iq.content().setName(stream->name);
+ iq.content().setSenders("both");
+
+ // description
+ iq.content().setDescriptionMedia(stream->media);
+ foreach (const QXmppJinglePayloadType &payload, stream->channel->localPayloadTypes())
+ iq.content().addPayloadType(payload);
+
+ // transport
+ iq.content().setTransportUser(stream->connection->localUser());
+ iq.content().setTransportPassword(stream->connection->localPassword());
+ foreach (const QXmppJingleCandidate &candidate, stream->connection->localCandidates())
+ iq.content().addTransportCandidate(candidate);
+
+ return sendRequest(iq);
+}
+
+/// Sends a Jingle IQ and adds it to outstanding requests.
+///
+
+bool QXmppCallPrivate::sendRequest(const QXmppJingleIq &iq)
+{
+ requests << iq;
+ return manager->client()->sendPacket(iq);
+}
+
+void QXmppCallPrivate::setState(QXmppCall::State newState)
+{
+ if (state != newState)
+ {
+ state = newState;
+ emit q->stateChanged(state);
+
+ if (state == QXmppCall::ActiveState)
+ emit q->connected();
+ else if (state == QXmppCall::FinishedState)
+ emit q->finished();
+ }
+}
+
+/// Request graceful call termination
+
+void QXmppCallPrivate::terminate(QXmppJingleIq::Reason::Type reasonType)
+{
+ if (state == QXmppCall::DisconnectingState ||
+ state == QXmppCall::FinishedState)
+ return;
+
+ // hangup call
+ QXmppJingleIq iq;
+ iq.setTo(jid);
+ iq.setType(QXmppIq::Set);
+ iq.setAction(QXmppJingleIq::SessionTerminate);
+ iq.setSid(sid);
+ iq.reason().setType(reasonType);
+ sendRequest(iq);
+ setState(QXmppCall::DisconnectingState);
+
+ // schedule forceful termination in 5s
+ QTimer::singleShot(5000, q, SLOT(terminated()));
+}
+
+QXmppCall::QXmppCall(const QString &jid, QXmppCall::Direction direction, QXmppCallManager *parent)
+ : QXmppLoggable(parent)
+{
+ d = new QXmppCallPrivate(this);
+ d->direction = direction;
+ d->jid = jid;
+ d->ownJid = parent->client()->configuration().jid();
+ d->manager = parent;
+
+ // create audio stream
+ QXmppCallPrivate::Stream *stream = d->createStream(AUDIO_MEDIA);
+ stream->creator = QLatin1String("initiator");
+ stream->name = QLatin1String("voice");
+ d->streams << stream;
+}
+
+QXmppCall::~QXmppCall()
+{
+ foreach (QXmppCallPrivate::Stream *stream, d->streams)
+ delete stream;
+ delete d;
+}
+
+/// Call this method if you wish to accept an incoming call.
+///
+
+void QXmppCall::accept()
+{
+ if (d->direction == IncomingDirection && d->state == ConnectingState)
+ {
+ Q_ASSERT(d->streams.size() == 1);
+ QXmppCallPrivate::Stream *stream = d->streams.first();
+
+ // accept incoming call
+ QXmppJingleIq iq;
+ iq.setTo(d->jid);
+ iq.setType(QXmppIq::Set);
+ iq.setAction(QXmppJingleIq::SessionAccept);
+ iq.setResponder(d->ownJid);
+ iq.setSid(d->sid);
+ iq.content().setCreator(stream->creator);
+ iq.content().setName(stream->name);
+
+ // description
+ iq.content().setDescriptionMedia(stream->media);
+ foreach (const QXmppJinglePayloadType &payload, stream->channel->localPayloadTypes())
+ iq.content().addPayloadType(payload);
+
+ // transport
+ iq.content().setTransportUser(stream->connection->localUser());
+ iq.content().setTransportPassword(stream->connection->localPassword());
+ foreach (const QXmppJingleCandidate &candidate, stream->connection->localCandidates())
+ iq.content().addTransportCandidate(candidate);
+
+ d->sendRequest(iq);
+
+ // notify user
+ d->manager->callStarted(this);
+
+ // check for call establishment
+ d->setState(QXmppCall::ActiveState);
+ }
+}
+
+/// Returns the RTP channel for the audio data.
+///
+/// It acts as a QIODevice so that you can read / write audio samples, for
+/// instance using a QAudioOutput and a QAudioInput.
+///
+
+QXmppRtpAudioChannel *QXmppCall::audioChannel() const
+{
+ QXmppCallPrivate::Stream *stream = d->findStreamByMedia(AUDIO_MEDIA);
+ if (stream)
+ return (QXmppRtpAudioChannel*)stream->channel;
+ else
+ return 0;
+}
+
+/// Returns the audio mode.
+
+QIODevice::OpenMode QXmppCall::audioMode() const
+{
+ return d->audioMode;
+}
+
+/// Returns the RTP channel for the video data.
+///
+
+QXmppRtpVideoChannel *QXmppCall::videoChannel() const
+{
+ QXmppCallPrivate::Stream *stream = d->findStreamByMedia(VIDEO_MEDIA);
+ if (stream)
+ return (QXmppRtpVideoChannel*)stream->channel;
+ else
+ return 0;
+}
+
+/// Returns the video mode.
+
+QIODevice::OpenMode QXmppCall::videoMode() const
+{
+ return d->videoMode;
+}
+
+void QXmppCall::terminated()
+{
+ // close streams
+ foreach (QXmppCallPrivate::Stream *stream, d->streams) {
+ stream->channel->close();
+ stream->connection->close();
+ }
+
+ // update state
+ d->setState(QXmppCall::FinishedState);
+}
+
+/// Returns the call's direction.
+///
+
+QXmppCall::Direction QXmppCall::direction() const
+{
+ return d->direction;
+}
+
+/// Hangs up the call.
+///
+
+void QXmppCall::hangup()
+{
+ d->terminate(QXmppJingleIq::Reason::None);
+}
+
+/// Sends a transport-info to inform the remote party of new local candidates.
+///
+
+void QXmppCall::localCandidatesChanged()
+{
+ // find the stream
+ QXmppIceConnection *conn = qobject_cast<QXmppIceConnection*>(sender());
+ QXmppCallPrivate::Stream *stream = 0;
+ foreach (QXmppCallPrivate::Stream *ptr, d->streams) {
+ if (ptr->connection == conn) {
+ stream = ptr;
+ break;
+ }
+ }
+ if (!stream)
+ return;
+
+ QXmppJingleIq iq;
+ iq.setTo(d->jid);
+ iq.setType(QXmppIq::Set);
+ iq.setAction(QXmppJingleIq::TransportInfo);
+ iq.setSid(d->sid);
+
+ iq.content().setCreator(stream->creator);
+ iq.content().setName(stream->name);
+
+ // transport
+ iq.content().setTransportUser(stream->connection->localUser());
+ iq.content().setTransportPassword(stream->connection->localPassword());
+ foreach (const QXmppJingleCandidate &candidate, stream->connection->localCandidates())
+ iq.content().addTransportCandidate(candidate);
+
+ d->sendRequest(iq);
+}
+
+/// Returns the remote party's JID.
+///
+
+QString QXmppCall::jid() const
+{
+ return d->jid;
+}
+
+void QXmppCall::updateOpenMode()
+{
+ QXmppCallPrivate::Stream *stream;
+ QIODevice::OpenMode mode;
+
+ // determine audio mode
+ mode = QIODevice::NotOpen;
+ stream = d->findStreamByMedia(AUDIO_MEDIA);
+ if (d->state == QXmppCall::ActiveState && stream && stream->connection->isConnected())
+ mode = stream->channel->openMode() & QIODevice::ReadWrite;
+ if (mode != d->audioMode) {
+ d->audioMode = mode;
+ emit audioModeChanged(mode);
+ }
+
+ // determine video mode
+ mode = QIODevice::NotOpen;
+ stream = d->findStreamByMedia(VIDEO_MEDIA);
+ if (d->state == QXmppCall::ActiveState && stream && stream->connection->isConnected()) {
+ mode |= (stream->channel->openMode() & QIODevice::ReadOnly);
+ if (d->sendVideo)
+ mode |= (stream->channel->openMode() & QIODevice::WriteOnly);
+ }
+ if (mode != d->videoMode) {
+ d->videoMode = mode;
+ emit videoModeChanged(mode);
+ }
+}
+
+/// Returns the call's session identifier.
+///
+
+QString QXmppCall::sid() const
+{
+ return d->sid;
+}
+
+/// Returns the call's state.
+///
+/// \sa stateChanged()
+
+QXmppCall::State QXmppCall::state() const
+{
+ return d->state;
+}
+
+/// Starts sending video to the remote party.
+
+void QXmppCall::startVideo()
+{
+ if (d->state != QXmppCall::ActiveState) {
+ warning("Cannot start video, call is not active");
+ return;
+ }
+
+ d->sendVideo = true;
+ QXmppCallPrivate::Stream *stream = d->findStreamByMedia(VIDEO_MEDIA);
+ if (stream) {
+ updateOpenMode();
+ return;
+ }
+
+ // create video stream
+ stream = d->createStream(VIDEO_MEDIA);
+ stream->creator = (d->direction == QXmppCall::OutgoingDirection) ? QLatin1String("initiator") : QLatin1String("responder");
+ stream->name = QLatin1String("webcam");
+ d->streams << stream;
+
+ // build request
+ QXmppJingleIq iq;
+ iq.setTo(d->jid);
+ iq.setType(QXmppIq::Set);
+ iq.setAction(QXmppJingleIq::ContentAdd);
+ iq.setSid(d->sid);
+ iq.content().setCreator(stream->creator);
+ iq.content().setName(stream->name);
+ iq.content().setSenders("both");
+
+ // description
+ iq.content().setDescriptionMedia(stream->media);
+ foreach (const QXmppJinglePayloadType &payload, stream->channel->localPayloadTypes())
+ iq.content().addPayloadType(payload);
+
+ // transport
+ iq.content().setTransportUser(stream->connection->localUser());
+ iq.content().setTransportPassword(stream->connection->localPassword());
+ foreach (const QXmppJingleCandidate &candidate, stream->connection->localCandidates())
+ iq.content().addTransportCandidate(candidate);
+
+ d->sendRequest(iq);
+}
+
+/// Stops sending video to the remote party.
+
+void QXmppCall::stopVideo()
+{
+ if (!d->sendVideo)
+ return;
+
+ d->sendVideo = false;
+ QXmppCallPrivate::Stream *stream = d->findStreamByMedia(VIDEO_MEDIA);
+ if (stream)
+ updateOpenMode();
+}
+
+QXmppCallManagerPrivate::QXmppCallManagerPrivate(QXmppCallManager *qq)
+ : stunPort(0),
+ turnPort(0),
+ q(qq)
+{
+}
+
+QXmppCall *QXmppCallManagerPrivate::findCall(const QString &sid) const
+{
+ foreach (QXmppCall *call, calls)
+ if (call->sid() == sid)
+ return call;
+ return 0;
+}
+
+QXmppCall *QXmppCallManagerPrivate::findCall(const QString &sid, QXmppCall::Direction direction) const
+{
+ foreach (QXmppCall *call, calls)
+ if (call->sid() == sid && call->direction() == direction)
+ return call;
+ return 0;
+}
+
+/// Constructs a QXmppCallManager object to handle incoming and outgoing
+/// Voice-Over-IP calls.
+///
+
+QXmppCallManager::QXmppCallManager()
+{
+ d = new QXmppCallManagerPrivate(this);
+}
+
+/// Destroys the QXmppCallManager object.
+
+QXmppCallManager::~QXmppCallManager()
+{
+ delete d;
+}
+
+QStringList QXmppCallManager::discoveryFeatures() const
+{
+ return QStringList()
+ << ns_jingle // XEP-0166 : Jingle
+ << ns_jingle_rtp // XEP-0167 : Jingle RTP Sessions
+ << ns_jingle_rtp_audio
+ << ns_jingle_rtp_video
+ << ns_jingle_ice_udp; // XEP-0176 : Jingle ICE-UDP Transport Method
+}
+
+bool QXmppCallManager::handleStanza(const QDomElement &element)
+{
+ if(element.tagName() == "iq")
+ {
+ // XEP-0166: Jingle
+ if (QXmppJingleIq::isJingleIq(element))
+ {
+ QXmppJingleIq jingleIq;
+ jingleIq.parse(element);
+ _q_jingleIqReceived(jingleIq);
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void QXmppCallManager::setClient(QXmppClient *client)
+{
+ bool check;
+ Q_UNUSED(check);
+
+ QXmppClientExtension::setClient(client);
+
+ check = connect(client, SIGNAL(disconnected()),
+ this, SLOT(_q_disconnected()));
+ Q_ASSERT(check);
+
+ check = connect(client, SIGNAL(iqReceived(QXmppIq)),
+ this, SLOT(_q_iqReceived(QXmppIq)));
+ Q_ASSERT(check);
+
+ check = connect(client, SIGNAL(presenceReceived(QXmppPresence)),
+ this, SLOT(_q_presenceReceived(QXmppPresence)));
+ Q_ASSERT(check);
+}
+
+/// Initiates a new outgoing call to the specified recipient.
+///
+/// \param jid
+
+QXmppCall *QXmppCallManager::call(const QString &jid)
+{
+ bool check;
+ Q_UNUSED(check);
+
+ if (jid.isEmpty()) {
+ warning("Refusing to call an empty jid");
+ return 0;
+ }
+
+ if (jid == client()->configuration().jid()) {
+ warning("Refusing to call self");
+ return 0;
+ }
+
+ QXmppCall *call = new QXmppCall(jid, QXmppCall::OutgoingDirection, this);
+ call->d->sid = generateStanzaHash();
+
+ // register call
+ d->calls << call;
+ check = connect(call, SIGNAL(destroyed(QObject*)),
+ this, SLOT(_q_callDestroyed(QObject*)));
+ Q_ASSERT(check);
+ emit callStarted(call);
+
+ call->d->sendInvite();
+
+ return call;
+}
+
+/// Sets the STUN server to use to determine server-reflexive addresses
+/// and ports.
+///
+/// \param host The address of the STUN server.
+/// \param port The port of the STUN server.
+
+void QXmppCallManager::setStunServer(const QHostAddress &host, quint16 port)
+{
+ d->stunHost = host;
+ d->stunPort = port;
+}
+
+/// Sets the TURN server to use to relay packets in double-NAT configurations.
+///
+/// \param host The address of the TURN server.
+/// \param port The port of the TURN server.
+
+void QXmppCallManager::setTurnServer(const QHostAddress &host, quint16 port)
+{
+ d->turnHost = host;
+ d->turnPort = port;
+}
+
+/// Sets the \a user used for authentication with the TURN server.
+///
+/// \param user
+
+void QXmppCallManager::setTurnUser(const QString &user)
+{
+ d->turnUser = user;
+}
+
+/// Sets the \a password used for authentication with the TURN server.
+///
+/// \param password
+
+void QXmppCallManager::setTurnPassword(const QString &password)
+{
+ d->turnPassword = password;
+}
+
+/// Handles call destruction.
+
+void QXmppCallManager::_q_callDestroyed(QObject *object)
+{
+ d->calls.removeAll(static_cast<QXmppCall*>(object));
+}
+
+/// Handles disconnection from server.
+
+void QXmppCallManager::_q_disconnected()
+{
+ foreach (QXmppCall *call, d->calls)
+ call->d->terminate(QXmppJingleIq::Reason::Gone);
+}
+
+/// Handles acknowledgements.
+///
+
+void QXmppCallManager::_q_iqReceived(const QXmppIq &ack)
+{
+ if (ack.type() != QXmppIq::Result)
+ return;
+
+ // find request
+ foreach (QXmppCall *call, d->calls)
+ call->d->handleAck(ack);
+}
+
+/// Handles a Jingle IQ.
+///
+
+void QXmppCallManager::_q_jingleIqReceived(const QXmppJingleIq &iq)
+{
+ bool check;
+ Q_UNUSED(check);
+
+ if (iq.type() != QXmppIq::Set)
+ return;
+
+ if (iq.action() == QXmppJingleIq::SessionInitiate)
+ {
+ // build call
+ QXmppCall *call = new QXmppCall(iq.from(), QXmppCall::IncomingDirection, this);
+ call->d->sid = iq.sid();
+
+ QXmppCallPrivate::Stream *stream = call->d->findStreamByMedia(iq.content().descriptionMedia());
+ if (!stream)
+ return;
+ stream->creator = iq.content().creator();
+ stream->name = iq.content().name();
+
+ // send ack
+ call->d->sendAck(iq);
+
+ // check content description and transport
+ if (!call->d->handleDescription(stream, iq.content()) ||
+ !call->d->handleTransport(stream, iq.content())) {
+
+ // terminate call
+ call->d->terminate(QXmppJingleIq::Reason::FailedApplication);
+ call->terminated();
+ delete call;
+ return;
+ }
+
+ // register call
+ d->calls << call;
+ check = connect(call, SIGNAL(destroyed(QObject*)),
+ this, SLOT(_q_callDestroyed(QObject*)));
+ Q_ASSERT(check);
+
+ // send ringing indication
+ QXmppJingleIq ringing;
+ ringing.setTo(call->jid());
+ ringing.setType(QXmppIq::Set);
+ ringing.setAction(QXmppJingleIq::SessionInfo);
+ ringing.setSid(call->sid());
+ ringing.setRinging(true);
+ call->d->sendRequest(ringing);
+
+ // notify user
+ emit callReceived(call);
+ return;
+
+ } else {
+
+ // for all other requests, require a valid call
+ QXmppCall *call = d->findCall(iq.sid());
+ if (!call) {
+ warning(QString("Remote party %1 sent a request for an unknown call %2").arg(iq.from(), iq.sid()));
+ return;
+ }
+ call->d->handleRequest(iq);
+ }
+}
+
+/// Handles a presence.
+
+void QXmppCallManager::_q_presenceReceived(const QXmppPresence &presence)
+{
+ if (presence.type() != QXmppPresence::Unavailable)
+ return;
+
+ foreach (QXmppCall *call, d->calls) {
+ if (presence.from() == call->jid()) {
+ // the remote party has gone away, terminate call
+ call->d->terminate(QXmppJingleIq::Reason::Gone);
+ }
+ }
+}
+
diff --git a/src/client/QXmppCallManager.h b/src/client/QXmppCallManager.h
new file mode 100644
index 00000000..cb309592
--- /dev/null
+++ b/src/client/QXmppCallManager.h
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2008-2011 The QXmpp developers
+ *
+ * Author:
+ * Jeremy Lainé
+ *
+ * Source:
+ * http://code.google.com/p/qxmpp
+ *
+ * This file is a part of QXmpp library.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ */
+
+#ifndef QXMPPCALLMANAGER_H
+#define QXMPPCALLMANAGER_H
+
+#include <QObject>
+#include <QIODevice>
+#include <QMetaType>
+
+#include "QXmppClientExtension.h"
+#include "QXmppLogger.h"
+
+class QHostAddress;
+class QXmppCallPrivate;
+class QXmppCallManager;
+class QXmppCallManagerPrivate;
+class QXmppIq;
+class QXmppJingleCandidate;
+class QXmppJingleIq;
+class QXmppJinglePayloadType;
+class QXmppPresence;
+class QXmppRtpAudioChannel;
+class QXmppRtpVideoChannel;
+
+/// \brief The QXmppCall class represents a Voice-Over-IP call to a remote party.
+///
+/// To get the QIODevice from which you can read / write audio samples, call
+/// audioChannel().
+///
+/// \note THIS API IS NOT FINALIZED YET
+
+class QXmppCall : public QXmppLoggable
+{
+ Q_OBJECT
+ Q_ENUMS(Direction State)
+ Q_FLAGS(QIODevice::OpenModeFlag QIODevice::OpenMode)
+ Q_PROPERTY(Direction direction READ direction CONSTANT)
+ Q_PROPERTY(QString jid READ jid CONSTANT)
+ Q_PROPERTY(State state READ state NOTIFY stateChanged)
+ Q_PROPERTY(QIODevice::OpenMode audioMode READ audioMode NOTIFY audioModeChanged)
+ Q_PROPERTY(QIODevice::OpenMode videoMode READ videoMode NOTIFY videoModeChanged)
+
+public:
+ /// This enum is used to describe the direction of a call.
+ enum Direction
+ {
+ IncomingDirection, ///< The call is incoming.
+ OutgoingDirection, ///< The call is outgoing.
+ };
+
+ /// This enum is used to describe the state of a call.
+ enum State
+ {
+ ConnectingState = 0, ///< The call is being connected.
+ ActiveState = 1, ///< The call is active.
+ DisconnectingState = 2, ///< The call is being disconnected.
+ FinishedState = 3, ///< The call is finished.
+ };
+
+ ~QXmppCall();
+
+ QXmppCall::Direction direction() const;
+ QString jid() const;
+ QString sid() const;
+ QXmppCall::State state() const;
+
+ QXmppRtpAudioChannel *audioChannel() const;
+ QIODevice::OpenMode audioMode() const;
+ QXmppRtpVideoChannel *videoChannel() const;
+ QIODevice::OpenMode videoMode() const;
+
+signals:
+ /// \brief This signal is emitted when a call is connected.
+ ///
+ /// Once this signal is emitted, you can connect a QAudioOutput and
+ /// QAudioInput to the call. You can determine the appropriate clockrate
+ /// and the number of channels by calling payloadType().
+ void connected();
+
+ /// \brief This signal is emitted when a call is finished.
+ ///
+ /// Note: Do not delete the call in the slot connected to this signal,
+ /// instead use deleteLater().
+ void finished();
+
+ /// \brief This signal is emitted when the remote party is ringing.
+ void ringing();
+
+ /// \brief This signal is emitted when the call state changes.
+ void stateChanged(QXmppCall::State state);
+
+ /// \brief This signal is emitted when the audio channel changes.
+ void audioModeChanged(QIODevice::OpenMode mode);
+
+ /// \brief This signal is emitted when the video channel changes.
+ void videoModeChanged(QIODevice::OpenMode mode);
+
+public slots:
+ void accept();
+ void hangup();
+ void startVideo();
+ void stopVideo();
+
+private slots:
+ void localCandidatesChanged();
+ void terminated();
+ void updateOpenMode();
+
+private:
+ QXmppCall(const QString &jid, QXmppCall::Direction direction, QXmppCallManager *parent);
+
+ QXmppCallPrivate *d;
+ friend class QXmppCallManager;
+ friend class QXmppCallManagerPrivate;
+ friend class QXmppCallPrivate;
+};
+
+/// \brief The QXmppCallManager class provides support for making and
+/// receiving voice calls.
+///
+/// Session initiation is performed as described by XEP-0166: Jingle,
+/// XEP-0167: Jingle RTP Sessions and XEP-0176: Jingle ICE-UDP Transport
+/// Method.
+///
+/// The data stream is connected using Interactive Connectivity Establishment
+/// (RFC 5245) and data is transferred using Real Time Protocol (RFC 3550)
+/// packets.
+///
+/// To make use of this manager, you need to instantiate it and load it into
+/// the QXmppClient instance as follows:
+///
+/// \code
+/// QXmppCallManager *manager = new QXmppCallManager;
+/// client->addExtension(manager);
+/// \endcode
+///
+/// \ingroup Managers
+
+class QXmppCallManager : public QXmppClientExtension
+{
+ Q_OBJECT
+
+public:
+ QXmppCallManager();
+ ~QXmppCallManager();
+ void setStunServer(const QHostAddress &host, quint16 port = 3478);
+ void setTurnServer(const QHostAddress &host, quint16 port = 3478);
+ void setTurnUser(const QString &user);
+ void setTurnPassword(const QString &password);
+
+ /// \cond
+ QStringList discoveryFeatures() const;
+ bool handleStanza(const QDomElement &element);
+ /// \endcond
+
+signals:
+ /// This signal is emitted when a new incoming call is received.
+ ///
+ /// To accept the call, invoke the call's QXmppCall::accept() method.
+ /// To refuse the call, invoke the call's QXmppCall::hangup() method.
+ void callReceived(QXmppCall *call);
+
+ void callStarted(QXmppCall *call);
+
+public slots:
+ QXmppCall *call(const QString &jid);
+
+protected:
+ /// \cond
+ void setClient(QXmppClient* client);
+ /// \endcond
+
+private slots:
+ void _q_callDestroyed(QObject *object);
+ void _q_disconnected();
+ void _q_iqReceived(const QXmppIq &iq);
+ void _q_jingleIqReceived(const QXmppJingleIq &iq);
+ void _q_presenceReceived(const QXmppPresence &presence);
+
+private:
+ QXmppCallManagerPrivate *d;
+ friend class QXmppCall;
+ friend class QXmppCallPrivate;
+ friend class QXmppCallManagerPrivate;
+};
+
+Q_DECLARE_METATYPE(QXmppCall::State)
+
+#endif
diff --git a/src/client/QXmppClient.cpp b/src/client/QXmppClient.cpp
new file mode 100644
index 00000000..3dd93d07
--- /dev/null
+++ b/src/client/QXmppClient.cpp
@@ -0,0 +1,585 @@
+/*
+ * Copyright (C) 2008-2011 The QXmpp developers
+ *
+ * Author:
+ * Manjeet Dahiya
+ *
+ * Source:
+ * http://code.google.com/p/qxmpp
+ *
+ * This file is a part of QXmpp library.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ */
+
+#include <QSslSocket>
+
+#include "QXmppClient.h"
+#include "QXmppClientExtension.h"
+#include "QXmppConstants.h"
+#include "QXmppLogger.h"
+#include "QXmppOutgoingClient.h"
+#include "QXmppMessage.h"
+#include "QXmppUtils.h"
+
+#include "QXmppReconnectionManager.h"
+#include "QXmppRosterManager.h"
+#include "QXmppVCardManager.h"
+#include "QXmppVersionManager.h"
+#include "QXmppEntityTimeManager.h"
+#include "QXmppDiscoveryManager.h"
+#include "QXmppDiscoveryIq.h"
+
+class QXmppClientPrivate
+{
+public:
+ QXmppClientPrivate(QXmppClient *qq);
+
+ QXmppPresence clientPresence; ///< Current presence of the client
+ QList<QXmppClientExtension*> extensions;
+ QXmppLogger *logger;
+ QXmppOutgoingClient *stream; ///< Pointer to the XMPP stream
+
+ QXmppReconnectionManager *reconnectionManager; ///< Pointer to the reconnection manager
+ QXmppRosterManager *rosterManager; ///< Pointer to the roster manager
+ QXmppVCardManager *vCardManager; ///< Pointer to the vCard manager
+ QXmppVersionManager *versionManager; ///< Pointer to the version manager
+
+ void addProperCapability(QXmppPresence& presence);
+
+private:
+ QXmppClient *q;
+};
+
+QXmppClientPrivate::QXmppClientPrivate(QXmppClient *qq)
+ : clientPresence(QXmppPresence::Available),
+ logger(0),
+ stream(0),
+ reconnectionManager(0),
+ rosterManager(0),
+ vCardManager(0),
+ versionManager(0),
+ q(qq)
+{
+}
+
+void QXmppClientPrivate::addProperCapability(QXmppPresence& presence)
+{
+ QXmppDiscoveryManager* ext = q->findExtension<QXmppDiscoveryManager>();
+ if(ext) {
+ presence.setCapabilityHash("sha-1");
+ presence.setCapabilityNode(ext->clientCapabilitiesNode());
+ presence.setCapabilityVer(ext->capabilities().verificationString());
+ }
+}
+
+/// \mainpage
+///
+/// QXmpp is a cross-platform C++ XMPP client library based on the Qt
+/// framework. It tries to use Qt's programming conventions in order to ease
+/// the learning curve for new programmers.
+///
+/// QXmpp based clients are built using QXmppClient instances which handle the
+/// establishment of the XMPP connection and provide a number of high-level
+/// "managers" to perform specific tasks. You can write your own managers to
+/// extend QXmpp by subclassing QXmppClientExtension.
+///
+/// <B>Main Class:</B>
+/// - QXmppClient
+///
+/// <B>Managers to perform specific tasks:</B>
+/// - QXmppRosterManager
+/// - QXmppVCardManager
+/// - QXmppTransferManager
+/// - QXmppMucManager
+/// - QXmppCallManager
+/// - QXmppArchiveManager
+/// - QXmppVersionManager
+/// - QXmppDiscoveryManager
+/// - QXmppEntityTimeManager
+///
+/// <B>XMPP stanzas:</B> If you are interested in a more low-level API, you can refer to these
+/// classes.
+/// - QXmppIq
+/// - QXmppMessage
+/// - QXmppPresence
+///
+/// <BR><BR>
+/// <B>Project Details:</B>
+///
+/// Project Page: http://code.google.com/p/qxmpp/
+/// <BR>
+/// Report Issues: http://code.google.com/p/qxmpp/issues/
+/// <BR>
+/// New Releases: http://code.google.com/p/qxmpp/downloads/
+///
+
+/// Creates a QXmppClient object.
+/// \param parent is passed to the QObject's constructor.
+/// The default value is 0.
+
+QXmppClient::QXmppClient(QObject *parent)
+ : QXmppLoggable(parent),
+ d(new QXmppClientPrivate(this))
+{
+ bool check;
+ Q_UNUSED(check);
+
+ d->stream = new QXmppOutgoingClient(this);
+ d->addProperCapability(d->clientPresence);
+
+ check = connect(d->stream, SIGNAL(elementReceived(QDomElement,bool&)),
+ this, SLOT(_q_elementReceived(QDomElement,bool&)));
+ Q_ASSERT(check);
+
+ check = connect(d->stream, SIGNAL(messageReceived(QXmppMessage)),
+ this, SIGNAL(messageReceived(QXmppMessage)));
+ Q_ASSERT(check);
+
+ check = connect(d->stream, SIGNAL(presenceReceived(QXmppPresence)),
+ this, SIGNAL(presenceReceived(QXmppPresence)));
+ Q_ASSERT(check);
+
+ check = connect(d->stream, SIGNAL(iqReceived(QXmppIq)),
+ this, SIGNAL(iqReceived(QXmppIq)));
+ Q_ASSERT(check);
+
+ check = connect(d->stream->socket(), SIGNAL(stateChanged(QAbstractSocket::SocketState)),
+ this, SLOT(_q_socketStateChanged(QAbstractSocket::SocketState)));
+ Q_ASSERT(check);
+
+ check = connect(d->stream, SIGNAL(connected()),
+ this, SLOT(_q_streamConnected()));
+ Q_ASSERT(check);
+
+ check = connect(d->stream, SIGNAL(disconnected()),
+ this, SLOT(_q_streamDisconnected()));
+ Q_ASSERT(check);
+
+ check = connect(d->stream, SIGNAL(error(QXmppClient::Error)),
+ this, SIGNAL(error(QXmppClient::Error)));
+ Q_ASSERT(check);
+
+ check = setReconnectionManager(new QXmppReconnectionManager(this));
+ Q_ASSERT(check);
+
+ // logging
+ setLogger(QXmppLogger::getLogger());
+
+ // create managers
+ // TODO move manager references to d->extensions
+ d->rosterManager = new QXmppRosterManager(this);
+ addExtension(d->rosterManager);
+
+ d->vCardManager = new QXmppVCardManager;
+ addExtension(d->vCardManager);
+
+ d->versionManager = new QXmppVersionManager;
+ addExtension(d->versionManager);
+
+ addExtension(new QXmppEntityTimeManager());
+
+ QXmppDiscoveryManager *discoveryManager = new QXmppDiscoveryManager;
+ addExtension(discoveryManager);
+
+ // obsolete signal
+ check = connect(discoveryManager, SIGNAL(infoReceived(QXmppDiscoveryIq)),
+ this, SIGNAL(discoveryIqReceived(QXmppDiscoveryIq)));
+ Q_ASSERT(check);
+
+ check = connect(discoveryManager, SIGNAL(itemsReceived(QXmppDiscoveryIq)),
+ this, SIGNAL(discoveryIqReceived(QXmppDiscoveryIq)));
+ Q_ASSERT(check);
+}
+
+/// Destructor, destroys the QXmppClient object.
+///
+
+QXmppClient::~QXmppClient()
+{
+ delete d;
+}
+
+/// Registers a new extension with the client.
+///
+/// \param extension
+
+bool QXmppClient::addExtension(QXmppClientExtension* extension)
+{
+ if (d->extensions.contains(extension))
+ {
+ qWarning("Cannot add extension, it has already been added");
+ return false;
+ }
+
+ extension->setParent(this);
+ extension->setClient(this);
+ d->extensions << extension;
+ return true;
+}
+
+/// Unregisters the given extension from the client. If the extension
+/// is found, it will be destroyed.
+///
+/// \param extension
+
+bool QXmppClient::removeExtension(QXmppClientExtension* extension)
+{
+ if (d->extensions.contains(extension))
+ {
+ d->extensions.removeAll(extension);
+ delete extension;
+ return true;
+ } else {
+ qWarning("Cannot remove extension, it was never added");
+ return false;
+ }
+}
+
+/// Returns a list containing all the client's extensions.
+///
+
+QList<QXmppClientExtension*> QXmppClient::extensions()
+{
+ return d->extensions;
+}
+
+/// Returns a modifiable reference to the current configuration of QXmppClient.
+/// \return Reference to the QXmppClient's configuration for the connection.
+
+QXmppConfiguration& QXmppClient::configuration()
+{
+ return d->stream->configuration();
+}
+
+/// Attempts to connect to the XMPP server. Server details and other configurations
+/// are specified using the config parameter. Use signals connected(), error(QXmppClient::Error)
+/// and disconnected() to know the status of the connection.
+/// \param config Specifies the configuration object for connecting the XMPP server.
+/// This contains the host name, user, password etc. See QXmppConfiguration for details.
+/// \param initialPresence The initial presence which will be set for this user
+/// after establishing the session. The default value is QXmppPresence::Available
+
+void QXmppClient::connectToServer(const QXmppConfiguration& config,
+ const QXmppPresence& initialPresence)
+{
+ d->stream->configuration() = config;
+ if(!config.autoReconnectionEnabled())
+ {
+ delete d->reconnectionManager;
+ d->reconnectionManager = 0;
+ }
+
+ d->clientPresence = initialPresence;
+ d->addProperCapability(d->clientPresence);
+
+ d->stream->connectToHost();
+}
+
+/// Overloaded function to simply connect to an XMPP server with a JID and password.
+///
+/// \param jid JID for the account.
+/// \param password Password for the account.
+
+void QXmppClient::connectToServer(const QString &jid, const QString &password)
+{
+ QXmppConfiguration config;
+ config.setJid(jid);
+ config.setPassword(password);
+ connectToServer(config);
+}
+
+/// After successfully connecting to the server use this function to send
+/// stanzas to the server. This function can solely be used to send various kind
+/// of stanzas to the server. QXmppPacket is a parent class of all the stanzas
+/// QXmppMessage, QXmppPresence, QXmppIq, QXmppBind, QXmppRosterIq, QXmppSession
+/// and QXmppVCard.
+///
+/// \return Returns true if the packet was sent, false otherwise.
+///
+/// Following code snippet illustrates how to send a message using this function:
+/// \code
+/// QXmppMessage message(from, to, message);
+/// client.sendPacket(message);
+/// \endcode
+///
+/// \param packet A valid XMPP stanza. It can be an iq, a message or a presence stanza.
+///
+
+bool QXmppClient::sendPacket(const QXmppPacket& packet)
+{
+ return d->stream->sendPacket(packet);
+}
+
+/// Disconnects the client and the current presence of client changes to
+/// QXmppPresence::Unavailable and status text changes to "Logged out".
+///
+/// \note Make sure that the clientPresence is changed to
+/// QXmppPresence::Available, if you are again calling connectToServer() after
+/// calling the disconnectFromServer() function.
+///
+
+void QXmppClient::disconnectFromServer()
+{
+ d->clientPresence.setType(QXmppPresence::Unavailable);
+ d->clientPresence.status().setType(QXmppPresence::Status::Offline);
+ d->clientPresence.status().setStatusText("Logged out");
+ if (d->stream->isConnected())
+ {
+ sendPacket(d->clientPresence);
+ }
+ d->stream->disconnectFromHost();
+}
+
+/// Returns true if the client is connected to the XMPP server.
+///
+
+bool QXmppClient::isConnected() const
+{
+ return d->stream->isConnected();
+}
+
+/// Returns the reference to QXmppRosterManager object of the client.
+/// \return Reference to the roster object of the connected client. Use this to
+/// get the list of friends in the roster and their presence information.
+///
+
+QXmppRosterManager& QXmppClient::rosterManager()
+{
+ return *d->rosterManager;
+}
+
+/// Utility function to send message to all the resources associated with the
+/// specified bareJid. If there are no resources available, that is the contact
+/// is offline or not present in the roster, it will still send a message to
+/// the bareJid.
+///
+/// \param bareJid bareJid of the receiving entity
+/// \param message Message string to be sent.
+
+void QXmppClient::sendMessage(const QString& bareJid, const QString& message)
+{
+ QStringList resources = rosterManager().getResources(bareJid);
+ if(!resources.isEmpty())
+ {
+ for(int i = 0; i < resources.size(); ++i)
+ {
+ sendPacket(QXmppMessage("", bareJid + "/" + resources.at(i), message));
+ }
+ }
+ else
+ {
+ sendPacket(QXmppMessage("", bareJid, message));
+ }
+}
+
+/// Returns the client's current state.
+
+QXmppClient::State QXmppClient::state() const
+{
+ if (d->stream->isConnected())
+ return QXmppClient::ConnectedState;
+ else if (d->stream->socket()->state() != QAbstractSocket::UnconnectedState &&
+ d->stream->socket()->state() != QAbstractSocket::ClosingState)
+ return QXmppClient::ConnectingState;
+ else
+ return QXmppClient::DisconnectedState;
+}
+
+/// Returns the client's current presence.
+///
+
+QXmppPresence QXmppClient::clientPresence() const
+{
+ return d->clientPresence;
+}
+
+/// Changes the presence of the connected client.
+///
+/// The connection to the server will be updated accordingly:
+///
+/// \li If the presence type is QXmppPresence::Unavailable, the connection
+/// to the server will be closed.
+///
+/// \li Otherwise, the connection to the server will be established
+/// as needed.
+///
+/// \param presence QXmppPresence object
+///
+
+void QXmppClient::setClientPresence(const QXmppPresence& presence)
+{
+ d->clientPresence = presence;
+ d->addProperCapability(d->clientPresence);
+
+ if (presence.type() == QXmppPresence::Unavailable)
+ {
+ // NOTE: we can't call disconnect() because it alters
+ // the client presence
+ if (d->stream->isConnected())
+ {
+ sendPacket(d->clientPresence);
+ d->stream->disconnectFromHost();
+ }
+ }
+ else if (d->stream->isConnected())
+ sendPacket(d->clientPresence);
+ else
+ connectToServer(d->stream->configuration(), presence);
+}
+
+/// Function to get reconnection manager. By default there exists a reconnection
+/// manager. See QXmppReconnectionManager for more details of the reconnection
+/// mechanism.
+///
+/// \return Pointer to QXmppReconnectionManager
+///
+
+QXmppReconnectionManager* QXmppClient::reconnectionManager()
+{
+ return d->reconnectionManager;
+}
+
+/// Sets the user defined reconnection manager.
+///
+/// \return true if all the signal-slot connections are made correctly.
+///
+
+bool QXmppClient::setReconnectionManager(QXmppReconnectionManager*
+ reconnectionManager)
+{
+ if(!reconnectionManager)
+ return false;
+
+ if(d->reconnectionManager)
+ delete d->reconnectionManager;
+
+ d->reconnectionManager = reconnectionManager;
+
+ bool check = connect(this, SIGNAL(connected()), d->reconnectionManager,
+ SLOT(connected()));
+ Q_ASSERT(check);
+ if(!check)
+ return false;
+
+ check = connect(this, SIGNAL(error(QXmppClient::Error)),
+ d->reconnectionManager, SLOT(error(QXmppClient::Error)));
+ Q_ASSERT(check);
+ if(!check)
+ return false;
+
+ return true;
+}
+
+/// Returns the socket error if error() is QXmppClient::SocketError.
+///
+
+QAbstractSocket::SocketError QXmppClient::socketError()
+{
+ return d->stream->socket()->error();
+}
+
+/// Returns the XMPP stream error if QXmppClient::Error is QXmppClient::XmppStreamError.
+///
+
+QXmppStanza::Error::Condition QXmppClient::xmppStreamError()
+{
+ return d->stream->xmppStreamError();
+}
+
+/// Returns the reference to QXmppVCardManager, implementation of XEP-0054.
+/// http://xmpp.org/extensions/xep-0054.html
+///
+
+QXmppVCardManager& QXmppClient::vCardManager()
+{
+ return *d->vCardManager;
+}
+
+/// Returns the reference to QXmppVersionManager, implementation of XEP-0092.
+/// http://xmpp.org/extensions/xep-0092.html
+///
+
+QXmppVersionManager& QXmppClient::versionManager()
+{
+ return *d->versionManager;
+}
+
+/// Give extensions a chance to handle incoming stanzas.
+///
+/// \param element
+/// \param handled
+
+void QXmppClient::_q_elementReceived(const QDomElement &element, bool &handled)
+{
+ foreach (QXmppClientExtension *extension, d->extensions)
+ {
+ if (extension->handleStanza(element))
+ {
+ handled = true;
+ return;
+ }
+ }
+}
+
+void QXmppClient::_q_socketStateChanged(QAbstractSocket::SocketState socketState)
+{
+ Q_UNUSED(socketState);
+ emit stateChanged(state());
+}
+
+/// At connection establishment, send initial presence.
+
+void QXmppClient::_q_streamConnected()
+{
+ // notify managers
+ emit connected();
+ emit stateChanged(QXmppClient::ConnectedState);
+
+ // send initial presence
+ sendPacket(d->clientPresence);
+}
+
+void QXmppClient::_q_streamDisconnected()
+{
+ // notify managers
+ emit disconnected();
+ emit stateChanged(QXmppClient::DisconnectedState);
+}
+
+/// Returns the QXmppLogger associated with the current QXmppClient.
+
+QXmppLogger *QXmppClient::logger() const
+{
+ return d->logger;
+}
+
+/// Sets the QXmppLogger associated with the current QXmppClient.
+
+void QXmppClient::setLogger(QXmppLogger *logger)
+{
+ if (logger != d->logger) {
+ if (d->logger) {
+ disconnect(this, SIGNAL(logMessage(QXmppLogger::MessageType,QString)),
+ d->logger, SLOT(log(QXmppLogger::MessageType,QString)));
+ }
+
+ d->logger = logger;
+ if (d->logger) {
+ connect(this, SIGNAL(logMessage(QXmppLogger::MessageType,QString)),
+ d->logger, SLOT(log(QXmppLogger::MessageType,QString)));
+ }
+
+ emit loggerChanged(d->logger);
+ }
+}
+
diff --git a/src/client/QXmppClient.h b/src/client/QXmppClient.h
new file mode 100644
index 00000000..3e1d8536
--- /dev/null
+++ b/src/client/QXmppClient.h
@@ -0,0 +1,244 @@
+/*
+ * Copyright (C) 2008-2011 The QXmpp developers
+ *
+ * Author:
+ * Manjeet Dahiya
+ *
+ * Source:
+ * http://code.google.com/p/qxmpp
+ *
+ * This file is a part of QXmpp library.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ */
+
+#ifndef QXMPPCLIENT_H
+#define QXMPPCLIENT_H
+
+#include <QObject>
+#include <QAbstractSocket>
+
+#include "QXmppConfiguration.h"
+#include "QXmppLogger.h"
+#include "QXmppPresence.h"
+
+class QXmppClientExtension;
+class QXmppClientPrivate;
+class QXmppPresence;
+class QXmppMessage;
+class QXmppPacket;
+class QXmppIq;
+class QXmppStream;
+
+// managers
+class QXmppDiscoveryIq;
+class QXmppReconnectionManager;
+class QXmppRosterManager;
+class QXmppVCardManager;
+class QXmppVersionManager;
+
+/// \defgroup Core
+
+/// \defgroup Managers
+
+/// \brief The QXmppClient class is the main class for using QXmpp.
+///
+/// It provides the user all the required functionality to connect to the server
+/// and perform operations afterwards.
+///
+/// This class will provide the handle/reference to QXmppRosterManager (roster management),
+/// QXmppVCardManager (vCard manager), QXmppReconnectionManager (reconnection
+/// mechanism) and QXmppVersionManager (software version information).
+///
+/// By default, a reconnection mechanism exists, which makes sure of reconnecting
+/// to the server on disconnections due to an error. User can have a custom
+/// reconnection mechanism as well.
+///
+/// Not all the managers or extensions have been enabled by default. One can
+/// enable/disable the managers using the funtions addExtension() and
+/// removeExtension(). findExtension() can be used to find reference/pointer to
+/// particular instansiated and enabled manager.
+///
+/// List of managers enabled by default:
+/// - QXmppRosterManager
+/// - QXmppVCardManager
+/// - QXmppVersionManager
+/// - QXmppDiscoveryManager
+/// - QXmppEntityTimeManager
+///
+/// \ingroup Core
+
+class QXmppClient : public QXmppLoggable
+{
+ Q_OBJECT
+ Q_ENUMS(Error State)
+ Q_PROPERTY(QXmppLogger* logger READ logger WRITE setLogger NOTIFY loggerChanged)
+ Q_PROPERTY(State state READ state NOTIFY stateChanged)
+
+public:
+ /// An enumeration for type of error.
+ /// Error could come due a TCP socket or XML stream or due to various stanzas.
+ enum Error
+ {
+ NoError, ///< No error.
+ SocketError, ///< Error due to TCP socket.
+ KeepAliveError, ///< Error due to no response to a keep alive.
+ XmppStreamError, ///< Error due to XML stream.
+ };
+
+ /// This enumeration describes a client state.
+ enum State
+ {
+ DisconnectedState, ///< Disconnected from the server.
+ ConnectingState, ///< Trying to connect to the server.
+ ConnectedState, ///< Connected to the server.
+ };
+
+ QXmppClient(QObject *parent = 0);
+ ~QXmppClient();
+
+ bool addExtension(QXmppClientExtension* extension);
+ bool removeExtension(QXmppClientExtension* extension);
+
+ QList<QXmppClientExtension*> extensions();
+
+ /// \brief Returns the extension which can be cast into type T*, or 0
+ /// if there is no such extension.
+ ///
+ /// Usage example:
+ /// \code
+ /// QXmppDiscoveryManager* ext = client->findExtension<QXmppDiscoveryManager>();
+ /// if(ext)
+ /// {
+ /// //extension found, do stuff...
+ /// }
+ /// \endcode
+ ///
+ template<typename T>
+ T* findExtension()
+ {
+ QList<QXmppClientExtension*> list = extensions();
+ for (int i = 0; i < list.size(); ++i)
+ {
+ T* extension = qobject_cast<T*>(list.at(i));
+ if(extension)
+ return extension;
+ }
+ return 0;
+ }
+
+ void connectToServer(const QXmppConfiguration&,
+ const QXmppPresence& initialPresence =
+ QXmppPresence());
+ bool isConnected() const;
+
+ QXmppPresence clientPresence() const;
+ void setClientPresence(const QXmppPresence &presence);
+
+ QXmppConfiguration &configuration();
+ QXmppLogger *logger() const;
+ void setLogger(QXmppLogger *logger);
+
+ QAbstractSocket::SocketError socketError();
+ State state() const;
+ QXmppStanza::Error::Condition xmppStreamError();
+
+ QXmppRosterManager& rosterManager();
+ QXmppVCardManager& vCardManager();
+ QXmppVersionManager& versionManager();
+
+ QXmppReconnectionManager* reconnectionManager();
+ bool setReconnectionManager(QXmppReconnectionManager*);
+
+signals:
+
+ /// This signal is emitted when the client connects successfully to the XMPP
+ /// server i.e. when a successful XMPP connection is established.
+ /// XMPP Connection involves following sequential steps:
+ /// - TCP socket connection
+ /// - Client sends start stream
+ /// - Server sends start stream
+ /// - TLS negotiation (encryption)
+ /// - Authentication
+ /// - Resource binding
+ /// - Session establishment
+ ///
+ /// After all these steps a successful XMPP connection is established and
+ /// connected() signal is emitted.
+ ///
+ /// After the connected() signal is emitted QXmpp will send the roster request
+ /// to the server. On receiving the roster, QXmpp will emit
+ /// QXmppRosterManager::rosterReceived(). After this signal, QXmppRosterManager object gets
+ /// populated and you can use rosterManager() to get the handle of QXmppRosterManager object.
+ ///
+ void connected();
+
+ /// This signal is emitted when the XMPP connection disconnects.
+ ///
+ void disconnected();
+
+ /// This signal is emitted when the XMPP connection encounters any error.
+ /// The QXmppClient::Error parameter specifies the type of error occurred.
+ /// It could be due to TCP socket or the xml stream or the stanza.
+ /// Depending upon the type of error occurred use the respective get function to
+ /// know the error.
+ void error(QXmppClient::Error);
+
+ /// This signal is emitted when the logger changes.
+ void loggerChanged(QXmppLogger *logger);
+
+ /// Notifies that an XMPP message stanza is received. The QXmppMessage
+ /// parameter contains the details of the message sent to this client.
+ /// In other words whenever someone sends you a message this signal is
+ /// emitted.
+ void messageReceived(const QXmppMessage &message);
+
+ /// Notifies that an XMPP presence stanza is received. The QXmppPresence
+ /// parameter contains the details of the presence sent to this client.
+ /// This signal is emitted when someone login/logout or when someone's status
+ /// changes Busy, Idle, Invisible etc.
+ void presenceReceived(const QXmppPresence &presence);
+
+ /// Notifies that an XMPP iq stanza is received. The QXmppIq
+ /// parameter contains the details of the iq sent to this client.
+ /// IQ stanzas provide a structured request-response mechanism. Roster
+ /// management, setting-getting vCards etc is done using iq stanzas.
+ void iqReceived(const QXmppIq &iq);
+
+ /// This signal is emitted when the client state changes.
+ void stateChanged(QXmppClient::State state);
+
+ /// \cond
+ // Deprecated in release 0.3.0
+ // Use QXmppDiscoveryManager::informationReceived(const QXmppDiscoveryIq&)
+ // Notifies that an XMPP service discovery iq stanza is received.
+ void discoveryIqReceived(const QXmppDiscoveryIq&);
+ /// \endcond
+
+public slots:
+ void connectToServer(const QString &jid,
+ const QString &password);
+ void disconnectFromServer();
+ bool sendPacket(const QXmppPacket&);
+ void sendMessage(const QString& bareJid, const QString& message);
+
+private slots:
+ void _q_elementReceived(const QDomElement &element, bool &handled);
+ void _q_socketStateChanged(QAbstractSocket::SocketState state);
+ void _q_streamConnected();
+ void _q_streamDisconnected();
+
+private:
+ QXmppClientPrivate * const d;
+};
+
+#endif // QXMPPCLIENT_H
diff --git a/src/client/QXmppClientExtension.cpp b/src/client/QXmppClientExtension.cpp
new file mode 100644
index 00000000..52da8907
--- /dev/null
+++ b/src/client/QXmppClientExtension.cpp
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2008-2011 The QXmpp developers
+ *
+ * Author:
+ * Jeremy Lainé
+ *
+ * Source:
+ * http://code.google.com/p/qxmpp
+ *
+ * This file is a part of QXmpp library.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ */
+
+#include <QStringList>
+
+#include "QXmppClientExtension.h"
+
+class QXmppClientExtensionPrivate
+{
+public:
+ QXmppClient *client;
+};
+
+/// Constructs a QXmppClient extension.
+///
+
+QXmppClientExtension::QXmppClientExtension()
+ : d(new QXmppClientExtensionPrivate)
+{
+ d->client = 0;
+}
+
+/// Destroys a QXmppClient extension.
+///
+
+QXmppClientExtension::~QXmppClientExtension()
+{
+ delete d;
+}
+
+/// Returns the discovery features to add to the client.
+///
+
+QStringList QXmppClientExtension::discoveryFeatures() const
+{
+ return QStringList();
+}
+
+/// Returns the discovery identities to add to the client.
+///
+
+QList<QXmppDiscoveryIq::Identity> QXmppClientExtension::discoveryIdentities() const
+{
+ return QList<QXmppDiscoveryIq::Identity>();
+}
+
+/// Returns the client which loaded this extension.
+///
+
+QXmppClient *QXmppClientExtension::client()
+{
+ return d->client;
+}
+
+/// Sets the client which loaded this extension.
+///
+/// \param client
+
+void QXmppClientExtension::setClient(QXmppClient *client)
+{
+ d->client = client;
+}
+
diff --git a/src/client/QXmppClientExtension.h b/src/client/QXmppClientExtension.h
new file mode 100644
index 00000000..8e65527a
--- /dev/null
+++ b/src/client/QXmppClientExtension.h
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2008-2011 The QXmpp developers
+ *
+ * Author:
+ * Jeremy Lainé
+ *
+ * Source:
+ * http://code.google.com/p/qxmpp
+ *
+ * This file is a part of QXmpp library.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ */
+
+#ifndef QXMPPCLIENTEXTENSION_H
+#define QXMPPCLIENTEXTENSION_H
+
+#include "QXmppDiscoveryIq.h"
+#include "QXmppLogger.h"
+
+class QDomElement;
+class QStringList;
+
+class QXmppClient;
+class QXmppClientExtensionPrivate;
+class QXmppStream;
+
+/// \brief The QXmppClientExtension class is the base class for QXmppClient
+/// extensions.
+///
+/// If you want to extend QXmppClient, for instance to support an IQ type
+/// which is not natively supported, you can subclass QXmppClientExtension
+/// and implement handleStanza(). You can then add your extension to the
+/// client instance using QXmppClient::addExtension().
+///
+/// \ingroup Core
+
+class QXmppClientExtension : public QXmppLoggable
+{
+ Q_OBJECT
+
+public:
+ QXmppClientExtension();
+ virtual ~QXmppClientExtension();
+
+ virtual QStringList discoveryFeatures() const;
+ virtual QList<QXmppDiscoveryIq::Identity> discoveryIdentities() const;
+
+ /// \brief You need to implement this method to process incoming XMPP
+ /// stanzas.
+ ///
+ /// You should return true if the stanza was handled and no further
+ /// processing should occur, or false to let other extensions process
+ /// the stanza.
+ virtual bool handleStanza(const QDomElement &stanza) = 0;
+
+protected:
+ QXmppClient *client();
+ virtual void setClient(QXmppClient *client);
+
+private:
+ QXmppClientExtensionPrivate * const d;
+
+ friend class QXmppClient;
+};
+
+#endif
diff --git a/src/client/QXmppConfiguration.cpp b/src/client/QXmppConfiguration.cpp
new file mode 100644
index 00000000..dc4087b7
--- /dev/null
+++ b/src/client/QXmppConfiguration.cpp
@@ -0,0 +1,485 @@
+/*
+ * Copyright (C) 2008-2011 The QXmpp developers
+ *
+ * Author:
+ * Manjeet Dahiya
+ *
+ * Source:
+ * http://code.google.com/p/qxmpp
+ *
+ * This file is a part of QXmpp library.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ */
+
+
+#include <QSslSocket>
+#include "QXmppConfiguration.h"
+#include "QXmppUtils.h"
+
+/// Creates a QXmppConfiguration object.
+
+QXmppConfiguration::QXmppConfiguration() : m_port(5222),
+ m_resource("QXmpp"),
+ m_autoAcceptSubscriptions(false),
+ m_sendIntialPresence(true),
+ m_sendRosterRequest(true),
+ m_keepAliveInterval(60),
+ m_keepAliveTimeout(20),
+ m_autoReconnectionEnabled(true),
+ m_useSASLAuthentication(true),
+ m_ignoreSslErrors(true),
+ m_streamSecurityMode(QXmppConfiguration::TLSEnabled),
+ m_nonSASLAuthMechanism(QXmppConfiguration::NonSASLDigest),
+ m_SASLAuthMechanism(QXmppConfiguration::SASLDigestMD5)
+{
+
+}
+
+/// Destructor, destroys the QXmppConfiguration object.
+///
+
+QXmppConfiguration::~QXmppConfiguration()
+{
+
+}
+
+/// Sets the host name.
+///
+/// \param host host name of the XMPP server where connection has to be made
+/// (e.g. "jabber.org" and "talk.google.com"). It can also be an IP address in
+/// the form of a string (e.g. "192.168.1.25").
+///
+
+void QXmppConfiguration::setHost(const QString& host)
+{
+ m_host = host;
+}
+
+/// Sets the domain name.
+///
+/// \param domain Domain name e.g. "gmail.com" and "jabber.org".
+/// \note host name and domain name can be different for google
+/// domain name is gmail.com and host name is talk.google.com
+///
+
+void QXmppConfiguration::setDomain(const QString& domain)
+{
+ m_domain = domain;
+}
+
+/// Sets the port number.
+///
+/// \param port Port number at which the XMPP server is listening. The default
+/// value is 5222.
+///
+
+void QXmppConfiguration::setPort(int port)
+{
+ m_port = port;
+}
+
+/// Sets the username.
+///
+/// \param user Username of the account at the specified XMPP server. It should
+/// be the name without the domain name. E.g. "qxmpp.test1" and not
+/// "qxmpp.test1@gmail.com"
+///
+
+void QXmppConfiguration::setUser(const QString& user)
+{
+ m_user = user;
+}
+
+/// Sets the password.
+///
+/// \param password Password for the specified username
+///
+
+void QXmppConfiguration::setPassword(const QString& password)
+{
+ m_password = password;
+}
+
+/// Sets the resource identifier.
+///
+/// Multiple resources (e.g., devices or locations) may connect simultaneously
+/// to a server on behalf of each authorized client, with each resource
+/// differentiated by the resource identifier of an XMPP address
+/// (e.g. node\@domain/home vs. node\@domain/work)
+///
+/// The default value is "QXmpp".
+///
+/// \param resource Resource identifier of the client in connection.
+
+void QXmppConfiguration::setResource(const QString& resource)
+{
+ m_resource = resource;
+}
+
+/// Sets the JID. If a full JID (i.e. one with a resource) is given, calling
+/// this method will update the username, domain and resource. Otherwise, only
+/// the username and the domain will be updated.
+///
+/// \param jid
+
+void QXmppConfiguration::setJid(const QString& jid)
+{
+ m_user = jidToUser(jid);
+ m_domain = jidToDomain(jid);
+ const QString resource = jidToResource(jid);
+ if (!resource.isEmpty())
+ m_resource = resource;
+}
+
+/// Returns the host name.
+///
+/// \return host name
+///
+
+QString QXmppConfiguration::host() const
+{
+ return m_host;
+}
+
+/// Returns the domain name.
+///
+/// \return domain name
+///
+
+QString QXmppConfiguration::domain() const
+{
+ return m_domain;
+}
+
+/// Returns the port number.
+///
+/// \return port number
+///
+
+int QXmppConfiguration::port() const
+{
+ return m_port;
+}
+
+/// Returns the username.
+///
+/// \return username
+///
+
+QString QXmppConfiguration::user() const
+{
+ return m_user;
+}
+
+/// Returns the password.
+///
+/// \return password
+///
+
+QString QXmppConfiguration::password() const
+{
+ return m_password;
+}
+
+/// Returns the resource identifier.
+///
+/// \return resource identifier
+///
+
+QString QXmppConfiguration::resource() const
+{
+ return m_resource;
+}
+
+/// Returns the jabber id (jid).
+///
+/// \return jabber id (jid)
+/// (e.g. "qxmpp.test1@gmail.com/resource" or qxmpptest@jabber.org/QXmpp156)
+///
+
+QString QXmppConfiguration::jid() const
+{
+ if (m_user.isEmpty())
+ return m_domain;
+ else
+ return jidBare() + "/" + m_resource;
+}
+
+/// Returns the bare jabber id (jid), without the resource identifier.
+///
+/// \return bare jabber id (jid)
+/// (e.g. "qxmpp.test1@gmail.com" or qxmpptest@jabber.org)
+///
+
+QString QXmppConfiguration::jidBare() const
+{
+ if (m_user.isEmpty())
+ return m_domain;
+ else
+ return m_user+"@"+m_domain;
+}
+
+/// Returns the access token used for X-FACEBOOK-PLATFORM authentication.
+
+QString QXmppConfiguration::facebookAccessToken() const
+{
+ return m_facebookAccessToken;
+}
+
+/// Sets the access token used for X-FACEBOOK-PLATFORM authentication.
+///
+/// This token is returned by Facebook at the end of the OAuth authentication
+/// process.
+///
+/// \param accessToken
+
+void QXmppConfiguration::setFacebookAccessToken(const QString& accessToken)
+{
+ m_facebookAccessToken = accessToken;
+}
+
+/// Returns the application ID used for X-FACEBOOK-PLATFORM authentication.
+
+QString QXmppConfiguration::facebookAppId() const
+{
+ return m_facebookAppId;
+}
+
+/// Sets the application ID used for X-FACEBOOK-PLATFORM authentication.
+///
+/// \param appId
+
+void QXmppConfiguration::setFacebookAppId(const QString& appId)
+{
+ m_facebookAppId = appId;
+}
+
+/// Returns the auto-accept-subscriptions-request configuration.
+///
+/// \return boolean value
+/// true means that auto-accept-subscriptions-request is enabled else disabled for false
+///
+
+bool QXmppConfiguration::autoAcceptSubscriptions() const
+{
+ return m_autoAcceptSubscriptions;
+}
+
+/// Sets the auto-accept-subscriptions-request configuration.
+///
+/// \param value boolean value
+/// true means that auto-accept-subscriptions-request is enabled else disabled for false
+///
+
+void QXmppConfiguration::setAutoAcceptSubscriptions(bool value)
+{
+ m_autoAcceptSubscriptions = value;
+}
+
+/// Returns the auto-reconnect-on-disconnection-on-error configuration.
+///
+/// \return boolean value
+/// true means that auto-reconnect is enabled else disabled for false
+///
+
+bool QXmppConfiguration::autoReconnectionEnabled() const
+{
+ return m_autoReconnectionEnabled;
+}
+
+/// Sets the auto-reconnect-on-disconnection-on-error configuration.
+///
+/// \param value boolean value
+/// true means that auto-reconnect is enabled else disabled for false
+///
+
+void QXmppConfiguration::setAutoReconnectionEnabled(bool value)
+{
+ m_autoReconnectionEnabled = value;
+}
+
+/// Returns whether SSL errors (such as certificate validation errors)
+/// are to be ignored when connecting to the XMPP server.
+
+bool QXmppConfiguration::ignoreSslErrors() const
+{
+ return m_ignoreSslErrors;
+}
+
+/// Specifies whether SSL errors (such as certificate validation errors)
+/// are to be ignored when connecting to an XMPP server.
+
+void QXmppConfiguration::setIgnoreSslErrors(bool value)
+{
+ m_ignoreSslErrors = value;
+}
+
+/// Returns the type of authentication system specified by the user.
+/// \return true if SASL was specified else false. If the specified
+/// system is not available QXmpp will resort to the other one.
+
+bool QXmppConfiguration::useSASLAuthentication() const
+{
+ return m_useSASLAuthentication;
+}
+
+/// Returns the type of authentication system specified by the user.
+/// \param useSASL to hint to use SASL authentication system if available.
+/// false will specify to use NonSASL XEP-0078: Non-SASL Authentication
+/// If the specified one is not availbe, library will use the othe one
+
+void QXmppConfiguration::setUseSASLAuthentication(bool useSASL)
+{
+ m_useSASLAuthentication = useSASL;
+}
+
+/// Returns the specified security mode for the stream. The default value is
+/// QXmppConfiguration::TLSEnabled.
+/// \return StreamSecurityMode
+
+QXmppConfiguration::StreamSecurityMode QXmppConfiguration::streamSecurityMode() const
+{
+ return m_streamSecurityMode;
+}
+
+/// Specifies the specified security mode for the stream. The default value is
+/// QXmppConfiguration::TLSEnabled.
+/// \param mode StreamSecurityMode
+
+void QXmppConfiguration::setStreamSecurityMode(
+ QXmppConfiguration::StreamSecurityMode mode)
+{
+ m_streamSecurityMode = mode;
+}
+
+/// Returns the Non-SASL authentication mechanism configuration.
+///
+/// \return QXmppConfiguration::NonSASLAuthMechanism
+///
+
+QXmppConfiguration::NonSASLAuthMechanism QXmppConfiguration::nonSASLAuthMechanism() const
+{
+ return m_nonSASLAuthMechanism;
+}
+
+/// Hints the library the Non-SASL authentication mechanism to be used for authentication.
+///
+/// \param mech QXmppConfiguration::NonSASLAuthMechanism
+///
+
+void QXmppConfiguration::setNonSASLAuthMechanism(
+ QXmppConfiguration::NonSASLAuthMechanism mech)
+{
+ m_nonSASLAuthMechanism = mech;
+}
+
+/// Returns the SASL authentication mechanism configuration.
+///
+/// \return QXmppConfiguration::SASLAuthMechanism
+///
+
+QXmppConfiguration::SASLAuthMechanism QXmppConfiguration::sASLAuthMechanism() const
+{
+ return m_SASLAuthMechanism;
+}
+
+/// Hints the library the SASL authentication mechanism to be used for authentication.
+///
+/// \param mech QXmppConfiguration::SASLAuthMechanism
+///
+
+void QXmppConfiguration::setSASLAuthMechanism(
+ QXmppConfiguration::SASLAuthMechanism mech)
+{
+ m_SASLAuthMechanism = mech;
+}
+
+/// Specifies the network proxy used for the connection made by QXmppClient.
+/// The default value is QNetworkProxy::DefaultProxy that is the proxy is
+/// determined based on the application proxy set using
+/// QNetworkProxy::setApplicationProxy().
+/// \param proxy QNetworkProxy
+
+void QXmppConfiguration::setNetworkProxy(const QNetworkProxy& proxy)
+{
+ m_networkProxy = proxy;
+}
+
+/// Returns the specified network proxy.
+/// The default value is QNetworkProxy::DefaultProxy that is the proxy is
+/// determined based on the application proxy set using
+/// QNetworkProxy::setApplicationProxy().
+/// \return QNetworkProxy
+
+QNetworkProxy QXmppConfiguration::networkProxy() const
+{
+ return m_networkProxy;
+}
+
+/// Specifies the interval in seconds at which keep alive (ping) packets
+/// will be sent to the server.
+///
+/// If set to zero, no keep alive packets will be sent.
+///
+/// The default value is 60 seconds.
+
+void QXmppConfiguration::setKeepAliveInterval(int secs)
+{
+ m_keepAliveInterval = secs;
+}
+
+/// Returns the keep alive interval in seconds.
+///
+/// The default value is 60 seconds.
+
+int QXmppConfiguration::keepAliveInterval() const
+{
+ return m_keepAliveInterval;
+}
+
+/// Specifies the maximum time in seconds to wait for a keep alive response
+/// from the server before considering we are disconnected.
+///
+/// If set to zero or a value larger than the keep alive interval,
+/// no timeout will occur.
+///
+/// The default value is 20 seconds.
+
+void QXmppConfiguration::setKeepAliveTimeout(int secs)
+{
+ m_keepAliveTimeout = secs;
+}
+
+/// Returns the keep alive timeout in seconds.
+///
+/// The default value is 20 seconds.
+
+int QXmppConfiguration::keepAliveTimeout() const
+{
+ return m_keepAliveTimeout;
+}
+
+/// Specifies a list of trusted CA certificates.
+
+void QXmppConfiguration::setCaCertificates(const QList<QSslCertificate> &caCertificates)
+{
+ m_caCertificates = caCertificates;
+}
+
+/// Returns the a list of trusted CA certificates.
+
+QList<QSslCertificate> QXmppConfiguration::caCertificates() const
+{
+ return m_caCertificates;
+}
+
diff --git a/src/client/QXmppConfiguration.h b/src/client/QXmppConfiguration.h
new file mode 100644
index 00000000..baa54c06
--- /dev/null
+++ b/src/client/QXmppConfiguration.h
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2008-2011 The QXmpp developers
+ *
+ * Author:
+ * Manjeet Dahiya
+ *
+ * Source:
+ * http://code.google.com/p/qxmpp
+ *
+ * This file is a part of QXmpp library.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ */
+
+
+#ifndef QXMPPCONFIGURATION_H
+#define QXMPPCONFIGURATION_H
+
+#include <QString>
+#include <QNetworkProxy>
+#include <QSslCertificate>
+
+/// \brief The QXmppConfiguration class holds configuration options.
+///
+/// It can be passed to QXmppClient to specify the options when connecting to
+/// an XMPP server.
+///
+/// It is a container of all the settings, configuration required for
+/// connecting to an XMPP server. E.g. server name, username, port, type
+/// of authentication mechanism, type of security used by stream (encryption),
+/// etc..
+///
+
+class QXmppConfiguration
+{
+public:
+ /// An enumeration for type of the Security Mode that is stream is encrypted or not.
+ /// The server may or may not have TLS feature. Server may force the encryption.
+ /// Depending upon all this user can specify following options.
+ enum StreamSecurityMode
+ {
+ TLSEnabled = 0, ///< Encryption is used if available (default)
+ TLSDisabled, ///< No encryption is server allows
+ TLSRequired ///< Encryption is a must otherwise connection would not
+ ///< be established
+ };
+
+ /// An enumeration for various Non-SASL authentication mechanisms available.
+ /// The server may or may not allow QXmppConfiguration::Plain mechanism. So
+ /// specifying the mechanism is just a hint to the library.
+ enum NonSASLAuthMechanism
+ {
+ NonSASLPlain = 0,///< Plain
+ NonSASLDigest ///< Digest (default)
+ };
+
+ /// An enumeration for various SASL authentication mechanisms available.
+ /// The server may or may not allow any particular mechanism. So depending
+ /// upon the availability of mechanisms on the server the library will choose
+ /// a mechanism.
+ enum SASLAuthMechanism
+ {
+ SASLPlain = 0, ///< Plain
+ SASLDigestMD5, ///< Digest MD5 (default)
+ SASLAnonymous, ///< Anonymous
+ SASLXFacebookPlatform, ///< Facebook Platform
+ };
+
+ /// An enumeration for stream compression methods.
+ enum CompressionMethod
+ {
+ ZlibCompression = 0 ///< zlib compression
+ };
+
+ QXmppConfiguration();
+ ~QXmppConfiguration();
+
+ QString host() const;
+ void setHost(const QString&);
+
+ QString domain() const;
+ void setDomain(const QString&);
+
+ int port() const;
+ void setPort(int);
+
+ QString user() const;
+ void setUser(const QString&);
+
+ QString password() const;
+ void setPassword(const QString&);
+
+ QString resource() const;
+ void setResource(const QString&);
+
+ QString jid() const;
+ void setJid(const QString &jid);
+
+ QString jidBare() const;
+
+ QString facebookAccessToken() const;
+ void setFacebookAccessToken(const QString&);
+
+ QString facebookAppId() const;
+ void setFacebookAppId(const QString&);
+
+ bool autoAcceptSubscriptions() const;
+ void setAutoAcceptSubscriptions(bool);
+
+ bool autoReconnectionEnabled() const;
+ void setAutoReconnectionEnabled(bool);
+
+ bool useSASLAuthentication() const;
+ void setUseSASLAuthentication(bool);
+
+ bool ignoreSslErrors() const;
+ void setIgnoreSslErrors(bool);
+
+ QXmppConfiguration::StreamSecurityMode streamSecurityMode() const;
+ void setStreamSecurityMode(QXmppConfiguration::StreamSecurityMode mode);
+
+ QXmppConfiguration::NonSASLAuthMechanism nonSASLAuthMechanism() const;
+ void setNonSASLAuthMechanism(QXmppConfiguration::NonSASLAuthMechanism);
+
+ QXmppConfiguration::SASLAuthMechanism sASLAuthMechanism() const;
+ void setSASLAuthMechanism(QXmppConfiguration::SASLAuthMechanism);
+
+ QNetworkProxy networkProxy() const;
+ void setNetworkProxy(const QNetworkProxy& proxy);
+
+ int keepAliveInterval() const;
+ void setKeepAliveInterval(int secs);
+
+ int keepAliveTimeout() const;
+ void setKeepAliveTimeout(int secs);
+
+ QList<QSslCertificate> caCertificates() const;
+ void setCaCertificates(const QList<QSslCertificate> &);
+
+private:
+ QString m_host;
+ int m_port;
+ QString m_user;
+ QString m_password;
+ QString m_domain;
+ QString m_resource;
+
+ // Facebook
+ QString m_facebookAccessToken;
+ QString m_facebookAppId;
+
+ // default is false
+ bool m_autoAcceptSubscriptions;
+ // default is true
+ bool m_sendIntialPresence;
+ // default is true
+ bool m_sendRosterRequest;
+ // interval in seconds, if zero won't ping
+ int m_keepAliveInterval;
+ // interval in seconds, if zero won't timeout
+ int m_keepAliveTimeout;
+ // will keep reconnecting if disconnected, default is true
+ bool m_autoReconnectionEnabled;
+ bool m_useSASLAuthentication; ///< flag to specify what authentication system
+ ///< to be used
+ ///< defualt is true and use SASL
+ ///< false would use NonSASL if available
+ // default is true
+ bool m_ignoreSslErrors;
+
+ StreamSecurityMode m_streamSecurityMode;
+ NonSASLAuthMechanism m_nonSASLAuthMechanism;
+ SASLAuthMechanism m_SASLAuthMechanism;
+
+ QNetworkProxy m_networkProxy;
+
+ QList<QSslCertificate> m_caCertificates;
+};
+
+#endif // QXMPPCONFIGURATION_H
diff --git a/src/client/QXmppDiscoveryManager.cpp b/src/client/QXmppDiscoveryManager.cpp
new file mode 100644
index 00000000..061f6472
--- /dev/null
+++ b/src/client/QXmppDiscoveryManager.cpp
@@ -0,0 +1,235 @@
+/*
+ * Copyright (C) 2008-2011 The QXmpp developers
+ *
+ * Author:
+ * Manjeet Dahiya
+ *
+ * Source:
+ * http://code.google.com/p/qxmpp
+ *
+ * This file is a part of QXmpp library.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ */
+
+#include "QXmppDiscoveryManager.h"
+
+#include <QDomElement>
+#include <QCoreApplication>
+
+#include "QXmppClient.h"
+#include "QXmppConstants.h"
+#include "QXmppDiscoveryIq.h"
+#include "QXmppStream.h"
+#include "QXmppGlobal.h"
+
+QXmppDiscoveryManager::QXmppDiscoveryManager() : QXmppClientExtension(),
+ m_clientCapabilitiesNode("http://code.google.com/p/qxmpp"),
+ m_clientCategory("client"),
+ m_clientType("pc"),
+ m_clientName(QString("%1 %2").arg(qApp->applicationName(), qApp->applicationVersion()))
+{
+ if(qApp->applicationName().isEmpty() && qApp->applicationVersion().isEmpty())
+ {
+ m_clientName = QString("%1 %2").arg("Based on QXmpp", QXmppVersion());
+ }
+}
+
+bool QXmppDiscoveryManager::handleStanza(const QDomElement &element)
+{
+ if (element.tagName() == "iq" && QXmppDiscoveryIq::isDiscoveryIq(element))
+ {
+ QXmppDiscoveryIq receivedIq;
+ receivedIq.parse(element);
+
+ if(receivedIq.type() == QXmppIq::Get &&
+ receivedIq.queryType() == QXmppDiscoveryIq::InfoQuery &&
+ (receivedIq.queryNode().isEmpty() || receivedIq.queryNode().startsWith(m_clientCapabilitiesNode)))
+ {
+ // respond to query
+ QXmppDiscoveryIq qxmppFeatures = capabilities();
+ qxmppFeatures.setId(receivedIq.id());
+ qxmppFeatures.setTo(receivedIq.from());
+ qxmppFeatures.setQueryNode(receivedIq.queryNode());
+ client()->sendPacket(qxmppFeatures);
+ }
+ else if(receivedIq.queryType() == QXmppDiscoveryIq::InfoQuery)
+ emit infoReceived(receivedIq);
+ else if(receivedIq.queryType() == QXmppDiscoveryIq::ItemsQuery)
+ emit itemsReceived(receivedIq);
+
+ return true;
+ }
+ return false;
+}
+
+/// Requests information from the specified XMPP entity.
+///
+/// \param jid The target entity's JID.
+/// \param node The target node (optional).
+
+QString QXmppDiscoveryManager::requestInfo(const QString& jid, const QString& node)
+{
+ QXmppDiscoveryIq request;
+ request.setType(QXmppIq::Get);
+ request.setQueryType(QXmppDiscoveryIq::InfoQuery);
+ request.setTo(jid);
+ if(!node.isEmpty())
+ request.setQueryNode(node);
+ if(client()->sendPacket(request))
+ return request.id();
+ else
+ return QString();
+}
+
+/// Requests items from the specified XMPP entity.
+///
+/// \param jid The target entity's JID.
+/// \param node The target node (optional).
+
+QString QXmppDiscoveryManager::requestItems(const QString& jid, const QString& node)
+{
+ QXmppDiscoveryIq request;
+ request.setType(QXmppIq::Get);
+ request.setQueryType(QXmppDiscoveryIq::ItemsQuery);
+ request.setTo(jid);
+ if(!node.isEmpty())
+ request.setQueryNode(node);
+ if(client()->sendPacket(request))
+ return request.id();
+ else
+ return QString();
+}
+
+QStringList QXmppDiscoveryManager::discoveryFeatures() const
+{
+ return QStringList() << ns_disco_info;
+}
+
+QXmppDiscoveryIq QXmppDiscoveryManager::capabilities()
+{
+ QXmppDiscoveryIq iq;
+ iq.setType(QXmppIq::Result);
+ iq.setQueryType(QXmppDiscoveryIq::InfoQuery);
+
+ // features
+ QStringList features;
+ features
+ << ns_chat_states // XEP-0085: Chat State Notifications
+ << ns_capabilities // XEP-0115 : Entity Capabilities
+ << ns_ping // XEP-0199: XMPP Ping
+ << ns_attention; // XEP-0224: Attention
+
+ foreach(QXmppClientExtension* extension, client()->extensions())
+ {
+ if(extension)
+ features << extension->discoveryFeatures();
+ }
+
+ iq.setFeatures(features);
+
+ // identities
+ QList<QXmppDiscoveryIq::Identity> identities;
+
+ QXmppDiscoveryIq::Identity identity;
+ identity.setCategory(clientCategory());
+ identity.setType(clientType());
+ identity.setName(clientName());
+ identities << identity;
+
+ foreach(QXmppClientExtension* extension, client()->extensions())
+ {
+ if(extension)
+ identities << extension->discoveryIdentities();
+ }
+
+ iq.setIdentities(identities);
+ return iq;
+}
+
+/// Sets the capabilities node of the local XMPP client.
+///
+/// \param node
+
+void QXmppDiscoveryManager::setClientCapabilitiesNode(const QString &node)
+{
+ m_clientCapabilitiesNode = node;
+}
+
+/// Sets the category of the local XMPP client.
+///
+/// You can find a list of valid categories at:
+/// http://xmpp.org/registrar/disco-categories.html
+///
+/// \param category
+
+void QXmppDiscoveryManager::setClientCategory(const QString& category)
+{
+ m_clientCategory = category;
+}
+
+/// Sets the type of the local XMPP client.
+///
+/// You can find a list of valid types at:
+/// http://xmpp.org/registrar/disco-categories.html
+///
+/// \param type
+
+void QXmppDiscoveryManager::setClientType(const QString& type)
+{
+ m_clientType = type;
+}
+
+/// Sets the name of the local XMPP client.
+///
+/// \param name
+
+void QXmppDiscoveryManager::setClientName(const QString& name)
+{
+ m_clientName = name;
+}
+
+/// Returns the capabilities node of the local XMPP client.
+///
+/// By default this is "http://code.google.com/p/qxmpp".
+
+QString QXmppDiscoveryManager::clientCapabilitiesNode() const
+{
+ return m_clientCapabilitiesNode;
+}
+
+/// Returns the category of the local XMPP client.
+///
+/// By default this is "client".
+
+QString QXmppDiscoveryManager::clientCategory() const
+{
+ return m_clientCategory;
+}
+
+/// Returns the type of the local XMPP client.
+///
+/// By default this is "pc".
+
+QString QXmppDiscoveryManager::clientType() const
+{
+ return m_clientType;
+}
+
+/// Returns the name of the local XMPP client.
+///
+/// By default this is "Based on QXmpp x.y.z".
+
+QString QXmppDiscoveryManager::clientName() const
+{
+ return m_clientName;
+}
diff --git a/src/client/QXmppDiscoveryManager.h b/src/client/QXmppDiscoveryManager.h
new file mode 100644
index 00000000..09fe0307
--- /dev/null
+++ b/src/client/QXmppDiscoveryManager.h
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2008-2011 The QXmpp developers
+ *
+ * Author:
+ * Manjeet Dahiya
+ *
+ * Source:
+ * http://code.google.com/p/qxmpp
+ *
+ * This file is a part of QXmpp library.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ */
+
+#ifndef QXMPPDISCOVERYMANAGER_H
+#define QXMPPDISCOVERYMANAGER_H
+
+#include "QXmppClientExtension.h"
+
+class QXmppDiscoveryIq;
+
+/// \brief The QXmppDiscoveryManager class makes it possible to discover information
+/// about other entities as defined by XEP-0030: Service Discovery.
+///
+/// \ingroup Managers
+
+class QXmppDiscoveryManager : public QXmppClientExtension
+{
+ Q_OBJECT
+
+public:
+ QXmppDiscoveryManager();
+
+ QString requestInfo(const QString& jid, const QString& node = "");
+ QString requestItems(const QString& jid, const QString& node = "");
+
+ QString clientCapabilitiesNode() const;
+ void setClientCapabilitiesNode(const QString&);
+
+ // http://xmpp.org/registrar/disco-categories.html#client
+ QString clientCategory() const;
+ void setClientCategory(const QString&);
+
+ void setClientName(const QString&);
+ QString clientName() const;
+
+ QString clientType() const;
+ void setClientType(const QString&);
+
+ /// \cond
+ QStringList discoveryFeatures() const;
+ bool handleStanza(const QDomElement &element);
+ QXmppDiscoveryIq capabilities();
+ /// \endcond
+
+signals:
+ /// This signal is emitted when an information response is received.
+ void infoReceived(const QXmppDiscoveryIq&);
+
+ /// This signal is emitted when an items response is received.
+ void itemsReceived(const QXmppDiscoveryIq&);
+
+private:
+ QString m_clientCapabilitiesNode;
+ QString m_clientCategory;
+ QString m_clientType;
+ QString m_clientName;
+};
+
+#endif // QXMPPDISCOVERYMANAGER_H
diff --git a/src/client/QXmppEntityTimeManager.cpp b/src/client/QXmppEntityTimeManager.cpp
new file mode 100644
index 00000000..59d54c4d
--- /dev/null
+++ b/src/client/QXmppEntityTimeManager.cpp
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2008-2011 The QXmpp developers
+ *
+ * Author:
+ * Manjeet Dahiya
+ *
+ * Source:
+ * http://code.google.com/p/qxmpp
+ *
+ * This file is a part of QXmpp library.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ */
+
+
+#include "QXmppEntityTimeManager.h"
+
+#include <QDomElement>
+#include <QDateTime>
+
+#include "QXmppClient.h"
+#include "QXmppConstants.h"
+#include "QXmppEntityTimeIq.h"
+#include "QXmppUtils.h"
+
+/// Request the time from an XMPP entity.
+///
+/// \param jid
+
+QString QXmppEntityTimeManager::requestTime(const QString& jid)
+{
+ QXmppEntityTimeIq request;
+ request.setType(QXmppIq::Get);
+ request.setTo(jid);
+ if(client()->sendPacket(request))
+ return request.id();
+ else
+ return QString();
+}
+
+QStringList QXmppEntityTimeManager::discoveryFeatures() const
+{
+ return QStringList() << ns_entity_time;
+}
+
+bool QXmppEntityTimeManager::handleStanza(const QDomElement &element)
+{
+ if(element.tagName() == "iq" && QXmppEntityTimeIq::isEntityTimeIq(element))
+ {
+ QXmppEntityTimeIq entityTime;
+ entityTime.parse(element);
+
+ if(entityTime.type() == QXmppIq::Get)
+ {
+ // respond to query
+ QXmppEntityTimeIq responseIq;
+ responseIq.setType(QXmppIq::Result);
+ responseIq.setId(entityTime.id());
+ responseIq.setTo(entityTime.from());
+
+ QDateTime currentTime = QDateTime::currentDateTime();
+ QDateTime utc = currentTime.toUTC();
+ responseIq.setUtc(utc);
+
+ currentTime.setTimeSpec(Qt::UTC);
+ responseIq.setTzo(utc.secsTo(currentTime));
+
+ client()->sendPacket(responseIq);
+ }
+
+ emit timeReceived(entityTime);
+ return true;
+ }
+
+ return false;
+}
diff --git a/src/client/QXmppEntityTimeManager.h b/src/client/QXmppEntityTimeManager.h
new file mode 100644
index 00000000..941334d3
--- /dev/null
+++ b/src/client/QXmppEntityTimeManager.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2008-2011 The QXmpp developers
+ *
+ * Author:
+ * Manjeet Dahiya
+ *
+ * Source:
+ * http://code.google.com/p/qxmpp
+ *
+ * This file is a part of QXmpp library.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ */
+
+
+#ifndef QXMPPENTITYTIMEMANAGER_H
+#define QXMPPENTITYTIMEMANAGER_H
+
+#include "QXmppClientExtension.h"
+
+class QXmppEntityTimeIq;
+
+/// \brief The QXmppEntityTimeManager class provided the functionality to get
+/// the local time of an entity as defined by XEP-0202: Entity Time.
+///
+/// \ingroup Managers
+
+class QXmppEntityTimeManager : public QXmppClientExtension
+{
+ Q_OBJECT
+
+public:
+ QString requestTime(const QString& jid);
+
+ /// \cond
+ QStringList discoveryFeatures() const;
+ bool handleStanza(const QDomElement &element);
+ /// \endcond
+
+signals:
+ /// \brief This signal is emitted when a time response is received.
+ void timeReceived(const QXmppEntityTimeIq&);
+};
+
+#endif // QXMPPENTITYTIMEMANAGER_H
diff --git a/src/client/QXmppMessageReceiptManager.cpp b/src/client/QXmppMessageReceiptManager.cpp
new file mode 100644
index 00000000..b78c1a77
--- /dev/null
+++ b/src/client/QXmppMessageReceiptManager.cpp
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2008-2011 The QXmpp developers
+ *
+ * Author:
+ * Georg Rudoy
+ * Jeremy Lainé
+ *
+ * Source:
+ * http://code.google.com/p/qxmpp
+ *
+ * This file is a part of QXmpp library.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ */
+
+#include "QXmppMessageReceiptManager.h"
+
+#include <QDomElement>
+
+#include "QXmppConstants.h"
+#include "QXmppMessage.h"
+#include "QXmppClient.h"
+
+/// Constructs a QXmppMessageReceiptManager to handle incoming and outgoing
+/// message delivery receipts.
+
+QXmppMessageReceiptManager::QXmppMessageReceiptManager()
+ : QXmppClientExtension()
+{
+}
+
+QStringList QXmppMessageReceiptManager::discoveryFeatures() const
+{
+ return QStringList(ns_message_receipts);
+}
+
+bool QXmppMessageReceiptManager::handleStanza(const QDomElement &stanza)
+{
+ if (stanza.tagName() != "message")
+ return false;
+
+ QXmppMessage message;
+ message.parse(stanza);
+
+ // Handle receipts and cancel any further processing.
+ if (!message.receiptId().isEmpty()) {
+ emit messageDelivered(message.from(), message.receiptId());
+ return true;
+ }
+
+ // If requested, send a receipt.
+ if (message.isReceiptRequested()
+ && !message.from().isEmpty()
+ && !message.id().isEmpty()) {
+ QXmppMessage receipt;
+ receipt.setTo(message.from());
+ receipt.setReceiptId(message.id());
+ client()->sendPacket(receipt);
+ }
+
+ // Continue processing.
+ return false;
+}
diff --git a/src/client/QXmppMessageReceiptManager.h b/src/client/QXmppMessageReceiptManager.h
new file mode 100644
index 00000000..012eec96
--- /dev/null
+++ b/src/client/QXmppMessageReceiptManager.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2008-2011 The QXmpp developers
+ *
+ * Authors:
+ * Georg Rudoy
+ * Jeremy Lainé
+ *
+ * Source:
+ * http://code.google.com/p/qxmpp
+ *
+ * This file is a part of QXmpp library.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ */
+
+#ifndef QXMPPMESSAGERECEIPTMANAGER_H
+#define QXMPPMESSAGERECEIPTMANAGER_H
+
+#include "QXmppClientExtension.h"
+
+/// \brief The QXmppMessageReceiptManager class makes it possible to
+/// send and receive message delivery receipts as defined in
+/// XEP-0184: Message Delivery Receipts.
+///
+/// \ingroup Managers
+
+class QXmppMessageReceiptManager : public QXmppClientExtension
+{
+ Q_OBJECT
+public:
+ QXmppMessageReceiptManager();
+ ~QXmppMessageReceiptManager();
+
+ /// \cond
+ virtual QStringList discoveryFeatures() const;
+ virtual bool handleStanza(const QDomElement &stanza);
+ /// \endcond
+
+signals:
+ /// This signal is emitted when receipt for the message with the
+ /// given id is received. The id could be previously obtained by
+ /// calling QXmppMessage::id().
+ void messageDelivered(const QString &jid, const QString &id);
+};
+
+#endif // QXMPPMESSAGERECEIPTMANAGER_H
diff --git a/src/client/QXmppMucManager.cpp b/src/client/QXmppMucManager.cpp
new file mode 100644
index 00000000..3f1cdef3
--- /dev/null
+++ b/src/client/QXmppMucManager.cpp
@@ -0,0 +1,661 @@
+/*
+ * Copyright (C) 2008-2011 The QXmpp developers
+ *
+ * Author:
+ * Jeremy Lainé
+ *
+ * Source:
+ * http://code.google.com/p/qxmpp
+ *
+ * This file is a part of QXmpp library.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ */
+
+#include <QDomElement>
+#include <QMap>
+
+#include "QXmppClient.h"
+#include "QXmppConstants.h"
+#include "QXmppMessage.h"
+#include "QXmppMucIq.h"
+#include "QXmppMucManager.h"
+#include "QXmppUtils.h"
+
+class QXmppMucManagerPrivate
+{
+public:
+ QMap<QString, QXmppMucRoom*> rooms;
+};
+
+class QXmppMucRoomPrivate
+{
+public:
+ QString ownJid() const { return jid + "/" + nickName; }
+ QXmppClient *client;
+ QXmppMucRoom::Actions allowedActions;
+ QString jid;
+ QMap<QString, QXmppPresence> participants;
+ QString password;
+ QMap<QString, QXmppMucItem> permissions;
+ QSet<QString> permissionsQueue;
+ QString nickName;
+ QString subject;
+};
+
+/// Constructs a new QXmppMucManager.
+
+QXmppMucManager::QXmppMucManager()
+{
+ d = new QXmppMucManagerPrivate;
+}
+
+/// Destroys a QXmppMucManager.
+
+QXmppMucManager::~QXmppMucManager()
+{
+ delete d;
+}
+
+/// Adds the given chat room to the set of managed rooms.
+///
+/// \param roomJid
+
+QXmppMucRoom *QXmppMucManager::addRoom(const QString &roomJid)
+{
+ QXmppMucRoom *room = d->rooms.value(roomJid);
+ if (!room) {
+ room = new QXmppMucRoom(client(), roomJid, this);
+ d->rooms.insert(roomJid, room);
+ connect(room, SIGNAL(destroyed(QObject*)),
+ this, SLOT(_q_roomDestroyed(QObject*)));
+
+ // emit signal
+ emit roomAdded(room);
+ }
+ return room;
+}
+
+/// Returns the list of managed rooms.
+
+QList<QXmppMucRoom*> QXmppMucManager::rooms() const
+{
+ return d->rooms.values();
+}
+
+void QXmppMucManager::setClient(QXmppClient* client)
+{
+ bool check;
+ Q_UNUSED(check);
+
+ QXmppClientExtension::setClient(client);
+
+ check = connect(client, SIGNAL(messageReceived(QXmppMessage)),
+ this, SLOT(_q_messageReceived(QXmppMessage)));
+ Q_ASSERT(check);
+}
+
+QStringList QXmppMucManager::discoveryFeatures() const
+{
+ // XEP-0045: Multi-User Chat
+ return QStringList()
+ << ns_muc
+ << ns_muc_admin
+ << ns_muc_owner
+ << ns_muc_user;
+}
+
+bool QXmppMucManager::handleStanza(const QDomElement &element)
+{
+ if (element.tagName() == "iq")
+ {
+ if (QXmppMucAdminIq::isMucAdminIq(element))
+ {
+ QXmppMucAdminIq iq;
+ iq.parse(element);
+
+ QXmppMucRoom *room = d->rooms.value(iq.from());
+ if (room && iq.type() == QXmppIq::Result && room->d->permissionsQueue.remove(iq.id())) {
+ foreach (const QXmppMucItem &item, iq.items()) {
+ const QString jid = item.jid();
+ if (!room->d->permissions.contains(jid))
+ room->d->permissions.insert(jid, item);
+ }
+ if (room->d->permissionsQueue.isEmpty()) {
+ emit room->permissionsReceived(room->d->permissions.values());
+ }
+ }
+ return true;
+ }
+ else if (QXmppMucOwnerIq::isMucOwnerIq(element))
+ {
+ QXmppMucOwnerIq iq;
+ iq.parse(element);
+
+ QXmppMucRoom *room = d->rooms.value(iq.from());
+ if (room && iq.type() == QXmppIq::Result && !iq.form().isNull())
+ emit room->configurationReceived(iq.form());
+ return true;
+ }
+ }
+ return false;
+}
+
+void QXmppMucManager::_q_messageReceived(const QXmppMessage &msg)
+{
+ if (msg.type() != QXmppMessage::Normal)
+ return;
+
+ // process room invitations
+ foreach (const QXmppElement &extension, msg.extensions())
+ {
+ if (extension.tagName() == "x" && extension.attribute("xmlns") == ns_conference)
+ {
+ const QString roomJid = extension.attribute("jid");
+ if (!roomJid.isEmpty() && (!d->rooms.contains(roomJid) || !d->rooms.value(roomJid)->isJoined()))
+ emit invitationReceived(roomJid, msg.from(), extension.attribute("reason"));
+ break;
+ }
+ }
+}
+
+void QXmppMucManager::_q_roomDestroyed(QObject *object)
+{
+ const QString key = d->rooms.key(static_cast<QXmppMucRoom*>(object));
+ d->rooms.remove(key);
+}
+
+/// Constructs a new QXmppMucRoom.
+///
+/// \param parent
+
+QXmppMucRoom::QXmppMucRoom(QXmppClient *client, const QString &jid, QObject *parent)
+ : QObject(parent)
+{
+ bool check;
+ Q_UNUSED(check);
+
+ d = new QXmppMucRoomPrivate;
+ d->allowedActions = NoAction;
+ d->client = client;
+ d->jid = jid;
+
+ check = connect(d->client, SIGNAL(disconnected()),
+ this, SLOT(_q_disconnected()));
+ Q_ASSERT(check);
+
+ check = connect(d->client, SIGNAL(messageReceived(QXmppMessage)),
+ this, SLOT(_q_messageReceived(QXmppMessage)));
+ Q_ASSERT(check);
+
+ check = connect(d->client, SIGNAL(presenceReceived(QXmppPresence)),
+ this, SLOT(_q_presenceReceived(QXmppPresence)));
+ Q_ASSERT(check);
+
+ // convenience signals for properties
+ check = connect(this, SIGNAL(joined()), this, SIGNAL(isJoinedChanged()));
+ Q_ASSERT(check);
+
+ check = connect(this, SIGNAL(left()), this, SIGNAL(isJoinedChanged()));
+ Q_ASSERT(check);
+}
+
+/// Destroys a QXmppMucRoom.
+
+QXmppMucRoom::~QXmppMucRoom()
+{
+ delete d;
+}
+
+/// Returns the actions you are allowed to perform on the room.
+
+QXmppMucRoom::Actions QXmppMucRoom::allowedActions() const
+{
+ return d->allowedActions;
+}
+
+/// Returns true if you are currently in the room.
+
+bool QXmppMucRoom::isJoined() const
+{
+ return d->participants.contains(d->ownJid());
+}
+
+/// Returns the chat room's bare JID.
+
+QString QXmppMucRoom::jid() const
+{
+ return d->jid;
+}
+
+/// Joins the chat room.
+///
+/// \return true if the request was sent, false otherwise
+
+bool QXmppMucRoom::join()
+{
+ if (isJoined() || d->nickName.isEmpty())
+ return false;
+
+ // reflect our current presence in the chat room
+ QXmppPresence packet = d->client->clientPresence();
+ packet.setTo(d->ownJid());
+ packet.setType(QXmppPresence::Available);
+ QXmppElement x;
+ x.setTagName("x");
+ x.setAttribute("xmlns", ns_muc);
+ if (!d->password.isEmpty())
+ {
+ QXmppElement p;
+ p.setTagName("password");
+ p.setValue(d->password);
+ x.appendChild(p);
+ }
+ packet.setExtensions(x);
+ return d->client->sendPacket(packet);
+}
+
+/// Kicks the specified user from the chat room.
+///
+/// \param jid
+/// \param reason
+///
+/// \return true if the request was sent, false otherwise
+
+bool QXmppMucRoom::kick(const QString &jid, const QString &reason)
+{
+ QXmppMucItem item;
+ item.setNick(jidToResource(jid));
+ item.setRole(QXmppMucItem::NoRole);
+ item.setReason(reason);
+
+ QXmppMucAdminIq iq;
+ iq.setType(QXmppIq::Set);
+ iq.setTo(d->jid);
+ iq.setItems(QList<QXmppMucItem>() << item);
+
+ return d->client->sendPacket(iq);
+}
+
+/// Leaves the chat room.
+///
+/// \param message An optional message.
+///
+/// \return true if the request was sent, false otherwise
+
+bool QXmppMucRoom::leave(const QString &message)
+{
+ QXmppPresence packet;
+ packet.setTo(d->ownJid());
+ packet.setType(QXmppPresence::Unavailable);
+
+ QXmppPresence::Status status;
+ status.setStatusText(message);
+ packet.setStatus(status);
+ return d->client->sendPacket(packet);
+}
+
+/// Returns your own nickname.
+
+QString QXmppMucRoom::nickName() const
+{
+ return d->nickName;
+}
+
+/// Invites a user to the chat room.
+///
+/// \param jid
+/// \param reason
+///
+/// \return true if the request was sent, false otherwise
+
+bool QXmppMucRoom::sendInvitation(const QString &jid, const QString &reason)
+{
+ QXmppElement x;
+ x.setTagName("x");
+ x.setAttribute("xmlns", ns_conference);
+ x.setAttribute("jid", d->jid);
+ x.setAttribute("reason", reason);
+
+ QXmppMessage message;
+ message.setTo(jid);
+ message.setType(QXmppMessage::Normal);
+ message.setExtensions(x);
+ return d->client->sendPacket(message);
+}
+
+/// Sends a message to the room.
+///
+/// \return true if the request was sent, false otherwise
+
+bool QXmppMucRoom::sendMessage(const QString &text)
+{
+ QXmppMessage msg;
+ msg.setTo(d->jid);
+ msg.setType(QXmppMessage::GroupChat);
+ msg.setBody(text);
+ return d->client->sendPacket(msg);
+}
+
+/// Sets your own nickname.
+///
+/// You need to set your nickname before calling join().
+///
+/// \param nickName
+
+void QXmppMucRoom::setNickName(const QString &nickName)
+{
+ if (nickName == d->nickName)
+ return;
+
+ const bool wasJoined = isJoined();
+ d->nickName = nickName;
+ emit nickNameChanged(nickName);
+
+ // if we had already joined the room, request nickname change
+ if (wasJoined) {
+ QXmppPresence packet = d->client->clientPresence();
+ packet.setTo(d->ownJid());
+ packet.setType(QXmppPresence::Available);
+ d->client->sendPacket(packet);
+ }
+}
+
+/// Returns the presence for the given participant.
+///
+/// \param jid
+
+QXmppPresence QXmppMucRoom::participantPresence(const QString &jid) const
+{
+ if (d->participants.contains(jid))
+ return d->participants.value(jid);
+
+ QXmppPresence presence;
+ presence.setFrom(jid);
+ presence.setType(QXmppPresence::Unavailable);
+ return presence;
+}
+
+/// Returns the list of participant JIDs.
+
+QStringList QXmppMucRoom::participants() const
+{
+ return d->participants.keys();
+}
+
+/// Returns the chat room password.
+
+QString QXmppMucRoom::password() const
+{
+ return d->password;
+}
+
+/// Sets the chat room password.
+///
+/// \param password
+
+void QXmppMucRoom::setPassword(const QString &password)
+{
+ d->password = password;
+}
+
+/// Returns the room's subject.
+
+QString QXmppMucRoom::subject() const
+{
+ return d->subject;
+}
+
+/// Sets the chat room's subject.
+///
+/// \param subject
+
+void QXmppMucRoom::setSubject(const QString &subject)
+{
+ QXmppMessage msg;
+ msg.setTo(d->jid);
+ msg.setType(QXmppMessage::GroupChat);
+ msg.setSubject(subject);
+ d->client->sendPacket(msg);
+}
+
+/// Request the configuration form for the chat room.
+///
+/// \return true if the request was sent, false otherwise
+///
+/// \sa configurationReceived()
+
+bool QXmppMucRoom::requestConfiguration()
+{
+ QXmppMucOwnerIq iq;
+ iq.setTo(d->jid);
+ return d->client->sendPacket(iq);
+}
+
+/// Send the configuration form for the chat room.
+///
+/// \param form
+///
+/// \return true if the request was sent, false otherwise
+
+bool QXmppMucRoom::setConfiguration(const QXmppDataForm &form)
+{
+ QXmppMucOwnerIq iqPacket;
+ iqPacket.setType(QXmppIq::Set);
+ iqPacket.setTo(d->jid);
+ iqPacket.setForm(form);
+ return d->client->sendPacket(iqPacket);
+}
+
+/// Request the room's permissions.
+///
+/// \return true if the request was sent, false otherwise
+///
+/// \sa permissionsReceived()
+
+bool QXmppMucRoom::requestPermissions()
+{
+ QList<QXmppMucItem::Affiliation> affiliations;
+ affiliations << QXmppMucItem::OwnerAffiliation;
+ affiliations << QXmppMucItem::AdminAffiliation;
+ affiliations << QXmppMucItem::MemberAffiliation;
+ affiliations << QXmppMucItem::OutcastAffiliation;
+
+ d->permissions.clear();
+ d->permissionsQueue.clear();
+ foreach (QXmppMucItem::Affiliation affiliation, affiliations) {
+ QXmppMucItem item;
+ item.setAffiliation(affiliation);
+
+ QXmppMucAdminIq iq;
+ iq.setTo(d->jid);
+ iq.setItems(QList<QXmppMucItem>() << item);
+ if (!d->client->sendPacket(iq))
+ return false;
+ d->permissionsQueue += iq.id();
+ }
+ return true;
+}
+
+/// Sets the room's permissions.
+///
+/// \param permissions
+///
+/// \return true if the request was sent, false otherwise
+
+bool QXmppMucRoom::setPermissions(const QList<QXmppMucItem> &permissions)
+{
+ QList<QXmppMucItem> items;
+
+ // Process changed members
+ foreach (const QXmppMucItem &item, permissions) {
+ const QString jid = item.jid();
+ if (d->permissions.value(jid).affiliation() != item.affiliation())
+ items << item;
+ d->permissions.remove(jid);
+ }
+
+ // Process deleted members
+ foreach (const QString &jid, d->permissions.keys()) {
+ QXmppMucItem item;
+ item.setAffiliation(QXmppMucItem::NoAffiliation);
+ item.setJid(jid);
+ items << item;
+ d->permissions.remove(jid);
+ }
+
+ // Don't send request if there are no changes
+ if (items.isEmpty())
+ return false;
+
+ QXmppMucAdminIq iq;
+ iq.setTo(d->jid);
+ iq.setType(QXmppIq::Set);
+ iq.setItems(items);
+ return d->client->sendPacket(iq);
+}
+
+void QXmppMucRoom::_q_disconnected()
+{
+ const bool wasJoined = isJoined();
+
+ // clear chat room participants
+ const QStringList removed = d->participants.keys();
+ d->participants.clear();
+ foreach (const QString &jid, removed)
+ emit participantRemoved(jid);
+ emit participantsChanged();
+
+ // update available actions
+ if (d->allowedActions != NoAction) {
+ d->allowedActions = NoAction;
+ emit allowedActionsChanged(d->allowedActions);
+ }
+
+ // emit "left" signal if we had joined the room
+ if (wasJoined)
+ emit left();
+}
+
+void QXmppMucRoom::_q_messageReceived(const QXmppMessage &message)
+{
+ if (jidToBareJid(message.from())!= d->jid)
+ return;
+
+ // handle message subject
+ const QString subject = message.subject();
+ if (!subject.isEmpty()) {
+ d->subject = subject;
+ emit subjectChanged(subject);
+ }
+
+ emit messageReceived(message);
+}
+
+void QXmppMucRoom::_q_presenceReceived(const QXmppPresence &presence)
+{
+ const QString jid = presence.from();
+
+ // if our own presence changes, reflect it in the chat room
+ if (isJoined() && jid == d->client->configuration().jid()) {
+ QXmppPresence packet = d->client->clientPresence();
+ packet.setTo(d->ownJid());
+ d->client->sendPacket(packet);
+ }
+
+ if (jidToBareJid(jid) != d->jid)
+ return;
+
+ if (presence.type() == QXmppPresence::Available) {
+ const bool added = !d->participants.contains(jid);
+ d->participants.insert(jid, presence);
+
+ // refresh allowed actions
+ if (jid == d->ownJid()) {
+
+ QXmppMucItem mucItem = presence.mucItem();
+ Actions newActions = NoAction;
+
+ // role
+ if (mucItem.role() == QXmppMucItem::ModeratorRole)
+ newActions |= (KickAction | SubjectAction);
+
+ // affiliation
+ if (mucItem.affiliation() == QXmppMucItem::OwnerAffiliation)
+ newActions |= (ConfigurationAction | PermissionsAction | SubjectAction);
+ else if (mucItem.affiliation() == QXmppMucItem::AdminAffiliation)
+ newActions |= (PermissionsAction | SubjectAction);
+
+ if (newActions != d->allowedActions) {
+ d->allowedActions = newActions;
+ emit allowedActionsChanged(d->allowedActions);
+ }
+ }
+
+ if (added) {
+ emit participantAdded(jid);
+ emit participantsChanged();
+ if (jid == d->ownJid())
+ emit joined();
+ } else {
+ emit participantChanged(jid);
+ }
+ }
+ else if (presence.type() == QXmppPresence::Unavailable) {
+ if (d->participants.contains(jid)) {
+ d->participants.insert(jid, presence);
+
+ emit participantRemoved(jid);
+ d->participants.remove(jid);
+ emit participantsChanged();
+
+ // check whether this was our own presence
+ if (jid == d->ownJid()) {
+
+ // check whether we were kicked
+ if (presence.mucStatusCodes().contains(307)) {
+ const QString actor = presence.mucItem().actor();
+ const QString reason = presence.mucItem().reason();
+ emit kicked(actor, reason);
+ }
+
+ // clear chat room participants
+ const QStringList removed = d->participants.keys();
+ d->participants.clear();
+ foreach (const QString &jid, removed)
+ emit participantRemoved(jid);
+ emit participantsChanged();
+
+ // update available actions
+ if (d->allowedActions != NoAction) {
+ d->allowedActions = NoAction;
+ emit allowedActionsChanged(d->allowedActions);
+ }
+
+ // notify user we left the room
+ emit left();
+ }
+ }
+ }
+ else if (presence.type() == QXmppPresence::Error) {
+ foreach (const QXmppElement &extension, presence.extensions()) {
+ if (extension.tagName() == "x" && extension.attribute("xmlns") == ns_muc) {
+ // emit error
+ emit error(presence.error());
+
+ // notify the user we left the room
+ emit left();
+ break;
+ }
+ }
+ }
+}
diff --git a/src/client/QXmppMucManager.h b/src/client/QXmppMucManager.h
new file mode 100644
index 00000000..3953a0fc
--- /dev/null
+++ b/src/client/QXmppMucManager.h
@@ -0,0 +1,214 @@
+/*
+ * Copyright (C) 2008-2011 The QXmpp developers
+ *
+ * Author:
+ * Jeremy Lainé
+ *
+ * Source:
+ * http://code.google.com/p/qxmpp
+ *
+ * This file is a part of QXmpp library.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ */
+
+#ifndef QXMPPMUCMANAGER_H
+#define QXMPPMUCMANAGER_H
+
+#include "QXmppClientExtension.h"
+#include "QXmppMucIq.h"
+#include "QXmppPresence.h"
+
+class QXmppDataForm;
+class QXmppMessage;
+class QXmppMucManagerPrivate;
+class QXmppMucRoom;
+class QXmppMucRoomPrivate;
+
+/// \brief The QXmppMucManager class makes it possible to interact with
+/// multi-user chat rooms as defined by XEP-0045: Multi-User Chat.
+///
+/// To make use of this manager, you need to instantiate it and load it into
+/// the QXmppClient instance as follows:
+///
+/// \code
+/// QXmppMucManager *manager = new QXmppMucManager;
+/// client->addExtension(manager);
+/// \endcode
+///
+/// You can then join a room as follows:
+///
+/// \code
+/// QXmppMucRoom *room = manager->addRoom("room@conference.example.com");
+/// room->setNickName("mynick");
+/// room->join();
+/// \endcode
+///
+/// \ingroup Managers
+
+class QXmppMucManager : public QXmppClientExtension
+{
+ Q_OBJECT
+ Q_PROPERTY(QList<QXmppMucRoom*> rooms READ rooms NOTIFY roomAdded)
+
+public:
+ QXmppMucManager();
+ ~QXmppMucManager();
+
+ QXmppMucRoom *addRoom(const QString &roomJid);
+ QList<QXmppMucRoom*> rooms() const;
+
+ /// \cond
+ QStringList discoveryFeatures() const;
+ bool handleStanza(const QDomElement &element);
+ /// \endcond
+
+signals:
+ /// This signal is emitted when an invitation to a chat room is received.
+ void invitationReceived(const QString &roomJid, const QString &inviter, const QString &reason);
+
+ /// This signal is emitted when a new room is managed.
+ void roomAdded(QXmppMucRoom *room);
+
+protected:
+ /// \cond
+ void setClient(QXmppClient* client);
+ /// \endcond
+
+private slots:
+ void _q_messageReceived(const QXmppMessage &message);
+ void _q_roomDestroyed(QObject *object);
+
+private:
+ QXmppMucManagerPrivate *d;
+};
+
+/// \brief The QXmppMucRoom class represents a multi-user chat room
+/// as defined by XEP-0045: Multi-User Chat.
+///
+/// \sa QXmppMucManager
+
+class QXmppMucRoom : public QObject
+{
+ Q_OBJECT
+ Q_FLAGS(Action Actions)
+ Q_PROPERTY(QXmppMucRoom::Actions allowedActions READ allowedActions NOTIFY allowedActionsChanged)
+ Q_PROPERTY(bool isJoined READ isJoined NOTIFY isJoinedChanged)
+ Q_PROPERTY(QString jid READ jid CONSTANT)
+ Q_PROPERTY(QString nickName READ nickName WRITE setNickName NOTIFY nickNameChanged)
+ Q_PROPERTY(QStringList participants READ participants NOTIFY participantsChanged)
+ Q_PROPERTY(QString password READ password WRITE setPassword)
+ Q_PROPERTY(QString subject READ subject WRITE setSubject NOTIFY subjectChanged)
+
+public:
+
+ /// This enum is used to describe chat room actions.
+ enum Action {
+ NoAction = 0, ///< no action
+ SubjectAction = 1, ///< change the room's subject
+ ConfigurationAction = 2, ///< change the room's configuration
+ PermissionsAction = 4, ///< change the room's permissions
+ KickAction = 8, ///< kick users from the room
+ };
+ Q_DECLARE_FLAGS(Actions, Action)
+
+ ~QXmppMucRoom();
+
+ Actions allowedActions() const;
+ bool isJoined() const;
+ QString jid() const;
+
+ QString nickName() const;
+ void setNickName(const QString &nickName);
+
+ QXmppPresence participantPresence(const QString &jid) const;
+ QStringList participants() const;
+
+ QString password() const;
+ void setPassword(const QString &password);
+
+ QString subject() const;
+ void setSubject(const QString &subject);
+
+signals:
+ /// This signal is emitted when the allowed actions change.
+ void allowedActionsChanged(QXmppMucRoom::Actions actions) const;
+
+ /// This signal is emitted when the configuration form for the room is received.
+ void configurationReceived(const QXmppDataForm &configuration);
+
+ /// This signal is emitted when an error is encountered.
+ void error(const QXmppStanza::Error &error);
+
+ /// This signal is emitted once you have joined the room.
+ void joined();
+
+ /// This signal is emitted if you get kicked from the room.
+ void kicked(const QString &jid, const QString &reason);
+
+ /// \cond
+ void isJoinedChanged();
+ /// \endcond
+
+ /// This signal is emitted once you have left the room.
+ void left();
+
+ /// This signal is emitted when a message is received.
+ void messageReceived(const QXmppMessage &message);
+
+ /// This signal is emitted when your own nick name changes.
+ void nickNameChanged(const QString &nickName);
+
+ /// This signal is emitted when a participant joins the room.
+ void participantAdded(const QString &jid);
+
+ /// This signal is emitted when a participant changes.
+ void participantChanged(const QString &jid);
+
+ /// This signal is emitted when a participant leaves the room.
+ void participantRemoved(const QString &jid);
+
+ /// \cond
+ void participantsChanged();
+ /// \endcond
+
+ /// This signal is emitted when the room's permissions are received.
+ void permissionsReceived(const QList<QXmppMucItem> &permissions);
+
+ /// This signal is emitted when the room's subject changes.
+ void subjectChanged(const QString &subject);
+
+public slots:
+ bool join();
+ bool kick(const QString &jid, const QString &reason);
+ bool leave(const QString &message = QString());
+ bool requestConfiguration();
+ bool requestPermissions();
+ bool setConfiguration(const QXmppDataForm &form);
+ bool setPermissions(const QList<QXmppMucItem> &permissions);
+ bool sendInvitation(const QString &jid, const QString &reason);
+ bool sendMessage(const QString &text);
+
+private slots:
+ void _q_disconnected();
+ void _q_messageReceived(const QXmppMessage &message);
+ void _q_presenceReceived(const QXmppPresence &presence);
+
+private:
+ QXmppMucRoom(QXmppClient *client, const QString &jid, QObject *parent);
+ QXmppMucRoomPrivate *d;
+ friend class QXmppMucManager;
+};
+
+Q_DECLARE_OPERATORS_FOR_FLAGS(QXmppMucRoom::Actions)
+
+#endif
diff --git a/src/client/QXmppOutgoingClient.cpp b/src/client/QXmppOutgoingClient.cpp
new file mode 100644
index 00000000..7953dad2
--- /dev/null
+++ b/src/client/QXmppOutgoingClient.cpp
@@ -0,0 +1,773 @@
+/*
+ * Copyright (C) 2008-2011 The QXmpp developers
+ *
+ * Authors:
+ * Manjeet Dahiya
+ * Jeremy Lainé
+ *
+ * Source:
+ * http://code.google.com/p/qxmpp
+ *
+ * This file is a part of QXmpp library.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ */
+
+#include <QCryptographicHash>
+#include <QSslSocket>
+#include <QUrl>
+#include "qdnslookup.h"
+
+#include "QXmppConfiguration.h"
+#include "QXmppConstants.h"
+#include "QXmppIq.h"
+#include "QXmppLogger.h"
+#include "QXmppMessage.h"
+#include "QXmppPacket.h"
+#include "QXmppPresence.h"
+#include "QXmppOutgoingClient.h"
+#include "QXmppStreamFeatures.h"
+#include "QXmppNonSASLAuth.h"
+#include "QXmppSaslAuth.h"
+#include "QXmppUtils.h"
+
+// IQ types
+#include "QXmppBindIq.h"
+#include "QXmppPingIq.h"
+#include "QXmppSessionIq.h"
+
+#include <QBuffer>
+#include <QCoreApplication>
+#include <QDomDocument>
+#include <QStringList>
+#include <QRegExp>
+#include <QHostAddress>
+#include <QXmlStreamWriter>
+#include <QTimer>
+
+class QXmppOutgoingClientPrivate
+{
+public:
+ QXmppOutgoingClientPrivate();
+
+ // This object provides the configuration
+ // required for connecting to the XMPP server.
+ QXmppConfiguration config;
+ QXmppStanza::Error::Condition xmppStreamError;
+
+ // DNS
+ QDnsLookup dns;
+
+ // Stream
+ QString streamId;
+ QString streamFrom;
+ QString streamVersion;
+
+ // Session
+ QString bindId;
+ QString sessionId;
+ bool sessionAvailable;
+ bool sessionStarted;
+
+ // Authentication
+ QString nonSASLAuthId;
+ QXmppSaslDigestMd5 saslDigest;
+ int saslDigestStep;
+ int saslMechanism;
+
+ // Timers
+ QTimer *pingTimer;
+ QTimer *timeoutTimer;
+};
+
+QXmppOutgoingClientPrivate::QXmppOutgoingClientPrivate()
+ : sessionAvailable(false),
+ saslDigestStep(0),
+ saslMechanism(-1)
+{
+}
+
+/// Constructs an outgoing client stream.
+///
+/// \param parent
+
+QXmppOutgoingClient::QXmppOutgoingClient(QObject *parent)
+ : QXmppStream(parent),
+ d(new QXmppOutgoingClientPrivate)
+{
+ bool check;
+ Q_UNUSED(check);
+
+ // initialise socket
+ QSslSocket *socket = new QSslSocket(this);
+ setSocket(socket);
+
+ check = connect(socket, SIGNAL(sslErrors(QList<QSslError>)),
+ this, SLOT(socketSslErrors(QList<QSslError>)));
+ Q_ASSERT(check);
+
+ check = connect(socket, SIGNAL(error(QAbstractSocket::SocketError)),
+ this, SLOT(socketError(QAbstractSocket::SocketError)));
+ Q_ASSERT(check);
+
+ // DNS lookups
+ check = connect(&d->dns, SIGNAL(finished()),
+ this, SLOT(_q_dnsLookupFinished()));
+ Q_ASSERT(check);
+
+ // XEP-0199: XMPP Ping
+ d->pingTimer = new QTimer(this);
+ check = connect(d->pingTimer, SIGNAL(timeout()),
+ this, SLOT(pingSend()));
+ Q_ASSERT(check);
+
+ d->timeoutTimer = new QTimer(this);
+ d->timeoutTimer->setSingleShot(true);
+ check = connect(d->timeoutTimer, SIGNAL(timeout()),
+ this, SLOT(pingTimeout()));
+ Q_ASSERT(check);
+
+ check = connect(this, SIGNAL(connected()),
+ this, SLOT(pingStart()));
+ Q_ASSERT(check);
+
+ check = connect(this, SIGNAL(disconnected()),
+ this, SLOT(pingStop()));
+ Q_ASSERT(check);
+}
+
+/// Destroys an outgoing client stream.
+
+QXmppOutgoingClient::~QXmppOutgoingClient()
+{
+ delete d;
+}
+
+/// Returns a reference to the stream's configuration.
+
+QXmppConfiguration& QXmppOutgoingClient::configuration()
+{
+ return d->config;
+}
+
+/// Attempts to connect to the XMPP server.
+
+void QXmppOutgoingClient::connectToHost()
+{
+ const QString host = configuration().host();
+ const quint16 port = configuration().port();
+
+ // override CA certificates if requested
+ if (!configuration().caCertificates().isEmpty()) {
+ socket()->setCaCertificates(configuration().caCertificates());
+ }
+
+ // if an explicit host was provided, connect to it
+ if (!host.isEmpty() && port) {
+ info(QString("Connecting to %1:%2").arg(host, QString::number(port)));
+ socket()->setProxy(configuration().networkProxy());
+ socket()->connectToHost(host, port);
+ return;
+ }
+
+ // otherwise, lookup server
+ const QString domain = configuration().domain();
+ debug(QString("Looking up server for domain %1").arg(domain));
+ d->dns.setName("_xmpp-client._tcp." + domain);
+ d->dns.setType(QDnsLookup::SRV);
+ d->dns.lookup();
+}
+
+void QXmppOutgoingClient::_q_dnsLookupFinished()
+{
+ QString host;
+ quint16 port;
+
+ if (d->dns.error() == QDnsLookup::NoError &&
+ !d->dns.serviceRecords().isEmpty()) {
+ // take the first returned record
+ host = d->dns.serviceRecords().first().target();
+ port = d->dns.serviceRecords().first().port();
+ } else {
+ // as a fallback, use domain as the host name
+ warning(QString("Lookup for domain %1 failed: %2")
+ .arg(d->dns.name(), d->dns.errorString()));
+ host = configuration().domain();
+ port = configuration().port();
+ }
+
+ // connect to server
+ info(QString("Connecting to %1:%2").arg(host, QString::number(port)));
+ socket()->setProxy(configuration().networkProxy());
+ socket()->connectToHost(host, port);
+}
+
+/// Returns true if the socket is connected and a session has been started.
+///
+
+bool QXmppOutgoingClient::isConnected() const
+{
+ return QXmppStream::isConnected() && d->sessionStarted;
+}
+
+void QXmppOutgoingClient::socketSslErrors(const QList<QSslError> & error)
+{
+ warning("SSL errors");
+ for(int i = 0; i< error.count(); ++i)
+ warning(error.at(i).errorString());
+
+ if (configuration().ignoreSslErrors())
+ socket()->ignoreSslErrors();
+}
+
+void QXmppOutgoingClient::socketError(QAbstractSocket::SocketError socketError)
+{
+ Q_UNUSED(socketError);
+ emit error(QXmppClient::SocketError);
+}
+
+void QXmppOutgoingClient::handleStart()
+{
+ QXmppStream::handleStart();
+
+ // reset stream information
+ d->streamId.clear();
+ d->streamFrom.clear();
+ d->streamVersion.clear();
+
+ // reset authentication step
+ d->saslDigestStep = 0;
+ d->saslMechanism = -1;
+
+ // reset session information
+ d->bindId.clear();
+ d->sessionId.clear();
+ d->sessionAvailable = false;
+ d->sessionStarted = false;
+
+ // start stream
+ QByteArray data = "<?xml version='1.0'?><stream:stream to='";
+ data.append(configuration().domain().toUtf8());
+ data.append("' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' version='1.0'>");
+ sendData(data);
+}
+
+void QXmppOutgoingClient::handleStream(const QDomElement &streamElement)
+{
+ if(d->streamId.isEmpty())
+ d->streamId = streamElement.attribute("id");
+ if (d->streamFrom.isEmpty())
+ d->streamFrom = streamElement.attribute("from");
+ if(d->streamVersion.isEmpty())
+ {
+ d->streamVersion = streamElement.attribute("version");
+
+ // no version specified, signals XMPP Version < 1.0.
+ // switch to old auth mechanism
+ if(d->streamVersion.isEmpty())
+ sendNonSASLAuthQuery();
+ }
+}
+
+void QXmppOutgoingClient::handleStanza(const QDomElement &nodeRecv)
+{
+ // if we receive any kind of data, stop the timeout timer
+ d->timeoutTimer->stop();
+
+ const QString ns = nodeRecv.namespaceURI();
+
+ // give client opportunity to handle stanza
+ bool handled = false;
+ emit elementReceived(nodeRecv, handled);
+ if (handled)
+ return;
+
+ if(QXmppStreamFeatures::isStreamFeatures(nodeRecv))
+ {
+ QXmppStreamFeatures features;
+ features.parse(nodeRecv);
+
+ if (!socket()->isEncrypted())
+ {
+ // determine TLS mode to use
+ const QXmppConfiguration::StreamSecurityMode localSecurity = configuration().streamSecurityMode();
+ const QXmppStreamFeatures::Mode remoteSecurity = features.tlsMode();
+ if (!socket()->supportsSsl() &&
+ (localSecurity == QXmppConfiguration::TLSRequired ||
+ remoteSecurity == QXmppStreamFeatures::Required))
+ {
+ warning("Disconnecting as TLS is required, but SSL support is not available");
+ disconnectFromHost();
+ return;
+ }
+ if (localSecurity == QXmppConfiguration::TLSRequired &&
+ remoteSecurity == QXmppStreamFeatures::Disabled)
+ {
+ warning("Disconnecting as TLS is required, but not supported by the server");
+ disconnectFromHost();
+ return;
+ }
+
+ if (socket()->supportsSsl() &&
+ localSecurity != QXmppConfiguration::TLSDisabled &&
+ remoteSecurity != QXmppStreamFeatures::Disabled)
+ {
+ // enable TLS as it is support by both parties
+ sendData("<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>");
+ return;
+ }
+ }
+
+ // handle authentication
+ const bool nonSaslAvailable = features.nonSaslAuthMode() != QXmppStreamFeatures::Disabled;
+ const bool saslAvailable = !features.authMechanisms().isEmpty();
+ const bool useSasl = configuration().useSASLAuthentication();
+ if((saslAvailable && nonSaslAvailable && !useSasl) ||
+ (!saslAvailable && nonSaslAvailable))
+ {
+ sendNonSASLAuthQuery();
+ }
+ else if(saslAvailable)
+ {
+ // determine SASL Authentication mechanism to use
+ const QList<QXmppConfiguration::SASLAuthMechanism> mechanisms = features.authMechanisms();
+ if (mechanisms.isEmpty())
+ {
+ warning("No supported SASL Authentication mechanism available");
+ disconnectFromHost();
+ return;
+ }
+ else if (!mechanisms.contains(configuration().sASLAuthMechanism()))
+ {
+ info("Desired SASL Auth mechanism is not available, selecting first available one");
+ d->saslMechanism = mechanisms.first();
+ } else {
+ d->saslMechanism = configuration().sASLAuthMechanism();
+ }
+
+ // send SASL Authentication request
+ switch(d->saslMechanism)
+ {
+ case QXmppConfiguration::SASLPlain:
+ {
+ QString userPass('\0' + configuration().user() +
+ '\0' + configuration().password());
+ QByteArray data = "<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>";
+ data += userPass.toUtf8().toBase64();
+ data += "</auth>";
+ sendData(data);
+ }
+ break;
+ case QXmppConfiguration::SASLDigestMD5:
+ sendData("<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='DIGEST-MD5'/>");
+ break;
+ case QXmppConfiguration::SASLAnonymous:
+ sendData("<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='ANONYMOUS'/>");
+ break;
+ case QXmppConfiguration::SASLXFacebookPlatform:
+ sendData("<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='X-FACEBOOK-PLATFORM'/>");
+ break;
+ }
+ }
+
+ // check whether bind is available
+ if (features.bindMode() != QXmppStreamFeatures::Disabled)
+ {
+ QXmppBindIq bind;
+ bind.setType(QXmppIq::Set);
+ bind.setResource(configuration().resource());
+ d->bindId = bind.id();
+ sendPacket(bind);
+ }
+
+ // check whether session is available
+ if (features.sessionMode() != QXmppStreamFeatures::Disabled)
+ d->sessionAvailable = true;
+ }
+ else if(ns == ns_stream && nodeRecv.tagName() == "error")
+ {
+ if (!nodeRecv.firstChildElement("conflict").isNull())
+ d->xmppStreamError = QXmppStanza::Error::Conflict;
+ else
+ d->xmppStreamError = QXmppStanza::Error::UndefinedCondition;
+ emit error(QXmppClient::XmppStreamError);
+ }
+ else if(ns == ns_tls)
+ {
+ if(nodeRecv.tagName() == "proceed")
+ {
+ debug("Starting encryption");
+ socket()->startClientEncryption();
+ return;
+ }
+ }
+ else if(ns == ns_sasl)
+ {
+ if(nodeRecv.tagName() == "success")
+ {
+ debug("Authenticated");
+ handleStart();
+ }
+ else if(nodeRecv.tagName() == "challenge")
+ {
+ switch(d->saslMechanism)
+ {
+ case QXmppConfiguration::SASLDigestMD5:
+ d->saslDigestStep++;
+ switch (d->saslDigestStep)
+ {
+ case 1 :
+ sendAuthDigestMD5ResponseStep1(nodeRecv.text());
+ break;
+ case 2 :
+ sendAuthDigestMD5ResponseStep2(nodeRecv.text());
+ break;
+ default :
+ warning("Too many authentication steps");
+ disconnectFromHost();
+ break;
+ }
+ break;
+ case QXmppConfiguration::SASLXFacebookPlatform:
+ sendAuthXFacebookResponse(nodeRecv.text());
+ break;
+ default:
+ warning("Unexpected SASL challenge");
+ disconnectFromHost();
+ break;
+ }
+ }
+ else if(nodeRecv.tagName() == "failure")
+ {
+ if (!nodeRecv.firstChildElement("not-authorized").isNull())
+ d->xmppStreamError = QXmppStanza::Error::NotAuthorized;
+ else
+ d->xmppStreamError = QXmppStanza::Error::UndefinedCondition;
+ emit error(QXmppClient::XmppStreamError);
+
+ warning("Authentication failure");
+ disconnectFromHost();
+ }
+ }
+ else if(ns == ns_client)
+ {
+
+ if(nodeRecv.tagName() == "iq")
+ {
+ QDomElement element = nodeRecv.firstChildElement();
+ QString id = nodeRecv.attribute("id");
+ QString type = nodeRecv.attribute("type");
+ if(type.isEmpty())
+ warning("QXmppStream: iq type can't be empty");
+
+ if(id == d->sessionId)
+ {
+ QXmppSessionIq session;
+ session.parse(nodeRecv);
+
+ // xmpp connection made
+ d->sessionStarted = true;
+ emit connected();
+ }
+ else if(QXmppBindIq::isBindIq(nodeRecv) && id == d->bindId)
+ {
+ QXmppBindIq bind;
+ bind.parse(nodeRecv);
+
+ // bind result
+ if (bind.type() == QXmppIq::Result)
+ {
+ if (!bind.jid().isEmpty())
+ {
+ QRegExp jidRegex("^([^@/]+)@([^@/]+)/(.+)$");
+ if (jidRegex.exactMatch(bind.jid()))
+ {
+ configuration().setUser(jidRegex.cap(1));
+ configuration().setDomain(jidRegex.cap(2));
+ configuration().setResource(jidRegex.cap(3));
+ } else {
+ warning("Bind IQ received with invalid JID: " + bind.jid());
+ }
+ }
+
+ // start session if it is available
+ if (d->sessionAvailable)
+ {
+ QXmppSessionIq session;
+ session.setType(QXmppIq::Set);
+ session.setTo(configuration().domain());
+ d->sessionId = session.id();
+ sendPacket(session);
+ }
+ }
+ }
+ // extensions
+
+ // XEP-0078: Non-SASL Authentication
+ else if(id == d->nonSASLAuthId && type == "result")
+ {
+ // successful Non-SASL Authentication
+ debug("Authenticated (Non-SASL)");
+
+ // xmpp connection made
+ d->sessionStarted = true;
+ emit connected();
+ }
+ else if(QXmppNonSASLAuthIq::isNonSASLAuthIq(nodeRecv))
+ {
+ if(type == "result")
+ {
+ bool digest = !nodeRecv.firstChildElement("query").
+ firstChildElement("digest").isNull();
+ bool plain = !nodeRecv.firstChildElement("query").
+ firstChildElement("password").isNull();
+ bool plainText = false;
+
+ if(plain && digest)
+ {
+ if(configuration().nonSASLAuthMechanism() ==
+ QXmppConfiguration::NonSASLDigest)
+ plainText = false;
+ else
+ plainText = true;
+ }
+ else if(plain)
+ plainText = true;
+ else if(digest)
+ plainText = false;
+ else
+ {
+ warning("No supported Non-SASL Authentication mechanism available");
+ disconnectFromHost();
+ return;
+ }
+ sendNonSASLAuth(plainText);
+ }
+ }
+ // XEP-0199: XMPP Ping
+ else if(QXmppPingIq::isPingIq(nodeRecv))
+ {
+ QXmppPingIq req;
+ req.parse(nodeRecv);
+
+ QXmppIq iq(QXmppIq::Result);
+ iq.setId(req.id());
+ iq.setTo(req.from());
+ sendPacket(iq);
+ }
+ else
+ {
+ QXmppIq iqPacket;
+ iqPacket.parse(nodeRecv);
+
+ // if we didn't understant the iq, reply with error
+ // except for "result" and "error" iqs
+ if (type != "result" && type != "error")
+ {
+ QXmppIq iq(QXmppIq::Error);
+ iq.setId(iqPacket.id());
+ iq.setTo(iqPacket.from());
+ QXmppStanza::Error error(QXmppStanza::Error::Cancel,
+ QXmppStanza::Error::FeatureNotImplemented);
+ iq.setError(error);
+ sendPacket(iq);
+ } else {
+ emit iqReceived(iqPacket);
+ }
+ }
+ }
+ else if(nodeRecv.tagName() == "presence")
+ {
+ QXmppPresence presence;
+ presence.parse(nodeRecv);
+
+ // emit presence
+ emit presenceReceived(presence);
+ }
+ else if(nodeRecv.tagName() == "message")
+ {
+ QXmppMessage message;
+ message.parse(nodeRecv);
+
+ // emit message
+ emit messageReceived(message);
+ }
+ }
+}
+
+void QXmppOutgoingClient::pingStart()
+{
+ const int interval = configuration().keepAliveInterval();
+ // start ping timer
+ if (interval > 0)
+ {
+ d->pingTimer->setInterval(interval * 1000);
+ d->pingTimer->start();
+ }
+}
+
+void QXmppOutgoingClient::pingStop()
+{
+ // stop all timers
+ d->pingTimer->stop();
+ d->timeoutTimer->stop();
+}
+
+void QXmppOutgoingClient::pingSend()
+{
+ // send ping packet
+ QXmppPingIq ping;
+ ping.setTo(configuration().domain());
+ sendPacket(ping);
+
+ // start timeout timer
+ const int timeout = configuration().keepAliveTimeout();
+ if (timeout > 0)
+ {
+ d->timeoutTimer->setInterval(timeout * 1000);
+ d->timeoutTimer->start();
+ }
+}
+
+void QXmppOutgoingClient::pingTimeout()
+{
+ warning("Ping timeout");
+ disconnectFromHost();
+ emit error(QXmppClient::KeepAliveError);
+}
+
+// challenge is BASE64 encoded string
+void QXmppOutgoingClient::sendAuthDigestMD5ResponseStep1(const QString& challenge)
+{
+ QByteArray ba = QByteArray::fromBase64(challenge.toAscii());
+ QMap<QByteArray, QByteArray> map = QXmppSaslDigestMd5::parseMessage(ba);
+
+ if (!map.contains("nonce"))
+ {
+ warning("sendAuthDigestMD5ResponseStep1: Invalid input");
+ disconnectFromHost();
+ return;
+ }
+
+ d->saslDigest.setAuthzid(map.value("authzid"));
+ d->saslDigest.setCnonce(QXmppSaslDigestMd5::generateNonce());
+ d->saslDigest.setDigestUri(QString("xmpp/%1").arg(configuration().domain()).toUtf8());
+ d->saslDigest.setNc("00000001");
+ d->saslDigest.setNonce(map.value("nonce"));
+ d->saslDigest.setQop("auth");
+ d->saslDigest.setSecret(QCryptographicHash::hash(
+ configuration().user().toUtf8() + ":" + map.value("realm") + ":" + configuration().password().toUtf8(),
+ QCryptographicHash::Md5));
+
+ // Build response
+ QMap<QByteArray, QByteArray> response;
+ response["username"] = configuration().user().toUtf8();
+ if (map.contains("realm"))
+ response["realm"] = map.value("realm");
+ response["nonce"] = d->saslDigest.nonce();
+ response["cnonce"] = d->saslDigest.cnonce();
+ response["nc"] = d->saslDigest.nc();
+ response["qop"] = d->saslDigest.qop();
+ response["digest-uri"] = d->saslDigest.digestUri();
+ response["response"] = d->saslDigest.calculateDigest(
+ QByteArray("AUTHENTICATE:") + d->saslDigest.digestUri());
+
+ if(!d->saslDigest.authzid().isEmpty())
+ response["authzid"] = d->saslDigest.authzid();
+ response["charset"] = "utf-8";
+
+ const QByteArray data = QXmppSaslDigestMd5::serializeMessage(response);
+ QByteArray packet = "<response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>"
+ + data.toBase64() + "</response>";
+ sendData(packet);
+}
+
+void QXmppOutgoingClient::sendAuthDigestMD5ResponseStep2(const QString &challenge)
+{
+ QByteArray ba = QByteArray::fromBase64(challenge.toAscii());
+ QMap<QByteArray, QByteArray> map = QXmppSaslDigestMd5::parseMessage(ba);
+
+ if (!map.contains("rspauth"))
+ {
+ warning("sendAuthDigestMD5ResponseStep2: Invalid input");
+ disconnectFromHost();
+ return;
+ }
+
+ // check new challenge
+ if (map["rspauth"] !=
+ d->saslDigest.calculateDigest(QByteArray(":") + d->saslDigest.digestUri()))
+ {
+ warning("sendAuthDigestMD5ResponseStep2: Bad challenge");
+ disconnectFromHost();
+ return;
+ }
+
+ sendData("<response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'/>");
+}
+
+void QXmppOutgoingClient::sendAuthXFacebookResponse(const QString& challenge)
+{
+ // parse request
+ QUrl request;
+ request.setEncodedQuery(QByteArray::fromBase64(challenge.toAscii()));
+ if (!request.hasQueryItem("method") || !request.hasQueryItem("nonce")) {
+ warning("sendAuthXFacebookResponse: Invalid input");
+ disconnectFromHost();
+ return;
+ }
+
+ // build response
+ QUrl response;
+ response.addQueryItem("access_token", configuration().facebookAccessToken());
+ response.addQueryItem("api_key", configuration().facebookAppId());
+ response.addQueryItem("call_id", 0);
+ response.addQueryItem("method", request.queryItemValue("method"));
+ response.addQueryItem("nonce", request.queryItemValue("nonce"));
+ response.addQueryItem("v", "1.0");
+
+ sendData("<response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>"
+ + response.encodedQuery().toBase64() + "</response>");
+}
+
+void QXmppOutgoingClient::sendNonSASLAuth(bool plainText)
+{
+ QXmppNonSASLAuthIq authQuery;
+ authQuery.setType(QXmppIq::Set);
+ authQuery.setUsername(configuration().user());
+ if (plainText)
+ authQuery.setPassword(configuration().password());
+ else
+ authQuery.setDigest(d->streamId, configuration().password());
+ authQuery.setResource(configuration().resource());
+ d->nonSASLAuthId = authQuery.id();
+ sendPacket(authQuery);
+}
+
+void QXmppOutgoingClient::sendNonSASLAuthQuery()
+{
+ QXmppNonSASLAuthIq authQuery;
+ authQuery.setType(QXmppIq::Get);
+ authQuery.setTo(d->streamFrom);
+ // FIXME : why are we setting the username, XEP-0078 states we should
+ // not attempt to guess the required fields?
+ authQuery.setUsername(configuration().user());
+ sendPacket(authQuery);
+}
+
+/// Returns the type of the last XMPP stream error that occured.
+
+QXmppStanza::Error::Condition QXmppOutgoingClient::xmppStreamError()
+{
+ return d->xmppStreamError;
+}
+
diff --git a/src/client/QXmppOutgoingClient.h b/src/client/QXmppOutgoingClient.h
new file mode 100644
index 00000000..4bb00d67
--- /dev/null
+++ b/src/client/QXmppOutgoingClient.h
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2008-2011 The QXmpp developers
+ *
+ * Authors:
+ * Manjeet Dahiya
+ * Jeremy Lainé
+ *
+ * Source:
+ * http://code.google.com/p/qxmpp
+ *
+ * This file is a part of QXmpp library.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ */
+
+
+#ifndef QXMPPOUTGOINGCLIENT_H
+#define QXMPPOUTGOINGCLIENT_H
+
+#include "QXmppClient.h"
+#include "QXmppStanza.h"
+#include "QXmppStream.h"
+
+class QDomElement;
+class QSslError;
+
+class QXmppConfiguration;
+class QXmppPresence;
+class QXmppIq;
+class QXmppMessage;
+
+class QXmppOutgoingClientPrivate;
+
+/// \brief The QXmppOutgoingClient class represents an outgoing XMPP stream
+/// to an XMPP server.
+///
+
+class QXmppOutgoingClient : public QXmppStream
+{
+ Q_OBJECT
+
+public:
+ QXmppOutgoingClient(QObject *parent);
+ ~QXmppOutgoingClient();
+
+ void connectToHost();
+ bool isConnected() const;
+
+ QSslSocket *socket() const { return QXmppStream::socket(); };
+ QXmppStanza::Error::Condition xmppStreamError();
+
+ QXmppConfiguration& configuration();
+
+signals:
+ /// This signal is emitted when an error is encountered.
+ void error(QXmppClient::Error);
+
+ /// This signal is emitted when an element is received.
+ void elementReceived(const QDomElement &element, bool &handled);
+
+ /// This signal is emitted when a presence is received.
+ void presenceReceived(const QXmppPresence&);
+
+ /// This signal is emitted when a message is received.
+ void messageReceived(const QXmppMessage&);
+
+ /// This signal is emitted when an IQ is received.
+ void iqReceived(const QXmppIq&);
+
+protected:
+ /// \cond
+ // Overridable methods
+ virtual void handleStart();
+ virtual void handleStanza(const QDomElement &element);
+ virtual void handleStream(const QDomElement &element);
+ /// \endcond
+
+private slots:
+ void _q_dnsLookupFinished();
+ void socketError(QAbstractSocket::SocketError);
+ void socketSslErrors(const QList<QSslError>&);
+
+ void pingStart();
+ void pingStop();
+ void pingSend();
+ void pingTimeout();
+
+private:
+ void sendAuthDigestMD5ResponseStep1(const QString& challenge);
+ void sendAuthDigestMD5ResponseStep2(const QString& challenge);
+ void sendAuthXFacebookResponse(const QString& challenge);
+ void sendNonSASLAuth(bool plaintext);
+ void sendNonSASLAuthQuery();
+
+ QXmppOutgoingClientPrivate * const d;
+};
+
+#endif // QXMPPOUTGOINGCLIENT_H
diff --git a/src/client/QXmppReconnectionManager.cpp b/src/client/QXmppReconnectionManager.cpp
new file mode 100644
index 00000000..adb84e9f
--- /dev/null
+++ b/src/client/QXmppReconnectionManager.cpp
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2008-2011 The QXmpp developers
+ *
+ * Author:
+ * Manjeet Dahiya
+ *
+ * Source:
+ * http://code.google.com/p/qxmpp
+ *
+ * This file is a part of QXmpp library.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ */
+
+
+#include "QXmppReconnectionManager.h"
+#include "QXmppClient.h"
+#include "QXmppLogger.h"
+#include "QXmppUtils.h"
+
+QXmppReconnectionManager::QXmppReconnectionManager(QXmppClient* client) :
+ QObject(client),
+ m_receivedConflict(false),
+ m_reconnectionTries(0),
+ m_timer(this),
+ m_client(client)
+{
+ m_timer.setSingleShot(true);
+ bool check = connect(&m_timer, SIGNAL(timeout()), SLOT(reconnect()));
+ Q_ASSERT(check);
+ Q_UNUSED(check);
+}
+
+void QXmppReconnectionManager::connected()
+{
+ m_receivedConflict = false;
+ m_reconnectionTries = 0;
+}
+
+void QXmppReconnectionManager::error(QXmppClient::Error error)
+{
+ if(m_client && error == QXmppClient::XmppStreamError)
+ {
+ // if we receive a resource conflict, inhibit reconnection
+ if(m_client->xmppStreamError() == QXmppStanza::Error::Conflict)
+ m_receivedConflict = true;
+ }
+ else if(m_client && error == QXmppClient::SocketError && !m_receivedConflict)
+ {
+ int time = getNextReconnectingInTime();
+
+ // time is in sec
+ m_timer.start(time*1000);
+ emit reconnectingIn(time);
+ }
+ else if (m_client && error == QXmppClient::KeepAliveError)
+ {
+ // if we got a keepalive error, reconnect in one second
+ m_timer.start(1000);
+ }
+}
+
+int QXmppReconnectionManager::getNextReconnectingInTime()
+{
+ int reconnectingIn;
+ if(m_reconnectionTries < 5)
+ reconnectingIn = 10;
+ else if(m_reconnectionTries < 10)
+ reconnectingIn = 20;
+ else if(m_reconnectionTries < 15)
+ reconnectingIn = 40;
+ else
+ reconnectingIn = 60;
+
+ return reconnectingIn;
+}
+
+void QXmppReconnectionManager::reconnect()
+{
+ if(m_client)
+ {
+ emit reconnectingNow();
+ m_client->connectToServer(m_client->configuration(), m_client->clientPresence());
+ }
+}
+
+void QXmppReconnectionManager::cancelReconnection()
+{
+ m_timer.stop();
+ m_receivedConflict = false;
+ m_reconnectionTries = 0;
+}
diff --git a/src/client/QXmppReconnectionManager.h b/src/client/QXmppReconnectionManager.h
new file mode 100644
index 00000000..d72acd16
--- /dev/null
+++ b/src/client/QXmppReconnectionManager.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2008-2011 The QXmpp developers
+ *
+ * Author:
+ * Manjeet Dahiya
+ *
+ * Source:
+ * http://code.google.com/p/qxmpp
+ *
+ * This file is a part of QXmpp library.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ */
+
+
+#ifndef QXMPPRECONNECTIONMANAGER_H
+#define QXMPPRECONNECTIONMANAGER_H
+
+#include <QObject>
+#include <QTimer>
+#include "QXmppClient.h"
+
+class QXmppReconnectionManager : public QObject
+{
+ Q_OBJECT
+
+public:
+ QXmppReconnectionManager(QXmppClient* client);
+
+signals:
+ void reconnectingIn(int);
+ void reconnectingNow();
+
+public slots:
+ void cancelReconnection();
+
+private slots:
+ void connected();
+ void error(QXmppClient::Error);
+ void reconnect();
+
+private:
+ int getNextReconnectingInTime();
+ bool m_receivedConflict;
+ int m_reconnectionTries;
+ QTimer m_timer;
+
+ // reference to to client object (no ownership)
+ QXmppClient* m_client;
+};
+
+#endif // QXMPPRECONNECTIONMANAGER_H
diff --git a/src/client/QXmppRemoteMethod.cpp b/src/client/QXmppRemoteMethod.cpp
new file mode 100644
index 00000000..5dd8e9ad
--- /dev/null
+++ b/src/client/QXmppRemoteMethod.cpp
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2008-2011 The QXmpp developers
+ *
+ * Authors:
+ * Ian Reinhart Geiser
+ *
+ * Source:
+ * http://code.google.com/p/qxmpp
+ *
+ * This file is a part of QXmpp library.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ */
+
+#include "QXmppRemoteMethod.h"
+#include "QXmppClient.h"
+#include "QXmppUtils.h"
+
+#include <QDebug>
+#include <QEventLoop>
+#include <QTimer>
+
+QXmppRemoteMethod::QXmppRemoteMethod(const QString &jid, const QString &method, const QVariantList &args, QXmppClient *client) :
+ QObject(client), m_client(client)
+{
+ m_payload.setTo( jid );
+ m_payload.setFrom( client->configuration().jid() );
+ m_payload.setMethod( method );
+ m_payload.setArguments( args );
+}
+
+QXmppRemoteMethodResult QXmppRemoteMethod::call( )
+{
+ // FIXME : spinning an event loop is a VERY bad idea, it can cause
+ // us to lose incoming packets
+ QEventLoop loop(this);
+ connect( this, SIGNAL(callDone()), &loop, SLOT(quit()));
+ QTimer::singleShot(30000,&loop, SLOT(quit())); // Timeout incase the other end hangs...
+
+ m_client->sendPacket( m_payload );
+
+ loop.exec( QEventLoop::ExcludeUserInputEvents | QEventLoop::WaitForMoreEvents );
+ return m_result;
+}
+
+void QXmppRemoteMethod::gotError( const QXmppRpcErrorIq &iq )
+{
+ if ( iq.id() == m_payload.id() )
+ {
+ m_result.hasError = true;
+ m_result.errorMessage = iq.error().text();
+ m_result.code = iq.error().type();
+ emit callDone();
+ }
+}
+
+void QXmppRemoteMethod::gotResult( const QXmppRpcResponseIq &iq )
+{
+ if ( iq.id() == m_payload.id() )
+ {
+ m_result.hasError = false;
+ // FIXME: we don't handle multiple responses
+ m_result.result = iq.values().first();
+ emit callDone();
+ }
+}
diff --git a/src/client/QXmppRemoteMethod.h b/src/client/QXmppRemoteMethod.h
new file mode 100644
index 00000000..eb69f28e
--- /dev/null
+++ b/src/client/QXmppRemoteMethod.h
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2008-2011 The QXmpp developers
+ *
+ * Authors:
+ * Ian Reinhart Geiser
+ *
+ * Source:
+ * http://code.google.com/p/qxmpp
+ *
+ * This file is a part of QXmpp library.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ */
+
+#ifndef QXMPPREMOTEMETHOD_H
+#define QXMPPREMOTEMETHOD_H
+
+#include <QObject>
+#include <QVariant>
+
+#include "QXmppRpcIq.h"
+
+class QXmppClient;
+
+struct QXmppRemoteMethodResult {
+ QXmppRemoteMethodResult() : hasError(false), code(0) { }
+ bool hasError;
+ int code;
+ QString errorMessage;
+ QVariant result;
+};
+
+class QXmppRemoteMethod : public QObject
+{
+ Q_OBJECT
+public:
+ QXmppRemoteMethod(const QString &jid, const QString &method, const QVariantList &args, QXmppClient *client);
+ QXmppRemoteMethodResult call( );
+
+private slots:
+ void gotError( const QXmppRpcErrorIq &iq );
+ void gotResult( const QXmppRpcResponseIq &iq );
+
+signals:
+ void callDone();
+
+private:
+ QXmppRpcInvokeIq m_payload;
+ QXmppClient *m_client;
+ QXmppRemoteMethodResult m_result;
+
+};
+
+#endif // QXMPPREMOTEMETHOD_H
diff --git a/src/client/QXmppRosterManager.cpp b/src/client/QXmppRosterManager.cpp
new file mode 100644
index 00000000..7c2f56ed
--- /dev/null
+++ b/src/client/QXmppRosterManager.cpp
@@ -0,0 +1,421 @@
+/*
+ * Copyright (C) 2008-2011 The QXmpp developers
+ *
+ * Authors:
+ * Manjeet Dahiya
+ * Jeremy Lainé
+ *
+ * Source:
+ * http://code.google.com/p/qxmpp
+ *
+ * This file is a part of QXmpp library.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ */
+
+#include <QDomElement>
+
+#include "QXmppClient.h"
+#include "QXmppPresence.h"
+#include "QXmppRosterIq.h"
+#include "QXmppRosterManager.h"
+#include "QXmppUtils.h"
+
+class QXmppRosterManagerPrivate
+{
+public:
+ QXmppRosterManagerPrivate(QXmppRosterManager *qq);
+
+ // map of bareJid and its rosterEntry
+ QMap<QString, QXmppRosterIq::Item> entries;
+
+ // map of resources of the jid and map of resources and presences
+ QMap<QString, QMap<QString, QXmppPresence> > presences;
+
+ // flag to store that the roster has been populated
+ bool isRosterReceived;
+
+ // id of the initial roster request
+ QString rosterReqId;
+
+private:
+ QXmppRosterManager *q;
+};
+
+QXmppRosterManagerPrivate::QXmppRosterManagerPrivate(QXmppRosterManager *qq)
+ : isRosterReceived(false),
+ q(qq)
+{
+}
+
+/// Constructs a roster manager.
+
+QXmppRosterManager::QXmppRosterManager(QXmppClient* client)
+{
+ bool check;
+ Q_UNUSED(check);
+
+ d = new QXmppRosterManagerPrivate(this);
+
+ check = connect(client, SIGNAL(connected()),
+ this, SLOT(_q_connected()));
+ Q_ASSERT(check);
+
+ check = connect(client, SIGNAL(disconnected()),
+ this, SLOT(_q_disconnected()));
+ Q_ASSERT(check);
+
+ check = connect(client, SIGNAL(presenceReceived(QXmppPresence)),
+ this, SLOT(_q_presenceReceived(QXmppPresence)));
+ Q_ASSERT(check);
+}
+
+QXmppRosterManager::~QXmppRosterManager()
+{
+ delete d;
+}
+
+/// Accepts a subscription request.
+///
+/// You can call this method in reply to the subscriptionRequest() signal.
+
+bool QXmppRosterManager::acceptSubscription(const QString &bareJid)
+{
+ QXmppPresence presence;
+ presence.setTo(bareJid);
+ presence.setType(QXmppPresence::Subscribed);
+ return client()->sendPacket(presence);
+}
+
+/// Upon XMPP connection, request the roster.
+///
+void QXmppRosterManager::_q_connected()
+{
+ QXmppRosterIq roster;
+ roster.setType(QXmppIq::Get);
+ roster.setFrom(client()->configuration().jid());
+ d->rosterReqId = roster.id();
+ client()->sendPacket(roster);
+}
+
+void QXmppRosterManager::_q_disconnected()
+{
+ d->entries.clear();
+ d->presences.clear();
+ d->isRosterReceived = false;
+}
+
+bool QXmppRosterManager::handleStanza(const QDomElement &element)
+{
+ if (element.tagName() != "iq" || !QXmppRosterIq::isRosterIq(element))
+ return false;
+
+ // Security check: only server should send this iq
+ // from() should be either empty or bareJid of the user
+ const QString fromJid = element.attribute("from");
+ if (!fromJid.isEmpty() && jidToBareJid(fromJid) != client()->configuration().jidBare())
+ return false;
+
+ QXmppRosterIq rosterIq;
+ rosterIq.parse(element);
+
+ bool isInitial = (d->rosterReqId == rosterIq.id());
+ switch(rosterIq.type())
+ {
+ case QXmppIq::Set:
+ {
+ // send result iq
+ QXmppIq returnIq(QXmppIq::Result);
+ returnIq.setId(rosterIq.id());
+ client()->sendPacket(returnIq);
+
+ // store updated entries and notify changes
+ const QList<QXmppRosterIq::Item> items = rosterIq.items();
+ foreach (const QXmppRosterIq::Item &item, items) {
+ const QString bareJid = item.bareJid();
+ if (item.subscriptionType() == QXmppRosterIq::Item::Remove) {
+ if (d->entries.remove(bareJid)) {
+ // notify the user that the item was removed
+ emit itemRemoved(bareJid);
+ }
+ } else {
+ const bool added = !d->entries.contains(bareJid);
+ d->entries.insert(bareJid, item);
+ if (added) {
+ // notify the user that the item was added
+ emit itemAdded(bareJid);
+ } else {
+ // notify the user that the item changed
+ emit itemChanged(bareJid);
+ }
+
+ // FIXME: remove legacy signal
+ emit rosterChanged(bareJid);
+ }
+ }
+ }
+ break;
+ case QXmppIq::Result:
+ {
+ const QList<QXmppRosterIq::Item> items = rosterIq.items();
+ foreach (const QXmppRosterIq::Item &item, items) {
+ const QString bareJid = item.bareJid();
+ d->entries.insert(bareJid, item);
+ if (!isInitial)
+ emit rosterChanged(bareJid);
+ }
+ if (isInitial)
+ {
+ d->isRosterReceived = true;
+ emit rosterReceived();
+ }
+ break;
+ }
+ default:
+ break;
+ }
+
+ return true;
+}
+
+void QXmppRosterManager::_q_presenceReceived(const QXmppPresence& presence)
+{
+ const QString jid = presence.from();
+ const QString bareJid = jidToBareJid(jid);
+ const QString resource = jidToResource(jid);
+
+ if (bareJid.isEmpty())
+ return;
+
+ switch(presence.type())
+ {
+ case QXmppPresence::Available:
+ d->presences[bareJid][resource] = presence;
+ emit presenceChanged(bareJid, resource);
+ break;
+ case QXmppPresence::Unavailable:
+ d->presences[bareJid].remove(resource);
+ emit presenceChanged(bareJid, resource);
+ break;
+ case QXmppPresence::Subscribe:
+ if (client()->configuration().autoAcceptSubscriptions())
+ {
+ // accept subscription request
+ acceptSubscription(bareJid);
+
+ // ask for reciprocal subscription
+ subscribe(bareJid);
+ } else {
+ emit subscriptionReceived(bareJid);
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+/// Refuses a subscription request.
+///
+/// You can call this method in reply to the subscriptionRequest() signal.
+
+bool QXmppRosterManager::refuseSubscription(const QString &bareJid)
+{
+ QXmppPresence presence;
+ presence.setTo(bareJid);
+ presence.setType(QXmppPresence::Unsubscribed);
+ return client()->sendPacket(presence);
+}
+
+/// Adds a new item to the roster without sending any subscription requests.
+///
+/// As a result, the server will initiate a roster push, causing the
+/// itemAdded() or itemChanged() signal to be emitted.
+///
+/// \param bareJid
+/// \param name Optional name for the item.
+/// \param groups Optional groups for the item.
+
+bool QXmppRosterManager::addItem(const QString &bareJid, const QString &name, const QSet<QString> &groups)
+{
+ QXmppRosterIq::Item item;
+ item.setBareJid(bareJid);
+ item.setName(name);
+ item.setGroups(groups);
+ item.setSubscriptionType(QXmppRosterIq::Item::NotSet);
+
+ QXmppRosterIq iq;
+ iq.setType(QXmppIq::Set);
+ iq.addItem(item);
+ return client()->sendPacket(iq);
+}
+
+/// Removes a roster item and cancels subscriptions to and from the contact.
+///
+/// As a result, the server will initiate a roster push, causing the
+/// itemRemoved() signal to be emitted.
+///
+/// \param bareJid
+
+bool QXmppRosterManager::removeItem(const QString &bareJid)
+{
+ QXmppRosterIq::Item item;
+ item.setBareJid(bareJid);
+ item.setSubscriptionType(QXmppRosterIq::Item::Remove);
+
+ QXmppRosterIq iq;
+ iq.setType(QXmppIq::Set);
+ iq.addItem(item);
+ return client()->sendPacket(iq);
+}
+
+/// Renames a roster item.
+///
+/// As a result, the server will initiate a roster push, causing the
+/// itemChanged() signal to be emitted.
+///
+/// \param bareJid
+/// \param name
+
+bool QXmppRosterManager::renameItem(const QString &bareJid, const QString &name)
+{
+ if (!d->entries.contains(bareJid))
+ return false;
+
+ QXmppRosterIq::Item item = d->entries.value(bareJid);
+ item.setName(name);
+
+ QXmppRosterIq iq;
+ iq.setType(QXmppIq::Set);
+ iq.addItem(item);
+ return client()->sendPacket(iq);
+}
+
+/// Requests a subscription to the given contact.
+///
+/// As a result, the server will initiate a roster push, causing the
+/// itemAdded() or itemChanged() signal to be emitted.
+
+bool QXmppRosterManager::subscribe(const QString &bareJid)
+{
+ QXmppPresence packet;
+ packet.setTo(jidToBareJid(bareJid));
+ packet.setType(QXmppPresence::Subscribe);
+ return client()->sendPacket(packet);
+}
+
+/// Removes a subscription to the given contact.
+///
+/// As a result, the server will initiate a roster push, causing the
+/// itemChanged() signal to be emitted.
+
+bool QXmppRosterManager::unsubscribe(const QString &bareJid)
+{
+ QXmppPresence packet;
+ packet.setTo(jidToBareJid(bareJid));
+ packet.setType(QXmppPresence::Unsubscribe);
+ return client()->sendPacket(packet);
+}
+
+/// Function to get all the bareJids present in the roster.
+///
+/// \return QStringList list of all the bareJids
+///
+
+QStringList QXmppRosterManager::getRosterBareJids() const
+{
+ return d->entries.keys();
+}
+
+/// Returns the roster entry of the given bareJid. If the bareJid is not in the
+/// database and empty QXmppRosterIq::Item will be returned.
+///
+/// \param bareJid as a QString
+///
+
+QXmppRosterIq::Item QXmppRosterManager::getRosterEntry(
+ const QString& bareJid) const
+{
+ // will return blank entry if bareJid does'nt exist
+ if(d->entries.contains(bareJid))
+ return d->entries.value(bareJid);
+ else
+ return QXmppRosterIq::Item();
+}
+
+/// Get all the associated resources with the given bareJid.
+///
+/// \param bareJid as a QString
+/// \return list of associated resources as a QStringList
+///
+
+QStringList QXmppRosterManager::getResources(const QString& bareJid) const
+{
+ if(d->presences.contains(bareJid))
+ return d->presences[bareJid].keys();
+ else
+ return QStringList();
+}
+
+/// Get all the presences of all the resources of the given bareJid. A bareJid
+/// can have multiple resources and each resource will have a presence
+/// associated with it.
+///
+/// \param bareJid as a QString
+/// \return Map of resource and its respective presence QMap<QString, QXmppPresence>
+///
+
+QMap<QString, QXmppPresence> QXmppRosterManager::getAllPresencesForBareJid(
+ const QString& bareJid) const
+{
+ if(d->presences.contains(bareJid))
+ return d->presences[bareJid];
+ else
+ return QMap<QString, QXmppPresence>();
+}
+
+/// Get the presence of the given resource of the given bareJid.
+///
+/// \param bareJid as a QString
+/// \param resource as a QString
+/// \return QXmppPresence
+///
+
+QXmppPresence QXmppRosterManager::getPresence(const QString& bareJid,
+ const QString& resource) const
+{
+ if(d->presences.contains(bareJid) && d->presences[bareJid].contains(resource))
+ return d->presences[bareJid][resource];
+ else
+ {
+ QXmppPresence presence;
+ presence.setType(QXmppPresence::Unavailable);
+ presence.setStatus(QXmppPresence::Status::Offline);
+ return presence;
+ }
+}
+
+/// Function to check whether the roster has been received or not.
+///
+/// \return true if roster received else false
+
+bool QXmppRosterManager::isRosterReceived() const
+{
+ return d->isRosterReceived;
+}
+
+// deprecated
+
+void QXmppRosterManager::removeRosterEntry(const QString &bareJid)
+{
+ removeItem(bareJid);
+}
+
diff --git a/src/client/QXmppRosterManager.h b/src/client/QXmppRosterManager.h
new file mode 100644
index 00000000..58e2ff97
--- /dev/null
+++ b/src/client/QXmppRosterManager.h
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2008-2011 The QXmpp developers
+ *
+ * Authors:
+ * Manjeet Dahiya
+ * Jeremy Lainé
+ *
+ * Source:
+ * http://code.google.com/p/qxmpp
+ *
+ * This file is a part of QXmpp library.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ */
+
+#ifndef QXMPPROSTERMANAGER_H
+#define QXMPPROSTERMANAGER_H
+
+#include <QObject>
+#include <QMap>
+#include <QStringList>
+
+#include "QXmppClientExtension.h"
+#include "QXmppPresence.h"
+#include "QXmppRosterIq.h"
+
+class QXmppRosterManagerPrivate;
+
+/// \brief The QXmppRosterManager class provides access to a connected client's roster.
+///
+/// \note It's object should not be created using it's constructor. Instead
+/// QXmppClient::rosterManager() should be used to get the reference of instantiated
+/// object this class.
+///
+/// It stores all the Roster and Presence details of all the roster entries (that
+/// is all the bareJids) in the client's friend's list. It provides the
+/// functionality to get all the bareJids in the client's roster and Roster and
+/// Presence details of the same.
+///
+/// After the successful xmpp connection that after the signal QXmppClient::connected()
+/// is emitted QXmpp requests for getting the roster. Once QXmpp receives the roster
+/// the signal QXmppRosterManager::rosterReceived() is emitted and after that user can
+/// use the functions of this class to get roster entries.
+///
+/// Function QXmppRosterManager::isRosterReceived() tells whether the roster has been
+/// received or not.
+///
+/// The itemAdded(), itemChanged() and itemRemoved() signals are emitted whenever roster
+/// entries are added, changed or removed.
+///
+/// The presenceChanged() signal is emitted whenever the presence for a roster item changes.
+///
+/// \ingroup Managers
+
+class QXmppRosterManager : public QXmppClientExtension
+{
+ Q_OBJECT
+
+public:
+ QXmppRosterManager(QXmppClient* stream);
+ ~QXmppRosterManager();
+
+ bool isRosterReceived() const;
+ QStringList getRosterBareJids() const;
+ QXmppRosterIq::Item getRosterEntry(const QString& bareJid) const;
+
+ QStringList getResources(const QString& bareJid) const;
+ QMap<QString, QXmppPresence> getAllPresencesForBareJid(
+ const QString& bareJid) const;
+ QXmppPresence getPresence(const QString& bareJid,
+ const QString& resource) const;
+
+ /// \cond
+ bool handleStanza(const QDomElement &element);
+ /// \endcond
+
+ // deprecated in release 0.4.0
+ /// \cond
+ void Q_DECL_DEPRECATED removeRosterEntry(const QString &bareJid);
+ /// \endcond
+
+public slots:
+ bool acceptSubscription(const QString &bareJid);
+ bool refuseSubscription(const QString &bareJid);
+ bool addItem(const QString &bareJid, const QString &name = QString(), const QSet<QString> &groups = QSet<QString>());
+ bool removeItem(const QString &bareJid);
+ bool renameItem(const QString &bareJid, const QString &name);
+ bool subscribe(const QString &bareJid);
+ bool unsubscribe(const QString &bareJid);
+
+signals:
+ /// This signal is emitted when the Roster IQ is received after a successful
+ /// connection. That is the roster entries are empty before this signal is emitted.
+ /// One should use getRosterBareJids() and getRosterEntry() only after
+ /// this signal has been emitted.
+ void rosterReceived();
+
+ /// This signal is emitted when the presence of a particular bareJid and resource changes.
+ void presenceChanged(const QString& bareJid, const QString& resource);
+
+ /// \cond
+ // deprecated in release 0.4.0
+ void rosterChanged(const QString& bareJid);
+ /// \endcond
+
+ /// This signal is emitted when a contact asks to subscribe to your presence.
+ ///
+ /// You can either accept the request by calling acceptSubscription() or refuse it
+ /// by calling refuseSubscription().
+ ///
+ /// \note If you set QXmppConfiguration::autoAcceptSubscriptions() to true, this
+ /// signal will not be emitted.
+ void subscriptionReceived(const QString& bareJid);
+
+ /// This signal is emitted when the roster entry of a particular bareJid is
+ /// added as a result of roster push.
+ void itemAdded(const QString& bareJid);
+
+ /// This signal is emitted when the roster entry of a particular bareJid
+ /// changes as a result of roster push.
+ void itemChanged(const QString& bareJid);
+
+ /// This signal is emitted when the roster entry of a particular bareJid is
+ /// removed as a result of roster push.
+ void itemRemoved(const QString& bareJid);
+
+private slots:
+ void _q_connected();
+ void _q_disconnected();
+ void _q_presenceReceived(const QXmppPresence&);
+
+private:
+ QXmppRosterManagerPrivate *d;
+};
+
+#endif // QXMPPROSTER_H
diff --git a/src/client/QXmppRpcManager.cpp b/src/client/QXmppRpcManager.cpp
new file mode 100644
index 00000000..9730b755
--- /dev/null
+++ b/src/client/QXmppRpcManager.cpp
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2008-2011 The QXmpp developers
+ *
+ * Author:
+ * Jeremy Lainé
+ *
+ * Source:
+ * http://code.google.com/p/qxmpp
+ *
+ * This file is a part of QXmpp library.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ */
+
+#include "QXmppClient.h"
+#include "QXmppConstants.h"
+#include "QXmppInvokable.h"
+#include "QXmppRemoteMethod.h"
+#include "QXmppRpcIq.h"
+#include "QXmppRpcManager.h"
+
+/// Constructs a QXmppRpcManager.
+
+QXmppRpcManager::QXmppRpcManager()
+{
+}
+
+/// Adds a local interface which can be queried using RPC.
+///
+/// \param interface
+
+void QXmppRpcManager::addInvokableInterface( QXmppInvokable *interface )
+{
+ m_interfaces[ interface->metaObject()->className() ] = interface;
+}
+
+/// Invokes a remote interface using RPC.
+///
+/// \param iq
+
+void QXmppRpcManager::invokeInterfaceMethod( const QXmppRpcInvokeIq &iq )
+{
+ QXmppStanza::Error error;
+
+ const QStringList methodBits = iq.method().split('.');
+ if (methodBits.size() != 2)
+ return;
+ const QString interface = methodBits.first();
+ const QString method = methodBits.last();
+ QXmppInvokable *iface = m_interfaces.value(interface);
+ if (iface)
+ {
+ if ( iface->isAuthorized( iq.from() ) )
+ {
+
+ if ( iface->interfaces().contains(method) )
+ {
+ QVariant result = iface->dispatch(method.toLatin1(),
+ iq.arguments() );
+ QXmppRpcResponseIq resultIq;
+ resultIq.setId(iq.id());
+ resultIq.setTo(iq.from());
+ resultIq.setValues(QVariantList() << result);
+ client()->sendPacket( resultIq );
+ return;
+ }
+ else
+ {
+ error.setType(QXmppStanza::Error::Cancel);
+ error.setCondition(QXmppStanza::Error::ItemNotFound);
+ }
+ }
+ else
+ {
+ error.setType(QXmppStanza::Error::Auth);
+ error.setCondition(QXmppStanza::Error::Forbidden);
+ }
+ }
+ else
+ {
+ error.setType(QXmppStanza::Error::Cancel);
+ error.setCondition(QXmppStanza::Error::ItemNotFound);
+ }
+ QXmppRpcErrorIq errorIq;
+ errorIq.setId(iq.id());
+ errorIq.setTo(iq.from());
+ errorIq.setQuery(iq);
+ errorIq.setError(error);
+ client()->sendPacket(errorIq);
+}
+
+/// Calls a remote method using RPC with the specified arguments.
+///
+/// \note This method blocks until the response is received, and it may
+/// cause XMPP stanzas to be lost!
+
+QXmppRemoteMethodResult QXmppRpcManager::callRemoteMethod( const QString &jid,
+ const QString &interface,
+ const QVariant &arg1,
+ const QVariant &arg2,
+ const QVariant &arg3,
+ const QVariant &arg4,
+ const QVariant &arg5,
+ const QVariant &arg6,
+ const QVariant &arg7,
+ const QVariant &arg8,
+ const QVariant &arg9,
+ const QVariant &arg10 )
+{
+ QVariantList args;
+ if( arg1.isValid() ) args << arg1;
+ if( arg2.isValid() ) args << arg2;
+ if( arg3.isValid() ) args << arg3;
+ if( arg4.isValid() ) args << arg4;
+ if( arg5.isValid() ) args << arg5;
+ if( arg6.isValid() ) args << arg6;
+ if( arg7.isValid() ) args << arg7;
+ if( arg8.isValid() ) args << arg8;
+ if( arg9.isValid() ) args << arg9;
+ if( arg10.isValid() ) args << arg10;
+
+ QXmppRemoteMethod method( jid, interface, args, client() );
+ connect(this, SIGNAL(rpcCallResponse(QXmppRpcResponseIq)),
+ &method, SLOT(gotResult(QXmppRpcResponseIq)));
+ connect(this, SIGNAL(rpcCallError(QXmppRpcErrorIq)),
+ &method, SLOT(gotError(QXmppRpcErrorIq)));
+
+ return method.call();
+}
+
+QStringList QXmppRpcManager::discoveryFeatures() const
+{
+ // XEP-0009: Jabber-RPC
+ return QStringList() << ns_rpc;
+}
+
+QList<QXmppDiscoveryIq::Identity> QXmppRpcManager::discoveryIdentities() const
+{
+ QXmppDiscoveryIq::Identity identity;
+ identity.setCategory("automation");
+ identity.setType("rpc");
+ return QList<QXmppDiscoveryIq::Identity>() << identity;
+}
+
+bool QXmppRpcManager::handleStanza(const QDomElement &element)
+{
+ // XEP-0009: Jabber-RPC
+ if (QXmppRpcInvokeIq::isRpcInvokeIq(element))
+ {
+ QXmppRpcInvokeIq rpcIqPacket;
+ rpcIqPacket.parse(element);
+ invokeInterfaceMethod(rpcIqPacket);
+ return true;
+ }
+ else if(QXmppRpcResponseIq::isRpcResponseIq(element))
+ {
+ QXmppRpcResponseIq rpcResponseIq;
+ rpcResponseIq.parse(element);
+ emit rpcCallResponse(rpcResponseIq);
+ return true;
+ }
+ else if(QXmppRpcErrorIq::isRpcErrorIq(element))
+ {
+ QXmppRpcErrorIq rpcErrorIq;
+ rpcErrorIq.parse(element);
+ emit rpcCallError(rpcErrorIq);
+ return true;
+ }
+ return false;
+}
+
diff --git a/src/client/QXmppRpcManager.h b/src/client/QXmppRpcManager.h
new file mode 100644
index 00000000..35615952
--- /dev/null
+++ b/src/client/QXmppRpcManager.h
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2008-2011 The QXmpp developers
+ *
+ * Author:
+ * Jeremy Lainé
+ *
+ * Source:
+ * http://code.google.com/p/qxmpp
+ *
+ * This file is a part of QXmpp library.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ */
+
+#ifndef QXMPPRPCMANAGER_H
+#define QXMPPRPCMANAGER_H
+
+#include <QMap>
+#include <QVariant>
+
+#include "QXmppClientExtension.h"
+#include "QXmppInvokable.h"
+#include "QXmppRemoteMethod.h"
+
+class QXmppRpcErrorIq;
+class QXmppRpcInvokeIq;
+class QXmppRpcResponseIq;
+
+/// \brief The QXmppRpcManager class make it possible to invoke remote methods
+/// and to expose local interfaces for remote procedure calls, as specified by
+/// XEP-0009: Jabber-RPC.
+///
+/// To make use of this manager, you need to instantiate it and load it into
+/// the QXmppClient instance as follows:
+///
+/// \code
+/// QXmppRpcManager *manager = new QXmppRpcManager;
+/// client->addExtension(manager);
+/// \endcode
+///
+/// \note THIS API IS NOT FINALIZED YET
+///
+/// \ingroup Managers
+
+class QXmppRpcManager : public QXmppClientExtension
+{
+ Q_OBJECT
+
+public:
+ QXmppRpcManager();
+
+ void addInvokableInterface( QXmppInvokable *interface );
+ QXmppRemoteMethodResult callRemoteMethod( const QString &jid,
+ const QString &interface,
+ const QVariant &arg1 = QVariant(),
+ const QVariant &arg2 = QVariant(),
+ const QVariant &arg3 = QVariant(),
+ const QVariant &arg4 = QVariant(),
+ const QVariant &arg5 = QVariant(),
+ const QVariant &arg6 = QVariant(),
+ const QVariant &arg7 = QVariant(),
+ const QVariant &arg8 = QVariant(),
+ const QVariant &arg9 = QVariant(),
+ const QVariant &arg10 = QVariant() );
+
+ /// \cond
+ QStringList discoveryFeatures() const;
+ virtual QList<QXmppDiscoveryIq::Identity> discoveryIdentities() const;
+ bool handleStanza(const QDomElement &element);
+ /// \endcond
+
+signals:
+ /// \cond
+ void rpcCallResponse(const QXmppRpcResponseIq& result);
+ void rpcCallError(const QXmppRpcErrorIq &err);
+ /// \endcond
+
+private:
+ void invokeInterfaceMethod(const QXmppRpcInvokeIq &iq);
+
+ QMap<QString,QXmppInvokable*> m_interfaces;
+};
+
+#endif
diff --git a/src/client/QXmppTransferManager.cpp b/src/client/QXmppTransferManager.cpp
new file mode 100644
index 00000000..b9d71408
--- /dev/null
+++ b/src/client/QXmppTransferManager.cpp
@@ -0,0 +1,1567 @@
+/*
+ * Copyright (C) 2008-2011 The QXmpp developers
+ *
+ * Author:
+ * Jeremy Lainé
+ *
+ * Source:
+ * http://code.google.com/p/qxmpp
+ *
+ * This file is a part of QXmpp library.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ */
+
+#include <QCryptographicHash>
+#include <QDomElement>
+#include <QFile>
+#include <QFileInfo>
+#include <QHash>
+#include <QHostAddress>
+#include <QNetworkInterface>
+#include <QTime>
+#include <QTimer>
+#include <QUrl>
+
+#include "QXmppByteStreamIq.h"
+#include "QXmppClient.h"
+#include "QXmppConstants.h"
+#include "QXmppIbbIq.h"
+#include "QXmppSocks.h"
+#include "QXmppStreamInitiationIq.h"
+#include "QXmppTransferManager.h"
+#include "QXmppUtils.h"
+
+// time to try to connect to a SOCKS host (7 seconds)
+const int socksTimeout = 7000;
+
+static QString streamHash(const QString &sid, const QString &initiatorJid, const QString &targetJid)
+{
+ QCryptographicHash hash(QCryptographicHash::Sha1);
+ QString str = sid + initiatorJid + targetJid;
+ hash.addData(str.toAscii());
+ return hash.result().toHex();
+}
+
+QXmppTransferFileInfo::QXmppTransferFileInfo()
+ : m_size(0)
+{
+}
+
+QDateTime QXmppTransferFileInfo::date() const
+{
+ return m_date;
+}
+
+void QXmppTransferFileInfo::setDate(const QDateTime &date)
+{
+ m_date = date;
+}
+
+QByteArray QXmppTransferFileInfo::hash() const
+{
+ return m_hash;
+}
+
+void QXmppTransferFileInfo::setHash(const QByteArray &hash)
+{
+ m_hash = hash;
+}
+
+QString QXmppTransferFileInfo::name() const
+{
+ return m_name;
+}
+
+void QXmppTransferFileInfo::setName(const QString &name)
+{
+ m_name = name;
+}
+
+qint64 QXmppTransferFileInfo::size() const
+{
+ return m_size;
+}
+
+void QXmppTransferFileInfo::setSize(qint64 size)
+{
+ m_size = size;
+}
+
+bool QXmppTransferFileInfo::operator==(const QXmppTransferFileInfo &other) const
+{
+ return other.m_size == m_size &&
+ other.m_hash == m_hash &&
+ other.m_name == m_name;
+}
+
+class QXmppTransferJobPrivate
+{
+public:
+ QXmppTransferJobPrivate();
+
+ int blockSize;
+ QXmppTransferJob::Direction direction;
+ qint64 done;
+ QXmppTransferJob::Error error;
+ QCryptographicHash hash;
+ QIODevice *iodevice;
+ QString offerId;
+ QString jid;
+ QUrl localFileUrl;
+ QString sid;
+ QXmppTransferJob::Method method;
+ QString mimeType;
+ QString requestId;
+ QXmppTransferJob::State state;
+ QTime transferStart;
+
+ // file meta-data
+ QXmppTransferFileInfo fileInfo;
+
+ // for in-band bytestreams
+ int ibbSequence;
+
+ // for socks5 bytestreams
+ QTcpSocket *socksSocket;
+ QXmppByteStreamIq::StreamHost socksProxy;
+};
+
+QXmppTransferJobPrivate::QXmppTransferJobPrivate()
+ : blockSize(16384),
+ done(0),
+ error(QXmppTransferJob::NoError),
+ hash(QCryptographicHash::Md5),
+ iodevice(0),
+ method(QXmppTransferJob::NoMethod),
+ state(QXmppTransferJob::OfferState),
+ ibbSequence(0),
+ socksSocket(0)
+{
+}
+
+QXmppTransferJob::QXmppTransferJob(const QString &jid, QXmppTransferJob::Direction direction, QObject *parent)
+ : QXmppLoggable(parent),
+ d(new QXmppTransferJobPrivate)
+{
+ d->direction = direction;
+ d->jid = jid;
+}
+
+QXmppTransferJob::~QXmppTransferJob()
+{
+ delete d;
+}
+
+/// Call this method if you wish to abort on ongoing transfer job.
+///
+
+void QXmppTransferJob::abort()
+{
+ terminate(AbortError);
+}
+
+/// Call this method if you wish to accept an incoming transfer job.
+///
+
+void QXmppTransferJob::accept(const QString &filePath)
+{
+ if (d->direction == IncomingDirection && d->state == OfferState && !d->iodevice)
+ {
+ QFile *file = new QFile(filePath, this);
+ if (!file->open(QIODevice::WriteOnly))
+ {
+ warning(QString("Could not write to %1").arg(filePath));
+ abort();
+ return;
+ }
+
+ d->iodevice = file;
+ setLocalFileUrl(QUrl::fromLocalFile(filePath));
+ setState(QXmppTransferJob::StartState);
+ }
+}
+
+/// Call this method if you wish to accept an incoming transfer job.
+///
+
+void QXmppTransferJob::accept(QIODevice *iodevice)
+{
+ if (d->direction == IncomingDirection && d->state == OfferState && !d->iodevice)
+ {
+ d->iodevice = iodevice;
+ setState(QXmppTransferJob::StartState);
+ }
+}
+
+void QXmppTransferJob::checkData()
+{
+ if ((d->fileInfo.size() && d->done != d->fileInfo.size()) ||
+ (!d->fileInfo.hash().isEmpty() && d->hash.result() != d->fileInfo.hash()))
+ terminate(QXmppTransferJob::FileCorruptError);
+ else
+ terminate(QXmppTransferJob::NoError);
+}
+
+/// Returns the job's transfer direction.
+///
+
+QXmppTransferJob::Direction QXmppTransferJob::direction() const
+{
+ return d->direction;
+}
+
+/// Returns the last error that was encountered.
+///
+
+QXmppTransferJob::Error QXmppTransferJob::error() const
+{
+ return d->error;
+}
+
+/// Returns the remote party's JID.
+///
+
+QString QXmppTransferJob::jid() const
+{
+ return d->jid;
+}
+
+/// Returns the local file URL.
+///
+
+QUrl QXmppTransferJob::localFileUrl() const
+{
+ return d->localFileUrl;
+}
+
+/// Sets the local file URL.
+///
+/// \note You do not need to call this method if you called accept()
+/// with a file path.
+
+void QXmppTransferJob::setLocalFileUrl(const QUrl &localFileUrl)
+{
+ if (localFileUrl != d->localFileUrl) {
+ d->localFileUrl = localFileUrl;
+ emit localFileUrlChanged(localFileUrl);
+ }
+}
+
+/// Returns meta-data about the file being transferred.
+///
+
+QXmppTransferFileInfo QXmppTransferJob::fileInfo() const
+{
+ return d->fileInfo;
+}
+
+QDateTime QXmppTransferJob::fileDate() const
+{
+ return d->fileInfo.date();
+}
+
+QByteArray QXmppTransferJob::fileHash() const
+{
+ return d->fileInfo.hash();
+}
+
+QString QXmppTransferJob::fileName() const
+{
+ return d->fileInfo.name();
+}
+
+qint64 QXmppTransferJob::fileSize() const
+{
+ return d->fileInfo.size();
+}
+
+/// Returns the job's transfer method.
+///
+
+QXmppTransferJob::Method QXmppTransferJob::method() const
+{
+ return d->method;
+}
+
+/// Returns the job's session identifier.
+///
+
+QString QXmppTransferJob::sid() const
+{
+ return d->sid;
+}
+
+/// Returns the job's transfer speed in bytes per second.
+///
+/// If the transfer has not started yet or is already finished, returns 0.
+///
+
+qint64 QXmppTransferJob::speed() const
+{
+ qint64 elapsed = d->transferStart.elapsed();
+ if (d->state != QXmppTransferJob::TransferState || !elapsed)
+ return 0;
+ return (d->done * 1000.0) / elapsed;
+}
+
+/// Returns the job's state.
+///
+
+QXmppTransferJob::State QXmppTransferJob::state() const
+{
+ return d->state;
+}
+
+void QXmppTransferJob::setState(QXmppTransferJob::State state)
+{
+ if (d->state != state)
+ {
+ d->state = state;
+ if (d->state == QXmppTransferJob::TransferState)
+ d->transferStart.start();
+ emit stateChanged(d->state);
+ }
+}
+
+void QXmppTransferJob::_q_disconnected()
+{
+ if (d->state == QXmppTransferJob::FinishedState)
+ return;
+
+ // terminate transfer
+ if (d->direction == QXmppTransferJob::IncomingDirection)
+ {
+ checkData();
+ } else {
+ if (fileSize() && d->done != fileSize())
+ terminate(QXmppTransferJob::ProtocolError);
+ else
+ terminate(QXmppTransferJob::NoError);
+ }
+}
+
+void QXmppTransferJob::_q_receiveData()
+{
+ if (d->state != QXmppTransferJob::TransferState)
+ return;
+
+ // receive data block
+ if (d->direction == QXmppTransferJob::IncomingDirection)
+ {
+ writeData(d->socksSocket->readAll());
+
+ // if we have received all the data, stop here
+ if (fileSize() && d->done >= fileSize())
+ checkData();
+ }
+}
+
+void QXmppTransferJob::_q_sendData()
+{
+ if (d->state != QXmppTransferJob::TransferState)
+ return;
+
+ // don't saturate the outgoing socket
+ if (d->socksSocket->bytesToWrite() > 2 * d->blockSize)
+ return;
+
+ // check whether we have written the whole file
+ if (d->fileInfo.size() && d->done >= d->fileInfo.size())
+ {
+ if (!d->socksSocket->bytesToWrite())
+ terminate(QXmppTransferJob::NoError);
+ return;
+ }
+
+ char *buffer = new char[d->blockSize];
+ qint64 length = d->iodevice->read(buffer, d->blockSize);
+ if (length < 0)
+ {
+ delete [] buffer;
+ terminate(QXmppTransferJob::FileAccessError);
+ return;
+ }
+ if (length > 0)
+ {
+ d->socksSocket->write(buffer, length);
+ delete [] buffer;
+ d->done += length;
+ emit progress(d->done, fileSize());
+ }
+}
+
+void QXmppTransferJob::_q_terminated()
+{
+ emit stateChanged(d->state);
+ if (d->error != NoError)
+ emit error(d->error);
+ emit finished();
+}
+
+void QXmppTransferJob::terminate(QXmppTransferJob::Error cause)
+{
+ if (d->state == FinishedState)
+ return;
+
+ // change state
+ d->error = cause;
+ d->state = FinishedState;
+
+ // close IO device
+ if (d->iodevice)
+ d->iodevice->close();
+
+ // close socket
+ if (d->socksSocket)
+ {
+ d->socksSocket->flush();
+ d->socksSocket->close();
+ }
+
+ // emit signals later
+ QTimer::singleShot(0, this, SLOT(_q_terminated()));
+}
+
+bool QXmppTransferJob::writeData(const QByteArray &data)
+{
+ const qint64 written = d->iodevice->write(data);
+ if (written < 0)
+ return false;
+ d->done += written;
+ if (!d->fileInfo.hash().isEmpty())
+ d->hash.addData(data);
+ progress(d->done, d->fileInfo.size());
+ return true;
+}
+
+/// Constructs a QXmppTransferManager to handle incoming and outgoing
+/// file transfers.
+
+QXmppTransferManager::QXmppTransferManager()
+ : m_ibbBlockSize(4096),
+ m_proxyOnly(false),
+ m_socksServer(0),
+ m_supportedMethods(QXmppTransferJob::AnyMethod)
+{
+ bool check;
+ Q_UNUSED(check);
+
+ // start SOCKS server
+ m_socksServer = new QXmppSocksServer(this);
+ if (m_socksServer->listen()) {
+ check = connect(m_socksServer, SIGNAL(newConnection(QTcpSocket*,QString,quint16)),
+ this, SLOT(_q_socksServerConnected(QTcpSocket*,QString,quint16)));
+ Q_ASSERT(check);
+ } else {
+ qWarning("QXmppSocksServer could not start listening");
+ }
+}
+
+void QXmppTransferManager::setClient(QXmppClient *client)
+{
+ bool check;
+ Q_UNUSED(check);
+
+ QXmppClientExtension::setClient(client);
+
+ // XEP-0047: In-Band Bytestreams
+ check = connect(client, SIGNAL(iqReceived(QXmppIq)),
+ this, SLOT(_q_iqReceived(QXmppIq)));
+ Q_ASSERT(check);
+}
+
+void QXmppTransferManager::byteStreamIqReceived(const QXmppByteStreamIq &iq)
+{
+ // handle IQ from proxy
+ foreach (QXmppTransferJob *job, m_jobs)
+ {
+ if (job->d->socksProxy.jid() == iq.from() && job->d->requestId == iq.id())
+ {
+ if (iq.type() == QXmppIq::Result && iq.streamHosts().size() > 0)
+ {
+ job->d->socksProxy = iq.streamHosts().first();
+ socksServerSendOffer(job);
+ return;
+ }
+ }
+ }
+
+ if (iq.type() == QXmppIq::Result)
+ byteStreamResultReceived(iq);
+ else if (iq.type() == QXmppIq::Set)
+ byteStreamSetReceived(iq);
+}
+
+/// Handle a response to a bystream set, i.e. after we informed the remote party
+/// that we connected to a stream host.
+void QXmppTransferManager::byteStreamResponseReceived(const QXmppIq &iq)
+{
+ QXmppTransferJob *job = getJobByRequestId(QXmppTransferJob::IncomingDirection, iq.from(), iq.id());
+ if (!job ||
+ job->method() != QXmppTransferJob::SocksMethod ||
+ job->state() != QXmppTransferJob::StartState)
+ return;
+
+ if (iq.type() == QXmppIq::Error)
+ job->terminate(QXmppTransferJob::ProtocolError);
+}
+
+/// Handle a bytestream result, i.e. after the remote party has connected to
+/// a stream host.
+void QXmppTransferManager::byteStreamResultReceived(const QXmppByteStreamIq &iq)
+{
+ bool check;
+ Q_UNUSED(check);
+
+ QXmppTransferJob *job = getJobByRequestId(QXmppTransferJob::OutgoingDirection, iq.from(), iq.id());
+ if (!job ||
+ job->method() != QXmppTransferJob::SocksMethod ||
+ job->state() != QXmppTransferJob::StartState)
+ return;
+
+ // check the stream host
+ if (iq.streamHostUsed() == job->d->socksProxy.jid())
+ {
+ const QXmppByteStreamIq::StreamHost streamHost = job->d->socksProxy;
+ info(QString("Connecting to proxy: %1 (%2:%3)").arg(
+ streamHost.jid(),
+ streamHost.host().toString(),
+ QString::number(streamHost.port())));
+
+ // connect to proxy
+ const QString hostName = streamHash(job->d->sid,
+ client()->configuration().jid(),
+ job->d->jid);
+
+ QXmppSocksClient *socksClient = new QXmppSocksClient(streamHost.host(), streamHost.port(), job);
+ socksClient->connectToHost(hostName, 0);
+ // FIXME : this should probably be made asynchronous as it blocks XMPP packet handling
+ if (!socksClient->waitForReady(socksTimeout))
+ {
+ warning(QString("Failed to connect to proxy: %1 (%2:%3)").arg(
+ streamHost.jid(),
+ streamHost.host().toString(),
+ QString::number(streamHost.port())));
+ delete socksClient;
+ job->terminate(QXmppTransferJob::ProtocolError);
+ return;
+ }
+ job->d->socksSocket = socksClient;
+ check = connect(job->d->socksSocket, SIGNAL(disconnected()),
+ job, SLOT(_q_disconnected()));
+ Q_ASSERT(check);
+
+ // activate stream
+ QXmppByteStreamIq streamIq;
+ streamIq.setType(QXmppIq::Set);
+ streamIq.setFrom(client()->configuration().jid());
+ streamIq.setTo(streamHost.jid());
+ streamIq.setSid(job->d->sid);
+ streamIq.setActivate(job->d->jid);
+ job->d->requestId = streamIq.id();
+ client()->sendPacket(streamIq);
+ return;
+ }
+
+ // direction connection, start sending data
+ if (!job->d->socksSocket)
+ {
+ warning("Client says they connected to our SOCKS server, but they did not");
+ job->terminate(QXmppTransferJob::ProtocolError);
+ return;
+ }
+ job->setState(QXmppTransferJob::TransferState);
+ check = connect(job->d->socksSocket, SIGNAL(disconnected()),
+ job, SLOT(_q_disconnected()));
+ Q_ASSERT(check);
+
+ check = connect(job->d->socksSocket, SIGNAL(bytesWritten(qint64)),
+ job, SLOT(_q_sendData()));
+ Q_ASSERT(check);
+
+ check = connect(job->d->iodevice, SIGNAL(readyRead()),
+ job, SLOT(_q_sendData()));
+ Q_ASSERT(check);
+
+ job->_q_sendData();
+}
+
+/// Handle a bytestream set, i.e. an invitation from the remote party to connect
+/// to a stream host.
+void QXmppTransferManager::byteStreamSetReceived(const QXmppByteStreamIq &iq)
+{
+ bool check;
+ Q_UNUSED(check);
+
+ QXmppIq response;
+ response.setId(iq.id());
+ response.setTo(iq.from());
+
+ QXmppTransferJob *job = getJobBySid(QXmppTransferJob::IncomingDirection, iq.from(), iq.sid());
+ if (!job ||
+ job->method() != QXmppTransferJob::SocksMethod ||
+ job->state() != QXmppTransferJob::StartState)
+ {
+ // the stream is unknown
+ QXmppStanza::Error error(QXmppStanza::Error::Auth, QXmppStanza::Error::NotAcceptable);
+ error.setCode(406);
+ response.setType(QXmppIq::Error);
+ response.setError(error);
+ client()->sendPacket(response);
+ return;
+ }
+
+ // try connecting to the offered stream hosts
+ foreach (const QXmppByteStreamIq::StreamHost &streamHost, iq.streamHosts())
+ {
+ info(QString("Connecting to streamhost: %1 (%2:%3)").arg(
+ streamHost.jid(),
+ streamHost.host().toString(),
+ QString::number(streamHost.port())));
+
+ const QString hostName = streamHash(job->d->sid,
+ job->d->jid,
+ client()->configuration().jid());
+
+ // try to connect to stream host
+ QXmppSocksClient *socksClient = new QXmppSocksClient(streamHost.host(), streamHost.port(), job);
+ socksClient->connectToHost(hostName, 0);
+ // FIXME : this should probably be made asynchronous as it blocks XMPP packet handling
+ if (socksClient->waitForReady(socksTimeout)) {
+ job->setState(QXmppTransferJob::TransferState);
+ job->d->socksSocket = socksClient;
+
+ check = connect(job->d->socksSocket, SIGNAL(readyRead()),
+ job, SLOT(_q_receiveData()));
+ Q_ASSERT(check);
+
+ check = connect(job->d->socksSocket, SIGNAL(disconnected()),
+ job, SLOT(_q_disconnected()));
+ Q_ASSERT(check);
+
+ QXmppByteStreamIq ackIq;
+ ackIq.setId(iq.id());
+ ackIq.setTo(iq.from());
+ ackIq.setType(QXmppIq::Result);
+ ackIq.setSid(job->d->sid);
+ ackIq.setStreamHostUsed(streamHost.jid());
+ client()->sendPacket(ackIq);
+ return;
+ } else {
+ warning(QString("Failed to connect to streamhost: %1 (%2:%3)").arg(
+ streamHost.jid(),
+ streamHost.host().toString(),
+ QString::number(streamHost.port())));
+ delete socksClient;
+ }
+ }
+
+ // could not connect to any stream host
+ QXmppStanza::Error error(QXmppStanza::Error::Cancel, QXmppStanza::Error::ItemNotFound);
+ error.setCode(404);
+ response.setType(QXmppIq::Error);
+ response.setError(error);
+ client()->sendPacket(response);
+
+ job->terminate(QXmppTransferJob::ProtocolError);
+}
+
+QStringList QXmppTransferManager::discoveryFeatures() const
+{
+ return QStringList()
+ << ns_ibb // XEP-0047: In-Band Bytestreams
+ << ns_bytestreams // XEP-0065: SOCKS5 Bytestreams
+ << ns_stream_initiation // XEP-0095: Stream Initiation
+ << ns_stream_initiation_file_transfer; // XEP-0096: SI File Transfer
+}
+
+bool QXmppTransferManager::handleStanza(const QDomElement &element)
+{
+ if (element.tagName() != "iq")
+ return false;
+
+ // XEP-0047 In-Band Bytestreams
+ if(QXmppIbbCloseIq::isIbbCloseIq(element))
+ {
+ QXmppIbbCloseIq ibbCloseIq;
+ ibbCloseIq.parse(element);
+ ibbCloseIqReceived(ibbCloseIq);
+ return true;
+ }
+ else if(QXmppIbbDataIq::isIbbDataIq(element))
+ {
+ QXmppIbbDataIq ibbDataIq;
+ ibbDataIq.parse(element);
+ ibbDataIqReceived(ibbDataIq);
+ return true;
+ }
+ else if(QXmppIbbOpenIq::isIbbOpenIq(element))
+ {
+ QXmppIbbOpenIq ibbOpenIq;
+ ibbOpenIq.parse(element);
+ ibbOpenIqReceived(ibbOpenIq);
+ return true;
+ }
+ // XEP-0065: SOCKS5 Bytestreams
+ else if(QXmppByteStreamIq::isByteStreamIq(element))
+ {
+ QXmppByteStreamIq byteStreamIq;
+ byteStreamIq.parse(element);
+ byteStreamIqReceived(byteStreamIq);
+ return true;
+ }
+ // XEP-0095: Stream Initiation
+ else if(QXmppStreamInitiationIq::isStreamInitiationIq(element))
+ {
+ QXmppStreamInitiationIq siIq;
+ siIq.parse(element);
+ streamInitiationIqReceived(siIq);
+ return true;
+ }
+
+ return false;
+}
+
+QXmppTransferJob* QXmppTransferManager::getJobByRequestId(QXmppTransferJob::Direction direction, const QString &jid, const QString &id)
+{
+ foreach (QXmppTransferJob *job, m_jobs)
+ if (job->d->direction == direction &&
+ job->d->jid == jid &&
+ job->d->requestId == id)
+ return job;
+ return 0;
+}
+
+QXmppTransferJob* QXmppTransferManager::getJobBySid(QXmppTransferJob::Direction direction, const QString &jid, const QString &sid)
+{
+ foreach (QXmppTransferJob *job, m_jobs)
+ if (job->d->direction == direction &&
+ job->d->jid == jid &&
+ job->d->sid == sid)
+ return job;
+ return 0;
+}
+
+void QXmppTransferManager::ibbCloseIqReceived(const QXmppIbbCloseIq &iq)
+{
+ QXmppIq response;
+ response.setTo(iq.from());
+ response.setId(iq.id());
+
+ QXmppTransferJob *job = getJobBySid(QXmppTransferJob::IncomingDirection, iq.from(), iq.sid());
+ if (!job ||
+ job->method() != QXmppTransferJob::InBandMethod)
+ {
+ // the job is unknown, cancel it
+ QXmppStanza::Error error(QXmppStanza::Error::Cancel, QXmppStanza::Error::ItemNotFound);
+ response.setType(QXmppIq::Error);
+ response.setError(error);
+ client()->sendPacket(response);
+ return;
+ }
+
+ // acknowledge the packet
+ response.setType(QXmppIq::Result);
+ client()->sendPacket(response);
+
+ // check received data
+ job->checkData();
+}
+
+void QXmppTransferManager::ibbDataIqReceived(const QXmppIbbDataIq &iq)
+{
+ QXmppIq response;
+ response.setTo(iq.from());
+ response.setId(iq.id());
+
+ QXmppTransferJob *job = getJobBySid(QXmppTransferJob::IncomingDirection, iq.from(), iq.sid());
+ if (!job ||
+ job->method() != QXmppTransferJob::InBandMethod ||
+ job->state() != QXmppTransferJob::TransferState)
+ {
+ // the job is unknown, cancel it
+ QXmppStanza::Error error(QXmppStanza::Error::Cancel, QXmppStanza::Error::ItemNotFound);
+ response.setType(QXmppIq::Error);
+ response.setError(error);
+ client()->sendPacket(response);
+ return;
+ }
+
+ if (iq.sequence() != job->d->ibbSequence)
+ {
+ // the packet is out of sequence
+ QXmppStanza::Error error(QXmppStanza::Error::Cancel, QXmppStanza::Error::UnexpectedRequest);
+ response.setType(QXmppIq::Error);
+ response.setError(error);
+ client()->sendPacket(response);
+ return;
+ }
+
+ // write data
+ job->writeData(iq.payload());
+ job->d->ibbSequence++;
+
+ // acknowledge the packet
+ response.setType(QXmppIq::Result);
+ client()->sendPacket(response);
+}
+
+void QXmppTransferManager::ibbOpenIqReceived(const QXmppIbbOpenIq &iq)
+{
+ QXmppIq response;
+ response.setTo(iq.from());
+ response.setId(iq.id());
+
+ QXmppTransferJob *job = getJobBySid(QXmppTransferJob::IncomingDirection, iq.from(), iq.sid());
+ if (!job ||
+ job->method() != QXmppTransferJob::InBandMethod)
+ {
+ // the job is unknown, cancel it
+ QXmppStanza::Error error(QXmppStanza::Error::Cancel, QXmppStanza::Error::ItemNotFound);
+ response.setType(QXmppIq::Error);
+ response.setError(error);
+ client()->sendPacket(response);
+ return;
+ }
+
+ if (iq.blockSize() > m_ibbBlockSize)
+ {
+ // we prefer a smaller block size
+ QXmppStanza::Error error(QXmppStanza::Error::Modify, QXmppStanza::Error::ResourceConstraint);
+ response.setType(QXmppIq::Error);
+ response.setError(error);
+ client()->sendPacket(response);
+ return;
+ }
+
+ job->d->blockSize = iq.blockSize();
+ job->setState(QXmppTransferJob::TransferState);
+
+ // accept transfer
+ response.setType(QXmppIq::Result);
+ client()->sendPacket(response);
+}
+
+void QXmppTransferManager::ibbResponseReceived(const QXmppIq &iq)
+{
+ QXmppTransferJob *job = getJobByRequestId(QXmppTransferJob::OutgoingDirection, iq.from(), iq.id());
+ if (!job ||
+ job->method() != QXmppTransferJob::InBandMethod ||
+ job->state() == QXmppTransferJob::FinishedState)
+ return;
+
+ // if the IO device is closed, do nothing
+ if (!job->d->iodevice->isOpen())
+ return;
+
+ if (iq.type() == QXmppIq::Result)
+ {
+ const QByteArray buffer = job->d->iodevice->read(job->d->blockSize);
+ job->setState(QXmppTransferJob::TransferState);
+ if (buffer.size())
+ {
+ // send next data block
+ QXmppIbbDataIq dataIq;
+ dataIq.setTo(job->d->jid);
+ dataIq.setSid(job->d->sid);
+ dataIq.setSequence(job->d->ibbSequence++);
+ dataIq.setPayload(buffer);
+ job->d->requestId = dataIq.id();
+ client()->sendPacket(dataIq);
+
+ job->d->done += buffer.size();
+ job->progress(job->d->done, job->fileSize());
+ } else {
+ // close the bytestream
+ QXmppIbbCloseIq closeIq;
+ closeIq.setTo(job->d->jid);
+ closeIq.setSid(job->d->sid);
+ job->d->requestId = closeIq.id();
+ client()->sendPacket(closeIq);
+
+ job->terminate(QXmppTransferJob::NoError);
+ }
+ }
+ else if (iq.type() == QXmppIq::Error)
+ {
+ // close the bytestream
+ QXmppIbbCloseIq closeIq;
+ closeIq.setTo(job->d->jid);
+ closeIq.setSid(job->d->sid);
+ job->d->requestId = closeIq.id();
+ client()->sendPacket(closeIq);
+
+ job->terminate(QXmppTransferJob::ProtocolError);
+ }
+}
+
+void QXmppTransferManager::_q_iqReceived(const QXmppIq &iq)
+{
+ bool check;
+ Q_UNUSED(check);
+
+ foreach (QXmppTransferJob *job, m_jobs)
+ {
+ // handle IQ from proxy
+ if (job->d->socksProxy.jid() == iq.from() && job->d->requestId == iq.id())
+ {
+ if (job->d->socksSocket)
+ {
+ // proxy connection activation result
+ if (iq.type() == QXmppIq::Result)
+ {
+ // proxy stream activated, start sending data
+ job->setState(QXmppTransferJob::TransferState);
+
+ check = connect(job->d->socksSocket, SIGNAL(bytesWritten(qint64)),
+ job, SLOT(_q_sendData()));
+ Q_ASSERT(check);
+
+ check = connect(job->d->iodevice, SIGNAL(readyRead()),
+ job, SLOT(_q_sendData()));
+ Q_ASSERT(check);
+
+ job->_q_sendData();
+ } else if (iq.type() == QXmppIq::Error) {
+ // proxy stream not activated, terminate
+ warning("Could not activate SOCKS5 proxy bytestream");
+ job->terminate(QXmppTransferJob::ProtocolError);
+ }
+ } else {
+ // we could not get host/port from proxy, procede without a proxy
+ if (iq.type() == QXmppIq::Error)
+ socksServerSendOffer(job);
+ }
+ return;
+ }
+
+ // handle IQ from peer
+ else if (job->d->jid == iq.from() && job->d->requestId == iq.id())
+ {
+ if (job->direction() == QXmppTransferJob::OutgoingDirection &&
+ job->method() == QXmppTransferJob::InBandMethod)
+ {
+ ibbResponseReceived(iq);
+ return;
+ }
+ else if (job->direction() == QXmppTransferJob::IncomingDirection &&
+ job->method() == QXmppTransferJob::SocksMethod)
+ {
+ byteStreamResponseReceived(iq);
+ return;
+ }
+ else if (job->direction() == QXmppTransferJob::OutgoingDirection &&
+ iq.type() == QXmppIq::Error)
+ {
+ // remote party cancelled stream initiation
+ job->terminate(QXmppTransferJob::AbortError);
+ return;
+ }
+ }
+ }
+}
+
+void QXmppTransferManager::_q_jobDestroyed(QObject *object)
+{
+ m_jobs.removeAll(static_cast<QXmppTransferJob*>(object));
+}
+
+void QXmppTransferManager::_q_jobError(QXmppTransferJob::Error error)
+{
+ QXmppTransferJob *job = qobject_cast<QXmppTransferJob *>(sender());
+ if (!job || !m_jobs.contains(job))
+ return;
+
+ if (job->direction() == QXmppTransferJob::OutgoingDirection &&
+ job->method() == QXmppTransferJob::InBandMethod &&
+ error == QXmppTransferJob::AbortError)
+ {
+ // close the bytestream
+ QXmppIbbCloseIq closeIq;
+ closeIq.setTo(job->d->jid);
+ closeIq.setSid(job->d->sid);
+ job->d->requestId = closeIq.id();
+ client()->sendPacket(closeIq);
+ }
+}
+
+void QXmppTransferManager::_q_jobFinished()
+{
+ QXmppTransferJob *job = qobject_cast<QXmppTransferJob *>(sender());
+ if (!job || !m_jobs.contains(job))
+ return;
+
+ emit jobFinished(job);
+}
+
+void QXmppTransferManager::_q_jobStateChanged(QXmppTransferJob::State state)
+{
+ bool check;
+ Q_UNUSED(check);
+
+ QXmppTransferJob *job = qobject_cast<QXmppTransferJob *>(sender());
+ if (!job || !m_jobs.contains(job))
+ return;
+
+ if (job->direction() != QXmppTransferJob::IncomingDirection)
+ return;
+
+ // disconnect from the signal
+ disconnect(job, SIGNAL(stateChanged(QXmppTransferJob::State)),
+ this, SLOT(_q_jobStateChanged(QXmppTransferJob::State)));
+
+ // the job was refused by the local party
+ if (state != QXmppTransferJob::StartState || !job->d->iodevice || !job->d->iodevice->isWritable())
+ {
+ QXmppStanza::Error error(QXmppStanza::Error::Cancel, QXmppStanza::Error::Forbidden);
+ error.setCode(403);
+
+ QXmppIq response;
+ response.setTo(job->jid());
+ response.setId(job->d->offerId);
+ response.setType(QXmppIq::Error);
+ response.setError(error);
+ client()->sendPacket(response);
+
+ job->terminate(QXmppTransferJob::AbortError);
+ return;
+ }
+
+ // the job was accepted by the local party
+ check = connect(job, SIGNAL(error(QXmppTransferJob::Error)),
+ this, SLOT(_q_jobError(QXmppTransferJob::Error)));
+ Q_ASSERT(check);
+
+ QXmppElement value;
+ value.setTagName("value");
+ if (job->method() == QXmppTransferJob::InBandMethod)
+ value.setValue(ns_ibb);
+ else if (job->method() == QXmppTransferJob::SocksMethod)
+ value.setValue(ns_bytestreams);
+
+ QXmppElement field;
+ field.setTagName("field");
+ field.setAttribute("var", "stream-method");
+ field.appendChild(value);
+
+ QXmppElement x;
+ x.setTagName("x");
+ x.setAttribute("xmlns", "jabber:x:data");
+ x.setAttribute("type", "submit");
+ x.appendChild(field);
+
+ QXmppElement feature;
+ feature.setTagName("feature");
+ feature.setAttribute("xmlns", ns_feature_negotiation);
+ feature.appendChild(x);
+
+ QXmppStreamInitiationIq response;
+ response.setTo(job->jid());
+ response.setId(job->d->offerId);
+ response.setType(QXmppIq::Result);
+ response.setProfile(QXmppStreamInitiationIq::FileTransfer);
+ response.setSiItems(feature);
+
+ client()->sendPacket(response);
+
+ // notify user
+ emit jobStarted(job);
+}
+
+/// Send file to a remote party.
+///
+/// The remote party will be given the choice to accept or refuse the transfer.
+///
+QXmppTransferJob *QXmppTransferManager::sendFile(const QString &jid, const QString &filePath, const QString &sid)
+{
+ if (jid.isEmpty()) {
+ warning("Refusing to send file to an empty jid");
+ return 0;
+ }
+
+ QFileInfo info(filePath);
+
+ QXmppTransferFileInfo fileInfo;
+ fileInfo.setDate(info.lastModified());
+ fileInfo.setName(info.fileName());
+ fileInfo.setSize(info.size());
+
+ // open file
+ QIODevice *device = new QFile(filePath);
+ if (!device->open(QIODevice::ReadOnly))
+ {
+ warning(QString("Could not read from %1").arg(filePath));
+ delete device;
+ device = 0;
+ }
+
+ // hash file
+ if (device && !device->isSequential())
+ {
+ QCryptographicHash hash(QCryptographicHash::Md5);
+ QByteArray buffer;
+ while (device->bytesAvailable())
+ {
+ buffer = device->read(16384);
+ hash.addData(buffer);
+ }
+ device->reset();
+ fileInfo.setHash(hash.result());
+ }
+
+ // create job
+ QXmppTransferJob *job = sendFile(jid, device, fileInfo, sid);
+ job->setLocalFileUrl(filePath);
+ return job;
+}
+
+/// Send file to a remote party.
+///
+/// The remote party will be given the choice to accept or refuse the transfer.
+///
+QXmppTransferJob *QXmppTransferManager::sendFile(const QString &jid, QIODevice *device, const QXmppTransferFileInfo &fileInfo, const QString &sid)
+{
+ bool check;
+ Q_UNUSED(check);
+
+ if (jid.isEmpty()) {
+ warning("Refusing to send file to an empty jid");
+ return 0;
+ }
+
+ QXmppTransferJob *job = new QXmppTransferJob(jid, QXmppTransferJob::OutgoingDirection, this);
+ if (sid.isEmpty())
+ job->d->sid = generateStanzaHash();
+ else
+ job->d->sid = sid;
+ job->d->fileInfo = fileInfo;
+ job->d->iodevice = device;
+ if (device)
+ device->setParent(job);
+
+ // check file is open
+ if (!device || !device->isReadable())
+ {
+ job->terminate(QXmppTransferJob::FileAccessError);
+ return job;
+ }
+
+ // check we support some methods
+ if (!m_supportedMethods)
+ {
+ job->terminate(QXmppTransferJob::ProtocolError);
+ return job;
+ }
+
+ // prepare negotiation
+ QXmppElementList items;
+
+ QXmppElement file;
+ file.setTagName("file");
+ file.setAttribute("xmlns", ns_stream_initiation_file_transfer);
+ file.setAttribute("date", datetimeToString(job->fileDate()));
+ file.setAttribute("hash", job->fileHash().toHex());
+ file.setAttribute("name", job->fileName());
+ file.setAttribute("size", QString::number(job->fileSize()));
+ items.append(file);
+
+ QXmppElement feature;
+ feature.setTagName("feature");
+ feature.setAttribute("xmlns", ns_feature_negotiation);
+
+ QXmppElement x;
+ x.setTagName("x");
+ x.setAttribute("xmlns", "jabber:x:data");
+ x.setAttribute("type", "form");
+ feature.appendChild(x);
+
+ QXmppElement field;
+ field.setTagName("field");
+ field.setAttribute("var", "stream-method");
+ field.setAttribute("type", "list-single");
+ x.appendChild(field);
+
+ // add supported stream methods
+ if (m_supportedMethods & QXmppTransferJob::InBandMethod)
+ {
+ QXmppElement option;
+ option.setTagName("option");
+ field.appendChild(option);
+
+ QXmppElement value;
+ value.setTagName("value");
+ value.setValue(ns_ibb);
+ option.appendChild(value);
+ }
+ if (m_supportedMethods & QXmppTransferJob::SocksMethod)
+ {
+ QXmppElement option;
+ option.setTagName("option");
+ field.appendChild(option);
+
+ QXmppElement value;
+ value.setTagName("value");
+ value.setValue(ns_bytestreams);
+ option.appendChild(value);
+ }
+
+ items.append(feature);
+
+ // start job
+ m_jobs.append(job);
+ check = connect(job, SIGNAL(destroyed(QObject*)),
+ this, SLOT(_q_jobDestroyed(QObject*)));
+ Q_ASSERT(check);
+
+ check = connect(job, SIGNAL(error(QXmppTransferJob::Error)),
+ this, SLOT(_q_jobError(QXmppTransferJob::Error)));
+ Q_ASSERT(check);
+
+ check = connect(job, SIGNAL(finished()),
+ this, SLOT(_q_jobFinished()));
+ Q_ASSERT(check);
+
+ QXmppStreamInitiationIq request;
+ request.setType(QXmppIq::Set);
+ request.setTo(jid);
+ request.setProfile(QXmppStreamInitiationIq::FileTransfer);
+ request.setSiItems(items);
+ request.setSiId(job->d->sid);
+ job->d->requestId = request.id();
+ client()->sendPacket(request);
+
+ // notify user
+ emit jobStarted(job);
+
+ return job;
+}
+
+void QXmppTransferManager::_q_socksServerConnected(QTcpSocket *socket, const QString &hostName, quint16 port)
+{
+ const QString ownJid = client()->configuration().jid();
+ foreach (QXmppTransferJob *job, m_jobs)
+ {
+ if (hostName == streamHash(job->d->sid, ownJid, job->jid()) && port == 0)
+ {
+ job->d->socksSocket = socket;
+ return;
+ }
+ }
+ warning("QXmppSocksServer got a connection for a unknown stream");
+ socket->close();
+}
+
+void QXmppTransferManager::socksServerSendOffer(QXmppTransferJob *job)
+{
+ const QString ownJid = client()->configuration().jid();
+ QList<QXmppByteStreamIq::StreamHost> streamHosts;
+
+ // discover local IPs
+ if (!m_proxyOnly)
+ {
+ foreach (const QNetworkInterface &interface, QNetworkInterface::allInterfaces())
+ {
+ if (!(interface.flags() & QNetworkInterface::IsRunning) ||
+ interface.flags() & QNetworkInterface::IsLoopBack)
+ continue;
+
+ foreach (const QNetworkAddressEntry &entry, interface.addressEntries())
+ {
+ if (entry.ip().protocol() != QAbstractSocket::IPv4Protocol ||
+ entry.netmask().isNull() ||
+ entry.netmask() == QHostAddress::Broadcast)
+ continue;
+
+ QXmppByteStreamIq::StreamHost streamHost;
+ streamHost.setHost(entry.ip());
+ streamHost.setPort(m_socksServer->serverPort());
+ streamHost.setJid(ownJid);
+ streamHosts.append(streamHost);
+ }
+ }
+ }
+
+ // add proxy
+ if (!job->d->socksProxy.jid().isEmpty())
+ streamHosts.append(job->d->socksProxy);
+
+ // check we have some stream hosts
+ if (!streamHosts.size())
+ {
+ warning("Could not determine local stream hosts");
+ job->terminate(QXmppTransferJob::ProtocolError);
+ return;
+ }
+
+ // send offer
+ QXmppByteStreamIq streamIq;
+ streamIq.setType(QXmppIq::Set);
+ streamIq.setTo(job->d->jid);
+ streamIq.setSid(job->d->sid);
+ streamIq.setStreamHosts(streamHosts);
+ job->d->requestId = streamIq.id();
+ client()->sendPacket(streamIq);
+}
+
+void QXmppTransferManager::streamInitiationIqReceived(const QXmppStreamInitiationIq &iq)
+{
+ if (iq.type() == QXmppIq::Result)
+ streamInitiationResultReceived(iq);
+ else if (iq.type() == QXmppIq::Set)
+ streamInitiationSetReceived(iq);
+}
+
+// The remote party has accepted an outgoing transfer.
+void QXmppTransferManager::streamInitiationResultReceived(const QXmppStreamInitiationIq &iq)
+{
+ QXmppTransferJob *job = getJobByRequestId(QXmppTransferJob::OutgoingDirection, iq.from(), iq.id());
+ if (!job ||
+ job->state() != QXmppTransferJob::OfferState)
+ return;
+
+ foreach (const QXmppElement &item, iq.siItems())
+ {
+ if (item.tagName() == "feature" && item.attribute("xmlns") == ns_feature_negotiation)
+ {
+ QXmppElement field = item.firstChildElement("x").firstChildElement("field");
+ while (!field.isNull())
+ {
+ if (field.attribute("var") == "stream-method")
+ {
+ if ((field.firstChildElement("value").value() == ns_ibb) &&
+ (m_supportedMethods & QXmppTransferJob::InBandMethod))
+ job->d->method = QXmppTransferJob::InBandMethod;
+ else if ((field.firstChildElement("value").value() == ns_bytestreams) &&
+ (m_supportedMethods & QXmppTransferJob::SocksMethod))
+ job->d->method = QXmppTransferJob::SocksMethod;
+ }
+ field = field.nextSiblingElement("field");
+ }
+ }
+ }
+
+ // remote party accepted stream initiation
+ job->setState(QXmppTransferJob::StartState);
+ if (job->method() == QXmppTransferJob::InBandMethod)
+ {
+ // lower block size for IBB
+ job->d->blockSize = m_ibbBlockSize;
+
+ QXmppIbbOpenIq openIq;
+ openIq.setTo(job->d->jid);
+ openIq.setSid(job->d->sid);
+ openIq.setBlockSize(job->d->blockSize);
+ job->d->requestId = openIq.id();
+ client()->sendPacket(openIq);
+ } else if (job->method() == QXmppTransferJob::SocksMethod) {
+ if (!m_socksServer->isListening())
+ {
+ warning("QXmppSocksServer is not listening");
+ job->terminate(QXmppTransferJob::ProtocolError);
+ return;
+ }
+ if (!m_proxy.isEmpty())
+ {
+ job->d->socksProxy.setJid(m_proxy);
+
+ // query proxy
+ QXmppByteStreamIq streamIq;
+ streamIq.setType(QXmppIq::Get);
+ streamIq.setTo(job->d->socksProxy.jid());
+ streamIq.setSid(job->d->sid);
+ job->d->requestId = streamIq.id();
+ client()->sendPacket(streamIq);
+ } else {
+ socksServerSendOffer(job);
+ }
+ } else {
+ warning("QXmppTransferManager received an unsupported method");
+ job->terminate(QXmppTransferJob::ProtocolError);
+ }
+}
+
+void QXmppTransferManager::streamInitiationSetReceived(const QXmppStreamInitiationIq &iq)
+{
+ bool check;
+ Q_UNUSED(check);
+
+ QXmppIq response;
+ response.setTo(iq.from());
+ response.setId(iq.id());
+
+ // check we support the profile
+ if (iq.profile() != QXmppStreamInitiationIq::FileTransfer)
+ {
+ // FIXME : we should add:
+ // <bad-profile xmlns='http://jabber.org/protocol/si'/>
+ QXmppStanza::Error error(QXmppStanza::Error::Cancel, QXmppStanza::Error::BadRequest);
+ error.setCode(400);
+
+ response.setType(QXmppIq::Error);
+ response.setError(error);
+ client()->sendPacket(response);
+ return;
+ }
+
+ // check there is a receiver connected to the fileReceived() signal
+ if (!receivers(SIGNAL(fileReceived(QXmppTransferJob*))))
+ {
+ QXmppStanza::Error error(QXmppStanza::Error::Cancel, QXmppStanza::Error::Forbidden);
+ error.setCode(403);
+
+ response.setType(QXmppIq::Error);
+ response.setError(error);
+ client()->sendPacket(response);
+ return;
+ }
+
+ // check the stream type
+ QXmppTransferJob *job = new QXmppTransferJob(iq.from(), QXmppTransferJob::IncomingDirection, this);
+ int offeredMethods = QXmppTransferJob::NoMethod;
+ job->d->offerId = iq.id();
+ job->d->sid = iq.siId();
+ job->d->mimeType = iq.mimeType();
+ foreach (const QXmppElement &item, iq.siItems())
+ {
+ if (item.tagName() == "feature" && item.attribute("xmlns") == ns_feature_negotiation)
+ {
+ QXmppElement field = item.firstChildElement("x").firstChildElement("field");
+ while (!field.isNull())
+ {
+ if (field.attribute("var") == "stream-method" && field.attribute("type") == "list-single")
+ {
+ QXmppElement option = field.firstChildElement("option");
+ while (!option.isNull())
+ {
+ if (option.firstChildElement("value").value() == ns_ibb)
+ offeredMethods = offeredMethods | QXmppTransferJob::InBandMethod;
+ else if (option.firstChildElement("value").value() == ns_bytestreams)
+ offeredMethods = offeredMethods | QXmppTransferJob::SocksMethod;
+ option = option.nextSiblingElement("option");
+ }
+ }
+ field = field.nextSiblingElement("field");
+ }
+ }
+ else if (item.tagName() == "file" && item.attribute("xmlns") == ns_stream_initiation_file_transfer)
+ {
+ job->d->fileInfo.setDate(datetimeFromString(item.attribute("date")));
+ job->d->fileInfo.setHash(QByteArray::fromHex(item.attribute("hash").toAscii()));
+ job->d->fileInfo.setName(item.attribute("name"));
+ job->d->fileInfo.setSize(item.attribute("size").toLongLong());
+ }
+ }
+
+ // select a method supported by both parties
+ int sharedMethods = (offeredMethods & m_supportedMethods);
+ if (sharedMethods & QXmppTransferJob::SocksMethod)
+ job->d->method = QXmppTransferJob::SocksMethod;
+ else if (sharedMethods & QXmppTransferJob::InBandMethod)
+ job->d->method = QXmppTransferJob::InBandMethod;
+ else
+ {
+ // FIXME : we should add:
+ // <no-valid-streams xmlns='http://jabber.org/protocol/si'/>
+ QXmppStanza::Error error(QXmppStanza::Error::Cancel, QXmppStanza::Error::BadRequest);
+ error.setCode(400);
+
+ response.setType(QXmppIq::Error);
+ response.setError(error);
+ client()->sendPacket(response);
+
+ delete job;
+ return;
+ }
+
+ // register job
+ m_jobs.append(job);
+ check = connect(job, SIGNAL(destroyed(QObject*)),
+ this, SLOT(_q_jobDestroyed(QObject*)));
+ Q_ASSERT(check);
+
+ check = connect(job, SIGNAL(finished()),
+ this, SLOT(_q_jobFinished()));
+ Q_ASSERT(check);
+
+ check = connect(job, SIGNAL(stateChanged(QXmppTransferJob::State)),
+ this, SLOT(_q_jobStateChanged(QXmppTransferJob::State)));
+ Q_ASSERT(check);
+
+ // allow user to accept or decline the job
+ emit fileReceived(job);
+}
+
+/// Return the JID of the bytestream proxy to use for
+/// outgoing transfers.
+///
+
+QString QXmppTransferManager::proxy() const
+{
+ return m_proxy;
+}
+
+/// Set the JID of the SOCKS5 bytestream proxy to use for
+/// outgoing transfers.
+///
+/// If you set a proxy, when you send a file the proxy will
+/// be offered to the recipient in addition to your own IP
+/// addresses.
+///
+
+void QXmppTransferManager::setProxy(const QString &proxyJid)
+{
+ m_proxy = proxyJid;
+}
+
+/// Return whether the proxy will systematically be used for
+/// outgoing SOCKS5 bytestream transfers.
+///
+
+bool QXmppTransferManager::proxyOnly() const
+{
+ return m_proxyOnly;
+}
+
+/// Set whether the proxy should systematically be used for
+/// outgoing SOCKS5 bytestream transfers.
+///
+/// \note If you set this to true and do not provide a proxy
+/// using setProxy(), your outgoing transfers will fail!
+///
+
+void QXmppTransferManager::setProxyOnly(bool proxyOnly)
+{
+ m_proxyOnly = proxyOnly;
+}
+
+/// Return the supported stream methods.
+///
+/// The methods are a combination of zero or more QXmppTransferJob::Method.
+///
+
+QXmppTransferJob::Methods QXmppTransferManager::supportedMethods() const
+{
+ return m_supportedMethods;
+}
+
+/// Set the supported stream methods. This allows you to selectively
+/// enable or disable stream methods (In-Band or SOCKS5 bytestreams).
+///
+/// The methods argument is a combination of zero or more
+/// QXmppTransferJob::Method.
+///
+
+void QXmppTransferManager::setSupportedMethods(QXmppTransferJob::Methods methods)
+{
+ m_supportedMethods = methods;
+}
diff --git a/src/client/QXmppTransferManager.h b/src/client/QXmppTransferManager.h
new file mode 100644
index 00000000..900d2163
--- /dev/null
+++ b/src/client/QXmppTransferManager.h
@@ -0,0 +1,293 @@
+/*
+ * Copyright (C) 2008-2011 The QXmpp developers
+ *
+ * Author:
+ * Jeremy Lainé
+ *
+ * Source:
+ * http://code.google.com/p/qxmpp
+ *
+ * This file is a part of QXmpp library.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ */
+
+#ifndef QXMPPTRANSFERMANAGER_H
+#define QXMPPTRANSFERMANAGER_H
+
+#include <QDateTime>
+#include <QUrl>
+#include <QVariant>
+
+#include "QXmppClientExtension.h"
+#include "QXmppIq.h"
+#include "QXmppByteStreamIq.h"
+
+class QTcpSocket;
+class QXmppByteStreamIq;
+class QXmppIbbCloseIq;
+class QXmppIbbDataIq;
+class QXmppIbbOpenIq;
+class QXmppSocksClient;
+class QXmppSocksServer;
+class QXmppStreamInitiationIq;
+class QXmppTransferJobPrivate;
+
+class QXmppTransferFileInfo
+{
+public:
+ QXmppTransferFileInfo();
+
+ QDateTime date() const;
+ void setDate(const QDateTime &date);
+
+ QByteArray hash() const;
+ void setHash(const QByteArray &hash);
+
+ QString name() const;
+ void setName(const QString &name);
+
+ qint64 size() const;
+ void setSize(qint64 size);
+
+ bool operator==(const QXmppTransferFileInfo &other) const;
+
+private:
+ QDateTime m_date;
+ QByteArray m_hash;
+ QString m_name;
+ qint64 m_size;
+};
+
+/// \brief The QXmppTransferJob class represents a single file transfer job.
+///
+/// \sa QXmppTransferManager
+///
+
+class QXmppTransferJob : public QXmppLoggable
+{
+ Q_OBJECT
+ Q_ENUMS(Direction Error State)
+ Q_FLAGS(Method Methods)
+ Q_PROPERTY(Direction direction READ direction CONSTANT)
+ Q_PROPERTY(QUrl localFileUrl READ localFileUrl WRITE setLocalFileUrl NOTIFY localFileUrlChanged)
+ Q_PROPERTY(QString jid READ jid CONSTANT)
+ Q_PROPERTY(Method method READ method CONSTANT)
+ Q_PROPERTY(State state READ state NOTIFY stateChanged)
+
+ Q_PROPERTY(QString fileName READ fileName CONSTANT)
+ Q_PROPERTY(qint64 fileSize READ fileSize CONSTANT)
+
+public:
+ /// This enum is used to describe the direction of a transfer job.
+ enum Direction
+ {
+ IncomingDirection, ///< The file is being received.
+ OutgoingDirection, ///< The file is being sent.
+ };
+
+ /// This enum is used to describe the type of error encountered by a transfer job.
+ enum Error
+ {
+ NoError = 0, ///< No error occurred.
+ AbortError, ///< The file transfer was aborted.
+ FileAccessError, ///< An error was encountered trying to access a local file.
+ FileCorruptError, ///< The file is corrupt: the file size or hash do not match.
+ ProtocolError, ///< An error was encountered in the file transfer protocol.
+ };
+
+ /// This enum is used to describe a transfer method.
+ enum Method
+ {
+ NoMethod = 0, ///< No transfer method.
+ InBandMethod = 1, ///< XEP-0047: In-Band Bytestreams
+ SocksMethod = 2, ///< XEP-0065: SOCKS5 Bytestreams
+ AnyMethod = 3, ///< Any supported transfer method.
+ };
+ Q_DECLARE_FLAGS(Methods, Method)
+
+ /// This enum is used to describe the state of a transfer job.
+ enum State
+ {
+ OfferState = 0, ///< The transfer is being offered to the remote party.
+ StartState = 1, ///< The transfer is being connected.
+ TransferState = 2, ///< The transfer is ongoing.
+ FinishedState = 3, ///< The transfer is finished.
+ };
+
+ ~QXmppTransferJob();
+
+ QXmppTransferJob::Direction direction() const;
+ QXmppTransferJob::Error error() const;
+ QString jid() const;
+ QXmppTransferJob::Method method() const;
+ QString sid() const;
+ qint64 speed() const;
+ QXmppTransferJob::State state() const;
+
+ // XEP-0096 : File transfer
+ QXmppTransferFileInfo fileInfo() const;
+ QUrl localFileUrl() const;
+ void setLocalFileUrl(const QUrl &localFileUrl);
+
+ /// \cond
+ QDateTime fileDate() const;
+ QByteArray fileHash() const;
+ QString fileName() const;
+ qint64 fileSize() const;
+ /// \endcond
+
+signals:
+ /// This signal is emitted when an error is encountered while
+ /// processing the transfer job.
+ void error(QXmppTransferJob::Error error);
+
+ /// This signal is emitted when the transfer job is finished.
+ ///
+ /// You can determine if the job completed successfully by testing whether
+ /// error() returns QXmppTransferJob::NoError.
+ ///
+ /// Note: Do not delete the job in the slot connected to this signal,
+ /// instead use deleteLater().
+ void finished();
+
+ /// This signal is emitted when the local file URL changes.
+ void localFileUrlChanged(const QUrl &localFileUrl);
+
+ /// This signal is emitted to indicate the progress of this transfer job.
+ void progress(qint64 done, qint64 total);
+
+ /// This signal is emitted when the transfer job changes state.
+ void stateChanged(QXmppTransferJob::State state);
+
+public slots:
+ void abort();
+ void accept(const QString &filePath);
+ void accept(QIODevice *output);
+
+private slots:
+ void _q_disconnected();
+ void _q_receiveData();
+ void _q_sendData();
+ void _q_terminated();
+
+private:
+ QXmppTransferJob(const QString &jid, QXmppTransferJob::Direction direction, QObject *parent);
+ void checkData();
+ void setState(QXmppTransferJob::State state);
+ void terminate(QXmppTransferJob::Error error);
+ bool writeData(const QByteArray &data);
+
+ QXmppTransferJobPrivate *const d;
+ friend class QXmppTransferManager;
+};
+
+/// \brief The QXmppTransferManager class provides support for sending and
+/// receiving files.
+///
+/// Stream initiation is performed as described in XEP-0095: Stream Initiation
+/// and XEP-0096: SI File Transfer. The actual file transfer is then performed
+/// using either XEP-0065: SOCKS5 Bytestreams or XEP-0047: In-Band Bytestreams.
+///
+/// To make use of this manager, you need to instantiate it and load it into
+/// the QXmppClient instance as follows:
+///
+/// \code
+/// QXmppTransferManager *manager = new QXmppTransferManager;
+/// client->addExtension(manager);
+/// \endcode
+///
+/// \ingroup Managers
+
+class QXmppTransferManager : public QXmppClientExtension
+{
+ Q_OBJECT
+ Q_PROPERTY(QString proxy READ proxy WRITE setProxy)
+ Q_PROPERTY(bool proxyOnly READ proxyOnly WRITE setProxyOnly)
+ Q_PROPERTY(QXmppTransferJob::Methods supportedMethods READ supportedMethods WRITE setSupportedMethods)
+
+public:
+ QXmppTransferManager();
+
+ QString proxy() const;
+ void setProxy(const QString &proxyJid);
+
+ bool proxyOnly() const;
+ void setProxyOnly(bool proxyOnly);
+
+ QXmppTransferJob::Methods supportedMethods() const;
+ void setSupportedMethods(QXmppTransferJob::Methods methods);
+
+ /// \cond
+ QStringList discoveryFeatures() const;
+ bool handleStanza(const QDomElement &element);
+ /// \endcond
+
+signals:
+ /// This signal is emitted when a new file transfer offer is received.
+ ///
+ /// To accept the transfer job, call the job's QXmppTransferJob::accept() method.
+ /// To refuse the transfer job, call the job's QXmppTransferJob::abort() method.
+ void fileReceived(QXmppTransferJob *job);
+
+ /// This signal is emitted whenever a transfer job is started.
+ void jobStarted(QXmppTransferJob *job);
+
+ /// This signal is emitted whenever a transfer job is finished.
+ ///
+ /// \sa QXmppTransferJob::finished()
+ void jobFinished(QXmppTransferJob *job);
+
+public slots:
+ QXmppTransferJob *sendFile(const QString &jid, const QString &filePath, const QString &sid = QString());
+ QXmppTransferJob *sendFile(const QString &jid, QIODevice *device, const QXmppTransferFileInfo &fileInfo, const QString &sid = QString());
+
+protected:
+ /// \cond
+ void setClient(QXmppClient* client);
+ /// \endcond
+
+private slots:
+ void _q_iqReceived(const QXmppIq&);
+ void _q_jobDestroyed(QObject *object);
+ void _q_jobError(QXmppTransferJob::Error error);
+ void _q_jobFinished();
+ void _q_jobStateChanged(QXmppTransferJob::State state);
+ void _q_socksServerConnected(QTcpSocket *socket, const QString &hostName, quint16 port);
+
+private:
+ QXmppTransferJob *getJobByRequestId(QXmppTransferJob::Direction direction, const QString &jid, const QString &id);
+ QXmppTransferJob *getJobBySid(QXmppTransferJob::Direction, const QString &jid, const QString &sid);
+ void byteStreamIqReceived(const QXmppByteStreamIq&);
+ void byteStreamResponseReceived(const QXmppIq&);
+ void byteStreamResultReceived(const QXmppByteStreamIq&);
+ void byteStreamSetReceived(const QXmppByteStreamIq&);
+ void ibbCloseIqReceived(const QXmppIbbCloseIq&);
+ void ibbDataIqReceived(const QXmppIbbDataIq&);
+ void ibbOpenIqReceived(const QXmppIbbOpenIq&);
+ void ibbResponseReceived(const QXmppIq&);
+ void streamInitiationIqReceived(const QXmppStreamInitiationIq&);
+ void streamInitiationResultReceived(const QXmppStreamInitiationIq&);
+ void streamInitiationSetReceived(const QXmppStreamInitiationIq&);
+ void socksServerSendOffer(QXmppTransferJob *job);
+
+ int m_ibbBlockSize;
+ QList<QXmppTransferJob*> m_jobs;
+ QString m_proxy;
+ bool m_proxyOnly;
+ QXmppSocksServer *m_socksServer;
+ QXmppTransferJob::Methods m_supportedMethods;
+};
+
+Q_DECLARE_OPERATORS_FOR_FLAGS(QXmppTransferJob::Methods)
+
+#endif
diff --git a/src/client/QXmppVCardManager.cpp b/src/client/QXmppVCardManager.cpp
new file mode 100644
index 00000000..732b4244
--- /dev/null
+++ b/src/client/QXmppVCardManager.cpp
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2008-2011 The QXmpp developers
+ *
+ * Author:
+ * Manjeet Dahiya
+ *
+ * Source:
+ * http://code.google.com/p/qxmpp
+ *
+ * This file is a part of QXmpp library.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ */
+
+
+#include "QXmppClient.h"
+#include "QXmppConstants.h"
+#include "QXmppUtils.h"
+#include "QXmppVCardManager.h"
+
+QXmppVCardManager::QXmppVCardManager()
+ : QXmppClientExtension(),
+ m_isClientVCardReceived(false)
+{
+}
+
+QStringList QXmppVCardManager::discoveryFeatures() const
+{
+ // XEP-0054: vcard-temp
+ return QStringList() << ns_vcard;
+}
+
+bool QXmppVCardManager::handleStanza(const QDomElement &element)
+{
+ if(element.tagName() == "iq" && QXmppVCardIq::isVCard(element))
+ {
+ QXmppVCardIq vCardIq;
+ vCardIq.parse(element);
+
+ if(vCardIq.from().isEmpty())
+ {
+ m_clientVCard = vCardIq;
+ m_isClientVCardReceived = true;
+ emit clientVCardReceived();
+ }
+
+ emit vCardReceived(vCardIq);
+
+ return true;
+ }
+
+ return false;
+}
+
+/// This function requests the server for vCard of the specified jid.
+/// Once received the signal vCardReceived() is emitted.
+///
+/// \param jid Jid of the specific entry in the roster
+///
+QString QXmppVCardManager::requestVCard(const QString& jid)
+{
+ QXmppVCardIq request(jid);
+ if(client()->sendPacket(request))
+ return request.id();
+ else
+ return QString();
+}
+
+/// Returns the vCard of the connected client.
+///
+/// \return QXmppVCard
+///
+const QXmppVCardIq& QXmppVCardManager::clientVCard() const
+{
+ return m_clientVCard;
+}
+
+/// Sets the vCard of the connected client.
+///
+/// \param clientVCard QXmppVCard
+///
+void QXmppVCardManager::setClientVCard(const QXmppVCardIq& clientVCard)
+{
+ m_clientVCard = clientVCard;
+ m_clientVCard.setTo("");
+ m_clientVCard.setFrom("");
+ m_clientVCard.setType(QXmppIq::Set);
+ client()->sendPacket(m_clientVCard);
+}
+
+/// This function requests the server for vCard of the connected user itself.
+/// Once received the signal clientVCardReceived() is emitted. Received vCard
+/// can be get using clientVCard().
+QString QXmppVCardManager::requestClientVCard()
+{
+ return requestVCard();
+}
+
+/// Returns true if vCard of the connected client has been
+/// received else false.
+///
+/// \return bool
+///
+bool QXmppVCardManager::isClientVCardReceived() const
+{
+ return m_isClientVCardReceived;
+}
diff --git a/src/client/QXmppVCardManager.h b/src/client/QXmppVCardManager.h
new file mode 100644
index 00000000..5c2ac250
--- /dev/null
+++ b/src/client/QXmppVCardManager.h
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2008-2011 The QXmpp developers
+ *
+ * Author:
+ * Manjeet Dahiya
+ *
+ * Source:
+ * http://code.google.com/p/qxmpp
+ *
+ * This file is a part of QXmpp library.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ */
+
+
+#ifndef QXMPPVCARDMANAGER_H
+#define QXMPPVCARDMANAGER_H
+
+#include <QObject>
+
+#include "QXmppClientExtension.h"
+#include "QXmppVCardIq.h"
+
+/// \brief The QXmppVCardManager class gets/sets XMPP vCards. It is an
+/// implementation of XEP-0054: vcard-temp.
+///
+/// \note It's object should not be created using it's constructor. Instead
+/// QXmppClient::vCardManager() should be used to get the reference of instantiated
+/// object this class.
+///
+/// <B>Getting vCards of entries in Roster:</B><BR>
+/// It doesn't store vCards of the JIDs in the roster of connected user. Instead
+/// client has to request for a particular vCard using requestVCard(). And connect to
+/// the signal vCardReceived() to get the requested vCard.
+///
+/// <B>Getting vCard of the connected client:</B><BR>
+/// For getting the vCard of the connected user itself. Client can call requestClientVCard()
+/// and on the signal clientVCardReceived() it can get its vCard using clientVCard().
+///
+/// <B>Setting vCard of the client:</B><BR>
+/// Using setClientVCard() client can set its vCard.
+///
+/// \note Client can't set/change vCards of roster entries.
+///
+/// \ingroup Managers
+
+class QXmppVCardManager : public QXmppClientExtension
+{
+ Q_OBJECT
+
+public:
+ QXmppVCardManager();
+ QString requestVCard(const QString& bareJid = "");
+
+ const QXmppVCardIq& clientVCard() const;
+ void setClientVCard(const QXmppVCardIq&);
+ QString requestClientVCard();
+ bool isClientVCardReceived() const;
+
+ /// \cond
+ QStringList discoveryFeatures() const;
+ bool handleStanza(const QDomElement &element);
+ /// \endcond
+
+signals:
+ /// This signal is emitted when the requested vCard is received
+ /// after calling the requestVCard() function.
+ void vCardReceived(const QXmppVCardIq&);
+
+ /// This signal is emitted when the client's vCard is received
+ /// after calling the requestClientVCard() function.
+ void clientVCardReceived();
+
+private:
+ QXmppVCardIq m_clientVCard; ///< Stores the vCard of the connected client
+ bool m_isClientVCardReceived;
+};
+
+#endif // QXMPPVCARDMANAGER_H
diff --git a/src/client/QXmppVersionManager.cpp b/src/client/QXmppVersionManager.cpp
new file mode 100644
index 00000000..b6f81794
--- /dev/null
+++ b/src/client/QXmppVersionManager.cpp
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2008-2011 The QXmpp developers
+ *
+ * Author:
+ * Manjeet Dahiya
+ *
+ * Source:
+ * http://code.google.com/p/qxmpp
+ *
+ * This file is a part of QXmpp library.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ */
+
+#include <QCoreApplication>
+#include <QDomElement>
+
+#include "QXmppClient.h"
+#include "QXmppConstants.h"
+#include "QXmppGlobal.h"
+#include "QXmppVersionManager.h"
+#include "QXmppVersionIq.h"
+
+QXmppVersionManager::QXmppVersionManager() : QXmppClientExtension(),
+ m_clientName(qApp->applicationName()),
+ m_clientVersion(qApp->applicationVersion())
+{
+ if(m_clientName.isEmpty())
+ m_clientName = "Based on QXmpp";
+
+#if defined(Q_OS_LINUX)
+ m_clientOs = QString::fromLatin1("Linux");
+#elif defined(Q_OS_MAC)
+ m_clientOs = QString::fromLatin1("Mac OS");
+#elif defined(Q_OS_SYMBIAN)
+ m_clientOs = QString::fromLatin1("Symbian");
+#elif defined(Q_OS_WIN)
+ m_clientOs = QString::fromLatin1("Windows");
+#endif
+
+ if(m_clientVersion.isEmpty())
+ m_clientVersion = QXmppVersion();
+}
+
+QStringList QXmppVersionManager::discoveryFeatures() const
+{
+ // XEP-0092: Software Version
+ return QStringList() << ns_version;
+}
+
+bool QXmppVersionManager::handleStanza(const QDomElement &element)
+{
+ if (element.tagName() == "iq" && QXmppVersionIq::isVersionIq(element))
+ {
+ QXmppVersionIq versionIq;
+ versionIq.parse(element);
+
+ if(versionIq.type() == QXmppIq::Get)
+ {
+ // respond to query
+ QXmppVersionIq responseIq;
+ responseIq.setType(QXmppIq::Result);
+ responseIq.setId(versionIq.id());
+ responseIq.setTo(versionIq.from());
+
+ responseIq.setName(clientName());
+ responseIq.setVersion(clientVersion());
+ responseIq.setOs(clientOs());
+
+ client()->sendPacket(responseIq);
+ }
+
+ emit versionReceived(versionIq);
+ return true;
+ }
+
+ return false;
+}
+
+/// Request version information from the specified XMPP entity.
+///
+/// \param jid
+
+QString QXmppVersionManager::requestVersion(const QString& jid)
+{
+ QXmppVersionIq request;
+ request.setType(QXmppIq::Get);
+ request.setTo(jid);
+ if(client()->sendPacket(request))
+ return request.id();
+ else
+ return QString();
+}
+
+/// Sets the local XMPP client's name.
+///
+/// \param name
+
+void QXmppVersionManager::setClientName(const QString& name)
+{
+ m_clientName = name;
+}
+
+/// Sets the local XMPP client's version.
+///
+/// \param version
+
+void QXmppVersionManager::setClientVersion(const QString& version)
+{
+ m_clientVersion = version;
+}
+
+/// Sets the local XMPP client's operating system.
+///
+/// \param os
+
+void QXmppVersionManager::setClientOs(const QString& os)
+{
+ m_clientOs = os;
+}
+
+/// Returns the local XMPP client's name.
+///
+/// By default this is set to the QApplication::applicationName(), or
+/// "Based on QXmpp" if not specified.
+
+QString QXmppVersionManager::clientName() const
+{
+ return m_clientName;
+}
+
+/// Returns the local XMPP client's version.
+///
+/// By default this is set to QApplication::applicationVersion(), or
+/// QXmpp's version if not specified.
+
+QString QXmppVersionManager::clientVersion() const
+{
+ return m_clientVersion;
+}
+
+/// Returns the local XMPP client's operating system.
+///
+/// By default this is "Linux", "Mac OS", "Symbian" or "Windows" depending
+/// on the platform QXmpp was compiled for.
+
+QString QXmppVersionManager::clientOs() const
+{
+ return m_clientOs;
+}
diff --git a/src/client/QXmppVersionManager.h b/src/client/QXmppVersionManager.h
new file mode 100644
index 00000000..932712fd
--- /dev/null
+++ b/src/client/QXmppVersionManager.h
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2008-2011 The QXmpp developers
+ *
+ * Author:
+ * Manjeet Dahiya
+ *
+ * Source:
+ * http://code.google.com/p/qxmpp
+ *
+ * This file is a part of QXmpp library.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ */
+
+#ifndef QXMPPVERSIONMANAGER_H
+#define QXMPPVERSIONMANAGER_H
+
+#include "QXmppClientExtension.h"
+
+class QXmppVersionIq;
+
+/// \brief The QXmppVersionManager class makes it possible to request for
+/// the software version of an entity as defined by XEP-0092: Software Version.
+///
+/// \ingroup Managers
+
+class QXmppVersionManager : public QXmppClientExtension
+{
+ Q_OBJECT
+
+public:
+ QXmppVersionManager();
+ QString requestVersion(const QString& jid);
+
+ void setClientName(const QString&);
+ void setClientVersion(const QString&);
+ void setClientOs(const QString&);
+
+ QString clientName() const;
+ QString clientVersion() const;
+ QString clientOs() const;
+
+ /// \cond
+ QStringList discoveryFeatures() const;
+ bool handleStanza(const QDomElement &element);
+ /// \endcond
+
+signals:
+ /// \brief This signal is emitted when a version response is received.
+ void versionReceived(const QXmppVersionIq&);
+
+private:
+ QString m_clientName;
+ QString m_clientVersion;
+ QString m_clientOs;
+};
+
+#endif // QXMPPVERSIONMANAGER_H