diff options
| author | Jeremy Lainé <jeremy.laine@m4x.org> | 2010-08-23 05:40:15 +0000 |
|---|---|---|
| committer | Jeremy Lainé <jeremy.laine@m4x.org> | 2010-08-23 05:40:15 +0000 |
| commit | 80e0607c97aef44e88f8e5af281132668114534f (patch) | |
| tree | 71594029b2bd5bf28dfe489dc1249d6c72b378be /src | |
| parent | 85a3546164c0816a4b94d6fdb45ca64b1547f541 (diff) | |
| download | qxmpp-80e0607c97aef44e88f8e5af281132668114534f.tar.gz | |
* make QXmppStream an abstract class
* move client-specific code to QXmppOutgoingClient
Diffstat (limited to 'src')
| -rw-r--r-- | src/QXmppArchiveManager.cpp | 4 | ||||
| -rw-r--r-- | src/QXmppArchiveManager.h | 6 | ||||
| -rw-r--r-- | src/QXmppCallManager.cpp | 4 | ||||
| -rw-r--r-- | src/QXmppCallManager.h | 6 | ||||
| -rw-r--r-- | src/QXmppClient.cpp | 12 | ||||
| -rw-r--r-- | src/QXmppMucManager.cpp | 4 | ||||
| -rw-r--r-- | src/QXmppMucManager.h | 6 | ||||
| -rw-r--r-- | src/QXmppOutgoingClient.cpp | 858 | ||||
| -rw-r--r-- | src/QXmppOutgoingClient.h | 151 | ||||
| -rw-r--r-- | src/QXmppRosterManager.cpp | 6 | ||||
| -rw-r--r-- | src/QXmppRosterManager.h | 6 | ||||
| -rw-r--r-- | src/QXmppStream.cpp | 1017 | ||||
| -rw-r--r-- | src/QXmppStream.h | 126 | ||||
| -rw-r--r-- | src/QXmppTransferManager.cpp | 4 | ||||
| -rw-r--r-- | src/QXmppTransferManager.h | 6 | ||||
| -rw-r--r-- | src/QXmppVCardManager.cpp | 4 | ||||
| -rw-r--r-- | src/QXmppVCardManager.h | 6 | ||||
| -rw-r--r-- | src/src.pro | 2 |
18 files changed, 1211 insertions, 1017 deletions
diff --git a/src/QXmppArchiveManager.cpp b/src/QXmppArchiveManager.cpp index edbcfdd8..c46e2dc9 100644 --- a/src/QXmppArchiveManager.cpp +++ b/src/QXmppArchiveManager.cpp @@ -23,11 +23,11 @@ #include "QXmppArchiveIq.h" #include "QXmppArchiveManager.h" -#include "QXmppStream.h" +#include "QXmppOutgoingClient.h" #include <QDebug> -QXmppArchiveManager::QXmppArchiveManager(QXmppStream *stream, QObject *parent) +QXmppArchiveManager::QXmppArchiveManager(QXmppOutgoingClient *stream, QObject *parent) : QObject(parent), m_stream(stream) { diff --git a/src/QXmppArchiveManager.h b/src/QXmppArchiveManager.h index 631bad4c..0fba89c8 100644 --- a/src/QXmppArchiveManager.h +++ b/src/QXmppArchiveManager.h @@ -31,7 +31,7 @@ class QXmppArchiveChat; class QXmppArchiveChatIq; class QXmppArchiveListIq; class QXmppArchivePrefIq; -class QXmppStream; +class QXmppOutgoingClient; /// \brief The QXmppArchiveManager class makes it possible to access message /// archives as defined by XEP-0136: Message Archiving. @@ -46,7 +46,7 @@ class QXmppArchiveManager : public QObject Q_OBJECT public: - QXmppArchiveManager(QXmppStream* stream, QObject *parent = 0); + QXmppArchiveManager(QXmppOutgoingClient* stream, QObject *parent = 0); void listCollections(const QString &jid, const QDateTime &start = QDateTime(), const QDateTime &end = QDateTime(), int max = 0); void retrieveCollection(const QString &jid, const QDateTime &start, int max = 0); @@ -66,7 +66,7 @@ private slots: private: // reference to xmpp stream (no ownership) - QXmppStream* m_stream; + QXmppOutgoingClient* m_stream; }; #endif diff --git a/src/QXmppCallManager.cpp b/src/QXmppCallManager.cpp index a95c90e2..379eed0b 100644 --- a/src/QXmppCallManager.cpp +++ b/src/QXmppCallManager.cpp @@ -27,7 +27,7 @@ #include "QXmppCodec.h" #include "QXmppConstants.h" #include "QXmppJingleIq.h" -#include "QXmppStream.h" +#include "QXmppOutgoingClient.h" #include "QXmppStun.h" #include "QXmppUtils.h" @@ -408,7 +408,7 @@ qint64 QXmppCall::writeData(const char * data, qint64 maxSize) return maxSize; } -QXmppCallManager::QXmppCallManager(QXmppStream *stream, QObject *parent) +QXmppCallManager::QXmppCallManager(QXmppOutgoingClient *stream, QObject *parent) : QObject(parent), m_stream(stream) { // setup logging diff --git a/src/QXmppCallManager.h b/src/QXmppCallManager.h index ba0d2212..7111ed1c 100644 --- a/src/QXmppCallManager.h +++ b/src/QXmppCallManager.h @@ -35,7 +35,7 @@ class QXmppIceConnection; class QXmppJingleCandidate; class QXmppJingleIq; class QXmppJinglePayloadType; -class QXmppStream; +class QXmppOutgoingClient; /// \brief The QXmppCall class represents a Voice-Over-IP call to a remote party. /// @@ -181,7 +181,7 @@ class QXmppCallManager : public QObject Q_OBJECT public: - QXmppCallManager(QXmppStream *stream, QObject *parent = 0); + QXmppCallManager(QXmppOutgoingClient *stream, QObject *parent = 0); QXmppCall *call(const QString &jid); signals: @@ -212,7 +212,7 @@ private: QList<QXmppCall*> m_calls; // reference to xmpp stream (no ownership) - QXmppStream* m_stream; + QXmppOutgoingClient* m_stream; }; #endif diff --git a/src/QXmppClient.cpp b/src/QXmppClient.cpp index eda85a24..9732b959 100644 --- a/src/QXmppClient.cpp +++ b/src/QXmppClient.cpp @@ -24,7 +24,7 @@ #include "QXmppClient.h" #include "QXmppLogger.h" -#include "QXmppStream.h" +#include "QXmppOutgoingClient.h" #include "QXmppMessage.h" #include "QXmppArchiveManager.h" @@ -44,7 +44,7 @@ class QXmppClientPrivate public: QXmppClientPrivate(); - QXmppStream* stream; ///< Pointer to QXmppStream object a wrapper over + QXmppOutgoingClient* stream; ///< Pointer to QXmppOutgoingClient object a wrapper over ///< TCP socket and XMPP protocol QXmppPresence clientPresence; ///< Stores the current presence of the connected client @@ -111,9 +111,7 @@ QXmppClient::QXmppClient(QObject *parent) : QObject(parent), d(new QXmppClientPrivate) { - QSslSocket *socket = new QSslSocket; - d->stream = new QXmppStream(socket, this); - socket->setParent(d->stream); + d->stream = new QXmppOutgoingClient(this); d->clientPresence.setExtensions(d->stream->presenceExtensions()); bool check = connect(d->stream, SIGNAL(elementReceived(const QDomElement&, bool&)), @@ -140,11 +138,11 @@ QXmppClient::QXmppClient(QObject *parent) SIGNAL(disconnected())); Q_ASSERT(check); - check = connect(d->stream, SIGNAL(xmppConnected()), this, + check = connect(d->stream, SIGNAL(connected()), this, SLOT(xmppConnected())); Q_ASSERT(check); - check = connect(d->stream, SIGNAL(xmppConnected()), this, + check = connect(d->stream, SIGNAL(connected()), this, SIGNAL(connected())); Q_ASSERT(check); diff --git a/src/QXmppMucManager.cpp b/src/QXmppMucManager.cpp index 6be60c73..d9203d4b 100644 --- a/src/QXmppMucManager.cpp +++ b/src/QXmppMucManager.cpp @@ -25,10 +25,10 @@ #include "QXmppMessage.h" #include "QXmppMucIq.h" #include "QXmppMucManager.h" -#include "QXmppStream.h" +#include "QXmppOutgoingClient.h" #include "QXmppUtils.h" -QXmppMucManager::QXmppMucManager(QXmppStream* stream, QObject *parent) +QXmppMucManager::QXmppMucManager(QXmppOutgoingClient* stream, QObject *parent) : QObject(parent), m_stream(stream) { diff --git a/src/QXmppMucManager.h b/src/QXmppMucManager.h index 4e93757c..35295b18 100644 --- a/src/QXmppMucManager.h +++ b/src/QXmppMucManager.h @@ -33,7 +33,7 @@ class QXmppDataForm; class QXmppMessage; class QXmppMucAdminIq; class QXmppMucOwnerIq; -class QXmppStream; +class QXmppOutgoingClient; /// \brief The QXmppMucManager class makes it possible to interact with /// multi-user chat rooms as defined by XEP-0045: Multi-User Chat. @@ -45,7 +45,7 @@ class QXmppMucManager : public QObject Q_OBJECT public: - QXmppMucManager(QXmppStream* stream, QObject *parent = 0); + QXmppMucManager(QXmppOutgoingClient* stream, QObject *parent = 0); bool joinRoom(const QString &roomJid, const QString &nickName); bool leaveRoom(const QString &roomJid); @@ -74,7 +74,7 @@ private slots: void presenceReceived(const QXmppPresence &presence); private: - QXmppStream *m_stream; + QXmppOutgoingClient *m_stream; QMap<QString, QString> m_nickNames; QMap<QString, QMap<QString, QXmppPresence> > m_participants; }; diff --git a/src/QXmppOutgoingClient.cpp b/src/QXmppOutgoingClient.cpp new file mode 100644 index 00000000..f9f939b2 --- /dev/null +++ b/src/QXmppOutgoingClient.cpp @@ -0,0 +1,858 @@ +/* + * Copyright (C) 2008-2010 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 <QSslSocket> + +#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 "QXmppUtils.h" + +// IQ types +#include "QXmppArchiveIq.h" +#include "QXmppBind.h" +#include "QXmppByteStreamIq.h" +#include "QXmppDiscoveryIq.h" +#include "QXmppIbbIq.h" +#include "QXmppJingleIq.h" +#include "QXmppMucIq.h" +#include "QXmppPingIq.h" +#include "QXmppRpcIq.h" +#include "QXmppRosterIq.h" +#include "QXmppSession.h" +#include "QXmppStreamInitiationIq.h" +#include "QXmppVCard.h" +#include "QXmppVersionIq.h" + +#include <QBuffer> +#include <QCoreApplication> +#include <QDomDocument> +#include <QStringList> +#include <QRegExp> +#include <QHostAddress> +#include <QXmlStreamWriter> +#include <QTimer> + +static bool randomSeeded = false; +static const QString capabilitiesNode = "http://code.google.com/p/qxmpp"; +static const QByteArray streamRootElementEnd = "</stream:stream>"; + +class QXmppOutgoingClientPrivate +{ +public: + QXmppOutgoingClientPrivate(); + + // This object provides the configuration + // required for connecting to the XMPP server. + QXmppConfiguration config; + QAbstractSocket::SocketError socketError; + QXmppStanza::Error::Condition xmppStreamError; + + // State data + int authStep; + QString bindId; + QString sessionId; + bool sessionAvailable; + QString streamId; + QString streamFrom; + QString streamVersion; + QString nonSASLAuthId; + + // Timers + QTimer *pingTimer; + QTimer *timeoutTimer; +}; + +QXmppOutgoingClientPrivate::QXmppOutgoingClientPrivate() + : authStep(0), + sessionAvailable(false) +{ +} + +QXmppOutgoingClient::QXmppOutgoingClient(QObject *parent) + : QXmppStream(parent), + d(new QXmppOutgoingClientPrivate) +{ + QSslSocket *socket = new QSslSocket(this); + setSocket(socket); + + // initialise logger + setLogger(QXmppLogger::getLogger()); + + bool check = connect(socket, SIGNAL(sslErrors(const QList<QSslError>&)), + this, SLOT(socketSslErrors(const QList<QSslError>&))); + Q_ASSERT(check); + + check = connect(socket, SIGNAL(error(QAbstractSocket::SocketError)), + this, SLOT(socketError(QAbstractSocket::SocketError))); + 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); +} + +QXmppOutgoingClient::~QXmppOutgoingClient() +{ + delete d; +} + +QXmppConfiguration& QXmppOutgoingClient::configuration() +{ + return d->config; +} + +void QXmppOutgoingClient::connectToHost() +{ + info(QString("Connecting to: %1:%2").arg(configuration(). + host()).arg(configuration().port())); + + socket()->setProxy(configuration().networkProxy()); + socket()->connectToHost(configuration().host(), + configuration().port()); +} + +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 ee) +{ + d->socketError = ee; + emit error(QXmppClient::SocketError); + warning(QString("Socket error: " + socket()->errorString())); +} + +void QXmppOutgoingClient::handleStart() +{ + // reset authentication step + d->authStep = 0; + + // start stream + QByteArray data = "<?xml version='1.0'?><stream:stream to='"; + data.append(configuration().domain()); + 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 QXmppConfiguration::StreamSecurityMode remoteSecurity = features.securityMode(); + if (!socket()->supportsSsl() && + (localSecurity == QXmppConfiguration::TLSRequired || + remoteSecurity == QXmppConfiguration::TLSRequired)) + { + warning("Disconnecting as TLS is required, but SSL support is not available"); + disconnectFromHost(); + return; + } + if (localSecurity == QXmppConfiguration::TLSRequired && + remoteSecurity == QXmppConfiguration::TLSDisabled) + { + warning("Disconnecting as TLS is required, but not supported by the server"); + disconnectFromHost(); + return; + } + + if (socket()->supportsSsl() && + remoteSecurity != QXmppConfiguration::TLSDisabled && + localSecurity != QXmppConfiguration::TLSDisabled) + { + // 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.isNonSaslAuthAvailable(); + 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 + QList<QXmppConfiguration::SASLAuthMechanism> mechanisms = features.authMechanisms(); + QXmppConfiguration::SASLAuthMechanism mechanism = configuration().sASLAuthMechanism(); + if (mechanisms.isEmpty()) + { + warning("No supported SASL Authentication mechanism available"); + disconnectFromHost(); + return; + } + else if (!mechanisms.contains(mechanism)) + { + info("Desired SASL Auth mechanism is not available, selecting first available one"); + mechanism = mechanisms.first(); + } + + // send SASL Authentication request + switch(mechanism) + { + case QXmppConfiguration::SASLPlain: + { + QString userPass('\0' + configuration().user() + + '\0' + configuration().passwd()); + 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; + } + } + + // check whether bind is available + if (features.isBindAvailable()) + { + QXmppBind bind(QXmppIq::Set); + bind.setResource(configuration().resource()); + d->bindId = bind.id(); + sendPacket(bind); + } + + // check whether session is available + if (features.isSessionAvailable()) + 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") + { + // TODO: Track which mechanism was used for when other SASL protocols which use challenges are supported + d->authStep++; + switch (d->authStep) + { + case 1 : + sendAuthDigestMD5ResponseStep1(nodeRecv.text()); + break; + case 2 : + sendAuthDigestMD5ResponseStep2(); + break; + default : + warning("Too many authentication steps"); + 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) + { + QXmppSession session; + session.parse(nodeRecv); + + // get back add configuration whether to send + // roster and intial presence in beginning + // process SessionIq + + // xmpp connection made + emit connected(); + } + else if(QXmppBind::isBind(nodeRecv) && id == d->bindId) + { + QXmppBind 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) + { + QXmppSession session(QXmppIq::Set); + session.setTo(configuration().domain()); + d->sessionId = session.id(); + sendPacket(session); + } + } + } + else if(QXmppRosterIq::isRosterIq(nodeRecv)) + { + QXmppRosterIq rosterIq; + rosterIq.parse(nodeRecv); + emit rosterIqReceived(rosterIq); + } + // extensions + + // XEP-0009: Jabber-RPC + else if(QXmppRpcInvokeIq::isRpcInvokeIq(nodeRecv)) + { + QXmppRpcInvokeIq rpcIqPacket; + rpcIqPacket.parse(nodeRecv); + emit rpcCallInvoke(rpcIqPacket); + } + else if(QXmppRpcResponseIq::isRpcResponseIq(nodeRecv)) + { + QXmppRpcResponseIq rpcResponseIq; + rpcResponseIq.parse(nodeRecv); + emit rpcCallResponse(rpcResponseIq); + } + else if(QXmppRpcErrorIq::isRpcErrorIq(nodeRecv)) + { + QXmppRpcErrorIq rpcErrorIq; + rpcErrorIq.parse(nodeRecv); + emit rpcCallError(rpcErrorIq); + } + + // XEP-0030: Service Discovery + else if(QXmppDiscoveryIq::isDiscoveryIq(nodeRecv)) + { + QXmppDiscoveryIq discoIq; + discoIq.parse(nodeRecv); + + if (discoIq.type() == QXmppIq::Get && + discoIq.queryType() == QXmppDiscoveryIq::InfoQuery && + (discoIq.queryNode().isEmpty() || discoIq.queryNode().startsWith(capabilitiesNode))) + { + // respond to info query + QXmppDiscoveryIq qxmppFeatures = capabilities(); + qxmppFeatures.setId(discoIq.id()); + qxmppFeatures.setTo(discoIq.from()); + qxmppFeatures.setQueryNode(discoIq.queryNode()); + sendPacket(qxmppFeatures); + } else { + emit discoveryIqReceived(discoIq); + } + } + // XEP-0045: Multi-User Chat + else if (QXmppMucAdminIq::isMucAdminIq(nodeRecv)) + { + QXmppMucAdminIq mucIq; + mucIq.parse(nodeRecv); + emit mucAdminIqReceived(mucIq); + } + else if (QXmppMucOwnerIq::isMucOwnerIq(nodeRecv)) + { + QXmppMucOwnerIq mucIq; + mucIq.parse(nodeRecv); + emit mucOwnerIqReceived(mucIq); + } + // XEP-0047 In-Band Bytestreams + else if(QXmppIbbCloseIq::isIbbCloseIq(nodeRecv)) + { + QXmppIbbCloseIq ibbCloseIq; + ibbCloseIq.parse(nodeRecv); + emit ibbCloseIqReceived(ibbCloseIq); + } + else if(QXmppIbbDataIq::isIbbDataIq(nodeRecv)) + { + QXmppIbbDataIq ibbDataIq; + ibbDataIq.parse(nodeRecv); + emit ibbDataIqReceived(ibbDataIq); + } + else if(QXmppIbbOpenIq::isIbbOpenIq(nodeRecv)) + { + QXmppIbbOpenIq ibbOpenIq; + ibbOpenIq.parse(nodeRecv); + emit ibbOpenIqReceived(ibbOpenIq); + } + // XEP-0054: vcard-temp + else if(nodeRecv.firstChildElement("vCard"). + namespaceURI() == ns_vcard) + { + QXmppVCard vcardIq; + vcardIq.parse(nodeRecv); + emit vCardIqReceived(vcardIq); + } + // XEP-0065: SOCKS5 Bytestreams + else if(QXmppByteStreamIq::isByteStreamIq(nodeRecv)) + { + QXmppByteStreamIq byteStreamIq; + byteStreamIq.parse(nodeRecv); + emit byteStreamIqReceived(byteStreamIq); + } + // XEP-0078: Non-SASL Authentication + else if(id == d->nonSASLAuthId && type == "result") + { + // successful Non-SASL Authentication + debug("Authenticated (Non-SASL)"); + + // xmpp connection made + 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-0092: Software Version + else if(QXmppVersionIq::isVersionIq(nodeRecv)) + { + QXmppVersionIq versionIq; + versionIq.parse(nodeRecv); + + if (versionIq.type() == QXmppIq::Get) + { + // respond to query + QXmppVersionIq responseIq; + responseIq.setType(QXmppIq::Result); + responseIq.setId(versionIq.id()); + responseIq.setTo(versionIq.from()); + responseIq.setName(qApp->applicationName()); + responseIq.setVersion(qApp->applicationVersion()); + sendPacket(responseIq); + } else { + emit iqReceived(versionIq); + } + + } + // XEP-0095: Stream Initiation + else if(QXmppStreamInitiationIq::isStreamInitiationIq(nodeRecv)) + { + QXmppStreamInitiationIq siIq; + siIq.parse(nodeRecv); + emit streamInitiationIqReceived(siIq); + } + // XEP-0136: Message Archiving + else if(QXmppArchiveChatIq::isArchiveChatIq(nodeRecv)) + { + QXmppArchiveChatIq archiveIq; + archiveIq.parse(nodeRecv); + emit archiveChatIqReceived(archiveIq); + } + else if(QXmppArchiveListIq::isArchiveListIq(nodeRecv)) + { + QXmppArchiveListIq archiveIq; + archiveIq.parse(nodeRecv); + emit archiveListIqReceived(archiveIq); + } + else if(QXmppArchivePrefIq::isArchivePrefIq(nodeRecv)) + { + QXmppArchivePrefIq archiveIq; + archiveIq.parse(nodeRecv); + emit archivePrefIqReceived(archiveIq); + } + // XEP-0166: Jingle + else if(QXmppJingleIq::isJingleIq(nodeRecv)) + { + QXmppJingleIq jingleIq; + jingleIq.parse(nodeRecv); + emit jingleIqReceived(jingleIq); + } + // 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()); + iq.setFrom(req.to()); + 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()); + iq.setFrom(iqPacket.to()); + 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.setFrom(configuration().jid()); + 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.toUtf8()); + + QMap<QByteArray, QByteArray> map = parseDigestMd5(ba); + + if (!map.contains("nonce")) + { + warning("sendAuthDigestMD5ResponseStep1: Invalid input"); + disconnectFromHost(); + return; + } + + const QByteArray user = configuration().user().toUtf8(); + const QByteArray passwd = configuration().passwd().toUtf8(); + const QByteArray domain = configuration().domain().toUtf8(); + const QByteArray realm = map.value("realm"); + + QByteArray cnonce(32, 'm'); + for(int n = 0; n < cnonce.size(); ++n) + cnonce[n] = (char)(256.0*qrand()/(RAND_MAX+1.0)); + + // The random data can the '=' char is not valid as it is a delimiter, + // so to be safe, base64 the nonce + cnonce = cnonce.toBase64(); + + const QByteArray nc = "00000001"; + const QByteArray digest_uri = "xmpp/" + domain; + const QByteArray authzid = map.value("authzid"); + const QByteArray nonce = map.value("nonce"); + const QByteArray a1 = user + ':' + realm + ':' + passwd; + + // Build response + QMap<QByteArray, QByteArray> response; + response["username"] = user; + if(!realm.isEmpty()) + response["realm"] = realm; + response["nonce"] = map["nonce"]; + response["cnonce"] = cnonce; + response["nc"] = nc; + response["qop"] = "auth"; + response["digest-uri"] = digest_uri; + response["response"] = calculateDigestMd5(a1, nonce, nc, cnonce, digest_uri, authzid).toHex(); + if(!authzid.isEmpty()) + response["authzid"] = authzid; + response["charset"] = "utf-8"; + + const QByteArray data = serializeDigestMd5(response); + QByteArray packet = "<response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>" + + data.toBase64() + "</response>"; + sendData(packet); +} + +void QXmppOutgoingClient::sendAuthDigestMD5ResponseStep2() +{ + sendData("<response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'/>"); +} + +void QXmppOutgoingClient::sendNonSASLAuth(bool plainText) +{ + QXmppNonSASLAuthIq authQuery; + authQuery.setType(QXmppIq::Set); + authQuery.setUsername(configuration().user()); + if (plainText) + authQuery.setPassword(configuration().passwd()); + else + authQuery.setDigest(d->streamId, configuration().passwd()); + 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); +} + +QAbstractSocket::SocketError QXmppOutgoingClient::socketError() +{ + return d->socketError; +} + +QXmppStanza::Error::Condition QXmppOutgoingClient::xmppStreamError() +{ + return d->xmppStreamError; +} + +QXmppDiscoveryIq QXmppOutgoingClient::capabilities() const +{ + QXmppDiscoveryIq iq; + iq.setType(QXmppIq::Result); + iq.setQueryType(QXmppDiscoveryIq::InfoQuery); + + // features + QStringList features; + features + << ns_rpc // XEP-0009: Jabber-RPC + << ns_disco_info // XEP-0030: Service Discovery + << ns_ibb // XEP-0047: In-Band Bytestreams + << ns_vcard // XEP-0054: vcard-temp + << ns_bytestreams // XEP-0065: SOCKS5 Bytestreams + << ns_chat_states // XEP-0085: Chat State Notifications + << ns_version // XEP-0092: Software Version + << ns_stream_initiation // XEP-0095: Stream Initiation + << ns_stream_initiation_file_transfer // XEP-0096: SI File Transfer + << ns_capabilities // XEP-0115 : Entity Capabilities + << ns_jingle // XEP-0166 : Jingle + << ns_jingle_rtp // XEP-0167 : Jingle RTP Sessions + << ns_jingle_rtp_audio + << ns_jingle_ice_udp // XEP-0176 : Jingle ICE-UDP Transport Method + << ns_ping; // XEP-0199: XMPP Ping + iq.setFeatures(features); + + // identities + QList<QXmppDiscoveryIq::Identity> identities; + QXmppDiscoveryIq::Identity identity; + + identity.setCategory("automation"); + identity.setType("rpc"); + identities.append(identity); + + identity.setCategory("client"); + identity.setType("pc"); + identity.setName(QString("%1 %2").arg(qApp->applicationName(), qApp->applicationVersion())); + identities.append(identity); + + iq.setIdentities(identities); + return iq; +} + +QXmppElementList QXmppOutgoingClient::presenceExtensions() const +{ + QXmppElementList extensions; + + QXmppElement caps; + caps.setTagName("c"); + caps.setAttribute("xmlns", ns_capabilities); + caps.setAttribute("hash", "sha-1"); + caps.setAttribute("node", capabilitiesNode); + caps.setAttribute("ver", capabilities().verificationString().toBase64()); + extensions << caps; + + return extensions; +} + diff --git a/src/QXmppOutgoingClient.h b/src/QXmppOutgoingClient.h new file mode 100644 index 00000000..1e217c28 --- /dev/null +++ b/src/QXmppOutgoingClient.h @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2008-2010 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 QXmppClient; +class QXmppConfiguration; +class QXmppPacket; +class QXmppPresence; +class QXmppIq; +class QXmppBind; +class QXmppRosterIq; +class QXmppVCard; +class QXmppMessage; +class QXmppRpcResponseIq; +class QXmppRpcErrorIq; +class QXmppArchiveChatIq; +class QXmppArchiveListIq; +class QXmppArchivePrefIq; +class QXmppByteStreamIq; +class QXmppDiscoveryIq; +class QXmppIbbCloseIq; +class QXmppIbbDataIq; +class QXmppIbbOpenIq; +class QXmppJingleIq; +class QXmppMucAdminIq; +class QXmppMucOwnerIq; +class QXmppStreamInitiationIq; +class QXmppVersionIq; + +class QXmppOutgoingClientPrivate; + +/// \brief The QXmppStream class represents an outgoing client-to-server +/// stream. +/// + +class QXmppOutgoingClient : public QXmppStream +{ + Q_OBJECT + +public: + QXmppOutgoingClient(QObject *parent); + ~QXmppOutgoingClient(); + + void connectToHost(); + + QAbstractSocket::SocketError socketError(); + QXmppStanza::Error::Condition xmppStreamError(); + + QXmppConfiguration& configuration(); + + QXmppElementList presenceExtensions() const; + +signals: + void error(QXmppClient::Error); + + void presenceReceived(const QXmppPresence&); + void messageReceived(const QXmppMessage&); + void iqReceived(const QXmppIq&); + void rosterIqReceived(const QXmppRosterIq&); + + // XEP-0009: Jabber-RPC + void rpcCallInvoke(const QXmppRpcInvokeIq &invoke); + void rpcCallResponse(const QXmppRpcResponseIq& result); + void rpcCallError(const QXmppRpcErrorIq &err); + + // XEP-0030: Service Discovery + void discoveryIqReceived(const QXmppDiscoveryIq&); + + // XEP-0047 In-Band Bytestreams + void ibbCloseIqReceived(const QXmppIbbCloseIq&); + void ibbDataIqReceived(const QXmppIbbDataIq&); + void ibbOpenIqReceived(const QXmppIbbOpenIq&); + + // XEP-0045: Multi-User Chat + void mucAdminIqReceived(const QXmppMucAdminIq&); + void mucOwnerIqReceived(const QXmppMucOwnerIq&); + + // XEP-0054: vcard-temp + void vCardIqReceived(const QXmppVCard&); + + // XEP-0065: SOCKS5 Bytestreams + void byteStreamIqReceived(const QXmppByteStreamIq&); + + // XEP-0095: Stream Initiation + void streamInitiationIqReceived(const QXmppStreamInitiationIq&); + + // XEP-0136: Message Archiving + void archiveChatIqReceived(const QXmppArchiveChatIq&); + void archiveListIqReceived(const QXmppArchiveListIq&); + void archivePrefIqReceived(const QXmppArchivePrefIq&); + + // XEP-0166: Jingle + void jingleIqReceived(const QXmppJingleIq&); + +protected: + // Overridable methods + virtual void handleStart(); + virtual void handleStanza(const QDomElement &element); + virtual void handleStream(const QDomElement &element); + +private slots: + void socketError(QAbstractSocket::SocketError); + void socketSslErrors(const QList<QSslError>&); + + void pingStart(); + void pingStop(); + void pingSend(); + void pingTimeout(); + +private: + QXmppDiscoveryIq capabilities() const; + void sendAuthDigestMD5ResponseStep1(const QString& challenge); + void sendAuthDigestMD5ResponseStep2(); + void sendNonSASLAuth(bool plaintext); + void sendNonSASLAuthQuery(); + + QXmppOutgoingClientPrivate * const d; +}; + +#endif // QXMPPOUTGOINGCLIENT_H diff --git a/src/QXmppRosterManager.cpp b/src/QXmppRosterManager.cpp index 684e6d1f..98907b64 100644 --- a/src/QXmppRosterManager.cpp +++ b/src/QXmppRosterManager.cpp @@ -27,14 +27,14 @@ #include "QXmppUtils.h" #include "QXmppRosterIq.h" #include "QXmppPresence.h" -#include "QXmppStream.h" +#include "QXmppOutgoingClient.h" -QXmppRosterManager::QXmppRosterManager(QXmppStream* stream, QObject *parent) +QXmppRosterManager::QXmppRosterManager(QXmppOutgoingClient* stream, QObject *parent) : QObject(parent), m_stream(stream), m_isRosterReceived(false) { - bool check = QObject::connect(m_stream, SIGNAL(xmppConnected()), + bool check = QObject::connect(m_stream, SIGNAL(connected()), this, SLOT(connected())); Q_ASSERT(check); diff --git a/src/QXmppRosterManager.h b/src/QXmppRosterManager.h index bde31752..1e21b4e3 100644 --- a/src/QXmppRosterManager.h +++ b/src/QXmppRosterManager.h @@ -34,7 +34,7 @@ class QXmppRosterIq; class QXmppPresence; -class QXmppStream; +class QXmppOutgoingClient; /// \brief The QXmppRosterManager class provides access to a connected client's roster. /// @@ -68,7 +68,7 @@ public: // FIXME : is this class really necessary? typedef QXmppRosterIq::Item QXmppRosterEntry; - QXmppRosterManager(QXmppStream* stream, QObject *parent = 0); + QXmppRosterManager(QXmppOutgoingClient* stream, QObject *parent = 0); ~QXmppRosterManager(); bool isRosterReceived(); @@ -102,7 +102,7 @@ signals: private: //reverse pointer to stream - QXmppStream* m_stream; + QXmppOutgoingClient* m_stream; //map of bareJid and its rosterEntry QMap<QString, QXmppRosterIq::Item> m_entries; // map of resources of the jid and map of resources and presences diff --git a/src/QXmppStream.cpp b/src/QXmppStream.cpp index 0ff172d6..fd1f38f9 100644 --- a/src/QXmppStream.cpp +++ b/src/QXmppStream.cpp @@ -1,8 +1,9 @@ /* * Copyright (C) 2008-2010 The QXmpp developers * - * Author: + * Authors: * Manjeet Dahiya + * Jeremy Lainé * * Source: * http://code.google.com/p/qxmpp @@ -22,41 +23,20 @@ */ -#include "QXmppConfiguration.h" #include "QXmppConstants.h" -#include "QXmppIq.h" #include "QXmppLogger.h" -#include "QXmppMessage.h" #include "QXmppPacket.h" -#include "QXmppPresence.h" #include "QXmppStream.h" -#include "QXmppStreamFeatures.h" -#include "QXmppNonSASLAuth.h" #include "QXmppUtils.h" -// IQ types -#include "QXmppArchiveIq.h" -#include "QXmppBind.h" -#include "QXmppByteStreamIq.h" -#include "QXmppDiscoveryIq.h" -#include "QXmppIbbIq.h" -#include "QXmppJingleIq.h" -#include "QXmppMucIq.h" -#include "QXmppPingIq.h" -#include "QXmppRpcIq.h" -#include "QXmppRosterIq.h" -#include "QXmppSession.h" -#include "QXmppStreamInitiationIq.h" -#include "QXmppVCard.h" -#include "QXmppVersionIq.h" - -#include <QCoreApplication> +#include <QBuffer> #include <QDomDocument> -#include <QStringList> -#include <QRegExp> #include <QHostAddress> +#include <QRegExp> +#include <QSslSocket> +#include <QStringList> +#include <QTime> #include <QXmlStreamWriter> -#include <QTimer> static bool randomSeeded = false; static const QString capabilitiesNode = "http://code.google.com/p/qxmpp"; @@ -67,39 +47,25 @@ class QXmppStreamPrivate public: QXmppStreamPrivate(); - // This object provides the configuration - // required for connecting to the XMPP server. - QXmppConfiguration config; QByteArray dataBuffer; QXmppLogger* logger; QSslSocket* socket; - QAbstractSocket::SocketError socketError; - QXmppStanza::Error::Condition xmppStreamError; - // State data - int authStep; - QString bindId; - QString sessionId; - bool sessionAvailable; - QString streamId; - QString streamFrom; + // stream state QByteArray streamStart; - QString streamVersion; - QString nonSASLAuthId; - - // Timers - QTimer *pingTimer; - QTimer *timeoutTimer; }; QXmppStreamPrivate::QXmppStreamPrivate() : logger(0), - authStep(0), - sessionAvailable(false) + socket(0) { } -QXmppStream::QXmppStream(QSslSocket *socket, QObject *parent) +/// Constructs a base XMPP stream. +/// +/// \param parent + +QXmppStream::QXmppStream(QObject *parent) : QObject(parent), d(new QXmppStreamPrivate) { @@ -109,165 +75,185 @@ QXmppStream::QXmppStream(QSslSocket *socket, QObject *parent) qsrand(QTime(0,0,0).secsTo(QTime::currentTime())); randomSeeded = true; } - - // initialise logger - setLogger(QXmppLogger::getLogger()); - - // create socket - d->socket = socket; - - bool check = QObject::connect(d->socket, SIGNAL(hostFound()), - this, SLOT(socketHostFound())); - Q_ASSERT(check); - check = QObject::connect(d->socket, SIGNAL(connected()), - this, SLOT(socketConnected())); - Q_ASSERT(check); - check = QObject::connect(d->socket, SIGNAL(disconnected()), - this, SLOT(socketDisconnected())); - Q_ASSERT(check); - check = QObject::connect(d->socket, SIGNAL(readyRead()), - this, SLOT(socketReadReady())); - Q_ASSERT(check); - check = QObject::connect(d->socket, SIGNAL(encrypted()), - this, SLOT(socketEncrypted())); - Q_ASSERT(check); - check = QObject::connect(d->socket, - SIGNAL(sslErrors(const QList<QSslError>&)), this, - SLOT(socketSslErrors(const QList<QSslError>&))); - Q_ASSERT(check); - check = QObject::connect(d->socket, - SIGNAL(error(QAbstractSocket::SocketError)), this, - SLOT(socketError(QAbstractSocket::SocketError))); - Q_ASSERT(check); - - // XEP-0199: XMPP Ping - d->pingTimer = new QTimer(this); - check = QObject::connect(d->pingTimer, SIGNAL(timeout()), this, SLOT(pingSend())); - Q_ASSERT(check); - - d->timeoutTimer = new QTimer(this); - d->timeoutTimer->setSingleShot(true); - check = QObject::connect(d->timeoutTimer, SIGNAL(timeout()), this, SLOT(pingTimeout())); - Q_ASSERT(check); - - check = QObject::connect(this, SIGNAL(xmppConnected()), this, SLOT(pingStart())); - Q_ASSERT(check); - - check = QObject::connect(this, SIGNAL(disconnected()), this, SLOT(pingStop())); - Q_ASSERT(check); } +/// Destroys a base XMPP stream. + QXmppStream::~QXmppStream() { delete d; } -QXmppConfiguration& QXmppStream::configuration() +/// Disconnects from the remote host. +/// + +void QXmppStream::disconnectFromHost() { - return d->config; + sendData(streamRootElementEnd); + if (d->socket) + { + d->socket->flush(); + d->socket->disconnectFromHost(); + } } -void QXmppStream::connectToHost() +void QXmppStream::handleStart() { - info(QString("Connecting to: %1:%2").arg(configuration(). - host()).arg(configuration().port())); +} - // prepare for connection - d->authStep = 0; +/// Returns the QXmppLogger associated with the current stream. - d->socket->setProxy(configuration().networkProxy()); - d->socket->connectToHost(configuration(). - host(), configuration().port()); +QXmppLogger *QXmppStream::logger() +{ + return d->logger; } -void QXmppStream::socketSslErrors(const QList<QSslError> & error) +/// Sets the QXmppLogger associated with the current stream. + +void QXmppStream::setLogger(QXmppLogger *logger) { - warning("SSL errors"); - for(int i = 0; i< error.count(); ++i) - warning(error.at(i).errorString()); + 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))); +} - if (configuration().ignoreSslErrors()) - d->socket->ignoreSslErrors(); +void QXmppStream::debug(const QString &data) +{ + emit logMessage(QXmppLogger::DebugMessage, data); } -void QXmppStream::socketHostFound() +void QXmppStream::info(const QString &data) { - debug("Host found"); - emit hostFound(); + emit logMessage(QXmppLogger::InformationMessage, data); } -void QXmppStream::socketConnected() +void QXmppStream::warning(const QString &data) { - flushDataBuffer(); - info("Connected"); - emit connected(); - sendStartStream(); + emit logMessage(QXmppLogger::WarningMessage, data); } -void QXmppStream::socketDisconnected() +/// Returns true if the stream is connected. +/// + +bool QXmppStream::isConnected() const { - flushDataBuffer(); - info("Disconnected"); - emit disconnected(); + return d->socket && + d->socket->state() == QAbstractSocket::ConnectedState; } -void QXmppStream::socketEncrypted() +/// Sends raw data to the peer. +/// +/// \param data + +bool QXmppStream::sendData(const QByteArray &data) { - debug("Encrypted"); - sendStartStream(); + emit logMessage(QXmppLogger::SentMessage, QString::fromUtf8(data)); + if (!d->socket || d->socket->state() != QAbstractSocket::ConnectedState) + return false; + return d->socket->write(data) == data.size(); } -void QXmppStream::socketError(QAbstractSocket::SocketError ee) +/// Sends an XML element to the peer. +/// +/// \param element + +bool QXmppStream::sendElement(const QDomElement &element) { - d->socketError = ee; - emit error(QXmppClient::SocketError); - warning(QString("Socket error: " + d->socket->errorString())); + // prepare packet + QBuffer buffer; + buffer.open(QIODevice::ReadWrite); + QXmlStreamWriter writer(&buffer); + const QStringList omitNamespaces = QStringList() << ns_client << ns_server; + helperToXmlAddDomElement(&writer, element, omitNamespaces); + + // send packet + return sendData(buffer.data()); } -void QXmppStream::socketReadReady() +/// Sends an XMPP packet to the peer. +/// +/// \param packet + +bool QXmppStream::sendPacket(const QXmppPacket &packet) { - const QByteArray data = d->socket->readAll(); - //debug("SERVER [COULD BE PARTIAL DATA]:" + data.left(20)); - parser(data); + // prepare packet + QByteArray data; + QXmlStreamWriter xmlStream(&data); + packet.toXml(&xmlStream); + + // send packet + return sendData(data); } -/// Returns the QXmppLogger associated with the current QXmppStream. +/// Returns the QSslSocket used for this stream. +/// -QXmppLogger *QXmppStream::logger() +QSslSocket *QXmppStream::socket() { - return d->logger; + return d->socket; } -/// Sets the QXmppLogger associated with the current QXmppStream. +/// Sets the QSslSocket used for this stream. +/// -void QXmppStream::setLogger(QXmppLogger *logger) +void QXmppStream::setSocket(QSslSocket *socket) { - if (d->logger) - QObject::disconnect(this, SIGNAL(logMessage(QXmppLogger::MessageType, QString)), - d->logger, SLOT(log(QXmppLogger::MessageType, QString))); - d->logger = logger; - if (d->logger) - QObject::connect(this, SIGNAL(logMessage(QXmppLogger::MessageType, QString)), - d->logger, SLOT(log(QXmppLogger::MessageType, QString))); + d->socket = socket; + + // socket events + bool check = connect(socket, SIGNAL(connected()), + this, SLOT(socketConnected())); + Q_ASSERT(check); + + check = connect(socket, SIGNAL(disconnected()), + this, SLOT(socketDisconnected())); + Q_ASSERT(check); + + check = connect(socket, SIGNAL(encrypted()), + this, SLOT(socketEncrypted())); + Q_ASSERT(check); + + check = connect(socket, SIGNAL(readyRead()), + this, SLOT(socketReadyRead())); + Q_ASSERT(check); + + // relay signals + check = connect(socket, SIGNAL(disconnected()), + this, SIGNAL(disconnected())); + Q_ASSERT(check); } -void QXmppStream::debug(const QString &data) +void QXmppStream::socketConnected() { - emit logMessage(QXmppLogger::DebugMessage, data); + info(QString("Socket connected to %1 %2").arg( + socket()->peerAddress().toString(), + QString::number(socket()->peerPort()))); + d->dataBuffer.clear(); + handleStart(); } -void QXmppStream::info(const QString &data) +void QXmppStream::socketDisconnected() { - emit logMessage(QXmppLogger::InformationMessage, data); + info("Socket disconnected"); + d->dataBuffer.clear(); } -void QXmppStream::warning(const QString &data) +void QXmppStream::socketEncrypted() { - emit logMessage(QXmppLogger::WarningMessage, data); + debug("Socket encrypted"); + d->dataBuffer.clear(); + handleStart(); } -void QXmppStream::parser(const QByteArray& data) +void QXmppStream::socketReadyRead() { + const QByteArray data = socket()->readAll(); + //debug("SERVER [COULD BE PARTIAL DATA]:" + data.left(20)); + d->dataBuffer.append(data); // FIXME : maybe these QRegExps could be static? @@ -298,7 +284,7 @@ void QXmppStream::parser(const QByteArray& data) // remove data from buffer emit logMessage(QXmppLogger::ReceivedMessage, strData); - flushDataBuffer(); + d->dataBuffer.clear(); // process stream start QDomElement nodeRecv = doc.documentElement().firstChildElement(); @@ -311,734 +297,9 @@ void QXmppStream::parser(const QByteArray& data) // process stanzas while(!nodeRecv.isNull()) { - // if we receive any kind of data, stop the timeout timer - d->timeoutTimer->stop(); - handleStanza(nodeRecv); nodeRecv = nodeRecv.nextSiblingElement(); } } -void QXmppStream::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 QXmppStream::handleStanza(const QDomElement &nodeRecv) -{ - 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); - - const bool nonSaslAvailable = features.isNonSaslAuthAvailable(); - const bool saslAvailable = !features.authMechanisms().isEmpty(); - const bool useSasl = configuration().useSASLAuthentication(); - - if (!d->socket->isEncrypted()) - { - // determine TLS mode to use - const QXmppConfiguration::StreamSecurityMode localSecurity = configuration().streamSecurityMode(); - const QXmppConfiguration::StreamSecurityMode remoteSecurity = features.securityMode(); - if (!d->socket->supportsSsl() && - (localSecurity == QXmppConfiguration::TLSRequired || - remoteSecurity == QXmppConfiguration::TLSRequired)) - { - warning("Disconnecting as TLS is required, but SSL support is not available"); - disconnectFromHost(); - return; - } - if (localSecurity == QXmppConfiguration::TLSRequired && - remoteSecurity == QXmppConfiguration::TLSDisabled) - { - warning("Disconnecting as TLS is required, but not supported by the server"); - disconnectFromHost(); - return; - } - - if (d->socket->supportsSsl() && - remoteSecurity != QXmppConfiguration::TLSDisabled && - localSecurity != QXmppConfiguration::TLSDisabled) - { - // enable TLS as it is support by both parties - sendData("<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>"); - return; - } - } - - // handle authentication - if((saslAvailable && nonSaslAvailable && !useSasl) || - (!saslAvailable && nonSaslAvailable)) - { - sendNonSASLAuthQuery(); - } - else if(saslAvailable) - { - // determine SASL Authentication mechanism to use - QList<QXmppConfiguration::SASLAuthMechanism> mechanisms = features.authMechanisms(); - QXmppConfiguration::SASLAuthMechanism mechanism = configuration().sASLAuthMechanism(); - if (mechanisms.isEmpty()) - { - warning("No supported SASL Authentication mechanism available"); - disconnectFromHost(); - return; - } - else if (!mechanisms.contains(mechanism)) - { - info("Desired SASL Auth mechanism is not available, selecting first available one"); - mechanism = mechanisms.first(); - } - - // send SASL Authentication request - switch(mechanism) - { - case QXmppConfiguration::SASLPlain: - { - QString userPass('\0' + configuration().user() + - '\0' + configuration().passwd()); - 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; - } - } - - // check whether bind is available - if (features.isBindAvailable()) - sendBindIQ(); - - // check whether session is available - if (features.isSessionAvailable()) - 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"); - d->socket->startClientEncryption(); - return; - } - } - else if(ns == ns_sasl) - { - if(nodeRecv.tagName() == "success") - { - debug("Authenticated"); - sendStartStream(); - } - else if(nodeRecv.tagName() == "challenge") - { - // TODO: Track which mechanism was used for when other SASL protocols which use challenges are supported - d->authStep++; - switch (d->authStep) - { - case 1 : - sendAuthDigestMD5ResponseStep1(nodeRecv.text()); - break; - case 2 : - sendAuthDigestMD5ResponseStep2(); - break; - default : - warning("Too many authentication steps"); - 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) - { - QXmppSession session; - session.parse(nodeRecv); - - // get back add configuration whether to send - // roster and intial presence in beginning - // process SessionIq - - // xmpp connection made - emit xmppConnected(); - } - else if(QXmppBind::isBind(nodeRecv) && id == d->bindId) - { - QXmppBind 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()); - } - } - if (d->sessionAvailable) - sendSessionIQ(); - } - } - else if(QXmppRosterIq::isRosterIq(nodeRecv)) - { - QXmppRosterIq rosterIq; - rosterIq.parse(nodeRecv); - emit rosterIqReceived(rosterIq); - } - // extensions - - // XEP-0009: Jabber-RPC - else if(QXmppRpcInvokeIq::isRpcInvokeIq(nodeRecv)) - { - QXmppRpcInvokeIq rpcIqPacket; - rpcIqPacket.parse(nodeRecv); - emit rpcCallInvoke(rpcIqPacket); - } - else if(QXmppRpcResponseIq::isRpcResponseIq(nodeRecv)) - { - QXmppRpcResponseIq rpcResponseIq; - rpcResponseIq.parse(nodeRecv); - emit rpcCallResponse(rpcResponseIq); - } - else if(QXmppRpcErrorIq::isRpcErrorIq(nodeRecv)) - { - QXmppRpcErrorIq rpcErrorIq; - rpcErrorIq.parse(nodeRecv); - emit rpcCallError(rpcErrorIq); - } - - // XEP-0030: Service Discovery - else if(QXmppDiscoveryIq::isDiscoveryIq(nodeRecv)) - { - QXmppDiscoveryIq discoIq; - discoIq.parse(nodeRecv); - - if (discoIq.type() == QXmppIq::Get && - discoIq.queryType() == QXmppDiscoveryIq::InfoQuery && - (discoIq.queryNode().isEmpty() || discoIq.queryNode().startsWith(capabilitiesNode))) - { - // respond to info query - QXmppDiscoveryIq qxmppFeatures = capabilities(); - qxmppFeatures.setId(discoIq.id()); - qxmppFeatures.setTo(discoIq.from()); - qxmppFeatures.setQueryNode(discoIq.queryNode()); - sendPacket(qxmppFeatures); - } else { - emit discoveryIqReceived(discoIq); - } - } - // XEP-0045: Multi-User Chat - else if (QXmppMucAdminIq::isMucAdminIq(nodeRecv)) - { - QXmppMucAdminIq mucIq; - mucIq.parse(nodeRecv); - emit mucAdminIqReceived(mucIq); - } - else if (QXmppMucOwnerIq::isMucOwnerIq(nodeRecv)) - { - QXmppMucOwnerIq mucIq; - mucIq.parse(nodeRecv); - emit mucOwnerIqReceived(mucIq); - } - // XEP-0047 In-Band Bytestreams - else if(QXmppIbbCloseIq::isIbbCloseIq(nodeRecv)) - { - QXmppIbbCloseIq ibbCloseIq; - ibbCloseIq.parse(nodeRecv); - emit ibbCloseIqReceived(ibbCloseIq); - } - else if(QXmppIbbDataIq::isIbbDataIq(nodeRecv)) - { - QXmppIbbDataIq ibbDataIq; - ibbDataIq.parse(nodeRecv); - emit ibbDataIqReceived(ibbDataIq); - } - else if(QXmppIbbOpenIq::isIbbOpenIq(nodeRecv)) - { - QXmppIbbOpenIq ibbOpenIq; - ibbOpenIq.parse(nodeRecv); - emit ibbOpenIqReceived(ibbOpenIq); - } - // XEP-0054: vcard-temp - else if(nodeRecv.firstChildElement("vCard"). - namespaceURI() == ns_vcard) - { - QXmppVCard vcardIq; - vcardIq.parse(nodeRecv); - emit vCardIqReceived(vcardIq); - } - // XEP-0065: SOCKS5 Bytestreams - else if(QXmppByteStreamIq::isByteStreamIq(nodeRecv)) - { - QXmppByteStreamIq byteStreamIq; - byteStreamIq.parse(nodeRecv); - emit byteStreamIqReceived(byteStreamIq); - } - // XEP-0078: Non-SASL Authentication - else if(id == d->nonSASLAuthId && type == "result") - { - // successful Non-SASL Authentication - debug("Authenticated (Non-SASL)"); - - // xmpp connection made - emit xmppConnected(); - } - 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-0092: Software Version - else if(QXmppVersionIq::isVersionIq(nodeRecv)) - { - QXmppVersionIq versionIq; - versionIq.parse(nodeRecv); - - if (versionIq.type() == QXmppIq::Get) - { - // respond to query - QXmppVersionIq responseIq; - responseIq.setType(QXmppIq::Result); - responseIq.setId(versionIq.id()); - responseIq.setTo(versionIq.from()); - responseIq.setName(qApp->applicationName()); - responseIq.setVersion(qApp->applicationVersion()); - sendPacket(responseIq); - } else { - emit iqReceived(versionIq); - } - - } - // XEP-0095: Stream Initiation - else if(QXmppStreamInitiationIq::isStreamInitiationIq(nodeRecv)) - { - QXmppStreamInitiationIq siIq; - siIq.parse(nodeRecv); - emit streamInitiationIqReceived(siIq); - } - // XEP-0136: Message Archiving - else if(QXmppArchiveChatIq::isArchiveChatIq(nodeRecv)) - { - QXmppArchiveChatIq archiveIq; - archiveIq.parse(nodeRecv); - emit archiveChatIqReceived(archiveIq); - } - else if(QXmppArchiveListIq::isArchiveListIq(nodeRecv)) - { - QXmppArchiveListIq archiveIq; - archiveIq.parse(nodeRecv); - emit archiveListIqReceived(archiveIq); - } - else if(QXmppArchivePrefIq::isArchivePrefIq(nodeRecv)) - { - QXmppArchivePrefIq archiveIq; - archiveIq.parse(nodeRecv); - emit archivePrefIqReceived(archiveIq); - } - // XEP-0166: Jingle - else if(QXmppJingleIq::isJingleIq(nodeRecv)) - { - QXmppJingleIq jingleIq; - jingleIq.parse(nodeRecv); - emit jingleIqReceived(jingleIq); - } - // 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()); - iq.setFrom(req.to()); - 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()); - iq.setFrom(iqPacket.to()); - 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); - } - } -} - -bool QXmppStream::sendStartStream() -{ - QByteArray data = "<?xml version='1.0'?><stream:stream to='"; - data.append(configuration().domain()); - data.append("' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' version='1.0'>"); - return sendData(data); -} - -bool QXmppStream::sendData(const QByteArray& packet) -{ - emit logMessage(QXmppLogger::SentMessage, QString::fromUtf8(packet)); - if (!isConnected()) - return false; - return d->socket->write( packet ) == packet.size(); -} - -void QXmppStream::sendNonSASLAuth(bool plainText) -{ - QXmppNonSASLAuthIq authQuery; - authQuery.setType(QXmppIq::Set); - authQuery.setUsername(configuration().user()); - if (plainText) - authQuery.setPassword(configuration().passwd()); - else - authQuery.setDigest(d->streamId, configuration().passwd()); - authQuery.setResource(configuration().resource()); - d->nonSASLAuthId = authQuery.id(); - sendPacket(authQuery); -} - -void QXmppStream::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); -} - -// challenge is BASE64 encoded string -void QXmppStream::sendAuthDigestMD5ResponseStep1(const QString& challenge) -{ - QByteArray ba = QByteArray::fromBase64(challenge.toUtf8()); - - QMap<QByteArray, QByteArray> map = parseDigestMd5(ba); - - if (!map.contains("nonce")) - { - warning("sendAuthDigestMD5ResponseStep1: Invalid input"); - disconnectFromHost(); - return; - } - - const QByteArray user = configuration().user().toUtf8(); - const QByteArray passwd = configuration().passwd().toUtf8(); - const QByteArray domain = configuration().domain().toUtf8(); - const QByteArray realm = map.value("realm"); - - QByteArray cnonce(32, 'm'); - for(int n = 0; n < cnonce.size(); ++n) - cnonce[n] = (char)(256.0*qrand()/(RAND_MAX+1.0)); - - // The random data can the '=' char is not valid as it is a delimiter, - // so to be safe, base64 the nonce - cnonce = cnonce.toBase64(); - - const QByteArray nc = "00000001"; - const QByteArray digest_uri = "xmpp/" + domain; - const QByteArray authzid = map.value("authzid"); - const QByteArray nonce = map.value("nonce"); - const QByteArray a1 = user + ':' + realm + ':' + passwd; - - // Build response - QMap<QByteArray, QByteArray> response; - response["username"] = user; - if(!realm.isEmpty()) - response["realm"] = realm; - response["nonce"] = map["nonce"]; - response["cnonce"] = cnonce; - response["nc"] = nc; - response["qop"] = "auth"; - response["digest-uri"] = digest_uri; - response["response"] = calculateDigestMd5(a1, nonce, nc, cnonce, digest_uri, authzid).toHex(); - if(!authzid.isEmpty()) - response["authzid"] = authzid; - response["charset"] = "utf-8"; - - const QByteArray data = serializeDigestMd5(response); - QByteArray packet = "<response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>" - + data.toBase64() + "</response>"; - sendData(packet); -} - -void QXmppStream::sendAuthDigestMD5ResponseStep2() -{ - sendData("<response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'/>"); -} - -void QXmppStream::sendBindIQ() -{ - QXmppBind bind(QXmppIq::Set); - bind.setResource(configuration().resource()); - d->bindId = bind.id(); - sendPacket(bind); -} - -void QXmppStream::sendSessionIQ() -{ - QXmppSession session(QXmppIq::Set); - session.setTo(configuration().domain()); - d->sessionId = session.id(); - sendPacket(session); -} - -void QXmppStream::disconnectFromHost() -{ - d->authStep = 0; - sendEndStream(); - d->socket->flush(); - d->socket->disconnectFromHost(); -} - -bool QXmppStream::isConnected() const -{ - return d->socket->state() == QAbstractSocket::ConnectedState; -} - -bool QXmppStream::sendPacket(const QXmppPacket& packet) -{ - // prepare packet - QByteArray data; - QXmlStreamWriter xmlStream(&data); - packet.toXml(&xmlStream); - - // send packet - return sendData(data); -} - -bool QXmppStream::sendEndStream() -{ - return sendData(streamRootElementEnd); -} - -void QXmppStream::pingStart() -{ - const int interval = configuration().keepAliveInterval(); - // start ping timer - if (interval > 0) - { - d->pingTimer->setInterval(interval * 1000); - d->pingTimer->start(); - } -} - -void QXmppStream::pingStop() -{ - // stop all timers - d->pingTimer->stop(); - d->timeoutTimer->stop(); -} - -void QXmppStream::pingSend() -{ - // send ping packet - QXmppPingIq ping; - ping.setFrom(configuration().jid()); - 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 QXmppStream::pingTimeout() -{ - warning("Ping timeout"); - disconnectFromHost(); - emit error(QXmppClient::KeepAliveError); -} - -QAbstractSocket::SocketError QXmppStream::socketError() -{ - return d->socketError; -} - -QXmppStanza::Error::Condition QXmppStream::xmppStreamError() -{ - return d->xmppStreamError; -} - -void QXmppStream::flushDataBuffer() -{ - d->dataBuffer.clear(); -} - -QXmppDiscoveryIq QXmppStream::capabilities() const -{ - QXmppDiscoveryIq iq; - iq.setType(QXmppIq::Result); - iq.setQueryType(QXmppDiscoveryIq::InfoQuery); - - // features - QStringList features; - features - << ns_rpc // XEP-0009: Jabber-RPC - << ns_disco_info // XEP-0030: Service Discovery - << ns_ibb // XEP-0047: In-Band Bytestreams - << ns_vcard // XEP-0054: vcard-temp - << ns_bytestreams // XEP-0065: SOCKS5 Bytestreams - << ns_chat_states // XEP-0085: Chat State Notifications - << ns_version // XEP-0092: Software Version - << ns_stream_initiation // XEP-0095: Stream Initiation - << ns_stream_initiation_file_transfer // XEP-0096: SI File Transfer - << ns_capabilities // XEP-0115 : Entity Capabilities - << ns_jingle // XEP-0166 : Jingle - << ns_jingle_rtp // XEP-0167 : Jingle RTP Sessions - << ns_jingle_rtp_audio - << ns_jingle_ice_udp // XEP-0176 : Jingle ICE-UDP Transport Method - << ns_ping; // XEP-0199: XMPP Ping - iq.setFeatures(features); - - // identities - QList<QXmppDiscoveryIq::Identity> identities; - QXmppDiscoveryIq::Identity identity; - - identity.setCategory("automation"); - identity.setType("rpc"); - identities.append(identity); - - identity.setCategory("client"); - identity.setType("pc"); - identity.setName(QString("%1 %2").arg(qApp->applicationName(), qApp->applicationVersion())); - identities.append(identity); - - iq.setIdentities(identities); - return iq; -} - -QXmppElementList QXmppStream::presenceExtensions() const -{ - QXmppElementList extensions; - - QXmppElement caps; - caps.setTagName("c"); - caps.setAttribute("xmlns", ns_capabilities); - caps.setAttribute("hash", "sha-1"); - caps.setAttribute("node", capabilitiesNode); - caps.setAttribute("ver", capabilities().verificationString().toBase64()); - extensions << caps; - - return extensions; -} diff --git a/src/QXmppStream.h b/src/QXmppStream.h index ddfe9d9e..ccc11c93 100644 --- a/src/QXmppStream.h +++ b/src/QXmppStream.h @@ -1,8 +1,9 @@ /* * Copyright (C) 2008-2010 The QXmpp developers * - * Author: + * Authors: * Manjeet Dahiya + * Jeremy Lainé * * Source: * http://code.google.com/p/qxmpp @@ -26,146 +27,69 @@ #define QXMPPSTREAM_H #include <QObject> -#include <QSslSocket> -#include "QXmppClient.h" #include "QXmppLogger.h" -#include "QXmppStanza.h" class QDomElement; - -class QXmppClient; -class QXmppConfiguration; +class QSslSocket; class QXmppPacket; -class QXmppPresence; -class QXmppIq; -class QXmppBind; -class QXmppRosterIq; -class QXmppVCard; -class QXmppMessage; -class QXmppRpcResponseIq; -class QXmppRpcErrorIq; -class QXmppArchiveChatIq; -class QXmppArchiveListIq; -class QXmppArchivePrefIq; -class QXmppByteStreamIq; -class QXmppDiscoveryIq; -class QXmppIbbCloseIq; -class QXmppIbbDataIq; -class QXmppIbbOpenIq; -class QXmppJingleIq; -class QXmppMucAdminIq; -class QXmppMucOwnerIq; -class QXmppStreamInitiationIq; class QXmppStreamPrivate; -class QXmppVersionIq; + +/// \brief The QXmppStream class is the base class for all XMPP streams. +/// class QXmppStream : public QObject { Q_OBJECT public: - QXmppStream(QSslSocket *socket, QObject *parent); + QXmppStream(QObject *parent); ~QXmppStream(); - void connectToHost(); - void disconnectFromHost(); - bool isConnected() const; - bool sendData(const QByteArray&); - bool sendPacket(const QXmppPacket&); - - QAbstractSocket::SocketError socketError(); - QXmppStanza::Error::Condition xmppStreamError(); - QXmppConfiguration& configuration(); + bool isConnected() const; + void disconnectFromHost(); QXmppLogger *logger(); void setLogger(QXmppLogger *logger); - QXmppElementList presenceExtensions() const; + bool sendData(const QByteArray&); + bool sendElement(const QDomElement&); + bool sendPacket(const QXmppPacket&); signals: - // socket host found - void hostFound(); - - // socket connected + /// This signal is emitted when the stream is connected. void connected(); - // socket disconnected + /// This signal is emitted when the stream is disconnected. void disconnected(); - // xmpp connected - void xmppConnected(); + /// This signal is emitted when an element is received. + void elementReceived(const QDomElement &element, bool &handled); /// This signal is emitted to send logging messages. void logMessage(QXmppLogger::MessageType type, const QString &msg); - void error(QXmppClient::Error); - void elementReceived(const QDomElement &element, bool &handled); - void presenceReceived(const QXmppPresence&); - void messageReceived(const QXmppMessage&); - void iqReceived(const QXmppIq&); - void rosterIqReceived(const QXmppRosterIq&); - void vCardIqReceived(const QXmppVCard&); - - void rpcCallInvoke(const QXmppRpcInvokeIq &invoke); - void rpcCallResponse(const QXmppRpcResponseIq& result); - void rpcCallError(const QXmppRpcErrorIq &err); - - void archiveChatIqReceived(const QXmppArchiveChatIq&); - void archiveListIqReceived(const QXmppArchiveListIq&); - void archivePrefIqReceived(const QXmppArchivePrefIq&); - - void discoveryIqReceived(const QXmppDiscoveryIq&); - - void byteStreamIqReceived(const QXmppByteStreamIq&); - void ibbCloseIqReceived(const QXmppIbbCloseIq&); - void ibbDataIqReceived(const QXmppIbbDataIq&); - void ibbOpenIqReceived(const QXmppIbbOpenIq&); - void streamInitiationIqReceived(const QXmppStreamInitiationIq&); - - // XEP-0045: Multi-User Chat - void mucAdminIqReceived(const QXmppMucAdminIq&); - void mucOwnerIqReceived(const QXmppMucOwnerIq&); - - // XEP-0166: Jingle - void jingleIqReceived(const QXmppJingleIq&); - protected: // Logging helpers void debug(const QString&); void info(const QString&); void warning(const QString&); + // Access to underlying socket + QSslSocket *socket(); + void setSocket(QSslSocket *socket); + // Overridable methods - virtual void handleStanza(const QDomElement &element); - virtual void handleStream(const QDomElement &element); - virtual bool sendStartStream(); - virtual bool sendEndStream(); + virtual void handleStart(); + virtual void handleStanza(const QDomElement &element) = 0; + virtual void handleStream(const QDomElement &element) = 0; private slots: - void socketHostFound(); - void socketReadReady(); - void socketEncrypted(); void socketConnected(); void socketDisconnected(); - void socketError(QAbstractSocket::SocketError); - void socketSslErrors(const QList<QSslError>&); - - void pingStart(); - void pingStop(); - void pingSend(); - void pingTimeout(); + void socketEncrypted(); + void socketReadyRead(); private: - QXmppDiscoveryIq capabilities() const; - void flushDataBuffer(); - void parser(const QByteArray&); - void sendNonSASLAuth(bool plaintext); - void sendNonSASLAuthQuery(); - void sendAuthDigestMD5ResponseStep1(const QString& challenge); - void sendAuthDigestMD5ResponseStep2(); - void sendBindIQ(); - void sendSessionIQ(); - QXmppStreamPrivate * const d; }; diff --git a/src/QXmppTransferManager.cpp b/src/QXmppTransferManager.cpp index 9ee1a76f..a371fb16 100644 --- a/src/QXmppTransferManager.cpp +++ b/src/QXmppTransferManager.cpp @@ -32,7 +32,7 @@ #include "QXmppIbbIq.h" #include "QXmppLogger.h" #include "QXmppSocks.h" -#include "QXmppStream.h" +#include "QXmppOutgoingClient.h" #include "QXmppStreamInitiationIq.h" #include "QXmppTransferManager.h" #include "QXmppUtils.h" @@ -360,7 +360,7 @@ bool QXmppTransferJob::writeData(const QByteArray &data) return true; } -QXmppTransferManager::QXmppTransferManager(QXmppStream *stream, QObject *parent) +QXmppTransferManager::QXmppTransferManager(QXmppOutgoingClient *stream, QObject *parent) : QObject(parent), m_stream(stream), m_ibbBlockSize(4096), diff --git a/src/QXmppTransferManager.h b/src/QXmppTransferManager.h index 8a8685b6..3386e0aa 100644 --- a/src/QXmppTransferManager.h +++ b/src/QXmppTransferManager.h @@ -36,7 +36,7 @@ class QTcpSocket; class QXmppByteStreamIq; -class QXmppStream; +class QXmppOutgoingClient; class QXmppIbbCloseIq; class QXmppIbbDataIq; class QXmppIbbOpenIq; @@ -213,7 +213,7 @@ class QXmppTransferManager : public QObject Q_OBJECT public: - QXmppTransferManager(QXmppStream *stream, QObject *parent = 0); + QXmppTransferManager(QXmppOutgoingClient *stream, QObject *parent = 0); QXmppTransferJob *sendFile(const QString &jid, const QString &fileName, const QString &sid = QString()); QXmppTransferJob *sendFile(const QString &jid, QIODevice *device, const QXmppTransferFileInfo &fileInfo, const QString &sid = QString()); @@ -266,7 +266,7 @@ private: void socksServerSendOffer(QXmppTransferJob *job); // reference to XMPP stream (no ownership) - QXmppStream* m_stream; + QXmppOutgoingClient* m_stream; int m_ibbBlockSize; QList<QXmppTransferJob*> m_jobs; QString m_proxy; diff --git a/src/QXmppVCardManager.cpp b/src/QXmppVCardManager.cpp index 7a30f506..8a3cb1dd 100644 --- a/src/QXmppVCardManager.cpp +++ b/src/QXmppVCardManager.cpp @@ -23,10 +23,10 @@ #include "QXmppVCardManager.h" -#include "QXmppStream.h" +#include "QXmppOutgoingClient.h" #include "QXmppUtils.h" -QXmppVCardManager::QXmppVCardManager(QXmppStream* stream, QObject *parent) +QXmppVCardManager::QXmppVCardManager(QXmppOutgoingClient* stream, QObject *parent) : QObject(parent), m_stream(stream), m_isClientVCardReceived(false) diff --git a/src/QXmppVCardManager.h b/src/QXmppVCardManager.h index a7efca48..0216b6eb 100644 --- a/src/QXmppVCardManager.h +++ b/src/QXmppVCardManager.h @@ -29,7 +29,7 @@ #include "QXmppVCard.h" -class QXmppStream; +class QXmppOutgoingClient; /// \brief The QXmppVCardManager gets/sets XMPP vCards. It is an /// implentation of <B>XEP-0054: vcard-temp</B>. @@ -59,7 +59,7 @@ class QXmppVCardManager : public QObject Q_OBJECT public: - QXmppVCardManager(QXmppStream* stream, QObject *parent = 0); + QXmppVCardManager(QXmppOutgoingClient* stream, QObject *parent = 0); void requestVCard(const QString& bareJid = ""); const QXmppVCard& clientVCard() const; @@ -81,7 +81,7 @@ private slots: private: // reference to the xmpp stream (no ownership) - QXmppStream* m_stream; + QXmppOutgoingClient* m_stream; QXmppVCard m_clientVCard; ///< Stores the vCard of the connected client bool m_isClientVCardReceived; diff --git a/src/src.pro b/src/src.pro index c850b896..04743abc 100644 --- a/src/src.pro +++ b/src/src.pro @@ -40,6 +40,7 @@ HEADERS += QXmppUtils.h \ QXmppMucIq.h \ QXmppMucManager.h \ QXmppNonSASLAuth.h \ + QXmppOutgoingClient.h \ QXmppPacket.h \ QXmppPingIq.h \ QXmppPresence.h \ @@ -84,6 +85,7 @@ SOURCES += QXmppUtils.cpp \ QXmppMucIq.cpp \ QXmppMucManager.cpp \ QXmppNonSASLAuth.cpp \ + QXmppOutgoingClient.cpp \ QXmppPacket.cpp \ QXmppPingIq.cpp \ QXmppPresence.cpp \ |
