/* 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 #include #include #include #include #include #include #include #ifndef QT_NO_OPENSSL #include #include #endif // QT_NO_OPENSSL #include #include 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 capabilityMessageReceived(\ref IrcCapabilityMessage* message) \li void errorMessageReceived(\ref IrcErrorMessage* message) \li void inviteMessageReceived(\ref IrcInviteMessage* message) \li void joinMessageReceived(\ref IrcJoinMessage* message) \li void kickMessageReceived(\ref IrcKickMessage* message) \li void modeMessageReceived(\ref IrcModeMessage* message) \li void namesMessageReceived(\ref IrcNamesMessage* message) \li void nickMessageReceived(\ref IrcNickMessage* message) \li void noticeMessageReceived(\ref IrcNoticeMessage* message) \li void numericMessageReceived(\ref IrcNumericMessage* message) \li void motdMessageReceived(\ref IrcMotdMessage* message) \li void partMessageReceived(\ref IrcPartMessage* message) \li void pingMessageReceived(\ref IrcPingMessage* message) \li void pongMessageReceived(\ref IrcPongMessage* message) \li void privateMessageReceived(\ref IrcPrivateMessage* message) \li void quitMessageReceived(\ref IrcQuitMessage* message) \li void topicMessageReceived(\ref IrcTopicMessage* message) */ #ifndef IRC_DOXYGEN template 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(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& 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(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(msg)); break; case IrcMessage::Quit: emit q->quitMessageReceived(static_cast(msg)); break; case IrcMessage::Join: emit q->joinMessageReceived(static_cast(msg)); break; case IrcMessage::Part: emit q->partMessageReceived(static_cast(msg)); break; case IrcMessage::Topic: emit q->topicMessageReceived(static_cast(msg)); break; case IrcMessage::WhoReply: emit q->whoReplyMessageReceived(static_cast(msg)); break; case IrcMessage::Invite: emit q->inviteMessageReceived(static_cast(msg)); break; case IrcMessage::Kick: emit q->kickMessageReceived(static_cast(msg)); break; case IrcMessage::Mode: emit q->modeMessageReceived(static_cast(msg)); break; case IrcMessage::Private: emit q->privateMessageReceived(static_cast(msg)); break; case IrcMessage::Notice: emit q->noticeMessageReceived(static_cast(msg)); break; case IrcMessage::Ping: emit q->pingMessageReceived(static_cast(msg)); break; case IrcMessage::Pong: emit q->pongMessageReceived(static_cast(msg)); break; case IrcMessage::Error: emit q->errorMessageReceived(static_cast(msg)); break; case IrcMessage::Numeric: emit q->numericMessageReceived(static_cast(msg)); break; case IrcMessage::Capability: emit q->capabilityMessageReceived(static_cast(msg)); break; case IrcMessage::Motd: emit q->motdMessageReceived(static_cast(msg)); break; case IrcMessage::Names: emit q->namesMessageReceived(static_cast(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(); } 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 encoding() const \li void setEncoding(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 host() const \li void setHost(const QString& host) \par Notifier signal: \li void hostChanged(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 port() const \li void setPort(int port) \par Notifier signal: \li void portChanged(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 userName() const \li void setUserName(const QString& name) \par Notifier signal: \li void userNameChanged(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 nickName() const \li void setNickName(const QString& name) \par Notifier signal: \li void nickNameChanged(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 realName() const \li void setRealName(const QString& name) \par Notifier signal: \li void realNameChanged(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 password() const \li void setPassword(const QString& password) \par Notifier signal: \li void passwordChanged(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 displayName() const \li void setDisplayName(const QString& name) \par Notifier signal: \li void displayNameChanged(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 userData() const \li void setUserData(const QVariantMap& data) \par Notifier signal: \li void userDataChanged(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 status() const \par Notifier signal: \li void statusChanged(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 isActive() 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 isConnected() const \par Notifier signal: \li void connected() */ 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 isEnabled() const \li void setEnabled(bool enabled) [slot] \li void setDisabled(bool disabled) [slot] \par Notifier signal: \li void enabledChanged(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 reconnectDelay() const \li void setReconnectDelay(int seconds) \par Notifier signal: \li void reconnectDelayChanged(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* socket() const \li void setSocket(\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)), 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 isSecure() const \li void setSecure(bool secure) \par Notifier signal: \li void secureChanged(bool secure) \sa secureSupported, IrcConnection::socket */ bool IrcConnection::isSecure() const { #ifdef QT_NO_OPENSSL return false; #else return qobject_cast(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(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 isSecureSupported() \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 saslMechanism() const \li void setSaslMechanism(const QString& mechanism) \par Notifier signal: \li void saslMechanismChanged(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 supportedSaslMechanisms() \sa saslMechanism */ QStringList IrcConnection::supportedSaslMechanisms() { return QStringList() << QLatin1String("PLAIN"); } /*! This property holds the network information. \par Access function: \li IrcNetwork* network() 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(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(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(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(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(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* protocol() const \li void setProtocol(\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