summaryrefslogtreecommitdiffstats
path: root/libcommuni/src/core
diff options
context:
space:
mode:
Diffstat (limited to 'libcommuni/src/core')
-rw-r--r--libcommuni/src/core/core.pri65
-rw-r--r--libcommuni/src/core/core.pro8
-rw-r--r--libcommuni/src/core/irc.cpp172
-rw-r--r--libcommuni/src/core/irccommand.cpp851
-rw-r--r--libcommuni/src/core/ircconnection.cpp1508
-rw-r--r--libcommuni/src/core/irccore.cpp90
-rw-r--r--libcommuni/src/core/ircfilter.cpp248
-rw-r--r--libcommuni/src/core/ircmessage.cpp1604
-rw-r--r--libcommuni/src/core/ircmessage_p.cpp284
-rw-r--r--libcommuni/src/core/ircmessagebuilder.cpp121
-rw-r--r--libcommuni/src/core/ircmessagedecoder.cpp73
-rw-r--r--libcommuni/src/core/ircmessagedecoder_icu.cpp68
-rw-r--r--libcommuni/src/core/ircmessagedecoder_uchardet.cpp56
-rw-r--r--libcommuni/src/core/ircnetwork.cpp687
-rw-r--r--libcommuni/src/core/ircprotocol.cpp480
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 &lt;Irc&gt;
+ */
+
+/*!
+ \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>&lt;nick&gt;</b> [ '!' &lt;ident&gt; ] [ '@' &lt;host&gt; ]
+ </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>
+ &lt;nick&gt; [ '!' <b>&lt;ident&gt;</b> ] [ '@' &lt;host&gt; ]
+ </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>
+ &lt;nick&gt; [ '!' &lt;ident&gt; ] [ '@' <b>&lt;host&gt;</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 &lt;IrcCommand&gt;
+ */
+
+/*!
+ \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 &lt;IrcConnection&gt;
+ */
+
+/*!
+ \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 &lt;IrcCore&gt;
+ */
+
+/*!
+ \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 &lt;\ref ircfilter.h "IrcFilter"&gt;
+ */
+
+/*!
+ \file ircmessagefilter.h
+ \brief \#include &lt;IrcMessageFilter&gt;
+ */
+
+/*!
+ \file irccommandfilter.h
+ \brief \#include &lt;IrcCommandFilter&gt;
+ */
+
+/*!
+ \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 &lt;IrcMessage&gt;
+ */
+
+/*!
+ \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>
+ &lt;prefix&gt; ::= &lt;\ref nick&gt; [ '!' &lt;\ref ident&gt; ] [ '@' &lt;\ref host&gt; ]
+ </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>&lt;nick&gt;</b> [ '!' &lt;\ref ident&gt; ] [ '@' &lt;\ref host&gt; ]
+ </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>
+ &lt;\ref nick&gt; [ '!' <b>&lt;ident&gt;</b> ] [ '@' &lt;\ref host&gt; ]
+ </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>
+ &lt;\ref nick&gt; [ '!' &lt;\ref ident&gt; ] [ '@' <b>&lt;host&gt;</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 &lt;IrcNetwork&gt;
+ */
+
+/*!
+ \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 &lt;IrcProtocol&gt;
+ */
+
+/*!
+ \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