diff options
| author | Markus Mittendrein <git@maxmitti.tk> | 2014-10-06 15:03:54 +0200 |
|---|---|---|
| committer | Markus Mittendrein <git@maxmitti.tk> | 2014-10-06 15:03:54 +0200 |
| commit | 529f38bd8878b6b1bea2b5457031ce936aab8d80 (patch) | |
| tree | 1193caefcad12f6a36f818048e4547e60add4398 /libcommuni/src/core/ircconnection.cpp | |
| parent | 3b58b5536935adff242928ed9f30e1c0262fbd7c (diff) | |
| download | manager-529f38bd8878b6b1bea2b5457031ce936aab8d80.tar.gz manager-529f38bd8878b6b1bea2b5457031ce936aab8d80.zip | |
addedd communi
Diffstat (limited to 'libcommuni/src/core/ircconnection.cpp')
| -rw-r--r-- | libcommuni/src/core/ircconnection.cpp | 1508 |
1 files changed, 1508 insertions, 0 deletions
diff --git a/libcommuni/src/core/ircconnection.cpp b/libcommuni/src/core/ircconnection.cpp new file mode 100644 index 0000000..837aa16 --- /dev/null +++ b/libcommuni/src/core/ircconnection.cpp @@ -0,0 +1,1508 @@ +/* + Copyright (C) 2008-2014 The Communi Project + + You may use this file under the terms of BSD license as follows: + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR + ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "ircconnection.h" +#include "ircconnection_p.h" +#include "ircnetwork_p.h" +#include "ircprotocol.h" +#include "ircnetwork.h" +#include "irccommand.h" +#include "ircmessage.h" +#include "ircfilter.h" +#include "irc.h" +#include <QLocale> +#include <QDateTime> +#include <QTcpSocket> +#include <QTextCodec> +#include <QStringList> +#include <QMetaObject> +#include <QMetaMethod> +#include <QMetaEnum> +#ifndef QT_NO_OPENSSL +#include <QSslSocket> +#include <QSslError> +#endif // QT_NO_OPENSSL +#include <QDataStream> +#include <QVariantMap> + +IRC_BEGIN_NAMESPACE + +/*! + \file ircconnection.h + \brief \#include <IrcConnection> + */ + +/*! + \class IrcConnection ircconnection.h IrcConnection + \ingroup core + \brief Provides means to establish a connection to an IRC server. + + \section connection-management Connection management + + Before \ref open() "opening" a connection, it must be first initialized + with \ref host, \ref userName, \ref nickName and \ref realName. + + The connection status may be queried at any time via status(). Also + \ref active "isActive()" and \ref connected "isConnected()" are provided + for convenience. In addition to the \ref status "statusChanged()" signal, + the most important statuses are informed via the following convenience + signals: + \li connecting() - + The connection is being established. + \li \ref connected "connected()" - + The IRC connection has been established, and the server is ready to receive commands. + \li disconnected() - + The connection has been lost. + + \section receiving-messages Receiving messages + + Whenever a message is received from the server, the messageReceived() + signal is emitted. Also message type specific signals are provided + for convenience. See messageReceived() and IrcMessage and its + subclasses for more details. + + \section sending-commands Sending commands + + Sending commands to a server is most conveniently done by creating + them via the various static \ref IrcCommand "IrcCommand::createXxx()" + methods and passing them to sendCommand(). Also sendData() is provided + for more low-level access. See IrcCommand for more details. + + \section example Example + + \code + IrcConnection* connection = new IrcConnection(this); + connect(connection, SIGNAL(messageReceived(IrcMessage*)), this, SLOT(onMessageReceived(IrcMessage*))); + connection->setHost("irc.server.com"); + connection->setUserName("me"); + connection->setNickName("myself"); + connection->setRealName("And I"); + connection->sendCommand(IrcCommand::createJoin("#mine")); + connection->open(); + \endcode + + \sa IrcNetwork, IrcMessage, IrcCommand + */ + +/*! + \enum IrcConnection::Status + This enum describes the connection status. + */ + +/*! + \var IrcConnection::Inactive + \brief The connection is inactive. + */ + +/*! + \var IrcConnection::Waiting + \brief The connection is waiting for a reconnect. + */ + +/*! + \var IrcConnection::Connecting + \brief The connection is being established. + */ + +/*! + \var IrcConnection::Connected + \brief The connection has been established. + */ + +/*! + \var IrcConnection::Closing + \brief The connection is being closed. + */ + +/*! + \var IrcConnection::Closed + \brief The connection has been closed. + */ + +/*! + \var IrcConnection::Error + \brief The connection has encountered an error. + */ + +/*! + \fn void IrcConnection::connecting() + + This signal is emitted when the connection is being established. + + The underlying \ref socket has connected, but the IRC handshake is + not yet finished and the server is not yet ready to receive commands. + */ + +/*! + \fn void IrcConnection::nickNameRequired(const QString& reserved, QString* alternate) + + This signal is emitted when the requested nick name is \a reserved + and an \a alternate nick name should be provided. + + An alternate nick name may be set via the provided argument, by changing + the \ref nickName property, or by sending a nick command directly. + + \sa IrcCommand::createNick(), Irc::ERR_NICKNAMEINUSE, Irc::ERR_NICKCOLLISION + */ + +/*! + \fn void IrcConnection::channelKeyRequired(const QString& channel, QString* key) + + This signal is emitted when joining a \a channel requires a \a key. + The key may be set via the provided argument, or by sending a new + join command directly. + + \sa IrcCommand::createJoin(), Irc::ERR_BADCHANNELKEY + */ + +/*! + \fn void IrcConnection::disconnected() + + This signal is emitted when the connection has been lost. + */ + +/*! + \fn void IrcConnection::socketError(QAbstractSocket::SocketError error) + + This signal is emitted when a \ref socket \a error occurs. + + \sa QAbstractSocket::error() + */ + +/*! + \fn void IrcConnection::socketStateChanged(QAbstractSocket::SocketState state) + + This signal is emitted when the \a state of the underlying \ref socket changes. + + \sa QAbstractSocket::stateChanged() + */ + +/*! + \since 3.2 + \fn void IrcConnection::secureError() + + This signal is emitted when SSL socket error occurs. + + Either QSslSocket::sslErrors() was emitted, or the remote host closed + the connection without QSslSocket::sslErrors() being emitted meaning + that the server is not SSL-enabled. + */ + +/*! + \fn void IrcConnection::messageReceived(IrcMessage* message) + + This signal is emitted when a \a message is received. + + In addition, message type specific signals are provided for convenience: + \li void <b>capabilityMessageReceived</b>(\ref IrcCapabilityMessage* message) + \li void <b>errorMessageReceived</b>(\ref IrcErrorMessage* message) + \li void <b>inviteMessageReceived</b>(\ref IrcInviteMessage* message) + \li void <b>joinMessageReceived</b>(\ref IrcJoinMessage* message) + \li void <b>kickMessageReceived</b>(\ref IrcKickMessage* message) + \li void <b>modeMessageReceived</b>(\ref IrcModeMessage* message) + \li void <b>namesMessageReceived</b>(\ref IrcNamesMessage* message) + \li void <b>nickMessageReceived</b>(\ref IrcNickMessage* message) + \li void <b>noticeMessageReceived</b>(\ref IrcNoticeMessage* message) + \li void <b>numericMessageReceived</b>(\ref IrcNumericMessage* message) + \li void <b>motdMessageReceived</b>(\ref IrcMotdMessage* message) + \li void <b>partMessageReceived</b>(\ref IrcPartMessage* message) + \li void <b>pingMessageReceived</b>(\ref IrcPingMessage* message) + \li void <b>pongMessageReceived</b>(\ref IrcPongMessage* message) + \li void <b>privateMessageReceived</b>(\ref IrcPrivateMessage* message) + \li void <b>quitMessageReceived</b>(\ref IrcQuitMessage* message) + \li void <b>topicMessageReceived</b>(\ref IrcTopicMessage* message) + */ + +#ifndef IRC_DOXYGEN +template<typename T> +static void irc_debug(IrcConnection* connection, const char* msg, const T& arg) +{ + static bool dbg = qgetenv("IRC_DEBUG").toInt(); + if (dbg) { + const QString desc = QString::fromLatin1("IrcConnection(%1)").arg(connection->displayName()); + qDebug() << qPrintable(desc) << msg << arg; + } +} + +IrcConnectionPrivate::IrcConnectionPrivate() : + q_ptr(0), + encoding("ISO-8859-15"), + network(0), + protocol(0), + socket(0), + host(), + port(6667), + userName(), + nickName(), + realName(), + enabled(true), + status(IrcConnection::Inactive), + sslErrors(false), + closed(false) +{ +} + +void IrcConnectionPrivate::init(IrcConnection* connection) +{ + q_ptr = connection; + network = IrcNetworkPrivate::create(connection); + connection->setSocket(new QTcpSocket(connection)); + connection->setProtocol(new IrcProtocol(connection)); + QObject::connect(&reconnecter, SIGNAL(timeout()), connection, SLOT(_irc_reconnect())); +} + +void IrcConnectionPrivate::_irc_connected() +{ + Q_Q(IrcConnection); + closed = false; + sslErrors = false; + emit q->connecting(); + if (q->isSecure()) + QMetaObject::invokeMethod(socket, "startClientEncryption"); + protocol->open(); +} + +void IrcConnectionPrivate::_irc_disconnected() +{ + Q_Q(IrcConnection); + protocol->close(); + emit q->disconnected(); + if (enabled && !sslErrors && (status != IrcConnection::Closed || !closed) && !reconnecter.isActive() && reconnecter.interval() > 0) { + reconnecter.start(); + setStatus(IrcConnection::Waiting); + } +} + +void IrcConnectionPrivate::_irc_error(QAbstractSocket::SocketError error) +{ + Q_Q(IrcConnection); + if (!closed && !sslErrors && error == QAbstractSocket::RemoteHostClosedError && q->isSecure()) { + irc_debug(q, "SSL error:", "no SSL available"); + setStatus(IrcConnection::Error); + sslErrors = true; + emit q->secureError(); + } else if (!closed || (error != QAbstractSocket::RemoteHostClosedError && error != QAbstractSocket::UnknownSocketError)) { + irc_debug(q, "socket error:", error); + emit q->socketError(error); + setStatus(IrcConnection::Error); + } +} + +void IrcConnectionPrivate::_irc_sslErrors() +{ + Q_Q(IrcConnection); + QStringList errors; +#ifndef QT_NO_OPENSSL + QSslSocket* ssl = qobject_cast<QSslSocket*>(socket); + if (ssl) { + foreach (const QSslError& error, ssl->sslErrors()) + errors += error.errorString(); + } +#endif + irc_debug(q, "SSL handshake errors:", errors); + sslErrors = true; + emit q->secureError(); +} + +void IrcConnectionPrivate::_irc_state(QAbstractSocket::SocketState state) +{ + Q_Q(IrcConnection); + switch (state) { + case QAbstractSocket::UnconnectedState: + if (closed) + setStatus(IrcConnection::Closed); + break; + case QAbstractSocket::ClosingState: + if (status != IrcConnection::Error) + setStatus(IrcConnection::Closing); + break; + case QAbstractSocket::HostLookupState: + case QAbstractSocket::ConnectingState: + case QAbstractSocket::ConnectedState: + default: + setStatus(IrcConnection::Connecting); + break; + } + emit q->socketStateChanged(state); +} + +void IrcConnectionPrivate::_irc_reconnect() +{ + Q_Q(IrcConnection); + if (!q->isActive()) { + reconnecter.stop(); + q->open(); + } +} + +void IrcConnectionPrivate::_irc_readData() +{ + protocol->read(); +} + +void IrcConnectionPrivate::_irc_filterDestroyed(QObject* filter) +{ + messageFilters.removeAll(filter); + commandFilters.removeAll(filter); +} + +void IrcConnectionPrivate::setNick(const QString& nick) +{ + Q_Q(IrcConnection); + if (nickName != nick) { + nickName = nick; + emit q->nickNameChanged(nick); + } +} + +void IrcConnectionPrivate::setStatus(IrcConnection::Status value) +{ + Q_Q(IrcConnection); + if (status != value) { + const bool wasConnected = q->isConnected(); + status = value; + emit q->statusChanged(value); + + if (!wasConnected && q->isConnected()) { + emit q->connected(); + foreach (IrcCommand* cmd, pendingCommands) + q->sendCommand(cmd); + pendingCommands.clear(); + } + irc_debug(q, "status:", status); + } +} + +void IrcConnectionPrivate::setInfo(const QHash<QString, QString>& info) +{ + Q_Q(IrcConnection); + const QString oldName = q->displayName(); + IrcNetworkPrivate* priv = IrcNetworkPrivate::get(network); + priv->setInfo(info); + const QString newName = q->displayName(); + if (oldName != newName) + emit q->displayNameChanged(newName); +} + +void IrcConnectionPrivate::receiveMessage(IrcMessage* msg) +{ + Q_Q(IrcConnection); + bool filtered = false; + for (int i = messageFilters.count() - 1; !filtered && i >= 0; --i) { + IrcMessageFilter* filter = qobject_cast<IrcMessageFilter*>(messageFilters.at(i)); + if (filter) + filtered |= filter->messageFilter(msg); + } + + if (!filtered) { + emit q->messageReceived(msg); + + switch (msg->type()) { + case IrcMessage::Nick: + emit q->nickMessageReceived(static_cast<IrcNickMessage*>(msg)); + break; + case IrcMessage::Quit: + emit q->quitMessageReceived(static_cast<IrcQuitMessage*>(msg)); + break; + case IrcMessage::Join: + emit q->joinMessageReceived(static_cast<IrcJoinMessage*>(msg)); + break; + case IrcMessage::Part: + emit q->partMessageReceived(static_cast<IrcPartMessage*>(msg)); + break; + case IrcMessage::Topic: + emit q->topicMessageReceived(static_cast<IrcTopicMessage*>(msg)); + break; + case IrcMessage::WhoReply: + emit q->whoReplyMessageReceived(static_cast<IrcWhoReplyMessage*>(msg)); + break; + case IrcMessage::Invite: + emit q->inviteMessageReceived(static_cast<IrcInviteMessage*>(msg)); + break; + case IrcMessage::Kick: + emit q->kickMessageReceived(static_cast<IrcKickMessage*>(msg)); + break; + case IrcMessage::Mode: + emit q->modeMessageReceived(static_cast<IrcModeMessage*>(msg)); + break; + case IrcMessage::Private: + emit q->privateMessageReceived(static_cast<IrcPrivateMessage*>(msg)); + break; + case IrcMessage::Notice: + emit q->noticeMessageReceived(static_cast<IrcNoticeMessage*>(msg)); + break; + case IrcMessage::Ping: + emit q->pingMessageReceived(static_cast<IrcPingMessage*>(msg)); + break; + case IrcMessage::Pong: + emit q->pongMessageReceived(static_cast<IrcPongMessage*>(msg)); + break; + case IrcMessage::Error: + emit q->errorMessageReceived(static_cast<IrcErrorMessage*>(msg)); + break; + case IrcMessage::Numeric: + emit q->numericMessageReceived(static_cast<IrcNumericMessage*>(msg)); + break; + case IrcMessage::Capability: + emit q->capabilityMessageReceived(static_cast<IrcCapabilityMessage*>(msg)); + break; + case IrcMessage::Motd: + emit q->motdMessageReceived(static_cast<IrcMotdMessage*>(msg)); + break; + case IrcMessage::Names: + emit q->namesMessageReceived(static_cast<IrcNamesMessage*>(msg)); + break; + case IrcMessage::Unknown: + default: + break; + } + } + msg->deleteLater(); +} + +IrcCommand* IrcConnectionPrivate::createCtcpReply(IrcPrivateMessage* request) +{ + Q_Q(IrcConnection); + IrcCommand* reply = 0; + const QMetaObject* metaObject = q->metaObject(); + int idx = metaObject->indexOfMethod("createCtcpReply(QVariant)"); + if (idx != -1) { + // QML: QVariant createCtcpReply(QVariant) + QVariant ret; + QMetaMethod method = metaObject->method(idx); + method.invoke(q, Q_RETURN_ARG(QVariant, ret), Q_ARG(QVariant, QVariant::fromValue(request))); + reply = ret.value<IrcCommand*>(); + } else { + // C++: IrcCommand* createCtcpReply(IrcPrivateMessage*) + idx = metaObject->indexOfMethod("createCtcpReply(IrcPrivateMessage*)"); + QMetaMethod method = metaObject->method(idx); + method.invoke(q, Q_RETURN_ARG(IrcCommand*, reply), Q_ARG(IrcPrivateMessage*, request)); + } + return reply; +} +#endif // IRC_DOXYGEN + +/*! + Constructs a new IRC connection with \a parent. + */ +IrcConnection::IrcConnection(QObject* parent) : QObject(parent), d_ptr(new IrcConnectionPrivate) +{ + Q_D(IrcConnection); + d->init(this); +} + +/*! + Constructs a new IRC connection with \a host and \a parent. + */ +IrcConnection::IrcConnection(const QString& host, QObject* parent) : QObject(parent), d_ptr(new IrcConnectionPrivate) +{ + Q_D(IrcConnection); + d->init(this); + setHost(host); +} + +/*! + Destructs the IRC connection. + */ +IrcConnection::~IrcConnection() +{ + close(); + emit destroyed(this); +} + +/*! + This property holds the FALLBACK encoding for received messages. + + The fallback encoding is used when the message is detected not + to be valid \c UTF-8 and the consequent auto-detection of message + encoding fails. See QTextCodec::availableCodecs() for the list of + supported encodings. + + The default value is \c ISO-8859-15. + + \par Access functions: + \li QByteArray <b>encoding</b>() const + \li void <b>setEncoding</b>(const QByteArray& encoding) + + \sa QTextCodec::availableCodecs(), QTextCodec::codecForLocale() + */ +QByteArray IrcConnection::encoding() const +{ + Q_D(const IrcConnection); + return d->encoding; +} + +void IrcConnection::setEncoding(const QByteArray& encoding) +{ + Q_D(IrcConnection); + extern bool irc_is_supported_encoding(const QByteArray& encoding); // ircmessagedecoder.cpp + if (!irc_is_supported_encoding(encoding)) { + qWarning() << "IrcConnection::setEncoding(): unsupported encoding" << encoding; + return; + } + d->encoding = encoding; +} + +/*! + This property holds the server host. + + \par Access functions: + \li QString <b>host</b>() const + \li void <b>setHost</b>(const QString& host) + + \par Notifier signal: + \li void <b>hostChanged</b>(const QString& host) + */ +QString IrcConnection::host() const +{ + Q_D(const IrcConnection); + return d->host; +} + +void IrcConnection::setHost(const QString& host) +{ + Q_D(IrcConnection); + if (d->host != host) { + if (isActive()) + qWarning("IrcConnection::setHost() has no effect until re-connect"); + const QString oldName = displayName(); + d->host = host; + emit hostChanged(host); + const QString newName = displayName(); + if (oldName != newName) + emit displayNameChanged(newName); + } +} + +/*! + This property holds the server port. + + The default value is \c 6667. + + \par Access functions: + \li int <b>port</b>() const + \li void <b>setPort</b>(int port) + + \par Notifier signal: + \li void <b>portChanged</b>(int port) + */ +int IrcConnection::port() const +{ + Q_D(const IrcConnection); + return d->port; +} + +void IrcConnection::setPort(int port) +{ + Q_D(IrcConnection); + if (d->port != port) { + if (isActive()) + qWarning("IrcConnection::setPort() has no effect until re-connect"); + d->port = port; + emit portChanged(port); + } +} + +/*! + This property holds the user name. + + \note Changing the user name has no effect until the connection is re-established. + + \par Access functions: + \li QString <b>userName</b>() const + \li void <b>setUserName</b>(const QString& name) + + \par Notifier signal: + \li void <b>userNameChanged</b>(const QString& name) + */ +QString IrcConnection::userName() const +{ + Q_D(const IrcConnection); + return d->userName; +} + +void IrcConnection::setUserName(const QString& name) +{ + Q_D(IrcConnection); + QString user = name.split(" ", QString::SkipEmptyParts).value(0).trimmed(); + if (d->userName != user) { + if (isActive()) + qWarning("IrcConnection::setUserName() has no effect until re-connect"); + d->userName = user; + emit userNameChanged(user); + } +} + +/*! + This property holds the nick name. + + \par Access functions: + \li QString <b>nickName</b>() const + \li void <b>setNickName</b>(const QString& name) + + \par Notifier signal: + \li void <b>nickNameChanged</b>(const QString& name) + */ +QString IrcConnection::nickName() const +{ + Q_D(const IrcConnection); + return d->nickName; +} + +void IrcConnection::setNickName(const QString& name) +{ + Q_D(IrcConnection); + QString nick = name.split(" ", QString::SkipEmptyParts).value(0).trimmed(); + if (d->nickName != nick) { + if (isActive()) + sendCommand(IrcCommand::createNick(nick)); + else + d->setNick(nick); + } +} + +/*! + This property holds the real name. + + \note Changing the real name has no effect until the connection is re-established. + + \par Access functions: + \li QString <b>realName</b>() const + \li void <b>setRealName</b>(const QString& name) + + \par Notifier signal: + \li void <b>realNameChanged</b>(const QString& name) + */ +QString IrcConnection::realName() const +{ + Q_D(const IrcConnection); + return d->realName; +} + +void IrcConnection::setRealName(const QString& name) +{ + Q_D(IrcConnection); + if (d->realName != name) { + if (isActive()) + qWarning("IrcConnection::setRealName() has no effect until re-connect"); + d->realName = name; + emit realNameChanged(name); + } +} + +/*! + This property holds the password. + + \par Access functions: + \li QString <b>password</b>() const + \li void <b>setPassword</b>(const QString& password) + + \par Notifier signal: + \li void <b>passwordChanged</b>(const QString& password) + */ +QString IrcConnection::password() const +{ + Q_D(const IrcConnection); + return d->password; +} + +void IrcConnection::setPassword(const QString& password) +{ + Q_D(IrcConnection); + if (d->password != password) { + if (isActive()) + qWarning("IrcConnection::setPassword() has no effect until re-connect"); + d->password = password; + emit passwordChanged(password); + } +} + +/*! + This property holds the display name. + + Unless explicitly set, display name resolves to IrcNetwork::name + or IrcConnection::host while the former is not known. + + \par Access functions: + \li QString <b>displayName</b>() const + \li void <b>setDisplayName</b>(const QString& name) + + \par Notifier signal: + \li void <b>displayNameChanged</b>(const QString& name) + */ +QString IrcConnection::displayName() const +{ + Q_D(const IrcConnection); + QString name = d->displayName; + if (name.isEmpty()) + name = d->network->name(); + if (name.isEmpty()) + name = d->host; + return name; +} + +void IrcConnection::setDisplayName(const QString& name) +{ + Q_D(IrcConnection); + if (d->displayName != name) { + d->displayName = name; + emit displayNameChanged(name); + } +} + +/*! + \since 3.1 + + This property holds arbitrary user data. + + \par Access functions: + \li QVariantMap <b>userData</b>() const + \li void <b>setUserData</b>(const QVariantMap& data) + + \par Notifier signal: + \li void <b>userDataChanged</b>(const QVariantMap& data) + */ +QVariantMap IrcConnection::userData() const +{ + Q_D(const IrcConnection); + return d->userData; +} + +void IrcConnection::setUserData(const QVariantMap& data) +{ + Q_D(IrcConnection); + if (d->userData != data) { + d->userData = data; + emit userDataChanged(data); + } +} + +/*! + \property Status IrcConnection::status + This property holds the connection status. + + \par Access function: + \li Status <b>status</b>() const + + \par Notifier signal: + \li void <b>statusChanged</b>(Status status) + */ +IrcConnection::Status IrcConnection::status() const +{ + Q_D(const IrcConnection); + return d->status; +} + +/*! + \property bool IrcConnection::active + This property holds whether the connection is active. + + The connection is considered active when its either connecting, connected or closing. + + \par Access function: + \li bool <b>isActive</b>() const + */ +bool IrcConnection::isActive() const +{ + Q_D(const IrcConnection); + return d->status == Connecting || d->status == Connected || d->status == Closing; +} + +/*! + \property bool IrcConnection::connected + This property holds whether the connection has been established. + + The connection has been established when the welcome message + has been received and the server is ready to receive commands. + + \sa Irc::RPL_WELCOME + + \par Access function: + \li bool <b>isConnected</b>() const + + \par Notifier signal: + \li void <b>connected</b>() + */ +bool IrcConnection::isConnected() const +{ + Q_D(const IrcConnection); + return d->status == Connected; +} + +/*! + \property bool IrcConnection::enabled + This property holds whether the connection is enabled. + + The default value is \c true. + + When set to \c false, a disabled connection does nothing when open() is called. + + \par Access functions: + \li bool <b>isEnabled</b>() const + \li void <b>setEnabled</b>(bool enabled) [slot] + \li void <b>setDisabled</b>(bool disabled) [slot] + + \par Notifier signal: + \li void <b>enabledChanged</b>(bool enabled) + */ +bool IrcConnection::isEnabled() const +{ + Q_D(const IrcConnection); + return d->enabled; +} + +void IrcConnection::setEnabled(bool enabled) +{ + Q_D(IrcConnection); + if (d->enabled != enabled) { + d->enabled = enabled; + emit enabledChanged(enabled); + } +} + +void IrcConnection::setDisabled(bool disabled) +{ + setEnabled(!disabled); +} + +/*! + \property int IrcConnection::reconnectDelay + This property holds the reconnect delay in seconds. + + A positive (greater than zero) value enables automatic reconnect. + When the connection is lost due to a socket error, IrcConnection + will automatically attempt to reconnect after the specified delay. + + The default value is \c 0 (automatic reconnect disabled). + + \par Access functions: + \li int <b>reconnectDelay</b>() const + \li void <b>setReconnectDelay</b>(int seconds) + + \par Notifier signal: + \li void <b>reconnectDelayChanged</b>(int seconds) + */ +int IrcConnection::reconnectDelay() const +{ + Q_D(const IrcConnection); + return d->reconnecter.interval() / 1000; +} + +void IrcConnection::setReconnectDelay(int seconds) +{ + Q_D(IrcConnection); + const int interval = qMax(0, seconds) * 1000; + if (d->reconnecter.interval() != interval) { + d->reconnecter.setInterval(interval); + emit reconnectDelayChanged(interval); + } +} + +/*! + This property holds the socket. The default value is an instance of QTcpSocket. + + The previously set socket is deleted if its parent is \c this. + + \note IrcConnection supports QSslSocket in the way that it automatically + calls QSslSocket::startClientEncryption() while connecting. + + \par Access functions: + \li \ref QAbstractSocket* <b>socket</b>() const + \li void <b>setSocket</b>(\ref QAbstractSocket* socket) + + \sa IrcConnection::secure + */ +QAbstractSocket* IrcConnection::socket() const +{ + Q_D(const IrcConnection); + return d->socket; +} + +void IrcConnection::setSocket(QAbstractSocket* socket) +{ + Q_D(IrcConnection); + if (d->socket != socket) { + if (d->socket) { + d->socket->disconnect(this); + if (d->socket->parent() == this) + d->socket->deleteLater(); + } + + d->socket = socket; + if (socket) { + connect(socket, SIGNAL(connected()), this, SLOT(_irc_connected())); + connect(socket, SIGNAL(disconnected()), this, SLOT(_irc_disconnected())); + connect(socket, SIGNAL(readyRead()), this, SLOT(_irc_readData())); + connect(socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(_irc_error(QAbstractSocket::SocketError))); + connect(socket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), this, SLOT(_irc_state(QAbstractSocket::SocketState))); + if (isSecure()) + connect(socket, SIGNAL(sslErrors(QList<QSslError>)), this, SLOT(_irc_sslErrors())); + } + } +} + +/*! + \property bool IrcConnection::secure + This property holds whether the socket is an SSL socket. + + This property is provided for convenience. Calling + \code + connection->setSecure(true); + \endcode + + is equivalent to: + + \code + QSslSocket* socket = new QSslSocket(socket); + socket->setPeerVerifyMode(QSslSocket::QueryPeer); + connection->setSocket(socket); + \endcode + + \note IrcConnection does not handle SSL errors, see + QSslSocket::sslErrors() for more details on the subject. + + \par Access functions: + \li bool <b>isSecure</b>() const + \li void <b>setSecure</b>(bool secure) + + \par Notifier signal: + \li void <b>secureChanged</b>(bool secure) + + \sa secureSupported, IrcConnection::socket + */ +bool IrcConnection::isSecure() const +{ +#ifdef QT_NO_OPENSSL + return false; +#else + return qobject_cast<QSslSocket*>(socket()); +#endif // QT_NO_OPENSSL +} + +void IrcConnection::setSecure(bool secure) +{ +#ifdef QT_NO_OPENSSL + if (secure) { + qWarning("IrcConnection::setSecure(): the Qt build does not support SSL"); + return; + } +#else + if (secure && !QSslSocket::supportsSsl()) { + qWarning("IrcConnection::setSecure(): the platform does not support SSL - try installing OpenSSL"); + return; + } + + QSslSocket* sslSocket = qobject_cast<QSslSocket*>(socket()); + if (secure && !sslSocket) { + sslSocket = new QSslSocket(this); + sslSocket->setPeerVerifyMode(QSslSocket::QueryPeer); + setSocket(sslSocket); + emit secureChanged(true); + } else if (!secure && sslSocket) { + setSocket(new QTcpSocket(this)); + emit secureChanged(false); + } +#endif // !QT_NO_OPENSSL +} + +/*! + \since 3.2 + \property bool IrcConnection::secureSupported + This property holds whether SSL is supported. + + The value may be \c false for the following reasons: + \li Qt was built without SSL support (\c QT_NO_SSL is defined), or + \li The platform does not support SSL (QSslSocket::supportsSsl() returns \c false). + + \par Access function: + \li static bool <b>isSecureSupported</b>() + + \sa secure, QSslSocket::supportsSsl() + */ + +bool IrcConnection::isSecureSupported() +{ +#ifdef QT_NO_OPENSSL + return false; +#else + return QSslSocket::supportsSsl(); +#endif +} + +/*! + This property holds the used SASL (Simple Authentication and Security Layer) mechanism. + + \par Access functions: + \li QString <b>saslMechanism</b>() const + \li void <b>setSaslMechanism</b>(const QString& mechanism) + + \par Notifier signal: + \li void <b>saslMechanismChanged</b>(const QString& mechanism) + + \sa supportedSaslMechanisms + */ +QString IrcConnection::saslMechanism() const +{ + Q_D(const IrcConnection); + return d->saslMechanism; +} + +void IrcConnection::setSaslMechanism(const QString& mechanism) +{ + Q_D(IrcConnection); + if (!mechanism.isEmpty() && !supportedSaslMechanisms().contains(mechanism.toUpper())) { + qWarning("IrcConnection::setSaslMechanism(): unsupported mechanism: '%s'", qPrintable(mechanism)); + return; + } + if (d->saslMechanism != mechanism) { + if (isActive()) + qWarning("IrcConnection::setSaslMechanism() has no effect until re-connect"); + d->saslMechanism = mechanism.toUpper(); + emit saslMechanismChanged(mechanism); + } +} + +/*! + This property holds the list of supported SASL (Simple Authentication and Security Layer) mechanisms. + + \par Access function: + \li static QStringList <b>supportedSaslMechanisms</b>() + + \sa saslMechanism + */ +QStringList IrcConnection::supportedSaslMechanisms() +{ + return QStringList() << QLatin1String("PLAIN"); +} + +/*! + This property holds the network information. + + \par Access function: + \li IrcNetwork* <b>network</b>() const + */ +IrcNetwork* IrcConnection::network() const +{ + Q_D(const IrcConnection); + return d->network; +} + +/*! + Opens a connection to the server. + + The function does nothing when the connection is already \ref active + or explicitly \ref enabled "disabled". + + \note The function merely outputs a warnings and returns immediately if + either \ref host, \ref userName, \ref nickName or \ref realName is empty. + */ +void IrcConnection::open() +{ + Q_D(IrcConnection); + if (d->host.isEmpty()) { + qWarning("IrcConnection::open(): host is empty!"); + return; + } + if (d->userName.isEmpty()) { + qWarning("IrcConnection::open(): userName is empty!"); + return; + } + if (d->nickName.isEmpty()) { + qWarning("IrcConnection::open(): nickName is empty!"); + return; + } + if (d->realName.isEmpty()) { + qWarning("IrcConnection::open(): realName is empty!"); + return; + } + if (d->enabled && d->socket && !isActive()) + d->socket->connectToHost(d->host, d->port); +} + +/*! + Immediately closes the connection to the server. + + Calling close() makes the connection close immediately and thus might lead to + "remote host closed the connection". In order to quit gracefully, call quit() + first. This function attempts to flush the underlying socket, but this does + not guarantee that the server ever receives the QUIT command if the connection + is closed immediately after sending the command. In order to ensure a graceful + quit, let the server handle closing the connection. + + C++ example: + \code + connection->quit(reason); + QTimer::singleShot(timeout, connection, SLOT(deleteLater())); + \endcode + + QML example: + \code + connection.quit(reason); + connection.destroy(timeout); + \endcode + + \sa quit() + */ +void IrcConnection::close() +{ + Q_D(IrcConnection); + if (d->socket) { + d->closed = true; + d->socket->flush(); + d->socket->abort(); + d->socket->disconnectFromHost(); + if (d->socket->state() == QAbstractSocket::UnconnectedState) + d->setStatus(Closed); + d->reconnecter.stop(); + } +} + +/*! + Sends a quit command with an optionally specified \a reason. + + This method is provided for convenience. It is equal to: + \code + IrcCommand* command = IrcCommand::createQuit(reason); + connection->sendCommand(command); + \endcode + + \sa IrcCommand::createQuit() + */ +void IrcConnection::quit(const QString& reason) +{ + sendCommand(IrcCommand::createQuit(reason)); +} + +/*! + Sends a \a command to the server. + + If the connection is not active, the \a command is queued and sent + later when the connection has been established. + + \note If the command has a valid parent, it is an indication that + the caller of this method is be responsible for freeing the command. + If the command does not have a valid parent (like the commands + created via various IrcCommand::createXxx() methods) the connection + will take ownership of the command and delete it once it has been + sent. Thus, the command must have been allocated on the heap and + it is not safe to access the command after it has been sent. + + \sa sendData() + */ +bool IrcConnection::sendCommand(IrcCommand* command) +{ + Q_D(IrcConnection); + bool res = false; + if (command) { + bool filtered = false; + for (int i = d->commandFilters.count() - 1; !filtered && i >= 0; --i) { + QObject* filter = d->commandFilters.at(i); + IrcCommandFilter* commandFilter = qobject_cast<IrcCommandFilter*>(filter); + if (commandFilter && !d->activeCommandFilters.contains(filter)) { + d->activeCommandFilters.push(filter); + filtered |= commandFilter->commandFilter(command); + d->activeCommandFilters.pop(); + } + } + if (filtered) + return false; + + if (isActive()) { + QTextCodec* codec = QTextCodec::codecForName(command->encoding()); + Q_ASSERT(codec); + res = sendData(codec->fromUnicode(command->toString())); + if (!command->parent()) + command->deleteLater(); + } else { + Q_D(IrcConnection); + d->pendingCommands += command; + } + } + return res; +} + +/*! + Sends raw \a data to the server. + + \sa sendCommand() + */ +bool IrcConnection::sendData(const QByteArray& data) +{ + Q_D(IrcConnection); + if (d->socket) { + static bool dbg = qgetenv("IRC_DEBUG").toInt(); + if (dbg) qDebug() << "->" << data; + if (!d->closed && data.length() >= 4) { + const QByteArray cmd = data.left(5).toUpper(); + if (cmd.startsWith("QUIT") && (data.length() == 4 || QChar(data.at(4)).isSpace())) + d->closed = true; + } + return d->protocol->write(data); + } + return false; +} + +/*! + Sends raw \a message to the server using UTF-8 encoding. + + \sa sendData(), sendCommand() + */ +bool IrcConnection::sendRaw(const QString& message) +{ + return sendData(message.toUtf8()); +} + +/*! + Installs a message \a filter on the connection. The \a filter must implement the IrcMessageFilter interface. + + A message filter receives all messages that are sent to the connection. The filter + receives messages via the \ref IrcMessageFilter::messageFilter() "messageFilter()" + function. The function must return \c true if the message should be filtered, + (i.e. stopped); otherwise it must return \c false. + + If multiple message filters are installed on the same connection, the filter + that was installed last is activated first. + + \sa removeMessageFilter() + */ +void IrcConnection::installMessageFilter(QObject* filter) +{ + Q_D(IrcConnection); + IrcMessageFilter* msgFilter = qobject_cast<IrcMessageFilter*>(filter); + if (msgFilter) { + d->messageFilters += filter; + connect(filter, SIGNAL(destroyed(QObject*)), this, SLOT(_irc_filterDestroyed(QObject*)), Qt::UniqueConnection); + } +} + +/*! + Removes a message \a filter from the connection. + + The request is ignored if such message filter has not been installed. + All message filters for a connection are automatically removed + when the connection is destroyed. + + \sa installMessageFilter() + */ +void IrcConnection::removeMessageFilter(QObject* filter) +{ + Q_D(IrcConnection); + IrcMessageFilter* msgFilter = qobject_cast<IrcMessageFilter*>(filter); + if (msgFilter) { + d->messageFilters.removeAll(filter); + disconnect(filter, SIGNAL(destroyed(QObject*)), this, SLOT(_irc_filterDestroyed(QObject*))); + } +} + +/*! + Installs a command \a filter on the connection. The \a filter must implement the IrcCommandFilter interface. + + A command filter receives all commands that are sent from the connection. The filter + receives commands via the \ref IrcCommandFilter::commandFilter() "commandFilter()" + function. The function must return \c true if the command should be filtered, + (i.e. stopped); otherwise it must return \c false. + + If multiple command filters are installed on the same connection, the filter + that was installed last is activated first. + + \sa removeCommandFilter() + */ +void IrcConnection::installCommandFilter(QObject* filter) +{ + Q_D(IrcConnection); + IrcCommandFilter* cmdFilter = qobject_cast<IrcCommandFilter*>(filter); + if (cmdFilter) { + d->commandFilters += filter; + connect(filter, SIGNAL(destroyed(QObject*)), this, SLOT(_irc_filterDestroyed(QObject*)), Qt::UniqueConnection); + } +} + +/*! + Removes a command \a filter from the connection. + + The request is ignored if such command filter has not been installed. + All command filters for a connection are automatically removed when + the connection is destroyed. + + \sa installCommandFilter() + */ +void IrcConnection::removeCommandFilter(QObject* filter) +{ + Q_D(IrcConnection); + IrcCommandFilter* cmdFilter = qobject_cast<IrcCommandFilter*>(filter); + if (cmdFilter) { + d->commandFilters.removeAll(filter); + disconnect(filter, SIGNAL(destroyed(QObject*)), this, SLOT(_irc_filterDestroyed(QObject*))); + } +} + +/*! + \since 3.1 + + Saves the state of the connection. The \a version number is stored as part of the state data. + + To restore the saved state, pass the return value and \a version number to restoreState(). + */ +QByteArray IrcConnection::saveState(int version) const +{ + QVariantMap args; + args.insert("version", version); + args.insert("host", host()); + args.insert("port", port()); + args.insert("userName", userName()); + args.insert("nickName", nickName()); + args.insert("realName", realName()); + args.insert("password", password()); + args.insert("displayName", displayName()); + args.insert("userData", userData()); + args.insert("encoding", encoding()); + args.insert("enabled", isEnabled()); + args.insert("reconnectDelay", reconnectDelay()); + args.insert("secure", isSecure()); + args.insert("saslMechanism", saslMechanism()); + + QByteArray state; + QDataStream out(&state, QIODevice::WriteOnly); + out << args; + return state; +} + +/*! + \since 3.1 + + Restores the \a state of the connection. The \a version number is compared with that stored in \a state. + If they do not match, the connection state is left unchanged, and this function returns \c false; otherwise, + the state is restored, and \c true is returned. + + \sa saveState() + */ +bool IrcConnection::restoreState(const QByteArray& state, int version) +{ + if (isActive()) + return false; + + QVariantMap args; + QDataStream in(state); + in >> args; + if (in.status() != QDataStream::Ok || args.value("version", -1).toInt() != version) + return false; + + setHost(args.value("host", host()).toString()); + setPort(args.value("port", port()).toInt()); + setUserName(args.value("userName", userName()).toString()); + setNickName(args.value("nickName", nickName()).toString()); + setRealName(args.value("realName", realName()).toString()); + setPassword(args.value("password", password()).toString()); + setDisplayName(args.value("displayName").toString()); + setUserData(args.value("userData", userData()).toMap()); + setEncoding(args.value("encoding", encoding()).toByteArray()); + setEnabled(args.value("enabled", isEnabled()).toBool()); + setReconnectDelay(args.value("reconnectDelay", reconnectDelay()).toInt()); + setSecure(args.value("secure", isSecure()).toBool()); + setSaslMechanism(args.value("saslMechanism", saslMechanism()).toString()); + return true; +} + +/*! + Creates a reply command for the CTCP \a request. + + The default implementation handles the following CTCP requests: CLIENTINFO, PING, SOURCE, TIME and VERSION. + + Reimplement this function in order to alter or omit the default replies. + */ +IrcCommand* IrcConnection::createCtcpReply(IrcPrivateMessage* request) const +{ + QString reply; + QString type = request->content().split(" ", QString::SkipEmptyParts).value(0).toUpper(); + if (type == "PING") + reply = request->content(); + else if (type == "TIME") + reply = QLatin1String("TIME ") + QLocale().toString(QDateTime::currentDateTime(), QLocale::ShortFormat); + else if (type == "VERSION") + reply = QLatin1String("VERSION Using libcommuni ") + Irc::version() + QLatin1String(" - http://communi.github.com"); + else if (type == "SOURCE") + reply = QLatin1String("SOURCE http://communi.github.com"); + else if (type == "CLIENTINFO") + reply = QLatin1String("CLIENTINFO PING SOURCE TIME VERSION"); + if (!reply.isEmpty()) + return IrcCommand::createCtcpReply(request->nick(), reply); + return 0; +} + +/*! + \since 3.2 + + This property holds the protocol. + + The previously set protocol is deleted if its parent is \c this. + + \par Access functions: + \li \ref IrcProtocol* <b>protocol</b>() const + \li void <b>setProtocol</b>(\ref IrcProtocol* protocol) + */ +IrcProtocol* IrcConnection::protocol() const +{ + Q_D(const IrcConnection); + return d->protocol; +} + +void IrcConnection::setProtocol(IrcProtocol* proto) +{ + Q_D(IrcConnection); + if (d->protocol != proto) { + if (d->protocol && d->protocol->parent() == this) + delete d->protocol; + d->protocol = proto; + } +} + +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug debug, IrcConnection::Status status) +{ + const int index = IrcConnection::staticMetaObject.indexOfEnumerator("Status"); + QMetaEnum enumerator = IrcConnection::staticMetaObject.enumerator(index); + const char* key = enumerator.valueToKey(status); + debug << (key ? key : "Unknown"); + return debug; +} + +QDebug operator<<(QDebug debug, const IrcConnection* connection) +{ + if (!connection) + return debug << "IrcConnection(0x0) "; + debug.nospace() << connection->metaObject()->className() << '(' << (void*) connection; + if (!connection->displayName().isEmpty()) + debug.nospace() << ", " << qPrintable(connection->displayName()); + debug.nospace() << ')'; + return debug.space(); +} +#endif // QT_NO_DEBUG_STREAM + +#include "moc_ircconnection.cpp" + +IRC_END_NAMESPACE |
