From 529f38bd8878b6b1bea2b5457031ce936aab8d80 Mon Sep 17 00:00:00 2001 From: Markus Mittendrein Date: Mon, 6 Oct 2014 15:03:54 +0200 Subject: addedd communi --- libcommuni/src/core/ircprotocol.cpp | 480 ++++++++++++++++++++++++++++++++++++ 1 file changed, 480 insertions(+) create mode 100644 libcommuni/src/core/ircprotocol.cpp (limited to 'libcommuni/src/core/ircprotocol.cpp') 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 + +IRC_BEGIN_NAMESPACE + +/*! + \file ircprotocol.h + \brief \#include <IrcProtocol> + */ + +/*! + \since 3.2 + \class IrcProtocol ircprotocol.h IrcProtocol + \ingroup core + \brief Implements the IRC protocol and provides means for implementing support for custom protocols. + + \sa IrcConnection::protocol + */ + +#ifndef IRC_DOXYGEN +class IrcProtocolPrivate +{ + Q_DECLARE_PUBLIC(IrcProtocol) + +public: + IrcProtocolPrivate(); + + void authenticate(bool secure); + + void readLines(const QByteArray& delimiter); + void processLine(const QByteArray& line); + + void handleNumericMessage(IrcNumericMessage* msg); + void handlePrivateMessage(IrcPrivateMessage* msg); + void handleCapabilityMessage(IrcCapabilityMessage* msg); + + void _irc_pauseHandshake(); + void _irc_resumeHandshake(); + + IrcProtocol* q_ptr; + IrcConnection* connection; + IrcMessageBuilder* builder; + QHash 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 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(msg)); + break; + case IrcMessage::Nick: + if (msg->flags() & IrcMessage::Own) + q->setNickName(static_cast(msg)->newNick()); + break; + case IrcMessage::Numeric: + handleNumericMessage(static_cast(msg)); + break; + case IrcMessage::Ping: + connection->sendRaw("PONG " + static_cast(msg)->argument()); + break; + case IrcMessage::Private: + handlePrivateMessage(static_cast(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* 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 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 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* connection() const + */ +IrcConnection* IrcProtocol::connection() const +{ + Q_D(const IrcProtocol); + return d->connection; +} + +/*! + This property holds the socket. + + \par Access functions: + \li \ref QAbstractSocket* socket() 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 + RFC 1459. + + Furthermore, it sends a CAP LS command as specified in + IRC Client Capabilities Extension. + */ +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 + RFC 1459. + + \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 + RFC 1459. + + \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(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& 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& 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& capabilities) +{ + Q_D(IrcProtocol); + IrcNetworkPrivate* priv = IrcNetworkPrivate::get(d->connection->network()); + priv->setActiveCapabilities(capabilities); +} + +#include "moc_ircprotocol.cpp" + +IRC_END_NAMESPACE -- cgit v1.2.3-54-g00ecf