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 | |
| parent | 3b58b5536935adff242928ed9f30e1c0262fbd7c (diff) | |
| download | manager-529f38bd8878b6b1bea2b5457031ce936aab8d80.tar.gz manager-529f38bd8878b6b1bea2b5457031ce936aab8d80.zip | |
addedd communi
Diffstat (limited to 'libcommuni/src/core')
| -rw-r--r-- | libcommuni/src/core/core.pri | 65 | ||||
| -rw-r--r-- | libcommuni/src/core/core.pro | 8 | ||||
| -rw-r--r-- | libcommuni/src/core/irc.cpp | 172 | ||||
| -rw-r--r-- | libcommuni/src/core/irccommand.cpp | 851 | ||||
| -rw-r--r-- | libcommuni/src/core/ircconnection.cpp | 1508 | ||||
| -rw-r--r-- | libcommuni/src/core/irccore.cpp | 90 | ||||
| -rw-r--r-- | libcommuni/src/core/ircfilter.cpp | 248 | ||||
| -rw-r--r-- | libcommuni/src/core/ircmessage.cpp | 1604 | ||||
| -rw-r--r-- | libcommuni/src/core/ircmessage_p.cpp | 284 | ||||
| -rw-r--r-- | libcommuni/src/core/ircmessagebuilder.cpp | 121 | ||||
| -rw-r--r-- | libcommuni/src/core/ircmessagedecoder.cpp | 73 | ||||
| -rw-r--r-- | libcommuni/src/core/ircmessagedecoder_icu.cpp | 68 | ||||
| -rw-r--r-- | libcommuni/src/core/ircmessagedecoder_uchardet.cpp | 56 | ||||
| -rw-r--r-- | libcommuni/src/core/ircnetwork.cpp | 687 | ||||
| -rw-r--r-- | libcommuni/src/core/ircprotocol.cpp | 480 |
15 files changed, 6315 insertions, 0 deletions
diff --git a/libcommuni/src/core/core.pri b/libcommuni/src/core/core.pri new file mode 100644 index 0000000..3d4a0b1 --- /dev/null +++ b/libcommuni/src/core/core.pri @@ -0,0 +1,65 @@ +###################################################################### +# Communi +###################################################################### + +DEFINES += BUILD_IRC_CORE +QT += network + +INCDIR = $$PWD/../../include/IrcCore + +DEPENDPATH += $$PWD $$INCDIR +INCLUDEPATH += $$PWD $$INCDIR + +CONV_HEADERS = $$INCDIR/Irc +CONV_HEADERS += $$INCDIR/IrcCommand +CONV_HEADERS += $$INCDIR/IrcCommandFilter +CONV_HEADERS += $$INCDIR/IrcConnection +CONV_HEADERS += $$INCDIR/IrcCore +CONV_HEADERS += $$INCDIR/IrcGlobal +CONV_HEADERS += $$INCDIR/IrcMessage +CONV_HEADERS += $$INCDIR/IrcMessageFilter +CONV_HEADERS += $$INCDIR/IrcNetwork +CONV_HEADERS += $$INCDIR/IrcProtocol + +PUB_HEADERS = $$INCDIR/irc.h +PUB_HEADERS += $$INCDIR/irccommand.h +PUB_HEADERS += $$INCDIR/ircconnection.h +PUB_HEADERS += $$INCDIR/irccore.h +PUB_HEADERS += $$INCDIR/ircfilter.h +PUB_HEADERS += $$INCDIR/ircglobal.h +PUB_HEADERS += $$INCDIR/ircmessage.h +PUB_HEADERS += $$INCDIR/ircnetwork.h +PUB_HEADERS += $$INCDIR/ircprotocol.h + +PRIV_HEADERS = $$INCDIR/ircconnection_p.h +PRIV_HEADERS += $$INCDIR/ircmessage_p.h +PRIV_HEADERS += $$INCDIR/ircmessagebuilder_p.h +PRIV_HEADERS += $$INCDIR/ircmessagedecoder_p.h +PRIV_HEADERS += $$INCDIR/ircnetwork_p.h + +HEADERS += $$PUB_HEADERS +HEADERS += $$PRIV_HEADERS + +SOURCES += $$PWD/irc.cpp +SOURCES += $$PWD/irccommand.cpp +SOURCES += $$PWD/ircconnection.cpp +SOURCES += $$PWD/irccore.cpp +SOURCES += $$PWD/ircfilter.cpp +SOURCES += $$PWD/ircmessage.cpp +SOURCES += $$PWD/ircmessage_p.cpp +SOURCES += $$PWD/ircmessagebuilder.cpp +SOURCES += $$PWD/ircmessagedecoder.cpp +SOURCES += $$PWD/ircnetwork.cpp +SOURCES += $$PWD/ircprotocol.cpp + +include(../3rdparty/mozilla/mozilla.pri) + +CONFIG(icu, icu|no_icu) { + DEFINES += HAVE_ICU + SOURCES += $$PWD/ircmessagedecoder_icu.cpp + include(../3rdparty/icu/icu.pri) +} else { + DEFINES += HAVE_UCHARDET + SOURCES += $$PWD/ircmessagedecoder_uchardet.cpp + include(../3rdparty/uchardet-0.0.1/uchardet.pri) +} diff --git a/libcommuni/src/core/core.pro b/libcommuni/src/core/core.pro new file mode 100644 index 0000000..a9a0c5a --- /dev/null +++ b/libcommuni/src/core/core.pro @@ -0,0 +1,8 @@ +###################################################################### +# Communi +###################################################################### + +IRC_MODULE = IrcCore +include(core.pri) +include(../module_build.pri) +include(../module_install.pri) diff --git a/libcommuni/src/core/irc.cpp b/libcommuni/src/core/irc.cpp new file mode 100644 index 0000000..5a51645 --- /dev/null +++ b/libcommuni/src/core/irc.cpp @@ -0,0 +1,172 @@ +/* + 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 "irc.h" +#include "irccore.h" +#include "irccommand.h" +#include "ircconnection.h" +#include "ircmessage_p.h" +#include <QMetaEnum> +#include <QDebug> + +IRC_BEGIN_NAMESPACE + +/*! + \file irc.h + \brief \#include <Irc> + */ + +/*! + \namespace Irc + \ingroup core + \brief Miscellaneous identifiers used throughout the library. + */ + +/*! + Returns the version number of Communi at run-time as a string (for example, "1.2.3"). + This may be a different version than the version the application was compiled against. + + \sa IRC_VERSION, IRC_VERSION_STR + */ +QString Irc::version() +{ + return QLatin1String(IRC_VERSION_STR); +} + +/*! + Returns the numeric \a code as a string or a null string if the code is unknown. + + \sa Irc::Code, IrcNumericMessage::code() + */ +QString Irc::codeToString(int code) +{ + const int index = Irc::staticMetaObject.indexOfEnumerator("Code"); + QMetaEnum enumerator = Irc::staticMetaObject.enumerator(index); + return QLatin1String(enumerator.valueToKey(code)); +} + +/*! + Returns the nick part of the specified \a prefix. + + Nick part of a prefix as specified in RFC 1459: + <pre> + <b><nick></b> [ '!' <ident> ] [ '@' <host> ] + </pre> + + \sa IrcMessage::prefix, IrcMessage::nick + */ +QString Irc::nickFromPrefix(const QString& prefix) +{ + QString nick; + IrcMessagePrivate::parsePrefix(prefix, &nick, 0, 0); + return nick; +} + +/*! + Returns the ident part of the specified \a prefix. + + Ident part of a prefix as specified in RFC 1459: + <pre> + <nick> [ '!' <b><ident></b> ] [ '@' <host> ] + </pre> + + \sa IrcMessage::prefix, IrcMessage::ident + */ +QString Irc::identFromPrefix(const QString& prefix) +{ + QString ident; + IrcMessagePrivate::parsePrefix(prefix, 0, &ident, 0); + return ident; +} + +/*! + Returns the host part of the specified \a prefix. + + Host part of a prefix as specified in RFC 1459: + <pre> + <nick> [ '!' <ident> ] [ '@' <b><host></b> ] + </pre> + + \sa IrcMessage::prefix, IrcMessage::host + */ +QString Irc::hostFromPrefix(const QString& prefix) +{ + QString host; + IrcMessagePrivate::parsePrefix(prefix, 0, 0, &host); + return host; +} + +/*! + \deprecated Use IrcCore::registerMetaTypes() instead. + */ +void Irc::registerMetaTypes() +{ + IrcCore::registerMetaTypes(); +} + +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug debug, Irc::Code code) +{ + const int index = Irc::staticMetaObject.indexOfEnumerator("Code"); + QMetaEnum enumerator = Irc::staticMetaObject.enumerator(index); + const char* key = enumerator.valueToKey(code); + debug << (key ? key : "Unknown"); + return debug; +} + +QDebug operator<<(QDebug debug, Irc::DataRole role) +{ + const int index = Irc::staticMetaObject.indexOfEnumerator("DataRole"); + QMetaEnum enumerator = Irc::staticMetaObject.enumerator(index); + const char* key = enumerator.valueToKey(role); + debug << (key ? key : "Unknown"); + return debug; +} + +QDebug operator<<(QDebug debug, Irc::Color color) +{ + const int index = Irc::staticMetaObject.indexOfEnumerator("Color"); + QMetaEnum enumerator = Irc::staticMetaObject.enumerator(index); + const char* key = enumerator.valueToKey(color); + debug << (key ? key : "Unknown"); + return debug; +} + +QDebug operator<<(QDebug debug, Irc::SortMethod method) +{ + const int index = Irc::staticMetaObject.indexOfEnumerator("SortMethod"); + QMetaEnum enumerator = Irc::staticMetaObject.enumerator(index); + const char* key = enumerator.valueToKey(method); + debug << (key ? key : "Unknown"); + return debug; +} +#endif // QT_NO_DEBUG_STREAM + +#include "moc_irc.cpp" + +IRC_END_NAMESPACE diff --git a/libcommuni/src/core/irccommand.cpp b/libcommuni/src/core/irccommand.cpp new file mode 100644 index 0000000..d7e9b30 --- /dev/null +++ b/libcommuni/src/core/irccommand.cpp @@ -0,0 +1,851 @@ +/* + 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 "irccommand.h" +#include "ircmessage.h" +#include <QTextCodec> +#include <QMetaEnum> +#include <QDebug> + +IRC_BEGIN_NAMESPACE + +/*! + \file irccommand.h + \brief \#include <IrcCommand> + */ + +/*! + \class IrcCommand irccommand.h <IrcCommand> + \ingroup core + \brief Provides the most common commands. + + The IrcCommand class supports the most common IRC commands out of the box, + and can be extended for custom commands as well. See IrcCommand::Type for + the list of built-in command types. IRC commands, as in IrcCommand instances, + are sent to the IRC server via IrcConnection::sendCommand(). + + \section creating-commands Creating commands + + It is recommended to create IrcCommand instances via static + IrcCommand::createXxx() methods. + + \warning IrcCommand instances must be allocated on the heap, since + IrcConnection::sendCommand() takes ownership of the command and deletes + it once it has been sent. + + \section custom-commands Custom commands + + A "custom command" here refers to command types not listed in IrcCommand::Type, + the list of built-in command types. There are two ways to send custom commands: + \li by passing the string representation of a command directly to + IrcConnection::sendRaw() or IrcConnection::sendData(), or + \li by subclassing IrcCommand and reimplementing + IrcCommand::toString(), which eventually creates the string representation + of the command. + + Example implementation of a custom command: + \code + class IrcServerCommand : public IrcCommand + { + Q_OBJECT + public: + explicit IrcServerCommand(QObject* parent = 0) : IrcCommand(parent) + { + } + + // provided for convenience, to ensure correct parameter order + static IrcCommand* create(const QString& serverName, int hopCount, const QString& info) + { + IrcCommand* command = new IrcServerCommand; + command->setParameters(QStringList() << serverName << QString::number(hopCount) << info); + return command; + } + + // reimplemented from IrcCommand::toString() + virtual toString() const + { + // SERVER <servername> <hopcount> <info> + return QString("SERVER %1 %2 %3").arg(params.value(0), params.value(1), params.value(2)); + } + }; + \endcode + + \sa IrcConnection::sendCommand(), IrcConnection::sendRaw(), IrcCommand::Type + */ + +/*! + \enum IrcCommand::Type + This enum describes the built-in command types. + */ + +/*! + \var IrcCommand::Admin + \brief An admin command (ADMIN) is used to query server admin info. + */ + +/*! + \var IrcCommand::Away + \brief An away command (AWAY) is used to set the away status. + */ + +/*! + \var IrcCommand::Capability + \brief A capability command (CAP) is used to manage connection capabilities. + */ + +/*! + \var IrcCommand::CtcpAction + \brief A CTCP action command is used to send an action message to channels and users. + */ + +/*! + \var IrcCommand::CtcpReply + \brief A CTCP reply command is used to send a reply to a request. + */ + +/*! + \var IrcCommand::CtcpRequest + \brief A CTCP request command is used to send a request. + */ + +/*! + \var IrcCommand::Custom + \brief A custom command + */ + +/*! + \var IrcCommand::Info + \brief An info command (INFO) is used to query server info. + */ + +/*! + \var IrcCommand::Invite + \brief An invite command (INVITE) is used to invite users to a channel. + */ + +/*! + \var IrcCommand::Join + \brief A join command (JOIN) is used to start listening a specific channel. + */ + +/*! + \var IrcCommand::Kick + \brief A kick command (KICK) is used to forcibly remove a user from a channel. + */ + +/*! + \var IrcCommand::Knock + \brief A knock command (KNOCK) is used to request channel invitation. + */ + +/*! + \var IrcCommand::List + \brief A list command (LIST) is used to list channels and their topics. + */ + +/*! + \var IrcCommand::Message + \brief A message command (PRIVMSG) is used to send private messages to channels and users. + */ + +/*! + \var IrcCommand::Mode + \brief A mode command (MODE) is used to change the mode of users and channels. + */ + +/*! + \var IrcCommand::Motd + \brief A message of the day command (MOTD) is used to query the message of the day. + */ + +/*! + \var IrcCommand::Names + \brief A names command (NAMES) is used to list all nicknames on a channel. + */ + +/*! + \var IrcCommand::Nick + \brief A nick command (NICK) is used to give user a nickname or change the previous one. + */ + +/*! + \var IrcCommand::Notice + \brief A notice command (NOTICE) is used to send notice messages to channels and users. + */ + +/*! + \var IrcCommand::Part + \brief A part command (PART) causes the client to be removed from the channel. + */ + +/*! + \var IrcCommand::Quit + \brief A quit command (QUIT) is used to end a client connection. + */ + +/*! + \var IrcCommand::Quote + \brief A quote command is used to send a raw message to the server. + */ + +/*! + \var IrcCommand::Stats + \brief A stats command (STATS) is used to query server statistics. + */ + +/*! + \var IrcCommand::Time + \brief A time command (TIME) is used to query local server time. + */ + +/*! + \var IrcCommand::Topic + \brief A topic command (TOPIC) is used to change or view the topic of a channel. + */ + +/*! + \var IrcCommand::Trace + \brief A trace command (TRACE) is used to trace the connection path to a target. + */ + +/*! + \var IrcCommand::Users + \brief A users command (USERS) is used to query server users. + */ + +/*! + \var IrcCommand::Version + \brief A version command (VERSION) is used to query user or server version. + */ + +/*! + \var IrcCommand::Who + \brief A who command (WHO) is used to generate a query which returns a list of matching users. + */ + +/*! + \var IrcCommand::Whois + \brief A whois command (WHOIS) is used to query information about a particular user. + */ + +/*! + \var IrcCommand::Whowas + \brief A whowas command (WHOWAS) is used to query information about a user that no longer exists. + */ + +#ifndef IRC_DOXYGEN +class IrcCommandPrivate +{ +public: + IrcCommandPrivate() : encoding("UTF-8") { } + + QString params(int index) const; + + IrcCommand::Type type; + QStringList parameters; + QByteArray encoding; + + static IrcCommand* createCommand(IrcCommand::Type type, const QStringList& parameters); +}; + +QString IrcCommandPrivate::params(int index) const +{ + return QStringList(parameters.mid(index)).join(QLatin1String(" ")); +} + +IrcCommand* IrcCommandPrivate::createCommand(IrcCommand::Type type, const QStringList& parameters) +{ + IrcCommand* command = new IrcCommand; + command->setType(type); + command->setParameters(parameters); + return command; +} +#endif // IRC_DOXYGEN + +/*! + Constructs a new IrcCommand with \a parent. + */ +IrcCommand::IrcCommand(QObject* parent) : QObject(parent), d_ptr(new IrcCommandPrivate) +{ + Q_D(IrcCommand); + d->type = Custom; +} + +/*! + Destructs the IRC command. + */ +IrcCommand::~IrcCommand() +{ +} + +/*! + This property holds the command type. + + \par Access functions: + \li IrcCommand::Type <b>type</b>() const + \li void <b>setType</b>(IrcCommand::Type type) + */ +IrcCommand::Type IrcCommand::type() const +{ + Q_D(const IrcCommand); + return d->type; +} + +void IrcCommand::setType(Type type) +{ + Q_D(IrcCommand); + d->type = type; +} + +/*! + This property holds the command parameters. + + \par Access functions: + \li QStringList <b>parameters</b>() const + \li void <b>setParameters</b>(const QStringList& parameters) + */ +QStringList IrcCommand::parameters() const +{ + Q_D(const IrcCommand); + return d->parameters; +} + +void IrcCommand::setParameters(const QStringList& parameters) +{ + Q_D(IrcCommand); + d->parameters = parameters; +} + +/*! + This property holds the encoding that is used when + sending the command via IrcConnection::sendCommand(). + + See QTextCodec::availableCodes() for the list of + supported encodings. The default value is \c "UTF-8". + + \par Access functions: + \li QByteArray <b>encoding</b>() const + \li void <b>setEncoding</b>(const QByteArray& encoding) + + \sa QTextCodec::availableCodecs() + */ +QByteArray IrcCommand::encoding() const +{ + Q_D(const IrcCommand); + return d->encoding; +} + +void IrcCommand::setEncoding(const QByteArray& encoding) +{ + Q_D(IrcCommand); + extern bool irc_is_supported_encoding(const QByteArray& encoding); // ircmessagedecoder.cpp + if (!irc_is_supported_encoding(encoding)) { + qWarning() << "IrcCommand::setEncoding(): unsupported encoding" << encoding; + return; + } + d->encoding = encoding; +} + +/*! + Returns the command as a string. + + Reimplement for custom commands. + \sa IrcCommand::Custom + */ +QString IrcCommand::toString() const +{ + Q_D(const IrcCommand); + const QString p0 = d->parameters.value(0); + const QString p1 = d->parameters.value(1); + const QString p2 = d->parameters.value(2); + + switch (d->type) { + case Admin: return QString("ADMIN %1").arg(p0); // server + case Away: return QString("AWAY :%1").arg(d->params(0)); // reason + case Capability: return QString("CAP %1 :%2").arg(p0, d->params(1)); // subcmd, caps + case CtcpAction: return QString("PRIVMSG %1 :\1ACTION %2\1").arg(p0, d->params(1)); // target, msg + case CtcpRequest: return QString("PRIVMSG %1 :\1%2\1").arg(p0, d->params(1)); // target, msg + case CtcpReply: return QString("NOTICE %1 :\1%2\1").arg(p0, d->params(1)); // target, msg + case Info: return QString("INFO %1").arg(p0); // server + case Invite: return QString("INVITE %1 %2").arg(p0, p1); // user, chan + case Join: return p1.isNull() ? QString("JOIN %1").arg(p0) : QString("JOIN %1 %2").arg(p0, p1); // chan, key + case Kick: return p2.isNull() ? QString("KICK %1 %2").arg(p0, p1) : QString("KICK %1 %2 :%3").arg(p0, p1, d->params(2)); // chan, user, reason + case Knock: return QString("KNOCK %1 %2").arg(p0, p1); // chan, msg + case List: return p1.isNull() ? QString("LIST %1").arg(p0) : QString("LIST %1 %2").arg(p0, p1); // chan, server + case Message: return QString("PRIVMSG %1 :%2").arg(p0, d->params(1)); // target, msg + case Mode: return QString("MODE ") + d->parameters.join(" "); // target, mode, arg + case Motd: return QString("MOTD %1").arg(p0); // server + case Names: return QString("NAMES %1").arg(p0); // chan + case Nick: return QString("NICK %1").arg(p0); // nick + case Notice: return QString("NOTICE %1 :%2").arg(p0, d->params(1)); // target, msg + case Part: return p1.isNull() ? QString("PART %1").arg(p0) : QString("PART %1 :%2").arg(p0, d->params(1)); // chan, reason + case Ping: return QString("PING %1").arg(p0); // argument + case Pong: return QString("PONG %1").arg(p0); // argument + case Quit: return QString("QUIT :%1").arg(d->params(0)); // reason + case Quote: return d->parameters.join(" "); + case Stats: return QString("STATS %1 %2").arg(p0, p1); // query, server + case Time: return QString("TIME %1").arg(p0); // server + case Topic: return p1.isNull() ? QString("TOPIC %1").arg(p0) : QString("TOPIC %1 :%2").arg(p0, d->params(1)); // chan, topic + case Trace: return QString("TRACE %1").arg(p0); // target + case Users: return QString("USERS %1").arg(p0); // server + case Version: return p0.isNull() ? QString("VERSION") : QString("PRIVMSG %1 :\1VERSION\1").arg(p0); // user + case Who: return QString("WHO %1").arg(p0); // user + case Whois: return QString("WHOIS %1 %1").arg(p0); // user + case Whowas: return QString("WHOWAS %1 %1").arg(p0); // user + + case Custom: qWarning("Reimplement IrcCommand::toString() for IrcCommand::Custom"); + default: return QString(); + } +} + +/*! + Creates a new message from this command for \a prefix and \a connection. + + Notice that IRC servers do not echo sent message commands back to the client. + This function is particularly useful for converting sent message commands as + messages for presentation purposes. + + \code + if (command->type() == IrcCommand::Message) { + IrcMessage* message = command->toMessage(connection->nickName(), connection); + receiveMessage(message); + message->deleteLater(); + } + \endcode + */ +IrcMessage* IrcCommand::toMessage(const QString& prefix, IrcConnection* connection) const +{ + return IrcMessage::fromData(":" + prefix.toUtf8() + " " + toString().toUtf8(), connection); +} + +/*! + Creates a new ADMIN command with type IrcCommand::Admin and optional parameter \a server. + + This command shows admin info for the specified \a server, + or the current server if not specified. + */ +IrcCommand* IrcCommand::createAdmin(const QString& server) +{ + return IrcCommandPrivate::createCommand(Admin, QStringList() << server); +} + +/*! + Creates a new AWAY command with type IrcCommand::Away and optional parameter \a reason. + + Provides the server with \a reason to automatically send in reply to a private + message directed at the user. If \a reason is omitted, the away status is removed. + */ +IrcCommand* IrcCommand::createAway(const QString& reason) +{ + return IrcCommandPrivate::createCommand(Away, QStringList() << reason); +} + +/*! + Creates a new capability command with type IrcCommand::Capability and parameters \a subCommand and a \a capability. + + Available subcommands are: LS, LIST, REQ, ACK, NAK, CLEAR and END. + */ +IrcCommand* IrcCommand::createCapability(const QString& subCommand, const QString& capability) +{ + return createCapability(subCommand, QStringList() << capability); +} + +/*! + Creates a new capability command with type IrcCommand::Capability and parameters \a subCommand and optional \a capabilities. + + Available subcommands are: LS, LIST, REQ, ACK, NAK, CLEAR and END. + */ +IrcCommand* IrcCommand::createCapability(const QString& subCommand, const QStringList& capabilities) +{ + return IrcCommandPrivate::createCommand(Capability, QStringList() << subCommand << capabilities.join(QLatin1String(" "))); +} + +/*! + Creates a new CTCP action command with type IrcCommand::CtcpAction and parameters \a target and \a action. + */ +IrcCommand* IrcCommand::createCtcpAction(const QString& target, const QString& action) +{ + return IrcCommandPrivate::createCommand(CtcpAction, QStringList() << target << action); +} + +/*! + Creates a new CTCP reply command with type IrcCommand::CtcpReply and parameters \a target and \a reply. + */ +IrcCommand* IrcCommand::createCtcpReply(const QString& target, const QString& reply) +{ + return IrcCommandPrivate::createCommand(CtcpReply, QStringList() << target << reply); +} + +/*! + Creates a new CTCP request command with type IrcCommand::CtcpRequest and parameters \a target and \a request. + */ +IrcCommand* IrcCommand::createCtcpRequest(const QString& target, const QString& request) +{ + return IrcCommandPrivate::createCommand(CtcpRequest, QStringList() << target << request); +} + +/*! + Creates a new INFO command with type IrcCommand::Info and optional parameter \a server. + + This command shows info for the specified \a server, + or the current server if not specified. + */ +IrcCommand* IrcCommand::createInfo(const QString& server) +{ + return IrcCommandPrivate::createCommand(Info, QStringList() << server); +} + +/*! + Creates a new INVITE command with type IrcCommand::Invite and parameters \a user and \a channel. + + This command invites \a user to the \a channel. The channel does not have to exist, but + if it does, only members of the channel are allowed to invite other clients. if the + channel mode +i (invite-only) is set, only channel operators may invite other clients. + */ +IrcCommand* IrcCommand::createInvite(const QString& user, const QString& channel) +{ + return IrcCommandPrivate::createCommand(Invite, QStringList() << user << channel); +} + +/*! + Creates a new JOIN command with type IrcCommand::Join and parameters \a channel and optional \a key. + + This command joins the \a channel using \a key if specified. + If the channel does not exist, it will be created. + */ +IrcCommand* IrcCommand::createJoin(const QString& channel, const QString& key) +{ + return IrcCommandPrivate::createCommand(Join, QStringList() << channel << key); +} + +/*! + This overload is provided for convenience. + */ +IrcCommand* IrcCommand::createJoin(const QStringList& channels, const QStringList& keys) +{ + return IrcCommandPrivate::createCommand(Join, QStringList() << channels.join(",") << keys.join(",")); +} + +/*! + Creates a new KICK command with type IrcCommand::Kick and parameters \a channel, \a user and optional \a reason. + + This command forcibly removes \a user from \a channel, + and may only be issued by channel operators. + */ +IrcCommand* IrcCommand::createKick(const QString& channel, const QString& user, const QString& reason) +{ + return IrcCommandPrivate::createCommand(Kick, QStringList() << channel << user << reason); +} + +/*! + Creates a new KNOCK command with type IrcCommand::Knock and parameters \a channel and optional \a message. + + This command sends an invitation request to a \a channel with an optional \a message. + + \note The command is not formally defined by an RFC, but is supported by most major IRC daemons. + Support is indicated in a RPL_ISUPPORT reply (numeric 005) with the KNOCK keyword. + */ +IrcCommand* IrcCommand::createKnock(const QString& channel, const QString& message) +{ + return IrcCommandPrivate::createCommand(Knock, QStringList() << channel << message); +} + +/*! + Creates a new LIST command with type IrcCommand::List and optional parameters \a channels and \a server. + + This command lists all channels on the server. If \a channels are given, it will list the channel topics. + If \a server is given, the command will be forwarded to \a server for evaluation. + */ +IrcCommand* IrcCommand::createList(const QStringList& channels, const QString& server) +{ + return IrcCommandPrivate::createCommand(List, QStringList() << channels.join(",") << server); +} + +/*! + Creates a new PRIVMSG command with type IrcCommand::Message and parameters \a target and \a message. + + This command sends \a message to \a target, which is usually a user or channel. + */ +IrcCommand* IrcCommand::createMessage(const QString& target, const QString& message) +{ + return IrcCommandPrivate::createCommand(Message, QStringList() << target << message); +} + +/*! + Creates a new MODE command with type IrcCommand::Mode and parameters \a target and optional \a mode and \a arg. + + This command is used to set both user and channel modes. + */ +IrcCommand* IrcCommand::createMode(const QString& target, const QString& mode, const QString& arg) +{ + return IrcCommandPrivate::createCommand(Mode, QStringList() << target << mode << arg); +} + +/*! + Creates a new MOTD command with type IrcCommand::Motd and optional parameter \a server. + + This command shows the message of the day on the specified \a server, + or the current server if not specified. + */ +IrcCommand* IrcCommand::createMotd(const QString& server) +{ + return IrcCommandPrivate::createCommand(Motd, QStringList() << server); +} + +/*! + Creates a new NAMES command with type IrcCommand::Names and parameter \a channel. + + This command lists all users on the \a channel, optionally limiting to the given \a server. + + If \a channel is omitted, all users are shown, grouped by channel name with + all users who are not on a channel being shown as part of channel "*". + If \a server is specified, the command is sent to \a server for evaluation. +*/ +IrcCommand* IrcCommand::createNames(const QString& channel, const QString& server) +{ + return IrcCommandPrivate::createCommand(Names, QStringList() << channel << server); +} + +/*! + This overload is provided for convenience. + */ +IrcCommand* IrcCommand::createNames(const QStringList& channels, const QString& server) +{ + return IrcCommandPrivate::createCommand(Names, QStringList() << channels.join(",") << server); +} + +/*! + Creates a new NICK command with type IrcCommand::Nick and parameter \a nick. + + This command allows a client to change their IRC nickname. + */ +IrcCommand* IrcCommand::createNick(const QString& nick) +{ + return IrcCommandPrivate::createCommand(Nick, QStringList() << nick); +} + +/*! + Creates a new NOTICE command with type IrcCommand::Notice and parameters \a target and \a message. + + This command sends \a notice to \a target, which is usually a user or channel. + + \note The command works similarly to PRIVMSG, except automatic replies must never be sent in reply to NOTICE messages. + */ +IrcCommand* IrcCommand::createNotice(const QString& target, const QString& message) +{ + return IrcCommandPrivate::createCommand(Notice, QStringList() << target << message); +} + +/*! + Creates a new PART command with type IrcCommand::Part and parameters \a channel and optional \a reason. + + This command causes the client to leave the specified channel. + */ +IrcCommand* IrcCommand::createPart(const QString& channel, const QString& reason) +{ + return IrcCommandPrivate::createCommand(Part, QStringList() << channel << reason); +} + +/*! + This overload is provided for convenience. + */ +IrcCommand* IrcCommand::createPart(const QStringList& channels, const QString& reason) +{ + return IrcCommandPrivate::createCommand(Part, QStringList() << channels.join(",") << reason); +} + +/*! + Creates a new PING command with type IrcCommand::Ping and \a argument. + */ +IrcCommand* IrcCommand::createPing(const QString& argument) +{ + return IrcCommandPrivate::createCommand(Ping, QStringList() << argument); +} + +/*! + Creates a new PONG command with type IrcCommand::Pong and \a argument. + */ +IrcCommand* IrcCommand::createPong(const QString& argument) +{ + return IrcCommandPrivate::createCommand(Pong, QStringList() << argument); +} + +/*! + Creates a new QUIT command with type IrcCommand::Quit and optional parameter \a reason. + */ +IrcCommand* IrcCommand::createQuit(const QString& reason) +{ + return IrcCommandPrivate::createCommand(Quit, QStringList() << reason); +} + +/*! + Creates a new QUOTE command with type IrcCommand::Quote and \a raw. + */ +IrcCommand* IrcCommand::createQuote(const QString& raw) +{ + return IrcCommandPrivate::createCommand(Quote, QStringList() << raw); +} + +/*! + Creates a new QUOTE command with type IrcCommand::Quote and \a parameters. + */ +IrcCommand* IrcCommand::createQuote(const QStringList& parameters) +{ + return IrcCommandPrivate::createCommand(Quote, parameters); +} + +/*! + Creates a new STATS command with type IrcCommand::Stats and parameters \a query and optional \a server. + + This command queries statistics about the specified \a server, + or the current server if not specified. + */ +IrcCommand* IrcCommand::createStats(const QString& query, const QString& server) +{ + return IrcCommandPrivate::createCommand(Stats, QStringList() << query << server); +} + +/*! + Creates a new TIME command with type IrcCommand::Time and optional parameter \a server. + + This command queries local time of the specified \a server, + or the current server if not specified. + */ +IrcCommand* IrcCommand::createTime(const QString& server) +{ + return IrcCommandPrivate::createCommand(Time, QStringList() << server); +} + +/*! + Creates a new TOPIC command with type IrcCommand::Topic and parameters \a channel and optional \a topic. + + This command allows the client to query or set the channel topic on \a channel. + If \a topic is given, it sets the channel topic to \a topic. + If channel mode +t is set, only a channel operator may set the topic. + */ +IrcCommand* IrcCommand::createTopic(const QString& channel, const QString& topic) +{ + return IrcCommandPrivate::createCommand(Topic, QStringList() << channel << topic); +} + +/*! + Creates a new TRACE command with type IrcCommand::Trace and optional parameter \a target. + + This command traces the connection path across the IRC network + to the current server or to a specific \a target (server or client) + in a similar method to traceroute. + */ +IrcCommand* IrcCommand::createTrace(const QString& target) +{ + return IrcCommandPrivate::createCommand(Trace, QStringList() << target); +} + +/*! + Creates a new USERS command with type IrcCommand::Users and optional parameter \a server. + + This command queries the users of the specified \a server, + or the current server if not specified. + */ +IrcCommand* IrcCommand::createUsers(const QString& server) +{ + return IrcCommandPrivate::createCommand(Users, QStringList() << server); +} + +/*! + Creates a new command with type IrcCommand::Version and optional parameter \a user. + + This command queries the version of the specified \a user's client (CTCP REQUEST VERSION), + or the current server (VERSION) if not specified. + */ +IrcCommand* IrcCommand::createVersion(const QString& user) +{ + return IrcCommandPrivate::createCommand(Version, QStringList() << user); +} + +/*! + Creates a new WHO command with type IrcCommand::Who and parameters \a mask and optional \a operators. + + This command returns a list of users who match \a mask, + optionally matching only IRC \a operators. + */ +IrcCommand* IrcCommand::createWho(const QString& mask, bool operators) +{ + return IrcCommandPrivate::createCommand(Who, QStringList() << mask << (operators ? "o" : "")); +} + +/*! + Creates a new WHOIS command with type IrcCommand::Whois and parameter \a user. + + This command returns information about \a user. + */ +IrcCommand* IrcCommand::createWhois(const QString& user) +{ + return IrcCommandPrivate::createCommand(Whois, QStringList() << user); +} + +/*! + Creates a new WHOWAS command with type IrcCommand::Whowas and parameters \a user and optional \a count. + + This command returns information about a \a user that is no longer online + (due to client disconnection, or nickname changes). If given, the server + will return information from the last \a count times the nickname has been used. + */ +IrcCommand* IrcCommand::createWhowas(const QString& user, int count) +{ + return IrcCommandPrivate::createCommand(Whowas, QStringList() << user << QString::number(count)); +} + +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug debug, IrcCommand::Type type) +{ + const int index = IrcCommand::staticMetaObject.indexOfEnumerator("Type"); + QMetaEnum enumerator = IrcCommand::staticMetaObject.enumerator(index); + const char* key = enumerator.valueToKey(type); + debug << (key ? key : "Unknown"); + return debug; +} + +QDebug operator<<(QDebug debug, const IrcCommand* command) +{ + if (!command) + return debug << "IrcCommand(0x0) "; + debug.nospace() << command->metaObject()->className() << '(' << (void*) command; + if (!command->objectName().isEmpty()) + debug.nospace() << ", name=" << qPrintable(command->objectName()); + debug.nospace() << ", type=" << command->type(); + QString str = command->toString(); + if (!str.isEmpty()) + debug.nospace() << ", " << str.left(20); + debug.nospace() << ')'; + return debug.space(); +} +#endif // QT_NO_DEBUG_STREAM + +#include "moc_irccommand.cpp" + +IRC_END_NAMESPACE 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 diff --git a/libcommuni/src/core/irccore.cpp b/libcommuni/src/core/irccore.cpp new file mode 100644 index 0000000..f6453c8 --- /dev/null +++ b/libcommuni/src/core/irccore.cpp @@ -0,0 +1,90 @@ +/* + 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 "irccore.h" + +IRC_BEGIN_NAMESPACE + +/*! + \file irccore.h + \brief \#include <IrcCore> + */ + +/*! + \namespace IrcCore + \ingroup core + \brief Module meta-type registration. + */ + +namespace IrcCore { + + /*! + Registers IrcCore types to the %Qt meta-system. + + \sa IrcModel::registerMetaTypes(), IrcUtil::registerMetaTypes(), qRegisterMetaType() + */ + void registerMetaTypes() + { + qRegisterMetaType<Irc::Color>("Irc::Color"); + qRegisterMetaType<Irc::DataRole>("Irc::DataRole"); + qRegisterMetaType<Irc::SortMethod>("Irc::SortMethod"); + qRegisterMetaType<Irc::Code>("Irc::Code"); + + qRegisterMetaType<IrcConnection*>("IrcConnection*"); + qRegisterMetaType<IrcConnection::Status>("IrcConnection::Status"); + + qRegisterMetaType<IrcNetwork*>("IrcNetwork*"); + + qRegisterMetaType<IrcCommand*>("IrcCommand*"); + qRegisterMetaType<IrcCommand::Type>("IrcCommand::Type"); + + qRegisterMetaType<IrcMessage*>("IrcMessage*"); + qRegisterMetaType<IrcMessage::Type>("IrcMessage::Type"); + + qRegisterMetaType<IrcCapabilityMessage*>("IrcCapabilityMessage*"); + qRegisterMetaType<IrcErrorMessage*>("IrcErrorMessage*"); + qRegisterMetaType<IrcInviteMessage*>("IrcInviteMessage*"); + qRegisterMetaType<IrcJoinMessage*>("IrcJoinMessage*"); + qRegisterMetaType<IrcKickMessage*>("IrcKickMessage*"); + qRegisterMetaType<IrcModeMessage*>("IrcModeMessage*"); + qRegisterMetaType<IrcNamesMessage*>("IrcNamesMessage*"); + qRegisterMetaType<IrcNickMessage*>("IrcNickMessage*"); + qRegisterMetaType<IrcNoticeMessage*>("IrcNoticeMessage*"); + qRegisterMetaType<IrcNumericMessage*>("IrcNumericMessage*"); + qRegisterMetaType<IrcMotdMessage*>("IrcMotdMessage*"); + qRegisterMetaType<IrcPartMessage*>("IrcPartMessage*"); + qRegisterMetaType<IrcPingMessage*>("IrcPingMessage*"); + qRegisterMetaType<IrcPongMessage*>("IrcPongMessage*"); + qRegisterMetaType<IrcPrivateMessage*>("IrcPrivateMessage*"); + qRegisterMetaType<IrcQuitMessage*>("IrcQuitMessage*"); + qRegisterMetaType<IrcTopicMessage*>("IrcTopicMessage*"); + qRegisterMetaType<IrcWhoReplyMessage*>("IrcWhoReplyMessage*"); + } +} + +IRC_END_NAMESPACE diff --git a/libcommuni/src/core/ircfilter.cpp b/libcommuni/src/core/ircfilter.cpp new file mode 100644 index 0000000..054badb --- /dev/null +++ b/libcommuni/src/core/ircfilter.cpp @@ -0,0 +1,248 @@ +/* + 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 "ircfilter.h" + +IRC_BEGIN_NAMESPACE + +/*! + \file ircfilter.h + \brief \#include <\ref ircfilter.h "IrcFilter"> + */ + +/*! + \file ircmessagefilter.h + \brief \#include <IrcMessageFilter> + */ + +/*! + \file irccommandfilter.h + \brief \#include <IrcCommandFilter> + */ + +/*! + \class IrcMessageFilter ircfilter.h <IrcMessageFilter> + \ingroup core + \brief Provides an interface for filtering messages + + IrcMessageFilter may be used to intercept messages before + IrcConnection::messageReceived() is emitted and the messages get delivered + further. In order to use IrcMessageFilter, it must be installed via + IrcConnection::installMessageFilter(). + + Message filtering is mostly useful for handling specific replies before + the rest of the application receives it. This way there is no need to + for example ignore and hide such replies later in the application code. + + The following example sends a PING command after each PRIVMSG command. + A consequent PONG reply from the server verifies that the PRIVMSG has + been also processed. + + \code + class CommandVerifier : public QObject, + public IrcCommandFilter, + public IrcMessageFilter + { + Q_OBJECT + Q_INTERFACES(IrcCommandFilter IrcMessageFilter) + + public: + CommandVerifier(IrcConnection* parent) : + QObject(parent), identifier(0), connection(parent) + { + connection->installMessageFilter(this); + connection->installCommandFilter(this); + } + + virtual bool commandFilter(IrcCommand* cmd) + { + if (cmd->type() == IrcCommand::Message) { + cmd->setParent(this); // take ownership + connection->sendCommand(cmd); + commands.insert(++identifier, cmd); + connection->sendData("PING communi/" + QByteArray::number(identifier)); + return true; + } + return false; + } + + virtual bool messageFilter(IrcMessage* msg) + { + if (msg->type() == IrcMessage::Pong) { + QString arg = static_cast<IrcPongMessage*>(msg)->argument(); + if (arg.startsWith("communi/")) { + bool ok = false; + quint64 id = arg.mid(8).toULongLong(&ok); + if (ok) { + IrcCommand* command = commands.take(id); + if (command) { + emit verified(command); + command->deleteLater(); + return true; + } + } + } + } + return false; + } + + signals: + void verified(IrcCommand* cmd); + + private: + quint64 identifier; + IrcConnection* connection; + QMap<quint64, IrcCommand*> commands; + }; + \endcode + + \sa IrcConnection::installMessageFilter(), IrcCommandFilter + */ + +/*! + \fn IrcMessageFilter::~IrcMessageFilter() + Destructs the message filter. + + The message filter is automatically removed from any connection(s) + it is installed on. + + \sa IrcConnection::removeMessageFilter() + */ + +/*! + \fn virtual bool IrcMessageFilter::messageFilter(IrcMessage* message) = 0 + + Reimplement this function to filter messages from installed connections. + + Return \c true to filter the message out, i.e. stop it being handled further; + otherwise return \c false. + + \sa IrcConnection::installMessageFilter() + */ + +/*! + \class IrcCommandFilter ircfilter.h <IrcCommandFilter> + \ingroup core + \brief Provides an interface for filtering commands + + IrcCommandFilter may be used to intercept commands before they + get sent further. In order to use IrcCommandFilter, it must be + installed via IrcConnection::installCommandFilter(). + + Command filtering can be useful doing extra tasks for specific + type of commands. The following example sends a PING command + after each PRIVMSG command. A consequent PONG reply from the + server verifies that the PRIVMSG has been also processed. + + \code + class CommandVerifier : public QObject, + public IrcCommandFilter, + public IrcMessageFilter + { + Q_OBJECT + Q_INTERFACES(IrcCommandFilter IrcMessageFilter) + + public: + CommandVerifier(IrcConnection* parent) : + QObject(parent), identifier(0), connection(parent) + { + connection->installMessageFilter(this); + connection->installCommandFilter(this); + } + + virtual bool commandFilter(IrcCommand* cmd) + { + if (cmd->type() == IrcCommand::Message) { + cmd->setParent(this); // take ownership + connection->sendCommand(cmd); + commands.insert(++identifier, cmd); + connection->sendData("PING communi/" + QByteArray::number(identifier)); + return true; + } + return false; + } + + virtual bool messageFilter(IrcMessage* msg) + { + if (msg->type() == IrcMessage::Pong) { + QString arg = static_cast<IrcPongMessage*>(msg)->argument(); + if (arg.startsWith("communi/")) { + bool ok = false; + quint64 id = arg.mid(8).toULongLong(&ok); + if (ok) { + IrcCommand* command = commands.take(id); + if (command) { + emit verified(command); + command->deleteLater(); + return true; + } + } + } + } + return false; + } + + signals: + void verified(IrcCommand* cmd); + + private: + quint64 identifier; + IrcConnection* connection; + QMap<quint64, IrcCommand*> commands; + }; + \endcode + + \note Notice that it is safe to call IrcConnection::sendCommand() + from IrcCommandFilter::commandFilter(). Such commands won't get + delivered back to the \b same filter to avoid recursion. + + \sa IrcConnection::installCommandFilter(), IrcMessageFilter + */ + +/*! + \fn IrcCommandFilter::~IrcCommandFilter() + Destructs the command filter. + + The command filter is automatically removed from any connection(s) + it is installed on. + + \sa IrcConnection::removeCommandFilter() + */ + +/*! + \fn virtual bool IrcCommandFilter::commandFilter(IrcCommand* command) = 0 + + Reimplement this function to filter commands to installed connections. + + Return \c true to filter the command out, i.e. stop it being handled further; + otherwise return \c false. + + \sa IrcConnection::installCommandFilter() + */ + +IRC_END_NAMESPACE diff --git a/libcommuni/src/core/ircmessage.cpp b/libcommuni/src/core/ircmessage.cpp new file mode 100644 index 0000000..eaca6ab --- /dev/null +++ b/libcommuni/src/core/ircmessage.cpp @@ -0,0 +1,1604 @@ +/* + 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 "ircmessage.h" +#include "ircmessage_p.h" +#include "ircconnection.h" +#include "ircconnection_p.h" +#include "ircnetwork.h" +#include "irccommand.h" +#include "irc.h" +#include <QMetaEnum> +#include <QVariant> +#include <QDebug> + +IRC_BEGIN_NAMESPACE + +/*! + \file ircmessage.h + \brief \#include <IrcMessage> + */ + +/*! + \class IrcMessage ircmessage.h <IrcMessage> + \ingroup core + \ingroup message + \brief The base class of all messages. + + IRC messages are received from an IRC server. IrcConnection translates received + messages into IrcMessage instances and emits the IrcConnection::messageReceived() + signal upon message received. + + Subclasses of IrcMessage contain specialized accessors for parameters that + are specific to the particular type of message. See IrcMessage::Type for + the list of supported message types. + + \sa IrcConnection::messageReceived(), IrcMessage::Type + */ + +/*! + \enum IrcMessage::Type + This enum describes the supported message types. + */ + +/*! + \var IrcMessage::Unknown + \brief An unknown message (IrcMessage). + */ + +/*! + \var IrcMessage::Capability + \brief A capability message (IrcCapabilityMessage). + */ + +/*! + \var IrcMessage::Error + \brief An error message (IrcErrorMessage). + */ + +/*! + \var IrcMessage::Invite + \brief An invite message (IrcInviteMessage). + */ + +/*! + \var IrcMessage::Join + \brief A join message (IrcJoinMessage). + */ + +/*! + \var IrcMessage::Kick + \brief A kick message (IrcKickMessage). + */ + +/*! + \var IrcMessage::Mode + \brief A mode message (IrcModeMessage). + */ + +/*! + \var IrcMessage::Motd + \brief A message of the day (IrcMotdMessage). + */ + +/*! + \var IrcMessage::Names + \brief A names message (IrcNamesMessage). + */ + +/*! + \var IrcMessage::Nick + \brief A nick message (IrcNickMessage). + */ + +/*! + \var IrcMessage::Notice + \brief A notice message (IrcNoticeMessage). + */ + +/*! + \var IrcMessage::Numeric + \brief A numeric message (IrcNumericMessage). + */ + +/*! + \var IrcMessage::Part + \brief A part message (IrcPartMessage). + */ + +/*! + \var IrcMessage::Ping + \brief A ping message (IrcPingMessage). + */ + +/*! + \var IrcMessage::Pong + \brief A pong message (IrcPongMessage). + */ + +/*! + \var IrcMessage::Private + \brief A private message (IrcPrivateMessage). + */ + +/*! + \var IrcMessage::Quit + \brief A quit message (IrcQuitMessage). + */ + +/*! + \var IrcMessage::Topic + \brief A topic message (IrcTopicMessage). + */ + +/*! + \enum IrcMessage::Flag + This enum describes the supported message flags. + */ + +/*! + \var IrcMessage::None + \brief The message has no flags. + */ + +/*! + \var IrcMessage::Own + \brief The message is user's own message. + */ + +/*! + \var IrcMessage::Identified + \brief The message is identified. + */ + +/*! + \var IrcMessage::Unidentified + \brief The message is unidentified. + */ + +/*! + \since 3.2 + \var IrcMessage::Playback + \brief The message is playback. + */ + +static const QMetaObject* irc_command_meta_object(const QString& command) +{ + static QHash<QString, const QMetaObject*> metaObjects; + if (metaObjects.isEmpty()) { + metaObjects.insert("CAP", &IrcCapabilityMessage::staticMetaObject); + metaObjects.insert("ERROR", &IrcErrorMessage::staticMetaObject); + metaObjects.insert("INVITE", &IrcInviteMessage::staticMetaObject); + metaObjects.insert("JOIN", &IrcJoinMessage::staticMetaObject); + metaObjects.insert("KICK", &IrcKickMessage::staticMetaObject); + metaObjects.insert("MODE", &IrcModeMessage::staticMetaObject); + metaObjects.insert("NICK", &IrcNickMessage::staticMetaObject); + metaObjects.insert("NOTICE", &IrcNoticeMessage::staticMetaObject); + metaObjects.insert("PART", &IrcPartMessage::staticMetaObject); + metaObjects.insert("PING", &IrcPingMessage::staticMetaObject); + metaObjects.insert("PONG", &IrcPongMessage::staticMetaObject); + metaObjects.insert("PRIVMSG", &IrcPrivateMessage::staticMetaObject); + metaObjects.insert("QUIT", &IrcQuitMessage::staticMetaObject); + metaObjects.insert("TOPIC", &IrcTopicMessage::staticMetaObject); + } + + const QMetaObject* metaObject = metaObjects.value(command.toUpper()); + if (!metaObject) { + bool ok = false; + command.toInt(&ok); + if (ok) + metaObject = &IrcNumericMessage::staticMetaObject; + } + if (!metaObject) + metaObject = &IrcMessage::staticMetaObject; + return metaObject; +} + +/*! + Constructs a new IrcMessage with \a connection. + */ +IrcMessage::IrcMessage(IrcConnection* connection) : QObject(connection), d_ptr(new IrcMessagePrivate) +{ + Q_D(IrcMessage); + d->connection = connection; +} + +/*! + Destructs the message. + */ +IrcMessage::~IrcMessage() +{ +} + +/*! + This property holds the message connection. + + \par Access function: + \li \ref IrcConnection* <b>connection</b>() const + */ +IrcConnection* IrcMessage::connection() const +{ + Q_D(const IrcMessage); + return d->connection; +} + +/*! + This property holds the message network. + + \par Access function: + \li \ref IrcNetwork* <b>network</b>() const + */ +IrcNetwork* IrcMessage::network() const +{ + Q_D(const IrcMessage); + return d->connection ? d->connection->network() : 0; +} + +/*! + This property holds the message type. + + \par Access function: + \li \ref IrcMessage::Type <b>type</b>() const + */ +IrcMessage::Type IrcMessage::type() const +{ + Q_D(const IrcMessage); + return d->type; +} + +/*! + \since 3.2 + \property bool IrcMessage::own + + This property holds whether the message is user's own. + + This property is provided for convenience. It is equivalent + of testing for the IrcMessage::Own flag. + + \par Access function: + \li bool <b>isOwn</b>() const + */ +bool IrcMessage::isOwn() const +{ + return flags() & Own; +} + +/*! + This property holds the message flags. + + \par Access function: + \li \ref IrcMessage::Flag "IrcMessage::Flags" <b>flags</b>() const + \li void <b>setFlags</b>(\ref IrcMessage::Flag "IrcMessage::Flags" flags) (\b Since 3.2) + */ +IrcMessage::Flags IrcMessage::flags() const +{ + Q_D(const IrcMessage); + if (d->flags == -1) { + d->flags = IrcMessage::None; + if (d->connection) { + if (!d->prefix().isEmpty() && d->nick() == d->connection->nickName()) + d->flags |= IrcMessage::Own; + + if ((d->type == IrcMessage::Private || d->type == IrcMessage::Notice) && + network()->activeCapabilities().contains("identify-msg")) { + QString msg = property("content").toString(); + if (msg.startsWith("+")) + d->flags |= IrcMessage::Identified; + else if (msg.startsWith("-")) + d->flags |= IrcMessage::Unidentified; + } + } + } + return IrcMessage::Flags(d->flags); +} + +void IrcMessage::setFlags(IrcMessage::Flags flags) +{ + Q_D(IrcMessage); + d->flags = flags; +} + +/*! + This property holds the message command. + + \par Access functions: + \li QString <b>command</b>() const + \li void <b>setCommand</b>(const QString& command) + */ +QString IrcMessage::command() const +{ + Q_D(const IrcMessage); + return d->command(); +} + +void IrcMessage::setCommand(const QString& command) +{ + Q_D(IrcMessage); + d->setCommand(command); +} + +/*! + This property holds the message sender prefix. + + The prefix consists of \ref nick, \ref ident and \ref host as specified in RFC 1459: + <pre> + <prefix> ::= <\ref nick> [ '!' <\ref ident> ] [ '@' <\ref host> ] + </pre> + + \par Access functions: + \li QString <b>prefix</b>() const + \li void <b>setPrefix</b>(const QString& prefix) + */ +QString IrcMessage::prefix() const +{ + Q_D(const IrcMessage); + return d->prefix(); +} + +void IrcMessage::setPrefix(const QString& prefix) +{ + Q_D(IrcMessage); + d->setPrefix(prefix); +} + +/*! + This property holds the message sender nick. + + Nick is part of the sender \ref prefix as specified in RFC 1459: + <pre> + <b><nick></b> [ '!' <\ref ident> ] [ '@' <\ref host> ] + </pre> + + \par Access function: + \li QString <b>nick</b>() const + */ +QString IrcMessage::nick() const +{ + Q_D(const IrcMessage); + return d->nick(); +} + +/*! + This property holds the message sender ident. + + Ident is part of the sender \ref prefix as specified in RFC 1459: + <pre> + <\ref nick> [ '!' <b><ident></b> ] [ '@' <\ref host> ] + </pre> + + \par Access function: + \li QString <b>ident</b>() const + */ +QString IrcMessage::ident() const +{ + Q_D(const IrcMessage); + return d->ident(); +} + +/*! + This property holds the message sender host. + + Host is part of the sender \ref prefix as specified in RFC 1459: + <pre> + <\ref nick> [ '!' <\ref ident> ] [ '@' <b><host></b> ] + </pre> + + \par Access function: + \li QString <b>host</b>() const + */ +QString IrcMessage::host() const +{ + Q_D(const IrcMessage); + return d->host(); +} + +/*! + This property holds the message parameters. + + \par Access functions: + \li QStringList <b>parameters</b>() const + \li void <b>setParameters</b>(const QStringList& parameters) + */ +QStringList IrcMessage::parameters() const +{ + Q_D(const IrcMessage); + return d->params(); +} + +void IrcMessage::setParameters(const QStringList& parameters) +{ + Q_D(IrcMessage); + d->setParams(parameters); +} + +/*! + This property holds the message time stamp. + + \par Access functions: + \li QDateTime <b>timeStamp</b>() const + \li void <b>setTimeStamp</b>(const QDateTime& timeStamp) + */ +QDateTime IrcMessage::timeStamp() const +{ + Q_D(const IrcMessage); + return d->timeStamp; +} + +void IrcMessage::setTimeStamp(const QDateTime& timeStamp) +{ + Q_D(IrcMessage); + d->timeStamp = timeStamp; +} + +/*! + This property holds the FALLBACK encoding for the message. + + The fallback encoding is used when the message is detected not + to be valid UTF-8 and the consequent auto-detection of message + encoding fails. See QTextCodec::availableCodes() for the list of + supported encodings. + + The default value is 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 IrcMessage::encoding() const +{ + Q_D(const IrcMessage); + return d->encoding; +} + +void IrcMessage::setEncoding(const QByteArray& encoding) +{ + Q_D(IrcMessage); + extern bool irc_is_supported_encoding(const QByteArray& encoding); // ircmessagedecoder.cpp + if (!irc_is_supported_encoding(encoding)) { + qWarning() << "IrcMessage::setEncoding(): unsupported encoding" << encoding; + return; + } + d->encoding = encoding; + d->invalidate(); +} + +/*! + \since 3.1 + + This property holds the message tags. + + \par Access functions: + \li QVariantMap <b>tags</b>() const + \li void <b>setTags</b>(const QVariantMap& tags) + + \sa <a href="http://ircv3.org/specification/message-tags-3.2">IRCv3.2 Message Tags</a> + */ +QVariantMap IrcMessage::tags() const +{ + Q_D(const IrcMessage); + return d->tags(); +} + +void IrcMessage::setTags(const QVariantMap& tags) +{ + Q_D(IrcMessage); + d->setTags(tags); +} + +/*! + Creates a new message from \a data and \a connection. + */ +IrcMessage* IrcMessage::fromData(const QByteArray& data, IrcConnection* connection) +{ + IrcMessage* message = 0; + IrcMessageData md = IrcMessageData::fromData(data); + const QMetaObject* metaObject = irc_command_meta_object(md.command); + if (metaObject) { + message = qobject_cast<IrcMessage*>(metaObject->newInstance(Q_ARG(IrcConnection*, connection))); + Q_ASSERT(message); + message->d_ptr->data = md; + } + return message; +} + +/*! + Creates a new message from \a prefix, \a command and \a parameters with \a connection. + */ +IrcMessage* IrcMessage::fromParameters(const QString& prefix, const QString& command, const QStringList& parameters, IrcConnection* connection) +{ + IrcMessage* message = 0; + const QMetaObject* metaObject = irc_command_meta_object(command); + if (metaObject) { + message = qobject_cast<IrcMessage*>(metaObject->newInstance(Q_ARG(IrcConnection*, connection))); + Q_ASSERT(message); + message->setPrefix(prefix); + message->setCommand(command); + message->setParameters(parameters); + } + return message; +} + +/*! + \property bool IrcMessage::valid + This property is \c true if the message is valid; otherwise \c false. + + A message is considered valid if the prefix is not empty + and the parameters match the message. + + \par Access function: + \li bool <b>isValid</b>() const + */ +bool IrcMessage::isValid() const +{ + Q_D(const IrcMessage); + return d->connection && !prefix().isNull(); +} + +/*! + Returns the message as received from an IRC server. + */ +QByteArray IrcMessage::toData() const +{ + Q_D(const IrcMessage); + return d->content(); +} + +/*! + \class IrcCapabilityMessage ircmessage.h <IrcMessage> + \ingroup message + \brief Represents a capability message. + */ + +/*! + Constructs a new IrcCapabilityMessage with \a connection. + */ +IrcCapabilityMessage::IrcCapabilityMessage(IrcConnection* connection) : IrcMessage(connection) +{ + Q_D(IrcMessage); + d->type = Capability; +} + +/*! + This property holds the subcommand. + + The following capability subcommands are defined: + LS, LIST, REQ, ACK, NAK, CLEAR, END + + \par Access function: + \li QString <b>subCommand</b>() const + */ +QString IrcCapabilityMessage::subCommand() const +{ + Q_D(const IrcMessage); + return d->param(1); +} + +/*! + This property holds the capabilities. + + A list of capabilities may exist for the following + subcommands: LS, LIST, REQ, ACK and NAK. + + \par Access function: + \li QStringList <b>capabilities</b>() const + */ +QStringList IrcCapabilityMessage::capabilities() const +{ + Q_D(const IrcMessage); + QStringList caps; + QStringList params = d->params(); + if (params.count() > 2) + caps = params.last().split(QLatin1Char(' '), QString::SkipEmptyParts); + return caps; +} + +bool IrcCapabilityMessage::isValid() const +{ + return IrcMessage::isValid(); +} + +/*! + \class IrcErrorMessage ircmessage.h <IrcMessage> + \ingroup message + \brief Represents an error message. + */ + +/*! + Constructs a new IrcErrorMessage with \a connection. + */ +IrcErrorMessage::IrcErrorMessage(IrcConnection* connection) : IrcMessage(connection) +{ + Q_D(IrcMessage); + d->type = Error; +} + +/*! + This property holds the error. + + \par Access function: + \li QString <b>error</b>() const + */ +QString IrcErrorMessage::error() const +{ + Q_D(const IrcMessage); + return d->param(0); +} + +bool IrcErrorMessage::isValid() const +{ + return IrcMessage::isValid() && !error().isEmpty(); +} + +/*! + \class IrcInviteMessage ircmessage.h <IrcMessage> + \ingroup message + \brief Represents an invite message. + */ + +/*! + Constructs a new IrcInviteMessage with \a connection. + */ +IrcInviteMessage::IrcInviteMessage(IrcConnection* connection) : IrcMessage(connection) +{ + Q_D(IrcMessage); + d->type = Invite; +} + +/*! + This property holds the user in question. + + \par Access function: + \li QString <b>user</b>() const + */ +QString IrcInviteMessage::user() const +{ + Q_D(const IrcMessage); + return d->param(0); +} + +/*! + This property holds the channel in question. + + \par Access function: + \li QString <b>channel</b>() const + */ +QString IrcInviteMessage::channel() const +{ + Q_D(const IrcMessage); + return d->param(1); +} + +bool IrcInviteMessage::isValid() const +{ + return IrcMessage::isValid() && !user().isEmpty() && !channel().isEmpty(); +} + +/*! + \class IrcJoinMessage ircmessage.h <IrcMessage> + \ingroup message + \brief Represents a join message. + */ + +/*! + Constructs a new IrcJoinMessage with \a connection. + */ +IrcJoinMessage::IrcJoinMessage(IrcConnection* connection) : IrcMessage(connection) +{ + Q_D(IrcMessage); + d->type = Join; +} + +/*! + This property holds the channel in question. + + \par Access function: + \li QString <b>channel</b>() const + */ +QString IrcJoinMessage::channel() const +{ + Q_D(const IrcMessage); + return d->param(0); +} + +bool IrcJoinMessage::isValid() const +{ + return IrcMessage::isValid() && !channel().isEmpty(); +} + +/*! + \class IrcKickMessage ircmessage.h <IrcMessage> + \ingroup message + \brief Represents a kick message. + */ + +/*! + Constructs a new IrcKickMessage with \a connection. + */ +IrcKickMessage::IrcKickMessage(IrcConnection* connection) : IrcMessage(connection) +{ + Q_D(IrcMessage); + d->type = Kick; +} + +/*! + This property holds the channel in question. + + \par Access function: + \li QString <b>channel</b>() const + */ +QString IrcKickMessage::channel() const +{ + Q_D(const IrcMessage); + return d->param(0); +} + +/*! + This property holds the user in question. + + \par Access function: + \li QString <b>user</b>() const + */ +QString IrcKickMessage::user() const +{ + Q_D(const IrcMessage); + return d->param(1); +} + +/*! + This property holds the optional kick reason. + + \par Access function: + \li QString <b>reason</b>() const + */ +QString IrcKickMessage::reason() const +{ + Q_D(const IrcMessage); + return d->param(2); +} + +bool IrcKickMessage::isValid() const +{ + return IrcMessage::isValid() && !channel().isEmpty() && !user().isEmpty(); +} + +/*! + \class IrcModeMessage ircmessage.h <IrcMessage> + \ingroup message + \brief Represents a mode message. + */ + +/*! + \enum IrcModeMessage::Kind + This enum describes the kind of modes. + */ + +/*! + \var IrcModeMessage::Channel + \brief Channel mode + */ + +/*! + \var IrcModeMessage::User + \brief User mode + */ + +/*! + Constructs a new IrcModeMessage with \a connection. + */ +IrcModeMessage::IrcModeMessage(IrcConnection* connection) : IrcMessage(connection) +{ + Q_D(IrcMessage); + d->type = Mode; +} + +/*! + This property holds the target channel or user in question. + + \par Access function: + \li QString <b>target</b>() const + */ +QString IrcModeMessage::target() const +{ + Q_D(const IrcMessage); + return d->param(0); +} + +/*! + This property holds the channel or user mode. + + \par Access function: + \li QString <b>mode</b>() const + */ +QString IrcModeMessage::mode() const +{ + Q_D(const IrcMessage); + return d->param(1); +} + +/*! + This property holds the first mode argument. + + \par Access function: + \li QString <b>argument</b>() const + */ +QString IrcModeMessage::argument() const +{ + Q_D(const IrcMessage); + return d->param(2); +} + +/*! + \since 3.1 + + This property holds the all mode arguments. + + \par Access function: + \li QStringList <b>arguments</b>() const + */ +QStringList IrcModeMessage::arguments() const +{ + Q_D(const IrcMessage); + return d->params().mid(2); +} + +/*! + This property holds whether the message is a reply. + + Mode messages are sent when a mode changes (\c false) + and when joining a channel (\c true). + + \par Access function: + \li bool <b>isReply</b>() const + + \sa Irc::RPL_CHANNELMODEIS + */ +bool IrcModeMessage::isReply() const +{ + Q_D(const IrcMessage); + int rpl = d->command().toInt(); + return rpl == Irc::RPL_CHANNELMODEIS; +} + +/*! + This property holds the kind of the mode. + + \par Access function: + \li Kind <b>kind</b>() const + */ +IrcModeMessage::Kind IrcModeMessage::kind() const +{ + Q_D(const IrcMessage); + const IrcNetwork* network = d->connection->network(); + const QStringList channelModes = network->channelModes(IrcNetwork::AllTypes); + const QString m = mode().remove(QLatin1Char('+')).remove(QLatin1Char('-')); + for (int i = 0; i < m.length(); ++i) { + if (!channelModes.contains(m.at(i))) + return User; + } + return Channel; +} + +bool IrcModeMessage::isValid() const +{ + return IrcMessage::isValid() && !target().isEmpty() && !mode().isEmpty(); +} + +/*! + \class IrcMotdMessage ircmessage.h <IrcMessage> + \ingroup message + \brief Represents a message of the day. + */ + +/*! + Constructs a new IrcMotdMessage with \a connection. + */ +IrcMotdMessage::IrcMotdMessage(IrcConnection* connection) : IrcMessage(connection) +{ + Q_D(IrcMessage); + d->type = Motd; + setCommand(QLatin1String("MOTD")); +} + +/*! + This property holds the message of the day lines. + + \par Access function: + \li QStringList <b>lines</b>() const + */ +QStringList IrcMotdMessage::lines() const +{ + Q_D(const IrcMessage); + return d->params().mid(1); +} + +bool IrcMotdMessage::isValid() const +{ + Q_D(const IrcMessage); + return IrcMessage::isValid() && !d->params().isEmpty(); +} + +/*! + \class IrcNamesMessage ircmessage.h <IrcMessage> + \ingroup message + \brief Represents a names list message. + */ + +/*! + Constructs a new IrcNamesMessage with \a connection. + */ +IrcNamesMessage::IrcNamesMessage(IrcConnection* connection) : IrcMessage(connection) +{ + Q_D(IrcMessage); + d->type = Names; + setCommand(QLatin1String("NAMES")); +} + +/*! + This property holds the channel. + + \par Access function: + \li QString <b>channel</b>() const + */ +QString IrcNamesMessage::channel() const +{ + Q_D(const IrcMessage); + return d->param(0); +} + +/*! + This property holds the list of names. + + \par Access function: + \li QStringList <b>names</b>() const + */ +QStringList IrcNamesMessage::names() const +{ + Q_D(const IrcMessage); + return d->params().mid(1); +} + +bool IrcNamesMessage::isValid() const +{ + Q_D(const IrcMessage); + return IrcMessage::isValid() && !d->params().isEmpty(); +} + +/*! + \class IrcNickMessage ircmessage.h <IrcMessage> + \ingroup message + \brief Represents a nick message. + */ + +/*! + Constructs a new IrcNickMessage with \a connection. + */ +IrcNickMessage::IrcNickMessage(IrcConnection* connection) : IrcMessage(connection) +{ + Q_D(IrcMessage); + d->type = Nick; +} + +/*! + This property holds the old nick. + + This property is provided for symmetry with \ref newNick + and is equal to \ref nick. + + \par Access function: + \li QString <b>oldNick</b>() const + */ +QString IrcNickMessage::oldNick() const +{ + Q_D(const IrcMessage); + return d->nick(); +} + +/*! + This property holds the new nick. + + \par Access function: + \li QString <b>newNick</b>() const + */ +QString IrcNickMessage::newNick() const +{ + Q_D(const IrcMessage); + return d->param(0); +} + +bool IrcNickMessage::isValid() const +{ + return IrcMessage::isValid() && !newNick().isEmpty(); +} + +/*! + \class IrcNoticeMessage ircmessage.h <IrcMessage> + \ingroup message + \brief Represents a notice message. + */ + +/*! + Constructs a new IrcNoticeMessage with \a connection. + */ +IrcNoticeMessage::IrcNoticeMessage(IrcConnection* connection) : IrcMessage(connection) +{ + Q_D(IrcMessage); + d->type = Notice; +} + +/*! + This property holds the target channel or user in question. + + \par Access function: + \li QString <b>target</b>() const + */ +QString IrcNoticeMessage::target() const +{ + Q_D(const IrcMessage); + return d->param(0); +} + +/*! + This property holds the message content. + + \par Access function: + \li QString <b>content</b>() const + */ +QString IrcNoticeMessage::content() const +{ + Q_D(const IrcMessage); + QString msg = d->param(1); + if (flags() & (Identified | Unidentified)) + msg.remove(0, 1); + if (isReply()) { + msg.remove(0, 1); + msg.chop(1); + } + return msg; +} + +/*! + \property bool IrcNoticeMessage::private + This property is \c true if the notice is private, + or \c false if it is a channel notice. + + \par Access function: + \li bool <b>isPrivate</b>() const + */ +bool IrcNoticeMessage::isPrivate() const +{ + Q_D(const IrcMessage); + if (d->connection) + return !target().compare(d->connection->nickName(), Qt::CaseInsensitive); + return false; +} + +/*! + \property bool IrcNoticeMessage::reply + This property is \c true if the message is a reply; otherwise \c false. + + \par Access function: + \li bool <b>isReply</b>() const + */ +bool IrcNoticeMessage::isReply() const +{ + Q_D(const IrcMessage); + QString msg = d->param(1); + return msg.startsWith('\1') && msg.endsWith('\1'); +} + +bool IrcNoticeMessage::isValid() const +{ + return IrcMessage::isValid() && !target().isEmpty() && !content().isEmpty(); +} + +/*! + \class IrcNumericMessage ircmessage.h <IrcMessage> + \ingroup message + \brief Represents a numeric message. + */ + +/*! + Constructs a new IrcNumericMessage with \a connection. + */ +IrcNumericMessage::IrcNumericMessage(IrcConnection* connection) : IrcMessage(connection) +{ + Q_D(IrcMessage); + d->type = Numeric; +} + +/*! + This property holds the numeric code. + + \par Access function: + \li int <b>code</b>() const + */ +int IrcNumericMessage::code() const +{ + Q_D(const IrcMessage); + bool ok = false; + int number = d->command().toInt(&ok); + return ok ? number : -1; +} + +bool IrcNumericMessage::isValid() const +{ + return IrcMessage::isValid() && code() != -1; +} + +/*! + \class IrcPartMessage ircmessage.h <IrcMessage> + \ingroup message + \brief Represents a part message. + */ + +/*! + Constructs a new IrcPartMessage with \a connection. + */ +IrcPartMessage::IrcPartMessage(IrcConnection* connection) : IrcMessage(connection) +{ + Q_D(IrcMessage); + d->type = Part; +} + +/*! + This property holds the channel in question. + + \par Access function: + \li QString <b>channel</b>() const + */ +QString IrcPartMessage::channel() const +{ + Q_D(const IrcMessage); + return d->param(0); +} + +/*! + This property holds the optional part reason. + + \par Access function: + \li QString <b>reason</b>() const + */ +QString IrcPartMessage::reason() const +{ + Q_D(const IrcMessage); + return d->param(1); +} + +bool IrcPartMessage::isValid() const +{ + return IrcMessage::isValid() && !channel().isEmpty(); +} + +/*! + \class IrcPingMessage ircmessage.h <IrcMessage> + \ingroup message + \brief Represents a ping message. + */ + +/*! + Constructs a new IrcPingMessage with \a connection. + */ +IrcPingMessage::IrcPingMessage(IrcConnection* connection) : IrcMessage(connection) +{ + Q_D(IrcMessage); + d->type = Ping; +} + +/*! + This property holds the optional message argument. + + \par Access function: + \li QString <b>argument</b>() const + */ +QString IrcPingMessage::argument() const +{ + Q_D(const IrcMessage); + return d->param(0); +} + +bool IrcPingMessage::isValid() const +{ + return IrcMessage::isValid(); +} + +/*! + \class IrcPongMessage ircmessage.h <IrcMessage> + \ingroup message + \brief Represents a pong message. + */ + +/*! + Constructs a new IrcPongMessage with \a connection. + */ +IrcPongMessage::IrcPongMessage(IrcConnection* connection) : IrcMessage(connection) +{ + Q_D(IrcMessage); + d->type = Pong; +} + +/*! + This property holds the optional message argument. + + \par Access function: + \li QString <b>argument</b>() const + */ +QString IrcPongMessage::argument() const +{ + Q_D(const IrcMessage); + return d->param(1); +} + +bool IrcPongMessage::isValid() const +{ + return IrcMessage::isValid(); +} + +/*! + \class IrcPrivateMessage ircmessage.h <IrcMessage> + \ingroup message + \brief Represents a private message. + */ + +/*! + Constructs a new IrcPrivateMessage with \a connection. + */ +IrcPrivateMessage::IrcPrivateMessage(IrcConnection* connection) : IrcMessage(connection) +{ + Q_D(IrcMessage); + d->type = Private; +} + +/*! + This property holds the target channel or user in question. + + \par Access function: + \li QString <b>target</b>() const + */ +QString IrcPrivateMessage::target() const +{ + Q_D(const IrcMessage); + return d->param(0); +} + +/*! + This property holds the message content. + + \par Access function: + \li QString <b>content</b>() const + */ +QString IrcPrivateMessage::content() const +{ + Q_D(const IrcMessage); + QString msg = d->param(1); + if (flags() & (Identified | Unidentified)) + msg.remove(0, 1); + const bool act = isAction(); + const bool req = isRequest(); + if (act) msg.remove(0, 8); + if (req) msg.remove(0, 1); + if (act || req) msg.chop(1); + return msg; +} + +/*! + \property bool IrcPrivateMessage::private + This property is \c true if the message is private, + or \c false if it is a channel message. + + \par Access function: + \li bool <b>isPrivate</b>() const + */ +bool IrcPrivateMessage::isPrivate() const +{ + Q_D(const IrcMessage); + if (d->connection) + return !target().compare(d->connection->nickName(), Qt::CaseInsensitive); + return false; +} + +/*! + \property bool IrcPrivateMessage::action + This property is \c true if the message is an action; otherwise \c false. + + \par Access function: + \li bool <b>isAction</b>() const + */ +bool IrcPrivateMessage::isAction() const +{ + Q_D(const IrcMessage); + QString msg = d->param(1); + if (flags() & (Identified | Unidentified)) + msg.remove(0, 1); + return msg.startsWith("\1ACTION ") && msg.endsWith('\1'); +} + +/*! + \property bool IrcPrivateMessage::request + This property is \c true if the message is a request; otherwise \c false. + + \par Access function: + \li bool <b>isRequest</b>() const + */ +bool IrcPrivateMessage::isRequest() const +{ + Q_D(const IrcMessage); + QString msg = d->param(1); + if (flags() & (Identified | Unidentified)) + msg.remove(0, 1); + return msg.startsWith('\1') && msg.endsWith('\1') && !isAction(); +} + +bool IrcPrivateMessage::isValid() const +{ + return IrcMessage::isValid() && !target().isEmpty() && !content().isEmpty(); +} + +/*! + \class IrcQuitMessage ircmessage.h <IrcMessage> + \ingroup message + \brief Represents a quit message. + */ + +/*! + Constructs a new IrcQuitMessage with \a connection. + */ +IrcQuitMessage::IrcQuitMessage(IrcConnection* connection) : IrcMessage(connection) +{ + Q_D(IrcMessage); + d->type = Quit; +} + +/*! + This property holds the optional quit reason. + + \par Access function: + \li QString <b>reason</b>() const + */ +QString IrcQuitMessage::reason() const +{ + Q_D(const IrcMessage); + return d->param(0); +} + +bool IrcQuitMessage::isValid() const +{ + return IrcMessage::isValid(); +} + +/*! + \class IrcTopicMessage ircmessage.h <IrcMessage> + \ingroup message + \brief Represents a topic message. + */ + +/*! + Constructs a new IrcTopicMessage with \a connection. + */ +IrcTopicMessage::IrcTopicMessage(IrcConnection* connection) : IrcMessage(connection) +{ + Q_D(IrcMessage); + d->type = Topic; +} + +/*! + This property holds the channel in question. + + \par Access function: + \li QString <b>channel</b>() const + */ +QString IrcTopicMessage::channel() const +{ + Q_D(const IrcMessage); + return d->param(0); +} + +/*! + This property holds the new channel topic. + + \par Access function: + \li QString <b>topic</b>() const + */ +QString IrcTopicMessage::topic() const +{ + Q_D(const IrcMessage); + return d->param(1); +} + +/*! + This property holds whether the message is a reply. + + Topic messages are sent in three situations: + \li as a notification of a topic change (\c false), + \li as a reply when joining a channel (\c true), or + \li as a reply when explicitly querying the channel topic (\c true). + + \par Access function: + \li bool <b>isReply</b>() const + + \sa Irc::RPL_TOPIC, Irc::RPL_NOTOPIC, IrcTopicCommand + */ +bool IrcTopicMessage::isReply() const +{ + Q_D(const IrcMessage); + int rpl = d->command().toInt(); + return rpl == Irc::RPL_TOPIC || rpl == Irc::RPL_NOTOPIC; +} + +bool IrcTopicMessage::isValid() const +{ + return IrcMessage::isValid() && !channel().isEmpty(); +} + +/*! + \since 3.1 + \class IrcWhoReplyMessage ircmessage.h <IrcMessage> + \ingroup message + \brief Represents a reply message to a WHO command. + */ + +/*! + Constructs a new IrcWhoReplyMessage with \a connection. + */ +IrcWhoReplyMessage::IrcWhoReplyMessage(IrcConnection* connection) : IrcMessage(connection) +{ + Q_D(IrcMessage); + d->type = WhoReply; +} + +/*! + This property holds the mask. + + \par Access function: + \li QString <b>mask</b>() const + */ +QString IrcWhoReplyMessage::mask() const +{ + Q_D(const IrcMessage); + return d->param(0); +} + +/*! + This property holds the server of the user. + + \par Access function: + \li QString <b>server</b>() const + */ +QString IrcWhoReplyMessage::server() const +{ + Q_D(const IrcMessage); + return d->param(1); +} + +/*! + \property bool IrcWhoReplyMessage::away + This property holds whether the user is away. + + \par Access function: + \li QString <b>isAway</b>() const + */ +bool IrcWhoReplyMessage::isAway() const +{ + Q_D(const IrcMessage); + return d->param(2).contains("G"); +} + +/*! + \property bool IrcWhoReplyMessage::servOp + This property holds whether the user is a server operator. + + \par Access function: + \li QString <b>isServOp</b>() const + */ +bool IrcWhoReplyMessage::isServOp() const +{ + Q_D(const IrcMessage); + return d->param(2).contains("*"); +} + +/*! + This property holds the real name of the user. + + \par Access function: + \li QString <b>realName</b>() const + */ +QString IrcWhoReplyMessage::realName() const +{ + Q_D(const IrcMessage); + return d->param(3); +} + +bool IrcWhoReplyMessage::isValid() const +{ + return IrcMessage::isValid() && !mask().isEmpty() && !nick().isEmpty(); +} + +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug debug, IrcMessage::Type type) +{ + const int index = IrcMessage::staticMetaObject.indexOfEnumerator("Type"); + QMetaEnum enumerator = IrcMessage::staticMetaObject.enumerator(index); + const char* key = enumerator.valueToKey(type); + debug << (key ? key : "Unknown"); + return debug; +} + +QDebug operator<<(QDebug debug, IrcMessage::Flag flag) +{ + const int index = IrcMessage::staticMetaObject.indexOfEnumerator("Flag"); + QMetaEnum enumerator = IrcMessage::staticMetaObject.enumerator(index); + const char* key = enumerator.valueToKey(flag); + debug << (key ? key : "None"); + return debug; +} + +QDebug operator<<(QDebug debug, IrcMessage::Flags flags) +{ + QStringList lst; + if (flags == IrcMessage::None) + lst << "None"; + if (flags & IrcMessage::Own) + lst << "Own"; + if (flags & IrcMessage::Identified) + lst << "Identified"; + if (flags & IrcMessage::Unidentified) + lst << "Unidentified"; + debug.nospace() << '(' << qPrintable(lst.join("|")) << ')'; + return debug; +} + +QDebug operator<<(QDebug debug, IrcModeMessage::Kind kind) +{ + const int index = IrcModeMessage::staticMetaObject.indexOfEnumerator("Kind"); + QMetaEnum enumerator = IrcModeMessage::staticMetaObject.enumerator(index); + const char* key = enumerator.valueToKey(kind); + debug << (key ? key : "Unknown"); + return debug; +} + +QDebug operator<<(QDebug debug, const IrcMessage* message) +{ + if (!message) + return debug << "IrcMessage(0x0) "; + debug.nospace() << message->metaObject()->className() << '(' << (void*) message; + if (!message->objectName().isEmpty()) + debug.nospace() << ", name=" << qPrintable(message->objectName()); + debug.nospace() << ", flags=" << message->flags(); + if (!message->prefix().isEmpty()) + debug.nospace() << ", prefix=" << qPrintable(message->prefix()); + if (!message->command().isEmpty()) + debug.nospace() << ", command=" << qPrintable(message->command()); + debug.nospace() << ')'; + return debug.space(); +} +#endif // QT_NO_DEBUG_STREAM + +#include "moc_ircmessage.cpp" + +IRC_END_NAMESPACE diff --git a/libcommuni/src/core/ircmessage_p.cpp b/libcommuni/src/core/ircmessage_p.cpp new file mode 100644 index 0000000..e5fdd24 --- /dev/null +++ b/libcommuni/src/core/ircmessage_p.cpp @@ -0,0 +1,284 @@ +/* + 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 "ircmessage_p.h" +#include "ircmessagedecoder_p.h" + +IRC_BEGIN_NAMESPACE + +#ifndef IRC_DOXYGEN +IrcMessagePrivate::IrcMessagePrivate() : + connection(0), type(IrcMessage::Unknown), timeStamp(QDateTime::currentDateTime()), encoding("ISO-8859-15"), flags(-1) +{ +} + +QString IrcMessagePrivate::prefix() const +{ + if (!m_prefix.isExplicit() && m_prefix.isNull() && !data.prefix.isNull()) { + if (data.prefix.startsWith(':')) { + if (data.prefix.length() > 1) + m_prefix = decode(data.prefix.mid(1), encoding); + } else { + // empty (not null) + m_prefix = QString(""); + } + } + return m_prefix.value(); +} + +void IrcMessagePrivate::setPrefix(const QString& prefix) +{ + m_prefix.setValue(prefix); + m_nick.clear(); + m_ident.clear(); + m_host.clear(); +} + +QString IrcMessagePrivate::nick() const +{ + if (m_nick.isNull()) + parsePrefix(prefix(), &m_nick, &m_ident, &m_host); + return m_nick; +} + +QString IrcMessagePrivate::ident() const +{ + if (m_ident.isNull()) + parsePrefix(prefix(), &m_nick, &m_ident, &m_host); + return m_ident; +} + +QString IrcMessagePrivate::host() const +{ + if (m_host.isNull()) + parsePrefix(prefix(), &m_nick, &m_ident, &m_host); + return m_host; +} + +QString IrcMessagePrivate::command() const +{ + if (!m_command.isExplicit() && m_command.isNull() && !data.command.isNull()) + m_command = decode(data.command, encoding); + return m_command.value(); +} + +void IrcMessagePrivate::setCommand(const QString& command) +{ + m_command.setValue(command); +} + +QStringList IrcMessagePrivate::params() const +{ + if (!m_params.isExplicit() && m_params.isNull() && !data.params.isEmpty()) { + QStringList params; + foreach (const QByteArray& param, data.params) + params += decode(param, encoding); + m_params = params; + } + return m_params.value(); +} + +QString IrcMessagePrivate::param(int index) const +{ + return params().value(index); +} + +void IrcMessagePrivate::setParams(const QStringList& params) +{ + m_params.setValue(params); +} + +QVariantMap IrcMessagePrivate::tags() const +{ + if (!m_tags.isExplicit() && m_tags.isNull() && !data.tags.isEmpty()) { + QVariantMap tags; + QMap<QByteArray, QByteArray>::const_iterator it; + for (it = data.tags.constBegin(); it != data.tags.constEnd(); ++it) + tags.insert(decode(it.key(), encoding), decode(it.value(), encoding)); + m_tags = tags; + } + return m_tags.value(); +} + +void IrcMessagePrivate::setTags(const QVariantMap& tags) +{ + m_tags.setValue(tags); +} + +QByteArray IrcMessagePrivate::content() const +{ + if (m_prefix.isExplicit() || m_command.isExplicit() || m_params.isExplicit() || m_tags.isExplicit()) { + QByteArray data; + + // format <tags> + QStringList tt; + const QVariantMap t = tags(); + for (QVariantMap::const_iterator it = t.begin(); it != t.end(); ++it) + tt += it.key() + QLatin1Char('=') + it.value().toString(); + if (!tt.isEmpty()) + data += '@' + tt.join(QLatin1String(";")).toUtf8() + ' '; + + // format <prefix> + const QString p = prefix(); + if (!p.isEmpty()) + data += ':' + p.toUtf8() + ' '; + + // format <command> + data += command().toUtf8(); + + // format <params> + foreach (const QString& param, params()) { + data += ' '; + if (param.contains(QLatin1Char(' '))) + data += ':'; + data += param.toUtf8(); + } + return data; + } + + return data.content; +} + +void IrcMessagePrivate::invalidate() +{ + m_nick.clear(); + m_ident.clear(); + m_host.clear(); + + m_prefix.clear(); + m_command.clear(); + m_params.clear(); + m_tags.clear(); +} + +IrcMessageData IrcMessageData::fromData(const QByteArray& data) +{ + IrcMessageData message; + message.content = data; + + // From RFC 1459: + // <message> ::= [':' <prefix> <SPACE> ] <command> <params> <crlf> + // <prefix> ::= <servername> | <nick> [ '!' <user> ] [ '@' <host> ] + // <command> ::= <letter> { <letter> } | <number> <number> <number> + // <SPACE> ::= ' ' { ' ' } + // <params> ::= <SPACE> [ ':' <trailing> | <middle> <params> ] + // <middle> ::= <Any *non-empty* sequence of octets not including SPACE + // or NUL or CR or LF, the first of which may not be ':'> + // <trailing> ::= <Any, possibly *empty*, sequence of octets not including + // NUL or CR or LF> + + // IRCv3.2 Message Tags + // <message> ::= ['@' <tags> <SPACE>] [':' <prefix> <SPACE> ] <command> <params> <crlf> + // <tags> ::= <tag> [';' <tag>]* + // <tag> ::= <key> ['=' <value>] + // <key> ::= [ <vendor> '/' ] <sequence of letters, digits, hyphens (`-`)> + // <value> ::= <sequence of any characters except NUL, BELL, CR, LF, semicolon (`;`) and SPACE> + // <vendor> ::= <host> + + QByteArray process = data; + + // parse <tags> + if (process.startsWith('@')) { + process.remove(0, 1); + QByteArray tags = process.left(process.indexOf(' ')); + foreach (const QByteArray& tag, tags.split(';')) { + const int idx = tag.indexOf('='); + if (idx != -1) + message.tags.insert(tag.left(idx), tag.mid(idx + 1)); + else + message.tags.insert(tag, QByteArray()); + } + process.remove(0, tags.length() + 1); + } + + // parse <prefix> + if (process.startsWith(':')) { + message.prefix = process.left(process.indexOf(' ')); + process.remove(0, message.prefix.length() + 1); + } else { + // empty (not null) + message.prefix = QByteArray(""); + } + + // parse <command> + message.command = process.mid(0, process.indexOf(' ')); + process.remove(0, message.command.length() + 1); + + // parse <params> + while (!process.isEmpty()) { + if (process.startsWith(':')) { + process.remove(0, 1); + message.params += process; + process.clear(); + } else { + QByteArray param = process.mid(0, process.indexOf(' ')); + process.remove(0, param.length() + 1); + message.params += param; + } + } + + return message; +} + +QString IrcMessagePrivate::decode(const QByteArray& data, const QByteArray& encoding) +{ + // TODO: not thread safe + static IrcMessageDecoder decoder; + return decoder.decode(data, encoding); +} + +bool IrcMessagePrivate::parsePrefix(const QString& prefix, QString* nick, QString* ident, QString* host) +{ + const QString trimmed = prefix.trimmed(); + if (trimmed.contains(QLatin1Char(' '))) + return false; + + const int len = trimmed.length(); + const int ex = trimmed.indexOf(QLatin1Char('!')); + const int at = trimmed.indexOf(QLatin1Char('@')); + + if (ex == -1 && at == -1) { + if (nick) *nick = trimmed; + } else if (ex > 0 && at > 0 && ex + 1 < at && at < len - 1) { + if (nick) *nick = trimmed.mid(0, ex); + if (ident) *ident = trimmed.mid(ex + 1, at - ex - 1); + if (host) *host = trimmed.mid(at + 1); + } else if (ex > 0 && ex < len - 1 && at == -1) { + if (nick) *nick = trimmed.mid(0, ex); + if (ident) *ident = trimmed.mid(ex + 1); + } else if (at > 0 && at < len - 1 && ex == -1) { + if (nick) *nick = trimmed.mid(0, at); + if (host) *host = trimmed.mid(at + 1); + } else { + return false; + } + return true; +} +#endif // IRC_DOXYGEN + +IRC_END_NAMESPACE diff --git a/libcommuni/src/core/ircmessagebuilder.cpp b/libcommuni/src/core/ircmessagebuilder.cpp new file mode 100644 index 0000000..298ac79 --- /dev/null +++ b/libcommuni/src/core/ircmessagebuilder.cpp @@ -0,0 +1,121 @@ +/* + 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 "ircmessagebuilder_p.h" +#include "ircmessage.h" +#include "irc.h" + +IRC_BEGIN_NAMESPACE + +#ifndef IRC_DOXYGEN +IrcMessageBuilder::IrcMessageBuilder(IrcConnection* connection) +{ + d.connection = connection; + d.message = 0; +} + +void IrcMessageBuilder::processMessage(IrcNumericMessage* message) +{ + switch (message->code()) { + case Irc::RPL_MOTDSTART: + d.message = new IrcMotdMessage(d.connection); + d.message->setPrefix(message->prefix()); + d.message->setParameters(QStringList(message->parameters().value(0))); + break; + case Irc::RPL_MOTD: + d.message->setParameters(d.message->parameters() << message->parameters().value(1)); + break; + case Irc::RPL_ENDOFMOTD: + d.message->setTimeStamp(message->timeStamp()); + emit messageReceived(d.message); + d.message = 0; + break; + + case Irc::RPL_NAMREPLY: { + if (!d.message) + d.message = new IrcNamesMessage(d.connection); + d.message->setPrefix(message->prefix()); + int count = message->parameters().count(); + QString channel = message->parameters().value(count - 2); + QStringList names = d.message->parameters().mid(1); + names += message->parameters().value(count - 1).split(QLatin1Char(' '), QString::SkipEmptyParts); + d.message->setParameters(QStringList() << channel << names); + break; + } + case Irc::RPL_ENDOFNAMES: + d.message->setTimeStamp(message->timeStamp()); + emit messageReceived(d.message); + d.message = 0; + break; + + case Irc::RPL_TOPIC: + case Irc::RPL_NOTOPIC: + d.message = new IrcTopicMessage(d.connection); + d.message->setPrefix(message->prefix()); + d.message->setTimeStamp(message->timeStamp()); + d.message->setCommand(QString::number(message->code())); + d.message->setParameters(QStringList() << message->parameters().value(1) << message->parameters().value(2)); + emit messageReceived(d.message); + d.message = 0; + break; + + case Irc::RPL_WHOREPLY: { + d.message = new IrcWhoReplyMessage(d.connection); + d.message->setPrefix(message->parameters().value(5) // nick + + QLatin1Char('!') + message->parameters().value(2) // ident + + QLatin1Char('@') + message->parameters().value(3)); // host + d.message->setTimeStamp(message->timeStamp()); + d.message->setCommand(QString::number(message->code())); + d.message->setParameters(QStringList() << message->parameters().value(1) // mask + << message->parameters().value(4) // server + << message->parameters().value(6)); // status + QString last = message->parameters().value(7); + int index = last.indexOf(QLatin1Char(' ')); // ignore hopcount + if (index != -1) + d.message->setParameters(d.message->parameters() << last.mid(index + 1)); // real name + emit messageReceived(d.message); + d.message = 0; + break; + } + + case Irc::RPL_CHANNELMODEIS: + d.message = new IrcModeMessage(d.connection); + d.message->setPrefix(message->prefix()); + d.message->setTimeStamp(message->timeStamp()); + d.message->setCommand(QString::number(message->code())); + d.message->setParameters(message->parameters().mid(1)); + emit messageReceived(d.message); + d.message = 0; + break; + } +} +#endif // IRC_DOXYGEN + +#include "moc_ircmessagebuilder_p.cpp" + +IRC_END_NAMESPACE diff --git a/libcommuni/src/core/ircmessagedecoder.cpp b/libcommuni/src/core/ircmessagedecoder.cpp new file mode 100644 index 0000000..1e2eb25 --- /dev/null +++ b/libcommuni/src/core/ircmessagedecoder.cpp @@ -0,0 +1,73 @@ +/* + 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 "ircmessagedecoder_p.h" +#include <IrcGlobal> +#include <QSet> + +#ifndef IRC_DOXYGEN +extern "C" { + int IsUTF8Text(const char* utf8, int len); +} + +IRC_BEGIN_NAMESPACE + +IRC_CORE_EXPORT bool irc_is_supported_encoding(const QByteArray& encoding) +{ + static QSet<QByteArray> codecs = QTextCodec::availableCodecs().toSet(); + return codecs.contains(encoding); +} + +IrcMessageDecoder::IrcMessageDecoder() +{ + initialize(); +} + +IrcMessageDecoder::~IrcMessageDecoder() +{ + uninitialize(); +} + +QString IrcMessageDecoder::decode(const QByteArray& data, const QByteArray& encoding) const +{ + QTextCodec* codec = 0; + if (IsUTF8Text(data, data.length())) { + codec = QTextCodec::codecForName("UTF-8"); + } else { + QByteArray name = codecForData(data); + codec = QTextCodec::codecForName(name); + } + + if (!codec) + codec = QTextCodec::codecForName(encoding); + Q_ASSERT(codec); + return codec->toUnicode(data); +} +#endif // IRC_DOXYGEN + +IRC_END_NAMESPACE diff --git a/libcommuni/src/core/ircmessagedecoder_icu.cpp b/libcommuni/src/core/ircmessagedecoder_icu.cpp new file mode 100644 index 0000000..2c303fc --- /dev/null +++ b/libcommuni/src/core/ircmessagedecoder_icu.cpp @@ -0,0 +1,68 @@ +/* + 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 "ircmessagedecoder_p.h" +#include <unicode/ucsdet.h> + +IRC_BEGIN_NAMESPACE + +#ifndef IRC_DOXYGEN +#define UCSD(x) reinterpret_cast<UCharsetDetector*>(x) + +void IrcMessageDecoder::initialize() +{ + UErrorCode status = U_ZERO_ERROR; + d.detector = ucsdet_open(&status); + if (U_FAILURE(status)) + qWarning("IrcMessageDecoder: ICU initialization failed: %s", u_errorName(status)); +} + +void IrcMessageDecoder::uninitialize() +{ + ucsdet_close(UCSD(d.detector)); +} + +QByteArray IrcMessageDecoder::codecForData(const QByteArray &data) const +{ + QByteArray encoding; + UErrorCode status = U_ZERO_ERROR; + if (d.detector) { + ucsdet_setText(UCSD(d.detector), data.constData(), data.length(), &status); + if (!U_FAILURE(status)) { + const UCharsetMatch* match = ucsdet_detect(UCSD(d.detector), &status); + if (match && !U_FAILURE(status)) + encoding = ucsdet_getName(match, &status); + } + } + if (U_FAILURE(status)) + qWarning("IrcMessageDecoder::codecForData() failed: %s", u_errorName(status)); + return encoding; +} +#endif // IRC_DOXYGEN + +IRC_END_NAMESPACE diff --git a/libcommuni/src/core/ircmessagedecoder_uchardet.cpp b/libcommuni/src/core/ircmessagedecoder_uchardet.cpp new file mode 100644 index 0000000..8f0be8b --- /dev/null +++ b/libcommuni/src/core/ircmessagedecoder_uchardet.cpp @@ -0,0 +1,56 @@ +/* + 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 "ircmessagedecoder_p.h" +#include "uchardet.h" + +IRC_BEGIN_NAMESPACE + +#ifndef IRC_DOXYGEN +#define UCD(x) reinterpret_cast<uchardet_t>(x) + +void IrcMessageDecoder::initialize() +{ + d.detector = uchardet_new(); +} + +void IrcMessageDecoder::uninitialize() +{ + uchardet_delete(UCD(d.detector)); +} + +QByteArray IrcMessageDecoder::codecForData(const QByteArray &data) const +{ + uchardet_reset(UCD(d.detector)); + uchardet_handle_data(UCD(d.detector), data.constData(), data.length()); + uchardet_data_end(UCD(d.detector)); + return uchardet_get_charset(UCD(d.detector)); +} +#endif // IRC_DOXYGEN + +IRC_END_NAMESPACE diff --git a/libcommuni/src/core/ircnetwork.cpp b/libcommuni/src/core/ircnetwork.cpp new file mode 100644 index 0000000..d73a9be --- /dev/null +++ b/libcommuni/src/core/ircnetwork.cpp @@ -0,0 +1,687 @@ +/* + 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 "ircnetwork.h" +#include "ircnetwork_p.h" +#include "ircconnection_p.h" +#include "ircprotocol.h" +#include "ircconnection.h" +#include "irccommand.h" +#include <QMetaEnum> +#include <QPointer> + +IRC_BEGIN_NAMESPACE + +/*! + \file ircnetwork.h + \brief \#include <IrcNetwork> + */ + +/*! + \class IrcNetwork ircnetwork.h IrcNetwork + \ingroup core + \brief Provides network information and capability management. + + \section info Network information + + IrcNetwork provides various information about the IRC network of a + \ref IrcConnection::network "connection". This includes the \ref name + "network name", supported \ref channelTypes "channel types", channel + user \ref modes "mode characters" and \ref prefixes "prefix letters", + and various \ref numericLimit "numeric limitations", such as the maximum + nick, channel, topic and message lengths. + + Furthermore, IrcNetwork provides convenient methods for converting channel user + \ref modeToPrefix() "modes to prefixes" and \ref prefixToMode() "vice versa" and + testing whether a target name \ref isChannel() "is a channel". + + \note Most properties have empty values until the network + information has been \ref initialized. + + \section capabilities Capability management + + IrcNetwork also provides means for capability management. It maintais a + list of \ref availableCapabilities "available" and \ref activeCapabilities + "active" capabilities, automatically \ref requestedCapabilities "requests" + desired capabilities, and provides convenient methods for \ref requestCapability() + "manual capability requests". + + \sa IrcConnection::network + */ + +/*! + \fn void IrcNetwork::requestingCapabilities() + + This signal is emitted when capabilities are being requested. + + Normally it is enough to add the desired capabilities to the + list of \ref requestedCapabilities "requested capabilities". + Connect to this signal in order to implement more advanced + capability handling eg. based on which capabilities are \ref + availableCapabilities "available". + + \sa requestedCapabilities, availableCapabilities + */ + +/*! + \enum IrcNetwork::ModeType + This enum describes the channel mode types. + */ + +/*! + \var IrcNetwork::TypeA + \brief Type A modes + + Modes that add or remove an address to or from a list. + These modes always take a parameter when sent by the server to a + client; when sent by a client, they may be specified without a + parameter, which requests the server to display the current + contents of the corresponding list on the channel to the client. + */ + +/*! + \var IrcNetwork::TypeB + \brief Type B modes + + Modes that change a setting on the channel. These modes + always take a parameter. + */ + +/*! + \var IrcNetwork::TypeC + \brief Type C modes + + Modes that change a setting on the channel. These modes + take a parameter only when set; the parameter is absent when the + mode is removed both in the client's and server's MODE command. + */ + +/*! + \var IrcNetwork::TypeD + \brief Type D modes + + Modes that change a setting on the channel. These modes + never take a parameter. + */ + +/*! + \var IrcNetwork::AllTypes + \brief All type modes + */ + +/*! + \enum IrcNetwork::Limit + This enum describes the numeric limit types. + */ + +/*! + \var IrcNetwork::NickLength + \brief The maximum nick name length + */ + +/*! + \var IrcNetwork::ChannelLength + \brief The maximum channel name length + */ + +/*! + \var IrcNetwork::TopicLength + \brief The maximum channel topic length + */ + +/*! + \var IrcNetwork::MessageLength + \brief The maximum message length + */ + +/*! + \var IrcNetwork::KickReasonLength + \brief The maximum kick reason length + */ + +/*! + \var IrcNetwork::AwayReasonLength + \brief The maximum away reason length + */ + +/*! + \var IrcNetwork::ModeCount + \brief The maximum number of channel modes allowed per mode command + */ + +#ifndef IRC_DOXYGEN +IrcNetworkPrivate::IrcNetworkPrivate() : q_ptr(0), initialized(false) +{ +} + +static QHash<QString, int> numericValues(const QString& parameter) +{ + QHash<QString, int> values; + const QStringList keyValues = parameter.split(",", QString::SkipEmptyParts); + foreach (const QString& keyValue, keyValues) + values.insert(keyValue.section(":", 0, 0), keyValue.section(":", 1, 1).toInt()); + return values; +} + +void IrcNetworkPrivate::setInfo(const QHash<QString, QString>& info) +{ + Q_Q(IrcNetwork); + if (info.contains("NETWORK")) + setName(info.value("NETWORK")); + if (info.contains("PREFIX")) { + const QString pfx = info.value("PREFIX"); + setModes(pfx.mid(1, pfx.indexOf(')') - 1).split("", QString::SkipEmptyParts)); + setPrefixes(pfx.mid(pfx.indexOf(')') + 1).split("", QString::SkipEmptyParts)); + } + if (info.contains("CHANTYPES")) + setChannelTypes(info.value("CHANTYPES").split("", QString::SkipEmptyParts)); + + // TODO: + if (info.contains("NICKLEN")) + numericLimits.insert("NICKLEN", info.value("NICKLEN").toInt()); + if (info.contains("CHANNELLEN")) + numericLimits.insert("CHANNELLEN", info.value("CHANNELLEN").toInt()); + if (info.contains("TOPICLEN")) + numericLimits.insert("TOPICLEN", info.value("TOPICLEN").toInt()); + if (info.contains("KICKLEN")) + numericLimits.insert("KICKLEN", info.value("KICKLEN").toInt()); + if (info.contains("AWAYLEN")) + numericLimits.insert("AWAYLEN", info.value("AWAYLEN").toInt()); + if (info.contains("MODES")) + numericLimits.insert("MODES", info.value("MODES").toInt()); + if (info.contains("CHANMODES")) + channelModes = info.value("CHANMODES").split(",", QString::SkipEmptyParts); + if (info.contains("MAXLIST")) + modeLimits = numericValues(info.value("MAXLIST")); + if (info.contains("CHANLIMIT")) + channelLimits = numericValues(info.value("CHANLIMIT")); + if (info.contains("TARGMAX")) + targetLimits = numericValues(info.value("TARGMAX")); + + if (!initialized) { + initialized = true; + emit q->initialized(); + } +} + +void IrcNetworkPrivate::setAvailableCapabilities(const QSet<QString>& capabilities) +{ + Q_Q(IrcNetwork); + if (availableCaps != capabilities) { + availableCaps = capabilities; + emit q->availableCapabilitiesChanged(availableCaps.toList()); + } +} + +void IrcNetworkPrivate::setActiveCapabilities(const QSet<QString>& capabilities) +{ + Q_Q(IrcNetwork); + if (activeCaps != capabilities) { + activeCaps = capabilities; + emit q->activeCapabilitiesChanged(activeCaps.toList()); + } +} + +void IrcNetworkPrivate::setName(const QString& value) +{ + Q_Q(IrcNetwork); + if (name != value) { + name = value; + emit q->nameChanged(value); + } +} + +void IrcNetworkPrivate::setModes(const QStringList& value) +{ + Q_Q(IrcNetwork); + if (modes != value) { + modes = value; + emit q->modesChanged(value); + } +} + +void IrcNetworkPrivate::setPrefixes(const QStringList& value) +{ + Q_Q(IrcNetwork); + if (prefixes != value) { + prefixes = value; + emit q->prefixesChanged(value); + } +} + +void IrcNetworkPrivate::setChannelTypes(const QStringList& value) +{ + Q_Q(IrcNetwork); + if (channelTypes != value) { + channelTypes = value; + emit q->channelTypesChanged(value); + } +} +#endif // IRC_DOXYGEN + +/*! + \internal + Constructs a new network object for IRC \a connection. + */ +IrcNetwork::IrcNetwork(IrcConnection* connection) : QObject(connection), d_ptr(new IrcNetworkPrivate) +{ + Q_D(IrcNetwork); + d->q_ptr = this; + d->connection = connection; +} + +/*! + \internal + Destructs the IRC network. + */ +IrcNetwork::~IrcNetwork() +{ +} + +/*! + \property bool IrcNetwork::initialized + This property holds whether the network information has been initialized. + + Most properties have empty values until the server provides the + relevant network information during the client-server handshake. + + \par Access function: + \li bool <b>isInitialized</b>() const + + \par Notifier signal: + \li void <b>initialized</b>() + */ +bool IrcNetwork::isInitialized() +{ + Q_D(const IrcNetwork); + return d->initialized; +} + +/*! + This property holds the network name. + + \par Access function: + \li QString <b>name</b>() const + + \par Notifier signal: + \li void <b>nameChanged</b>(const QString& name) + */ +QString IrcNetwork::name() const +{ + Q_D(const IrcNetwork); + return d->name; +} + +/*! + This property holds the supported channel user mode letters. + + Examples of typical channel user modes: + Description | Mode | Prefix + -----------------|------------|------- + Channel operator | \b o | @ + Voiced user | \b v | + + + \par Access function: + \li QStringList <b>modes</b>() const + + \par Notifier signal: + \li void <b>modesChanged</b>(const QStringList& modes) + + \sa prefixes, modeToPrefix() + */ +QStringList IrcNetwork::modes() const +{ + Q_D(const IrcNetwork); + return d->modes; +} + +/*! + This property holds the supported channel user mode prefix characters. + + Examples of typical channel user modes: + Description | Mode | Prefix + -----------------|------------|------- + Channel operator | o | \b @ + Voiced user | v | \b + + + \par Access function: + \li QStringList <b>prefixes</b>() const + + \par Notifier signal: + \li void <b>prefixesChanged</b>(const QStringList& prefixes) + + \sa modes, prefixToMode() + */ +QStringList IrcNetwork::prefixes() const +{ + Q_D(const IrcNetwork); + return d->prefixes; +} + +/*! + Converts a channel user mode letter to a prefix character. + + \sa modes, prefixToMode() + */ +QString IrcNetwork::modeToPrefix(const QString& mode) const +{ + Q_D(const IrcNetwork); + return d->prefixes.value(d->modes.indexOf(mode)); +} + +/*! + Converts a channel mode prefix character to a mode letter. + + \sa prefixes, modeToPrefix() + */ +QString IrcNetwork::prefixToMode(const QString& prefix) const +{ + Q_D(const IrcNetwork); + return d->modes.value(d->prefixes.indexOf(prefix)); +} + +/*! + This property holds the supported channel type prefix characters. + + Examples of typical channel types: + Description | Type | Example + -----------------|------|--------- + Normal channel | \# | \#communi + Local channel | & | &foo + + \par Access function: + \li QStringList <b>channelTypes</b>() const + + \par Notifier signal: + \li void <b>channelTypesChanged</b>(const QStringList& types) + */ +QStringList IrcNetwork::channelTypes() const +{ + Q_D(const IrcNetwork); + return d->channelTypes; +} + +/*! + Returns \c true if the \a name is a channel. + + \code + QString name = ...; + if (connection->network()->isChannel(name)) + doSomeChannelAction(name); + \endcode + + \sa channelTypes + */ +bool IrcNetwork::isChannel(const QString& name) const +{ + Q_D(const IrcNetwork); + return !name.isEmpty() && d->channelTypes.contains(name.at(0)); +} + +/*! + Returns the supported channel modes for specified \a types. + + \sa ModeType + */ +QStringList IrcNetwork::channelModes(IrcNetwork::ModeTypes types) const +{ + Q_D(const IrcNetwork); + QStringList modes; + if (types & TypeA) + modes += d->channelModes.value(0).split("", QString::SkipEmptyParts); + if (types & TypeB) + modes += d->channelModes.value(1).split("", QString::SkipEmptyParts); + if (types & TypeC) + modes += d->channelModes.value(2).split("", QString::SkipEmptyParts); + if (types & TypeD) + modes += d->channelModes.value(3).split("", QString::SkipEmptyParts); + return modes; +} + +/*! + Returns a numeric type of \a limit, or \c -1 if the limitation is not known. + + \sa modeLimit(), channelLimit(), targetLimit() + */ +int IrcNetwork::numericLimit(Limit limit) const +{ + Q_D(const IrcNetwork); + QString key; + switch (limit) { + case NickLength: key = QLatin1String("NICKLEN"); break; + case ChannelLength: key = QLatin1String("CHANNELLEN"); break; + case TopicLength: key = QLatin1String("TOPICLEN"); break; + case MessageLength: return 512; // RFC 1459 + case KickReasonLength: key = QLatin1String("KICKLEN"); break; + case AwayReasonLength: key = QLatin1String("AWAYLEN"); break; + case ModeCount: key = QLatin1String("MODES"); break; + } + return d->numericLimits.value(key, -1); +} + +/*! + Returns the limit of entries in the list per \a mode, or \c -1 if the limitation is not known. + + \sa modes() + */ +int IrcNetwork::modeLimit(const QString& mode) const +{ + Q_D(const IrcNetwork); + return d->modeLimits.value(mode); +} + +/*! + Returns the limit for a \a type of channels, or \c -1 if the limitation is not known. + + \sa channelTypes() + */ +int IrcNetwork::channelLimit(const QString& type) const +{ + Q_D(const IrcNetwork); + return d->channelLimits.value(type); +} + +/*! + Returns the limit of targets for a \a command, or \c -1 if the limitation is not known. + */ +int IrcNetwork::targetLimit(const QString& command) const +{ + Q_D(const IrcNetwork); + return d->targetLimits.value(command); +} + +/*! + This property holds the available capabilities. + + \par Access function: + \li QStringList <b>availableCapabilities</b>() const + + \par Notifier signal: + \li void <b>availableCapabilitiesChanged</b>(const QStringList& capabilities) + + \sa requestedCapabilities, activeCapabilities + */ +QStringList IrcNetwork::availableCapabilities() const +{ + Q_D(const IrcNetwork); + return d->availableCaps.toList(); +} + +/*! + This property holds the active capabilities. + + \par Access function: + \li QStringList <b>activeCapabilities</b>() const + + \par Notifier signal: + \li void <b>activeCapabilitiesChanged</b>(const QStringList& capabilities) + + \sa requestedCapabilities, availableCapabilities + */ +QStringList IrcNetwork::activeCapabilities() const +{ + Q_D(const IrcNetwork); + return d->activeCaps.toList(); +} + +/*! + Returns \c true if the \a capability is \b available. + + \sa availableCapabilities + */ +bool IrcNetwork::hasCapability(const QString& capability) const +{ + Q_D(const IrcNetwork); + return d->availableCaps.contains(capability); +} + +/*! + Returns \c true if the \a capability is \b active. + + \sa activeCapabilities + */ +bool IrcNetwork::isCapable(const QString& capability) const +{ + Q_D(const IrcNetwork); + return d->activeCaps.contains(capability); +} + +/*! + Requests the specified \a capability. + + \note The \a capability is NOT added to the list of \ref requestedCapabilities + "requested capabilities" to avoid them "piling up". + */ +bool IrcNetwork::requestCapability(const QString& capability) +{ + Q_D(IrcNetwork); + if (d->connection) + return d->connection->sendCommand(IrcCommand::createCapability(QLatin1String("REQ"), capability)); + return false; +} + +/*! + Requests the specified \a capabilities. + + \note The \a capabilities are NOT added to the list of \ref requestedCapabilities + "requested capabilities" to avoid them "piling up". + */ +bool IrcNetwork::requestCapabilities(const QStringList& capabilities) +{ + Q_D(IrcNetwork); + if (d->connection && d->connection->isActive()) + return d->connection->sendCommand(IrcCommand::createCapability(QLatin1String("REQ"), capabilities)); + return false; +} + +/*! + This property holds the requested capabilities. + + These capabilities are automatically requested during the handshake, + right after requestingCapabilities() has been emitted. + + \par Access functions: + \li QStringList <b>requestedCapabilities</b>() const + \li void <b>setRequestedCapabilities</b>(const QStringList& capabilities) + + \par Notifier signal: + \li void <b>requestedCapabilitiesChanged</b>(const QStringList& capabilities) + + \sa availableCapabilities, activeCapabilities + */ +QStringList IrcNetwork::requestedCapabilities() const +{ + Q_D(const IrcNetwork); + return d->requestedCaps.toList(); +} + +void IrcNetwork::setRequestedCapabilities(const QStringList& capabilities) +{ + Q_D(IrcNetwork); + const QSet<QString> caps = capabilities.toSet(); + if (d->requestedCaps != caps) { + d->requestedCaps = caps; + emit requestedCapabilitiesChanged(caps.toList()); + } +} + +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug debug, IrcNetwork::Limit limit) +{ + const int index = IrcNetwork::staticMetaObject.indexOfEnumerator("Limit"); + QMetaEnum enumerator = IrcNetwork::staticMetaObject.enumerator(index); + const char* key = enumerator.valueToKey(limit); + debug << (key ? key : "Unknown"); + return debug; +} + +QDebug operator<<(QDebug debug, IrcNetwork::ModeType type) +{ + const int index = IrcNetwork::staticMetaObject.indexOfEnumerator("ModeType"); + QMetaEnum enumerator = IrcNetwork::staticMetaObject.enumerator(index); + const char* key = enumerator.valueToKey(type); + debug << (key ? key : "Unknown"); + return debug; +} + +QDebug operator<<(QDebug debug, IrcNetwork::ModeTypes types) +{ + QStringList lst; + if (types == IrcNetwork::AllTypes) { + lst << "AllTypes"; + } else { + if (types & IrcNetwork::TypeA) + lst << "TypeA"; + if (types & IrcNetwork::TypeB) + lst << "TypeB"; + if (types & IrcNetwork::TypeC) + lst << "TypeC"; + if (types & IrcNetwork::TypeD) + lst << "TypeD"; + } + debug.nospace() << '(' << qPrintable(lst.join("|")) << ')'; + return debug; +} + +QDebug operator<<(QDebug debug, const IrcNetwork* network) +{ + if (!network) + return debug << "IrcNetwork(0x0) "; + debug.nospace() << network->metaObject()->className() << '(' << (void*) network; + if (!network->objectName().isEmpty()) + debug.nospace() << ", name=" << qPrintable(network->objectName()); + if (!network->name().isEmpty()) + debug.nospace() << ", network=" << qPrintable(network->name()); + debug.nospace() << ')'; + return debug.space(); +} +#endif // QT_NO_DEBUG_STREAM + +#include "moc_ircnetwork.cpp" + +IRC_END_NAMESPACE diff --git a/libcommuni/src/core/ircprotocol.cpp b/libcommuni/src/core/ircprotocol.cpp new file mode 100644 index 0000000..9d42126 --- /dev/null +++ b/libcommuni/src/core/ircprotocol.cpp @@ -0,0 +1,480 @@ +/* + 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 "ircprotocol.h" +#include "ircconnection_p.h" +#include "ircmessagebuilder_p.h" +#include "ircnetwork_p.h" +#include "ircconnection.h" +#include "ircmessage.h" +#include "irccommand.h" +#include "irc.h" +#include <QDebug> + +IRC_BEGIN_NAMESPACE + +/*! + \file ircprotocol.h + \brief \#include <IrcProtocol> + */ + +/*! + \since 3.2 + \class IrcProtocol ircprotocol.h IrcProtocol + \ingroup core + \brief Implements the IRC protocol and provides means for implementing support for custom protocols. + + \sa IrcConnection::protocol + */ + +#ifndef IRC_DOXYGEN +class IrcProtocolPrivate +{ + Q_DECLARE_PUBLIC(IrcProtocol) + +public: + IrcProtocolPrivate(); + + void authenticate(bool secure); + + void readLines(const QByteArray& delimiter); + void processLine(const QByteArray& line); + + void handleNumericMessage(IrcNumericMessage* msg); + void handlePrivateMessage(IrcPrivateMessage* msg); + void handleCapabilityMessage(IrcCapabilityMessage* msg); + + void _irc_pauseHandshake(); + void _irc_resumeHandshake(); + + IrcProtocol* q_ptr; + IrcConnection* connection; + IrcMessageBuilder* builder; + QHash<QString, QString> info; + QByteArray buffer; + bool resumed; + bool authed; +}; + +IrcProtocolPrivate::IrcProtocolPrivate() : q_ptr(0), connection(0), builder(0), resumed(false), authed(false) +{ +} + +void IrcProtocolPrivate::authenticate(bool secure) +{ + const QString password = connection->password(); + if (!password.isEmpty()) { + if (secure) { + const QByteArray userName = connection->userName().toUtf8(); + const QByteArray data = userName + '\0' + userName + '\0' + password.toUtf8(); + authed = connection->sendData("AUTHENTICATE " + data.toBase64()); + } else { + authed = connection->sendRaw(QString("PASS %1").arg(password)); + } + } +} + +void IrcProtocolPrivate::readLines(const QByteArray& delimiter) +{ + int i = -1; + while ((i = buffer.indexOf(delimiter)) != -1) { + QByteArray line = buffer.left(i).trimmed(); + buffer = buffer.mid(i + delimiter.length()); + if (!line.isEmpty()) + processLine(line); + } +} + +void IrcProtocolPrivate::processLine(const QByteArray& line) +{ + Q_Q(IrcProtocol); + static bool dbg = qgetenv("IRC_DEBUG").toInt(); + if (dbg) qDebug() << line; + + if (line.startsWith("AUTHENTICATE") && !connection->saslMechanism().isEmpty()) { + const QList<QByteArray> args = line.split(' '); + if (args.count() == 2 && args.at(1) == "+") + authenticate(true); + if (!connection->isConnected()) + QMetaObject::invokeMethod(q, "_irc_resumeHandshake", Qt::QueuedConnection); + return; + } + + IrcMessage* msg = IrcMessage::fromData(line, connection); + if (msg) { + msg->setEncoding(connection->encoding()); + + switch (msg->type()) { + case IrcMessage::Capability: + handleCapabilityMessage(static_cast<IrcCapabilityMessage*>(msg)); + break; + case IrcMessage::Nick: + if (msg->flags() & IrcMessage::Own) + q->setNickName(static_cast<IrcNickMessage*>(msg)->newNick()); + break; + case IrcMessage::Numeric: + handleNumericMessage(static_cast<IrcNumericMessage*>(msg)); + break; + case IrcMessage::Ping: + connection->sendRaw("PONG " + static_cast<IrcPingMessage*>(msg)->argument()); + break; + case IrcMessage::Private: + handlePrivateMessage(static_cast<IrcPrivateMessage*>(msg)); + break; + default: + break; + } + q->receiveMessage(msg); + } +} + +void IrcProtocolPrivate::handleNumericMessage(IrcNumericMessage* msg) +{ + Q_Q(IrcProtocol); + switch (msg->code()) { + case Irc::RPL_WELCOME: + q->setNickName(msg->parameters().value(0)); + q->setStatus(IrcConnection::Connected); + break; + case Irc::RPL_ISUPPORT: { + foreach (const QString& param, msg->parameters().mid(1)) { + QStringList keyValue = param.split("=", QString::SkipEmptyParts); + info.insert(keyValue.value(0), keyValue.value(1)); + } + break; + } + case Irc::ERR_NOMOTD: + case Irc::RPL_MOTDSTART: + q->setInfo(info); + break; + case Irc::ERR_NICKNAMEINUSE: + case Irc::ERR_NICKCOLLISION: { + QString alternate = connection->nickName(); + emit connection->nickNameReserved(&alternate); + if (!alternate.isEmpty() && alternate != connection->nickName()) { + connection->setNickName(alternate); + } else { + emit connection->nickNameRequired(msg->parameters().value(1), &alternate); + if (!alternate.isEmpty() && alternate != connection->nickName()) + connection->setNickName(alternate); + } + break; + } + case Irc::ERR_BADCHANNELKEY: { + QString key; + QString channel = msg->parameters().value(1); + emit connection->channelKeyRequired(channel, &key); + if (!key.isEmpty()) + connection->sendCommand(IrcCommand::createJoin(channel, key)); + break; + } + default: + break; + } +} + +void IrcProtocolPrivate::handlePrivateMessage(IrcPrivateMessage* msg) +{ + if (msg->isRequest()) { + IrcCommand* reply = IrcConnectionPrivate::get(connection)->createCtcpReply(msg); + if (reply) + connection->sendCommand(reply); + } +} + +static void handleCapability(QSet<QString>* caps, const QString& cap) +{ + Q_ASSERT(caps); + // sticky modifier (once the cap is enabled, it cannot be disabled) + QLatin1Char stickyMod('='); + // ack modifier (the cap must be acked by the client to fully enable/disable) + QLatin1Char ackMod('~'); + // disable modifier (the cap should be disabled) + QLatin1Char disMod('-'); + + QString name = cap; + while (name.startsWith(stickyMod) || name.startsWith(ackMod)) + name.remove(0, 1); + + if (name.startsWith(disMod)) + caps->remove(name.mid(1)); + else + caps->insert(name); +} + +void IrcProtocolPrivate::handleCapabilityMessage(IrcCapabilityMessage* msg) +{ + Q_Q(IrcProtocol); + const bool connected = connection->isConnected(); + const QString subCommand = msg->subCommand(); + if (subCommand == "LS") { + QSet<QString> availableCaps = connection->network()->availableCapabilities().toSet(); + foreach (const QString& cap, msg->capabilities()) + handleCapability(&availableCaps, cap); + q->setAvailableCapabilities(availableCaps); + + if (!connected) { + QMetaObject::invokeMethod(connection->network(), "requestingCapabilities"); + QStringList requestedCaps = connection->network()->requestedCapabilities(); + const QStringList params = msg->parameters(); + if (params.value(params.count() - 1) != QLatin1String("*")) { + if (!connection->saslMechanism().isEmpty() && availableCaps.contains(QLatin1String("sasl"))) + requestedCaps += QLatin1String("sasl"); + } + if (!requestedCaps.isEmpty()) + connection->sendRaw("CAP REQ :" + requestedCaps.join(" ")); + else + QMetaObject::invokeMethod(q, "_irc_resumeHandshake", Qt::QueuedConnection); + } + } else if (subCommand == "ACK" || subCommand == "NAK") { + bool auth = false; + if (subCommand == "ACK") { + QSet<QString> activeCaps = connection->network()->activeCapabilities().toSet(); + foreach (const QString& cap, msg->capabilities()) { + handleCapability(&activeCaps, cap); + if (cap == "sasl" && !connection->saslMechanism().isEmpty() && !connection->password().isEmpty()) + auth = connection->sendRaw("AUTHENTICATE " + connection->saslMechanism()); + } + q->setActiveCapabilities(activeCaps); + } + + if (!connected && !auth) + QMetaObject::invokeMethod(q, "_irc_resumeHandshake", Qt::QueuedConnection); + } +} + +void IrcProtocolPrivate::_irc_pauseHandshake() +{ + // Send CAP LS first; if the server understands it this will + // temporarily pause the handshake until CAP END is sent, so we + // know whether the server supports the CAP extension. + connection->sendData("CAP LS"); + resumed = false; + authed = false; +} + +void IrcProtocolPrivate::_irc_resumeHandshake() +{ + if (!resumed && !connection->isConnected()) { + if (!authed && !connection->saslMechanism().isEmpty() && !connection->password().isEmpty()) + authenticate(false); + connection->sendData("CAP END"); + } + resumed = true; +} +#endif // IRC_DOXYGEN + +/*! + Constructs a new IRC protocol for \a connection. + */ +IrcProtocol::IrcProtocol(IrcConnection* connection) : QObject(connection), d_ptr(new IrcProtocolPrivate) +{ + Q_D(IrcProtocol); + d->q_ptr = this; + d->connection = connection; + d->builder = new IrcMessageBuilder(connection); + connect(d->builder, SIGNAL(messageReceived(IrcMessage*)), this, SLOT(receiveMessage(IrcMessage*))); +} + +/*! + Destructs the IRC protocol. + */ +IrcProtocol::~IrcProtocol() +{ + Q_D(IrcProtocol); + delete d->builder; +} + +/*! + This property holds the connection. + + \par Access function: + \li \ref IrcConnection* <b>connection</b>() const + */ +IrcConnection* IrcProtocol::connection() const +{ + Q_D(const IrcProtocol); + return d->connection; +} + +/*! + This property holds the socket. + + \par Access functions: + \li \ref QAbstractSocket* <b>socket</b>() const + */ +QAbstractSocket* IrcProtocol::socket() const +{ + Q_D(const IrcProtocol); + return d->connection->socket(); +} + +/*! + This method is called when the connection has been established. + + The default implementation sends the \c NICK, \c USER and \c PASSWORD commands as defined in + <a href="http://tools.ietf.org/html/rfc1459">RFC 1459</a>. + + Furthermore, it sends a <tt>CAP LS</tt> command as specified in + <a href="http://tools.ietf.org/html/draft-mitchell-irc-capabilities-01">IRC Client Capabilities Extension</a>. + */ +void IrcProtocol::open() +{ + Q_D(IrcProtocol); + d->_irc_pauseHandshake(); + + if (d->connection->saslMechanism().isEmpty() && !d->connection->password().isEmpty()) + d->authenticate(false); + + d->connection->sendRaw(QString("NICK %1").arg(d->connection->nickName())); + d->connection->sendRaw(QString("USER %1 hostname servername :%2").arg(d->connection->userName(), d->connection->realName())); +} + +/*! + This method is called when the connection has been lost. + */ +void IrcProtocol::close() +{ +} + +/*! + This method is called when the \ref socket has new data available for read. + + The default implementation reads lines as specified in + <a href="http://tools.ietf.org/html/rfc1459">RFC 1459</a>. + + \sa socket + */ +void IrcProtocol::read() +{ + Q_D(IrcProtocol); + d->buffer += socket()->readAll(); + // try reading RFC compliant message lines first + d->readLines("\r\n"); + // fall back to RFC incompliant lines... + d->readLines("\n"); +} + +/*! + This method is called when raw \a data should be written to the \ref socket. + + The default implementation writes the data and appends \c "\r\n" as specified in + <a href="http://tools.ietf.org/html/rfc1459">RFC 1459</a>. + + \sa socket + */ +bool IrcProtocol::write(const QByteArray& data) +{ + return socket()->write(data + QByteArray("\r\n")) != -1; +} + +/*! + This method should be called by the protocol implementation + to make the underlying IRC connection receive a \a message. + + \sa IrcConnection::messageReceived() + */ +void IrcProtocol::receiveMessage(IrcMessage* message) +{ + Q_D(IrcProtocol); + IrcConnectionPrivate* priv = IrcConnectionPrivate::get(d->connection); + priv->receiveMessage(message); + if (message->type() == IrcMessage::Numeric) + d->builder->processMessage(static_cast<IrcNumericMessage*>(message)); +} + +/*! + This method should be called by the protocol implementation to + notify the underlying IRC connection about a nick \a name change. + + \sa IrcConnection::nickName + */ +void IrcProtocol::setNickName(const QString& name) +{ + Q_D(IrcProtocol); + IrcConnectionPrivate* priv = IrcConnectionPrivate::get(d->connection); + priv->setNick(name); +} + +/*! + This method should be called by the protocol implementation + to notify the underlying IRC connection about a \a status change. + + \sa IrcConnection::status + */ +void IrcProtocol::setStatus(IrcConnection::Status status) +{ + Q_D(IrcProtocol); + IrcConnectionPrivate* priv = IrcConnectionPrivate::get(d->connection); + priv->setStatus(status); +} + +/*! + This method should be called by the protocol implementation + to initialize the underlying IRC network connection \a info. + + \sa IrcNetwork::initialized + */ +void IrcProtocol::setInfo(const QHash<QString, QString>& info) +{ + Q_D(IrcProtocol); + if (!info.isEmpty()) { + IrcConnectionPrivate* priv = IrcConnectionPrivate::get(d->connection); + priv->setInfo(info); + } +} + +/*! + This method should be called by the protocol implementation to notify + the underlying IRC network about a change in available \a capabilities. + + \sa IrcNetwork::availableCapabilities + */ +void IrcProtocol::setAvailableCapabilities(const QSet<QString>& capabilities) +{ + Q_D(IrcProtocol); + IrcNetworkPrivate* priv = IrcNetworkPrivate::get(d->connection->network()); + priv->setAvailableCapabilities(capabilities); +} + +/*! + This method should be called by the protocol implementation to notify + the underlying IRC network about a change in active \a capabilities. + + \sa IrcNetwork::activeCapabilities + */ +void IrcProtocol::setActiveCapabilities(const QSet<QString>& capabilities) +{ + Q_D(IrcProtocol); + IrcNetworkPrivate* priv = IrcNetworkPrivate::get(d->connection->network()); + priv->setActiveCapabilities(capabilities); +} + +#include "moc_ircprotocol.cpp" + +IRC_END_NAMESPACE |
