diff options
| author | Jeremy Lainé <jeremy.laine@m4x.org> | 2012-02-08 12:33:41 +0000 |
|---|---|---|
| committer | Jeremy Lainé <jeremy.laine@m4x.org> | 2012-02-08 12:33:41 +0000 |
| commit | 21acd67e9b65bea87902032b12709675905aa922 (patch) | |
| tree | ed5ae9066b10400c4fe6e67dfaf2f4c37a09c32e /src/client | |
| parent | cea7ae1e702b82d2d0d0a851de1aae58270b14f6 (diff) | |
| download | qxmpp-21acd67e9b65bea87902032b12709675905aa922.tar.gz | |
start moving client-specific code
Diffstat (limited to 'src/client')
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 |
