diff --git a/src/irc/inputfilter.cpp b/src/irc/inputfilter.cpp index 4c8fb72e..30bc9e65 100644 --- a/src/irc/inputfilter.cpp +++ b/src/irc/inputfilter.cpp @@ -1,2538 +1,2539 @@ /* This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. */ /* Copyright (C) 2002 Dario Abatianni Copyright (C) 2004, 2016 Peter Simonsson Copyright (C) 2006-2008 Eike Hein */ #include "inputfilter.h" #include "server.h" #include "replycodes.h" #include "application.h" #include "version.h" #include "commit.h" #include "query.h" #include "channel.h" #include "statuspanel.h" #include "common.h" #include "notificationhandler.h" #include #include #include #include #include InputFilter::InputFilter() : m_server(0), m_lagMeasuring(false) { m_connecting = false; } InputFilter::~InputFilter() { } void InputFilter::setServer(Server* newServer) { m_server = newServer; } template int posOrLen(T chr, const QString& str, int from=0) { int p=str.indexOf(QLatin1Char(chr), from); if (p<0) return str.size(); return p; } /// "[22:08] >> :thiago!n=thiago@kde/thiago QUIT :Read error: 110 (Connection timed out)" /// "[21:47] >> :Zarin!n=x365@kde/developer/lmurray PRIVMSG #plasma :If the decoration doesn't have paint( QPixmap ) it falls back to the old one" /// "[21:49] >> :niven.freenode.net 352 argonel #kde-forum i=beezle konversation/developer/argonel irc.freenode.net argonel H :0 Konversation User " void InputFilter::parseLine(const QString& line) { int start=0; QHash messageTags; if (line[0] == QLatin1Char('@')) { messageTags = parseMessageTags(line, &start); } QString prefix; int end(posOrLen(' ', line, start)); if (line[start]==QLatin1Char(':')) { prefix = line.mid(start + 1, end - start - 1); //skips the colon and does not include the trailing space start = end + 1; end = posOrLen(' ', line, start); } //even though the standard is UPPER CASE, someone when through a great deal of trouble to make this lower case... QString command = QString(line.mid(start, end-start)).toLower(); start=end+1; int trailing=line.indexOf(QStringLiteral(" :"), end); if (trailing >= 0) end=trailing; else end=line.size(); QStringList parameterList; while (start < end) { if (line[start]==QLatin1Char(' ')) start++; else { int p=line.indexOf(QLatin1Char(' '), start); //easier to have Qt loop for me :) if (p<0) p=end; parameterList << line.mid(start, p-start); start=p+1; } }; /* Quote: "The final colon is specified as a "last argument" designator, and * is always valid before the final argument." * Quote: "The last parameter may be an empty string." * Quote: "After extracting the parameter list, all parameters are equal * whether matched by or . is just a * syntactic trick to allow SPACE within the parameter." */ if (trailing >= 0 ) //<-- trailing == ":" - per above we need the empty string parameterList << line.mid( qMin(trailing+2, line.size()) ); Q_ASSERT(m_server); //how could we have gotten a line without a server? // Server command, if no "!" was found in prefix if ((!prefix.contains(QLatin1Char('!'))) && (prefix != m_server->getNickname())) { parseServerCommand(prefix, command, parameterList, messageTags); } else { parseClientCommand(prefix, command, parameterList, messageTags); } } #define trailing (parameterList.isEmpty() ? QString() : parameterList.last()) #define plHas(x) _plHas(parameterList.count(), (x)) bool _plHad=false; int _plWanted = 0; bool _plHas(int count, int x) { _plHad=(count >= x); _plWanted = x; if (!_plHad) qDebug() << "plhad" << count << "wanted" << x; return _plHad; } void InputFilter::parseClientCommand(const QString &prefix, const QString &command, QStringList ¶meterList, QHash messageTags) { Application* konv_app = Application::instance(); Q_ASSERT(konv_app); Q_ASSERT(m_server); // Extract nickname from prefix int pos = prefix.indexOf(QLatin1Char('!')); QString sourceNick = prefix.left(pos); QString sourceHostmask = prefix.mid(pos + 1); // remember hostmask for this nick, it could have changed m_server->addHostmaskToNick(sourceNick, sourceHostmask); bool isNumeric = false; int numeric = command.toInt(&isNumeric); if(isNumeric) { parseNumeric(prefix, numeric, parameterList, messageTags); } //PRIVMSG #channel :message else if (command == QStringLiteral("privmsg") && plHas(2)) { bool isChan = isAChannel(parameterList.value(0)); // CTCP message? if (m_server->identifyMsg() && (trailing.length() > 1 && (trailing.at(0) == QLatin1Char('+') || trailing.at(0) == QLatin1Char('-')))) { trailing = trailing.mid(1); } if (!trailing.isEmpty() && trailing.at(0)==QChar(0x01)) { // cut out the CTCP command QString ctcp = trailing.mid(1,trailing.indexOf(QChar(0x01),1)-1); //QString::left(-1) returns the entire string QString ctcpCommand = ctcp.left(ctcp.indexOf(QLatin1Char(' '))).toLower(); bool hasArg = ctcp.indexOf(QLatin1Char(' ')) > 0; //QString::mid(-1)+1 = 0, which returns the entire string if there is no space, resulting in command==arg QString ctcpArgument = hasArg ? ctcp.mid(ctcp.indexOf(QLatin1Char(' '))+1) : QString(); hasArg = !ctcpArgument.isEmpty(); if (hasArg) ctcpArgument = konv_app->doAutoreplace(ctcpArgument, false).first; // If it was a ctcp action, build an action string if (ctcpCommand == QStringLiteral("action") && isChan) { if (!isIgnore(prefix, Ignore::Channel)) { Channel* channel = m_server->getChannelByName( parameterList.value(0) ); if (!channel) { qCritical() << "Didn't find the channel " << parameterList.value(0) << endl; return; } channel->appendAction(sourceNick, ctcpArgument, messageTags); if (sourceNick != m_server->getNickname()) { if (hasArg && ctcpArgument.toLower().contains(QRegExp(QStringLiteral("(^|[^\\d\\w])") + QRegExp::escape(m_server->loweredNickname()) + QStringLiteral("([^\\d\\w]|$)")))) { konv_app->notificationHandler()->nick(channel, sourceNick, ctcpArgument); } else { konv_app->notificationHandler()->message(channel, sourceNick, ctcpArgument); } } } } // If it was a ctcp action, build an action string else if (ctcpCommand == QStringLiteral("action") && !isChan) { // Check if we ignore queries from this nick if (!isIgnore(prefix, Ignore::Query)) { NickInfoPtr nickinfo = m_server->obtainNickInfo(sourceNick); nickinfo->setHostmask(sourceHostmask); // create new query (server will check for dupes) Query* query = m_server->addQuery(nickinfo, false /* we didn't initiate this*/ ); // send action to query query->appendAction(sourceNick, ctcpArgument, messageTags); if (sourceNick != m_server->getNickname() && query) konv_app->notificationHandler()->queryMessage(query, sourceNick, ctcpArgument); } } // Answer ping requests else if (ctcpCommand == QStringLiteral("ping") && hasArg) { if (!isIgnore(prefix,Ignore::CTCP)) { if (isChan) { m_server->appendMessageToFrontmost(i18n("CTCP"), i18n("Received CTCP-PING request from %1 to channel %2, sending answer.", sourceNick, parameterList.value(0)), messageTags ); } else { m_server->appendMessageToFrontmost(i18n("CTCP"), i18n("Received CTCP-%1 request from %2, sending answer.", QStringLiteral("PING"), sourceNick), messageTags ); } m_server->ctcpReply(sourceNick, QString(QStringLiteral("PING %1")).arg(ctcpArgument)); } } // Maybe it was a version request, so act appropriately else if (ctcpCommand == QStringLiteral("version")) { if(!isIgnore(prefix,Ignore::CTCP)) { if (isChan) { m_server->appendMessageToFrontmost(i18n("CTCP"), i18n("Received Version request from %1 to channel %2.", sourceNick, parameterList.value(0)), messageTags ); } else { m_server->appendMessageToFrontmost(i18n("CTCP"), i18n("Received Version request from %1.", sourceNick), messageTags ); } QString reply; if (Preferences::self()->customVersionReplyEnabled()) { reply = Preferences::self()->customVersionReply().trimmed(); } else { // Do not internationalize the below version string reply = QString(QStringLiteral("Konversation %1 Build %2 (C) 2002-2018 by the Konversation team")) .arg(QStringLiteral(KONVI_VERSION)) .arg(QString::number(COMMIT)); } if (!reply.isEmpty()) m_server->ctcpReply(sourceNick,QStringLiteral("VERSION ")+reply); } } // DCC request? else if (ctcpCommand==QStringLiteral("dcc") && !isChan && hasArg) { if (!isIgnore(prefix,Ignore::DCC)) { // Extract DCC type and argument list QString dccType=ctcpArgument.toLower().section(QLatin1Char(' '),0,0); // Support file names with spaces QString dccArguments = ctcpArgument.mid(ctcpArgument.indexOf(QLatin1Char(' '))+1); QStringList dccArgumentList; if ((dccArguments.count(QLatin1Char('\"')) >= 2) && (dccArguments.startsWith(QLatin1Char('\"')))) { int lastQuotePos = dccArguments.lastIndexOf(QLatin1Char('\"')); if (dccArguments[lastQuotePos+1] == QLatin1Char(' ')) { QString fileName = dccArguments.mid(1, lastQuotePos-1); dccArguments = dccArguments.mid(lastQuotePos+2); dccArgumentList.append(fileName); } } dccArgumentList += dccArguments.split(QLatin1Char(' '), QString::SkipEmptyParts); if (dccType==QStringLiteral("send")) { if (dccArgumentList.count()==4) { // incoming file konv_app->notificationHandler()->dccIncoming(m_server->getStatusView(), sourceNick); emit addDccGet(sourceNick,dccArgumentList); } else if (dccArgumentList.count() >= 5) { if (dccArgumentList[dccArgumentList.size() - 3] == QStringLiteral("0")) { // incoming file (Reverse DCC) konv_app->notificationHandler()->dccIncoming(m_server->getStatusView(), sourceNick); emit addDccGet(sourceNick,dccArgumentList); } else { // the receiver accepted the offer for Reverse DCC emit startReverseDccSendTransfer(sourceNick,dccArgumentList); } } else { m_server->appendMessageToFrontmost(i18n("DCC"), i18n("Received invalid DCC SEND request from %1.", sourceNick), messageTags ); } } else if (dccType==QStringLiteral("accept")) { // resume request was accepted if (dccArgumentList.count() >= 3) { emit resumeDccGetTransfer(sourceNick,dccArgumentList); } else { m_server->appendMessageToFrontmost(i18n("DCC"), i18n("Received invalid DCC ACCEPT request from %1.", sourceNick), messageTags ); } } // Remote client wants our sent file resumed else if (dccType==QStringLiteral("resume")) { if (dccArgumentList.count() >= 3) { emit resumeDccSendTransfer(sourceNick,dccArgumentList); } else { m_server->appendMessageToFrontmost(i18n("DCC"), i18n("Received invalid DCC RESUME request from %1.", sourceNick), messageTags ); } } else if (dccType==QStringLiteral("chat")) { if (dccArgumentList.count() == 3) { // incoming chat emit addDccChat(sourceNick,dccArgumentList); } else if (dccArgumentList.count() == 4) { if (dccArgumentList[dccArgumentList.size() - 2] == QStringLiteral("0")) { // incoming chat (Reverse DCC) emit addDccChat(sourceNick,dccArgumentList); } else { // the receiver accepted the offer for Reverse DCC chat emit startReverseDccChat(sourceNick,dccArgumentList); } } else { m_server->appendMessageToFrontmost(i18n("DCC"), i18n("Received invalid DCC CHAT request from %1.", sourceNick), messageTags ); } } else { m_server->appendMessageToFrontmost(i18n("DCC"), i18n("Unknown DCC command %1 received from %2.", ctcpArgument, sourceNick), messageTags ); } } } else if (ctcpCommand==QStringLiteral("clientinfo") && !isChan) { if (!isIgnore(prefix, Ignore::CTCP)) { m_server->appendMessageToFrontmost(i18n("CTCP"), i18n("Received CTCP-%1 request from %2, sending answer.", QString::fromLatin1("CLIENTINFO"), sourceNick), messageTags ); m_server->ctcpReply(sourceNick, QStringLiteral("CLIENTINFO ACTION CLIENTINFO DCC PING TIME VERSION")); } } else if (ctcpCommand==QStringLiteral("time") && !isChan) { if (!isIgnore(prefix, Ignore::CTCP)) { m_server->appendMessageToFrontmost(i18n("CTCP"), i18n("Received CTCP-%1 request from %2, sending answer.", QString::fromLatin1("TIME"), sourceNick), messageTags ); m_server->ctcpReply(sourceNick, QStringLiteral("TIME ")+QDateTime::currentDateTime().toString()); } } // No known CTCP request, give a general message else { if (!isIgnore(prefix,Ignore::CTCP)) { if (isChan) m_server->appendServerMessageToChannel( parameterList.value(0), QStringLiteral("CTCP"), i18n("Received unknown CTCP-%1 request from %2 to Channel %3.", ctcp, sourceNick, parameterList.value(0)), messageTags ); else m_server->appendMessageToFrontmost(i18n("CTCP"), i18n("Received unknown CTCP-%1 request from %2.", ctcp, sourceNick), messageTags ); } } } // No CTCP, so it's an ordinary channel or query message else { parsePrivMsg(prefix, parameterList, messageTags); } } else if (command==QStringLiteral("notice") && plHas(2)) { if (!isIgnore(prefix,Ignore::Notice)) { // Channel notice? if(isAChannel(parameterList.value(0))) { if (m_server->identifyMsg() && (trailing.length() > 1 && (trailing.at(0) == QLatin1Char('+') || trailing.at(0) == QLatin1Char('-')))) { trailing = trailing.mid(1); } m_server->appendServerMessageToChannel(parameterList.value(0), i18n("Notice"), i18n("-%1 to %2- %3", sourceNick, parameterList.value(0), trailing), messageTags ); } // Private notice else { // Was this a CTCP reply? if (!trailing.isEmpty() && trailing.at(0) == QChar(0x01)) { // cut 0x01 bytes from trailing string QString ctcp(trailing.mid(1,trailing.length()-2)); QString replyReason(ctcp.section(QLatin1Char(' '),0,0)); QString reply(ctcp.section(QLatin1Char(' '),1)); // pong reply, calculate turnaround time if (replyReason.toLower()==QStringLiteral("ping")) { int dateArrived=QDateTime::currentDateTime().toTime_t(); int dateSent=reply.toInt(); int time = dateArrived-dateSent; QString unit = i18np("second", "seconds", time); m_server->appendMessageToFrontmost(i18n("CTCP"), i18n("Received CTCP-PING reply from %1: %2 %3.", sourceNick, time, unit), messageTags ); } else if (replyReason.toLower() == QStringLiteral("dcc")) { qDebug() << reply; QStringList dccList = reply.split(QLatin1Char(' ')); //all dcc notices we receive are rejects if (dccList.count() >= 2 && dccList.first().toLower() == QStringLiteral("reject")) { dccList.removeFirst(); if (dccList.count() >= 2 && dccList.first().toLower() == QStringLiteral("send")) { dccList.removeFirst(); emit rejectDccSendTransfer(sourceNick,dccList); } else if (dccList.first().toLower() == QStringLiteral("chat")) { emit rejectDccChat(sourceNick); } } } // all other ctcp replies get a general message else { m_server->appendMessageToFrontmost(i18n("CTCP"), i18n("Received CTCP-%1 reply from %2: %3.", replyReason, sourceNick, reply), messageTags ); } } // No, so it was a normal notice else { #ifdef HAVE_QCA2 //Key exchange if (trailing.startsWith(QLatin1String("DH1080_INIT "))) { m_server->appendMessageToFrontmost(i18n("Notice"), i18n("Received DH1080_INIT from %1", sourceNick), messageTags); m_server->parseInitKeyX(sourceNick, trailing.mid(12)); } else if (trailing.startsWith(QLatin1String("DH1080_FINISH "))) { m_server->appendMessageToFrontmost(i18n("Notice"), i18n("Received DH1080_FINISH from %1", sourceNick), messageTags); m_server->parseFinishKeyX(sourceNick, trailing.mid(14)); } else { #endif m_server->appendMessageToFrontmost(i18n("Notice"), i18n("-%1- %2", sourceNick, m_server->identifyMsg() ? trailing.mid(1) : trailing), messageTags); #ifdef HAVE_QCA2 } #endif } } } } else if (command==QStringLiteral("join") && plHas(1)) { QString channelName; QString account; QString realName; channelName = parameterList[0]; if (m_server->capabilities() & Server::ExtendedJoin && plHas(3)) { if (parameterList[1] != "*") account = parameterList[1]; realName = parameterList[2]; } // Did we join the channel, or was it someone else? if (m_server->isNickname(sourceNick)) { /* QString key; // TODO: Try to remember channel keys for autojoins and manual joins, so // we can get %k to work if(channelName.contains(' ')) { key=channelName.section(' ',1,1); channelName=channelName.section(' ',0,0); } */ // Join the channel Channel* channel = m_server->joinChannel(channelName, sourceHostmask, messageTags); // Upon JOIN we're going to receive some NAMES input from the server which // we need to be able to tell apart from manual invocations of /names setAutomaticRequest(QStringLiteral("NAMES"),channelName,true); channel->clearModeList(); // Request modes for the channel m_server->queue(QStringLiteral("MODE ")+channelName, Server::LowPriority); // Initiate channel ban list channel->clearBanList(); setAutomaticRequest(QStringLiteral("BANLIST"),channelName,true); m_server->queue(QStringLiteral("MODE ")+channelName+QStringLiteral(" +b"), Server::LowPriority); } else { Channel* channel = m_server->nickJoinsChannel(channelName, sourceNick, sourceHostmask, account, realName, messageTags); konv_app->notificationHandler()->join(channel, sourceNick); } } else if (command==QStringLiteral("kick") && plHas(2)) { m_server->nickWasKickedFromChannel(parameterList.value(0), parameterList.value(1), sourceNick, trailing, messageTags); } else if (command==QStringLiteral("part") && plHas(1)) { // A version of the PART line encountered on ircu: ":Nick!user@host PART :#channel" QString channel(parameterList.value(0)); QString reason(parameterList.value(1)); Channel* channelPtr = m_server->removeNickFromChannel(channel, sourceNick, reason, messageTags); if (sourceNick != m_server->getNickname()) { konv_app->notificationHandler()->part(channelPtr, sourceNick); } } else if (command==QStringLiteral("quit") && plHas(1)) { m_server->removeNickFromServer(sourceNick, trailing, messageTags); if (sourceNick != m_server->getNickname()) { konv_app->notificationHandler()->quit(m_server->getStatusView(), sourceNick); } } else if (command==QStringLiteral("nick") && plHas(1)) { QString newNick(parameterList.value(0)); // Message may not include ":" in front of the new nickname m_server->renameNick(sourceNick, newNick, messageTags); if (sourceNick != m_server->getNickname()) { konv_app->notificationHandler()->nickChange(m_server->getStatusView(), sourceNick, newNick); } } else if (command==QStringLiteral("topic") && plHas(2)) { m_server->setChannelTopic(sourceNick, parameterList.value(0), trailing, messageTags); } else if (command==QStringLiteral("mode") && plHas(2)) // mode #channel -/+ mmm params { parseModes(sourceNick, parameterList, messageTags); Channel* channel = m_server->getChannelByName(parameterList.value(0)); konv_app->notificationHandler()->mode(channel, sourceNick, parameterList.value(0), QStringList(parameterList.mid(1)).join (QStringLiteral(" "))); } else if (command==QStringLiteral("invite") && plHas(2)) //:ejm!i=beezle@bas5-oshawa95-1176455927.dsl.bell.ca INVITE argnl :#sug4 { if (!isIgnore(prefix, Ignore::Invite)) { QString channel(trailing); m_server->appendMessageToFrontmost(i18n("Invite"), i18n("%1 invited you to channel %2.", sourceNick, channel), messageTags ); emit invitation(sourceNick, channel); } } else if (command == QStringLiteral("away")) { NickInfoPtr nickInfo = m_server->getNickInfo(sourceNick); if (nickInfo) { if (!parameterList.isEmpty()) { nickInfo->setAway(true); nickInfo->setAwayMessage(parameterList.first()); } else { nickInfo->setAway(false); nickInfo->setAwayMessage(QString()); } } else { qDebug() << "Received away message for unknown nick," << sourceNick; } } else if (command == QStringLiteral("account") && plHas(1)) { NickInfoPtr nickInfo = m_server->getNickInfo(sourceNick); QString account = parameterList.first(); if (account == "*") { nickInfo->setAccount(QString()); } else { nickInfo->setAccount(account); } } else { qDebug() << "unknown client command" << parameterList.count() << _plHad << _plWanted << command << parameterList.join(QStringLiteral(" ")); m_server->appendMessageToFrontmost(command, parameterList.join(QStringLiteral(" ")), messageTags); } } void InputFilter::parseServerCommand(const QString &prefix, const QString &command, QStringList ¶meterList, QHash messageTags) { bool isNumeric; int numeric = command.toInt(&isNumeric); Q_ASSERT(m_server); if (!m_server) return; if (!isNumeric) { if (command == QStringLiteral("ping")) { QString text; text = (!trailing.isEmpty()) ? trailing : parameterList.join(QStringLiteral(" ")); if (!trailing.isEmpty()) { text = prefix + QStringLiteral(" :") + text; } if (!text.startsWith(QLatin1Char(' '))) { text.prepend(QLatin1Char(' ')); } // queue the reply to send it as soon as possible m_server->queue(QStringLiteral("PONG")+text, Server::HighPriority); } else if (command == QStringLiteral("error :closing link:")) { qDebug() << "link closed"; } else if (command == QStringLiteral("pong")) { // double check if we are in lag measuring mode since some servers fail to send // the LAG cookie back in PONG if (trailing.startsWith(QLatin1String("LAG")) || getLagMeasuring()) { m_server->pongReceived(); } } else if (command == QStringLiteral("mode")) { parseModes(prefix, parameterList, messageTags); } else if (command == QStringLiteral("notice")) { m_server->appendStatusMessage(i18n("Notice"), i18n("-%1- %2", prefix, trailing), messageTags); } else if (command == QStringLiteral("kick") && plHas(3)) { m_server->nickWasKickedFromChannel(parameterList.value(1), parameterList.value(2), prefix, trailing, messageTags); } else if (command == QStringLiteral("privmsg")) { parsePrivMsg(prefix, parameterList, messageTags); } else if (command==QStringLiteral("cap") && plHas(3)) { QString command = parameterList.value(1).toLower(); if (command == QStringLiteral("ack") || command == QStringLiteral("nak")) { m_server->capReply(); QStringList capabilities = parameterList.value(2).split(QLatin1Char(' '), QString::SkipEmptyParts); foreach(const QString& capability, capabilities) { int nameStart = capability.indexOf(QRegExp(QStringLiteral("[a-z0-9]"), Qt::CaseInsensitive)); QString modifierString = capability.left(nameStart); QString name = capability.mid(nameStart); Server::CapModifiers modifiers = Server::NoModifiers; if (modifierString.contains(QLatin1Char('-'))) { modifiers = modifiers | Server::DisMod; modifiers = modifiers ^ Server::NoModifiers; } if (modifierString.contains(QLatin1Char('='))) { modifiers = modifiers | Server::StickyMod; modifiers = modifiers ^ Server::NoModifiers; } if (modifierString.contains(QLatin1Char('~'))) { modifiers = modifiers | Server::AckMod; modifiers = modifiers ^ Server::NoModifiers; } if (command == QStringLiteral("ack")) m_server->capAcknowledged(name, modifiers); else m_server->capDenied(name); } if(!m_server->capEndDelayed()) { m_server->capEndNegotiation(); } } else if (command == "ls" || command == "list") { - m_server->appendStatusMessage(i18n("Capabilities"), parameterList.mid(2).join(QStringLiteral(" ")), messageTags); + m_server->appendStatusMessage(i18n("Capabilities"), trailing, messageTags); if (getAutomaticRequest(QStringLiteral("CAP LS"), QString()) != 0) { - setAutomaticRequest (QStringLiteral("CAP LS"), QString (), false); - m_server->capInitiateNegotiation (parameterList.mid(2).join(QStringLiteral(" "))); + if (parameterList.count() == 3) + setAutomaticRequest (QStringLiteral("CAP LS"), QString (), false); + m_server->capInitiateNegotiation (trailing); } } } else if (command == QStringLiteral("authenticate") && plHas(1)) { if ((m_server->getLastAuthenticateCommand() == QStringLiteral("PLAIN") || m_server->getLastAuthenticateCommand() == QStringLiteral("EXTERNAL")) && parameterList.value(0) == QStringLiteral("+")) m_server->registerWithServices(); } // All yet unknown messages go into the frontmost window unaltered else { qDebug() << "unknown server command" << command; m_server->appendMessageToFrontmost(command, parameterList.join(QStringLiteral(" ")), messageTags); } } else if (plHas(2)) //[0]==ourNick, [1] needs to be *something* { parseNumeric(prefix, numeric, parameterList, messageTags); } // end of numeric elseif else { qDebug() << "unknown message format" << parameterList.count() << _plHad << _plWanted << command << parameterList.join(QStringLiteral(" ")); } } // end of server void InputFilter::parseModes(const QString &sourceNick, const QStringList ¶meterList, const QHash &messageTags) { const QString modestring=parameterList.value(1); if (!isAChannel(parameterList.value(0))) { QString message; if (parameterList.value(0) == m_server->getNickname()) { if (sourceNick == m_server->getNickname()) { //XXX someone might care about the potentially unnecessary plural here message = i18n("You have set personal modes: %1", modestring); } else { //XXX someone might care about the potentially unnecessary plural here message = i18n("%1 has changed your personal modes: %2", sourceNick, modestring); } } if (!message.isEmpty()) m_server->appendStatusMessage(i18n("Mode"), message, messageTags); return; } bool plus=false; int parameterIndex=0; // List of modes that need a parameter (note exception with -k and -l) // Mode q is quiet on freenode and acts like b... if this is a channel mode on other // networks then more logic is needed here. --MrGrim //FIXME: The assumptions being made here about which modes have parameters and // which don't strike me as very wrong, as some of these mode chars are used // both as user and channel modes (e.g. A and O) and whether they use a para- // meter can vary between those cases. This would seem to lead to trouble with // the list indices down there. --hein, Thu Aug 6 19:48:02 CEST 2009 / r1008015 QString parameterModes = QStringLiteral("aAoOvhkbleIq"); QString message = i18n("%1 sets mode: %2", sourceNick, modestring); for (int index=0;indexupdateChannelMode(sourceNick, parameterList.value(0), mode, plus, parameter, messageTags); } } // endfor if (Preferences::self()->useLiteralModes()) { m_server->appendCommandMessageToChannel(parameterList.value(0), i18n("Mode"), message, messageTags); } } // # & + and ! are *often*, but not necessarily, Channel identifiers. + and ! are non-RFC, // so if a server doesn't offer 005 and supports + and ! channels, I think thats broken behaviour // on their part - not ours. --Argonel bool InputFilter::isAChannel(const QString &check) { if (check.isEmpty()) return false; Q_ASSERT(m_server); // if we ever see the assert, we need the ternary return m_server? m_server->isAChannel(check) : bool(QString(QStringLiteral("#&")).contains(check.at(0))); } bool InputFilter::isIgnore(const QString &sender, Ignore::Type type) { bool doIgnore = false; foreach (Ignore* item, Preferences::ignoreList()) { QRegExp ignoreItem(QRegExp::escape(item->getName()).replace(QStringLiteral("\\*"), QStringLiteral("(.*)")), Qt::CaseInsensitive); if (ignoreItem.exactMatch(sender) && (item->getFlags() & type)) doIgnore = true; if (ignoreItem.exactMatch(sender) && (item->getFlags() & Ignore::Exception)) return false; } return doIgnore; } void InputFilter::reset() { m_automaticRequest.clear(); m_whoRequestList.clear(); } void InputFilter::setAutomaticRequest(const QString& command, const QString& name, bool yes) { m_automaticRequest[command][name.toLower()] += (yes) ? 1 : -1; if(m_automaticRequest[command][name.toLower()]<0) { qDebug() << "( " << command << ", " << name << " ) was negative! Resetting!"; m_automaticRequest[command][name.toLower()]=0; } } int InputFilter::getAutomaticRequest(const QString& command, const QString& name) { return m_automaticRequest[command][name.toLower()]; } void InputFilter::addWhoRequest(const QString& name) { m_whoRequestList << name.toLower(); } bool InputFilter::isWhoRequestUnderProcess(const QString& name) { return (m_whoRequestList.contains(name.toLower())>0); } void InputFilter::setLagMeasuring(bool state) { m_lagMeasuring=state; } bool InputFilter::getLagMeasuring() { return m_lagMeasuring; } void InputFilter::parsePrivMsg(const QString& prefix, QStringList& parameterList, const QHash &messageTags) { int pos = prefix.indexOf(QLatin1Char('!')); QString source; QString sourceHostmask; QString message(trailing); if(pos > 0) { source = prefix.left(pos); sourceHostmask = prefix.mid(pos + 1); } else { source = prefix; } Application* konv_app = Application::instance(); message = konv_app->doAutoreplace(message, false).first; if(isAChannel(parameterList.value(0))) { if(!isIgnore(prefix, Ignore::Channel)) { Channel* channel = m_server->getChannelByName(parameterList.value(0)); if(channel) { QString label; if (m_server->getServerNickPrefixes().contains(parameterList.value(0).at(0))) { label = parameterList.value(0); } channel->append(source, message, messageTags, label); if(source != m_server->getNickname()) { QRegExp regexp(QStringLiteral("(^|[^\\d\\w])") + QRegExp::escape(m_server->loweredNickname()) + QStringLiteral("([^\\d\\w]|$)")); regexp.setCaseSensitivity(Qt::CaseInsensitive); if(message.contains(regexp)) { konv_app->notificationHandler()->nick(channel, source, message); } else { konv_app->notificationHandler()->message(channel, source, message); } } } } } else { if(!isIgnore(prefix,Ignore::Query)) { QString queryName = source; // Handle znc.in/self-message correctly if (source == m_server->getNickname()) queryName = parameterList[0]; NickInfoPtr nickinfo = m_server->obtainNickInfo(queryName); if (queryName == source) nickinfo->setHostmask(sourceHostmask); // Create a new query (server will check for dupes) Query* query = m_server->addQuery(nickinfo, false /*we didn't initiate this*/ ); // send action to query query->appendQuery(source, message, messageTags); if(source != m_server->getNickname() && query) { QRegExp regexp(QStringLiteral("(^|[^\\d\\w])") + QRegExp::escape(m_server->loweredNickname()) + QStringLiteral("([^\\d\\w]|$)")); regexp.setCaseSensitivity(Qt::CaseInsensitive); if(message.contains(regexp)) { konv_app->notificationHandler()->nick(query, source, message); } else { konv_app->notificationHandler()->queryMessage(query, source, message); } } } } } QHash InputFilter::parseMessageTags(const QString &line, int *startOfMessage) { int index = line.indexOf(QLatin1Char(' ')); *startOfMessage = index + 1; QStringList tags = line.mid(1, index - 1).split(QLatin1Char(';')); QHash tagHash; foreach(const QString &tag, tags) { QStringList tagList = tag.split(QLatin1Char('=')); tagHash.insert(tagList.first(), tagList.last()); } return tagHash; } void InputFilter::parseNumeric(const QString &prefix, int command, QStringList ¶meterList, const QHash &messageTags) { //:niven.freenode.net 353 argnl @ #konversation :@argonel psn @argnl bugbot pinotree CIA-13 //QString m_serverAssignedNick(parameterList.takeFirst()); QString m_serverAssignedNick(parameterList.first()); switch (command) { case RPL_WELCOME: case RPL_YOURHOST: case RPL_CREATED: { if (plHas(0)) //make the script happy { if (command == RPL_WELCOME) { QString host; if (trailing.contains(QStringLiteral("@"))) host = trailing.section(QLatin1Char('@'), 1); // re-set nickname, since the server may have truncated it if (m_serverAssignedNick != m_server->getNickname()) { m_server->renameNick(m_server->getNickname(), m_serverAssignedNick, messageTags); } // Send the welcome signal, so the server class knows we are connected properly emit welcome(host); m_connecting = true; } m_server->appendStatusMessage(i18n("Welcome"), trailing, messageTags); } break; } case RPL_MYINFO: { if (plHas(5)) { m_server->appendStatusMessage(i18n("Welcome"), i18n("Server %1 (Version %2), User modes: %3, Channel modes: %4", parameterList.value(1), parameterList.value(2), parameterList.value(3), parameterList.value(4)), messageTags ); QString allowed = m_server->allowedChannelModes(); QString newModes = parameterList.value(4); if(!allowed.isEmpty()) //attempt to merge the two { for(int i=0; i < allowed.length(); i++) { if(!newModes.contains(allowed.at(i))) newModes.append(allowed.at(i)); } } m_server->setAllowedChannelModes(newModes); } break; } //case RPL_BOUNCE: // RFC 1459 name, now seems to be obsoleted by ... case RPL_ISUPPORT: // ... DALnet RPL_ISUPPORT { if (plHas(0)) //make the script happy { m_server->appendStatusMessage(i18n("Support"), parameterList.join(QStringLiteral(" ")), messageTags); // The following behaviour is neither documented in RFC 1459 nor in 2810-2813 // Nowadays, most ircds send server capabilities out via 005 (BOUNCE). // refer to http://www.irc.org/tech_docs/005.html for a kind of documentation. // More on http://www.irc.org/tech_docs/draft-brocklesby-irc-isupport-03.txt QStringList::const_iterator it = parameterList.constBegin(); // don't want the user name ++it; for (; it != parameterList.constEnd(); ++it ) { QString property, value; int pos; if ((pos=(*it).indexOf( QLatin1Char('=') )) !=-1) { property = (*it).left(pos); value = (*it).mid(pos+1); } else { property = *it; } if (property==QStringLiteral("PREFIX")) { pos = value.indexOf(QLatin1Char(')'),1); if(pos==-1) { m_server->setPrefixes(QString(), value); // XXX if ) isn't in the string, NOTHING should be there. anyone got a server if (value.length() || property.length()) m_server->appendStatusMessage(QString(), QStringLiteral("XXX Server sent bad PREFIX in RPL_ISUPPORT, please report."), messageTags); } else { m_server->setPrefixes(value.mid(1, pos-1), value.mid(pos+1)); } } else if (property==QStringLiteral("CHANTYPES")) { m_server->setChannelTypes(value); } else if (property==QStringLiteral("MODES")) { if (!value.isEmpty()) { bool ok = false; // If a value is given, it must be numeric. int modesCount = value.toInt(&ok, 10); if(ok) m_server->setModesCount(modesCount); } } else if (property == QStringLiteral("CAPAB")) { // Disable as we don't use this for anything yet //server->queue("CAPAB IDENTIFY-MSG"); } else if (property == QStringLiteral("CHANMODES")) { if(!value.isEmpty()) { m_server->setChanModes(value); QString allowed = m_server->allowedChannelModes(); QString newModes = value.remove(QLatin1Char(',')); if(!allowed.isEmpty()) //attempt to merge the two { for(int i=0; i < allowed.length(); i++) { if(!newModes.contains(allowed.at(i))) newModes.append(allowed.at(i)); } } m_server->setAllowedChannelModes(newModes); } } else if (property == QStringLiteral("TOPICLEN")) { if (!value.isEmpty()) { bool ok = false; int topicLength = value.toInt(&ok); if (ok) m_server->setTopicLength(topicLength); } } else if (property == QStringLiteral("WHOX")) { m_server->setHasWHOX(true); } else { //qDebug() << "Ignored server-capability: " << property << " with value '" << value << "'"; } } // endfor } break; } case RPL_UMODEIS: { if (plHas(0)) { // TODO check this one... I amputated + ' '+trailing QString message=QString(QStringLiteral("%1 %2")).arg(i18n("Your personal modes are:")).arg(parameterList.join(QLatin1Char(' ')).section(QLatin1Char(' '),1)); m_server->appendMessageToFrontmost(QStringLiteral("Info"), message, messageTags); } break; } case RPL_CHANNELMODEIS: { if (plHas(2)) { const QString modeString=parameterList.value(2); // TEST this one was a 2 // This is the string the user will see QString modesAre; QString message = i18n("Channel modes: ") + modeString; int parameterCount=3; QHash channelModesHash = Konversation::getChannelModesHash(); for (int index=0;indexupdateChannelModeWidgets(parameterList.value(1), mode, parameter); } } // endfor if (!modesAre.isEmpty() && Preferences::self()->useLiteralModes()) { m_server->appendCommandMessageToChannel(parameterList.value(1), i18n("Mode"), message, messageTags); } else { m_server->appendCommandMessageToChannel(parameterList.value(1), i18n("Mode"), i18n("Channel modes: ") + modesAre, messageTags); } } break; } case RPL_CHANNELURLIS: {// :niven.freenode.net 328 argonel #channel :http://www.buggeroff.com/ if (plHas(3)) { m_server->appendCommandMessageToChannel(parameterList.value(1), i18n("URL"), i18n("Channel URL: %1", trailing), messageTags); } break; } case RPL_CHANNELCREATED: { if (plHas(3)) { QDateTime when; when.setTime_t(parameterList.value(2).toUInt()); m_server->appendCommandMessageToChannel(parameterList.value(1), i18n("Created"), i18n("This channel was created on %1.", QLocale().toString(when, QLocale::ShortFormat)), messageTags ); } break; } case RPL_WHOISACCOUNT: { if (plHas(2)) { NickInfoPtr nickInfo = m_server->getNickInfo(parameterList.value(1)); if (nickInfo) { nickInfo->setIdentified(true); } // Display message only if this was not an automatic request. if (getAutomaticRequest(QStringLiteral("WHOIS"), parameterList.value(1)) == 0) { m_server->appendMessageToFrontmost(i18n("Whois"), i18n("%1 is logged in as %2.", parameterList.value(1), parameterList.value(2)), messageTags); } } break; } //:niven.freenode.net 353 argnl @ #konversation :@argonel psn @argnl bugbot pinotree CIA-13 case RPL_NAMREPLY: { if (plHas(4)) { QStringList nickList; if (!trailing.isEmpty()) { nickList = trailing.split(QLatin1Char(' '), QString::SkipEmptyParts); } else if (parameterList.count() > 3) { for(int i = 3; i < parameterList.count(); i++) { nickList.append(parameterList.value(i)); } } else { qDebug() << "Hmm seems something is broken... can't get to the names!"; } // send list to channel m_server->queueNicks(parameterList.value(2), nickList); // TEST this was a 2 // Display message only if this was not an automatic request. if (getAutomaticRequest(QStringLiteral("NAMES"), parameterList.value(2)) == 0) { m_server->appendMessageToFrontmost(i18n("Names"), trailing, messageTags); } } break; } case RPL_ENDOFNAMES: { if (plHas(2)) { if (getAutomaticRequest(QStringLiteral("NAMES"),parameterList.value(1)) != 0) { // This code path was taken for the automatic NAMES input on JOIN, upcoming // NAMES input for this channel will be manual invocations of /names setAutomaticRequest(QStringLiteral("NAMES"), parameterList.value(1), false); } else { m_server->appendMessageToFrontmost(i18n("Names"), i18n("End of NAMES list."), messageTags); } emit endOfNames(parameterList.value(1)); } break; } // Topic set messages case RPL_NOTOPIC: { if (plHas(2)) { //this really has 3, but [2] is "No topic has been set" m_server->appendMessageToFrontmost(i18n("TOPIC"), i18n("The channel %1 has no topic set.", parameterList.value(1)), messageTags); } break; } case RPL_TOPIC: { if (plHas(3)) { QString topic(trailing); // FIXME: This is an abuse of the automaticRequest system: We're // using it in an inverted manner, i.e. the automaticRequest is // set to true by a manual invocation of /topic. Bad bad bad - // needs rethinking of automaticRequest. if (getAutomaticRequest(QStringLiteral("TOPIC"), parameterList.value(1)) == 0) { // Update channel window m_server->setChannelTopic(parameterList.value(1), topic, messageTags); } else { m_server->appendMessageToFrontmost(i18n("Topic"), i18n("The channel topic for %1 is: \"%2\"", parameterList.value(1), topic), messageTags); } } break; } case RPL_TOPICSETBY: { if (plHas(4)) { // Inform user who set the topic and when QDateTime when; when.setTime_t(parameterList.value(3).toUInt()); // See FIXME in RPL_TOPIC if (getAutomaticRequest(QStringLiteral("TOPIC"), parameterList.value(1)) == 0) { m_server->appendCommandMessageToChannel(parameterList.value(1), i18n("Topic"), i18n("The topic was set by %1 on %2.", parameterList.value(2), QLocale().toString(when, QLocale::ShortFormat)), messageTags, false, false); } else { m_server->appendMessageToFrontmost(i18n("Topic"), i18n("The topic for %1 was set by %2 on %3.", parameterList.value(1), parameterList.value(2), QLocale().toString(when, QLocale::ShortFormat)), messageTags, false); setAutomaticRequest(QStringLiteral("TOPIC"),parameterList.value(1), false); } emit topicAuthor(parameterList.value(1), parameterList.value(2), when); } break; } case RPL_WHOISACTUALLY: { if (plHas(3)) { // Display message only if this was not an automatic request. if (getAutomaticRequest(QStringLiteral("WHOIS"),parameterList.value(1)) == 0) { m_server->appendMessageToFrontmost(i18n("Whois"), i18n("%1 is actually using the host %2.", parameterList.value(1), parameterList.value(2)), messageTags); } } break; } case ERR_NOSUCHNICK: { if (plHas(2)) { // Display slightly different error message in case we performed a WHOIS for // IP resolve purposes, and clear it from the automaticRequest list if (getAutomaticRequest(QStringLiteral("DNS"), parameterList.value(1)) == 0) { m_server->appendMessageToFrontmost(i18n("Error"), i18n("%1: No such nick/channel.", parameterList.value(1)), messageTags); } else if(getAutomaticRequest(QStringLiteral("WHOIS"), parameterList.value(1)) == 0) //Display message only if this was not an automatic request. { m_server->appendMessageToFrontmost(i18n("Error"), i18n("No such nick: %1.", parameterList.value(1)), messageTags); setAutomaticRequest(QStringLiteral("DNS"), parameterList.value(1), false); } } break; } case ERR_NOSUCHCHANNEL: { if (plHas(2)) { // Display message only if this was not an automatic request. if (getAutomaticRequest(QStringLiteral("WHOIS"), parameterList.value(1)) == 0) { m_server->appendMessageToFrontmost(i18n("Error"), i18n("%1: No such channel.", parameterList.value(1)), messageTags); } } break; } // Nick already on the server, so try another one case ERR_NICKNAMEINUSE: { if (plHas(1)) { // if we are already connected, don't try tro find another nick ourselves if (m_server->isConnected()) // Show message { m_server->appendMessageToFrontmost(i18n("Nick"), i18n("Nickname already in use, try a different one."), messageTags); } else // not connected yet, so try to find a nick that's not in use { // Get the next nick from the list or ask for a new one QString newNick = m_server->getNextNickname(); // The user chose to disconnect... if (newNick.isNull()) { if (m_server->isConnecting()) // ... or did they? m_server->disconnectServer(); else // No they didn't! m_server->appendMessageToFrontmost(i18n("Info"), i18n("The nickname %1 was already in use, but the connection failed before you responded.", m_server->getNickname()), messageTags); } else { // Update Server window m_server->obtainNickInfo(m_server->getNickname()) ; m_server->renameNick(m_server->getNickname(), newNick, messageTags); // Show message m_server->appendMessageToFrontmost(i18n("Nick"), i18n("Nickname already in use. Trying %1.", newNick), messageTags); // Send nickchange request to the server m_server->queue(QStringLiteral("NICK ")+newNick); } } } break; } case ERR_ERRONEUSNICKNAME: { if (plHas(1)) { if (m_server->isConnected()) { // We are already connected. Just print the error message m_server->appendMessageToFrontmost(i18n("Nick"), trailing, messageTags); } else // Find a new nick as in ERR_NICKNAMEINUSE { QString newNick = m_server->getNextNickname(); // The user chose to disconnect if (newNick.isNull()) { m_server->disconnectServer(); } else { m_server->obtainNickInfo(m_server->getNickname()) ; m_server->renameNick(m_server->getNickname(), newNick, messageTags); m_server->appendMessageToFrontmost(i18n("Nick"), i18n("Erroneous nickname. Changing nick to %1.", newNick), messageTags); m_server->queue(QStringLiteral("NICK ")+newNick); } } } break; } case ERR_NOTONCHANNEL: { if (plHas(2)) { m_server->appendMessageToFrontmost(i18n("Error"), i18n("You are not on %1.", parameterList.value(1)), messageTags); setAutomaticRequest(QStringLiteral("TOPIC"),parameterList.value(1), false); } break; } case RPL_MOTDSTART: { if (plHas(1)) { if (!m_connecting || !Preferences::self()->skipMOTD()) m_server->appendStatusMessage(i18n("MOTD"), i18n("Message of the day:"), messageTags); } break; } case RPL_MOTD: { if (plHas(2)) { if (!m_connecting || !Preferences::self()->skipMOTD()) m_server->appendStatusMessage(i18n("MOTD"), trailing, messageTags); } break; } case RPL_ENDOFMOTD: { if (plHas(1)) { if (!m_connecting || !Preferences::self()->skipMOTD()) m_server->appendStatusMessage(i18n("MOTD"), i18n("End of message of the day"), messageTags); if (m_connecting) m_server->autoCommandsAndChannels(); m_connecting = false; } break; } case ERR_NOMOTD: { if (plHas(1)) { if (m_connecting) m_server->autoCommandsAndChannels(); m_connecting = false; } break; } case ERR_CHANOPRIVSNEEDED: { if (plHas(2)) { m_server->appendMessageToFrontmost(i18n("Error"), i18n("You need to be a channel operator in %1 to do that.", parameterList.value(1)), messageTags); } break; } case RPL_YOUREOPER: { if (plHas(1)) { m_server->appendMessageToFrontmost(i18n("Notice"), i18n("You are now an IRC operator on this server."), messageTags); } break; } case RPL_HOSTHIDDEN: { if (plHas(2)) { m_server->appendStatusMessage(i18n("Info"), i18n("'%1' is now your hidden host (set by services).", parameterList.value(1)), messageTags); } break; } case RPL_GLOBALUSERS: // Current global users: 589 Max: 845 { if (plHas(2)) { QString current(trailing.section(QLatin1Char(' '),3)); //QString max(trailing.section(' ',5,5)); m_server->appendStatusMessage(i18n("Users"), i18n("Current users on the network: %1", current), messageTags); } break; } case RPL_LOCALUSERS: // Current local users: 589 Max: 845 { if (plHas(2)) { QString current(trailing.section(QLatin1Char(' '), 3)); //QString max(trailing.section(' ',5,5)); m_server->appendStatusMessage(i18n("Users"), i18n("Current users on %1: %2.", prefix, current), messageTags); } break; } case RPL_ISON: { if (plHas(2)) { // Tell server to start the next notify timer round emit notifyResponse(trailing); } break; } case RPL_AWAY: { if (plHas(3)) { NickInfoPtr nickInfo = m_server->getNickInfo(parameterList.value(1)); if (nickInfo) { nickInfo->setAway(true); if (nickInfo->getAwayMessage() != trailing) // FIXME i think this check should be in the setAwayMessage method { nickInfo->setAwayMessage(trailing); // TEST this used to skip the automatic request handler below } } if (getAutomaticRequest(QStringLiteral("WHOIS"), parameterList.value(1)) == 0) { m_server->appendMessageToFrontmost(i18n("Away"), i18n("%1 is away: %2", parameterList.value(1), trailing), messageTags ); } } break; } case RPL_INVITING: { if (plHas(3)) { m_server->appendMessageToFrontmost(i18n("Invite"), i18n("You invited %1 to channel %2.", parameterList.value(1), parameterList.value(2)), messageTags ); } break; } //Sample WHOIS response //"/WHOIS psn" //[19:11] :zahn.freenode.net 311 PhantomsDad psn ~psn h106n2fls23o1068.bredband.comhem.se * :Peter Simonsson //[19:11] :zahn.freenode.net 319 PhantomsDad psn :#kde-devel #koffice //[19:11] :zahn.freenode.net 312 PhantomsDad psn irc.freenode.net :http://freenode.net/ //[19:11] :zahn.freenode.net 301 PhantomsDad psn :away //[19:11] :zahn.freenode.net 320 PhantomsDad psn :is an identified user //[19:11] :zahn.freenode.net 317 PhantomsDad psn 4921 1074973024 :seconds idle, signon time //[19:11] :zahn.freenode.net 318 PhantomsDad psn :End of /WHOIS list. case RPL_WHOISUSER: { if (plHas(4)) { NickInfoPtr nickInfo = m_server->getNickInfo(parameterList.value(1)); if (nickInfo) { nickInfo->setHostmask(i18n("%1@%2", parameterList.value(2), parameterList.value(3))); nickInfo->setRealName(trailing); } // Display message only if this was not an automatic request. if (getAutomaticRequest(QStringLiteral("WHOIS"), parameterList.value(1)) == 0) { // escape html tags QString escapedRealName(trailing); m_server->appendMessageToFrontmost(i18n("Whois"), i18n("%1 is %2@%3 (%4)", parameterList.value(1), parameterList.value(2), parameterList.value(3), escapedRealName), messageTags, false); // Don't parse any urls } else { // This WHOIS was requested by Server for DNS resolve purposes; try to resolve the host if (getAutomaticRequest(QStringLiteral("DNS"), parameterList.value(1)) != 0) { QHostInfo resolved = QHostInfo::fromName(parameterList.value(3)); if (resolved.error() == QHostInfo::NoError && !resolved.addresses().isEmpty()) { QString ip = resolved.addresses().first().toString(); m_server->appendMessageToFrontmost(i18n("DNS"), i18n("Resolved %1 (%2) to address: %3", parameterList.value(1), parameterList.value(3), ip), messageTags ); } else { m_server->appendMessageToFrontmost(i18n("Error"), i18n("Unable to resolve address for %1 (%2)", parameterList.value(1), parameterList.value(3)), messageTags ); } // Clear this from the automaticRequest list so it works repeatedly setAutomaticRequest(QStringLiteral("DNS"), parameterList.value(1), false); } } } break; } // From a WHOIS. //[19:11] :zahn.freenode.net 320 PhantomsDad psn :is an identified user case RPL_WHOISIDENTIFY: case RPL_IDENTIFIED: { if (plHas(2)) { NickInfoPtr nickInfo = m_server->getNickInfo(parameterList.value(1)); if (nickInfo) { nickInfo->setIdentified(true); } if (getAutomaticRequest(QStringLiteral("WHOIS"), parameterList.value(1)) == 0) { // Prints "psn is an identified user" //server->appendStatusMessage(i18n("Whois"),parameterList.join(" ").section(' ',1)+' '+trailing); // The above line works fine, but can't be i18n'ised. So use the below instead.. I hope this is okay. m_server->appendMessageToFrontmost(i18n("Whois"), i18n("%1 is an identified user.", parameterList.value(1)), messageTags); } } break; } case RPL_WHOISSECURE: { if (plHas(2)) { if (getAutomaticRequest(QStringLiteral("WHOIS"), parameterList.value(1)) == 0) m_server->appendMessageToFrontmost(i18n("Whois"), i18n("%1 is using a secure connection.", parameterList.value(1)), messageTags); } break; } // Sample WHO response //"/WHO #lounge" //[21:39] [352] #lounge jasmine bots.worldforge.org irc.worldforge.org jasmine H 0 jasmine //[21:39] [352] #lounge ~Nottingha worldforge.org irc.worldforge.org SherwoodSpirit H 0 Arboreal Entity case RPL_WHOSPCRPL: case RPL_WHOREPLY: { if (plHas(6)) { NickInfoPtr nickInfo = m_server->getNickInfo(parameterList.value(5)); // G=away G@=away,op G+=away,voice bool bAway = parameterList.value(6).toUpper().startsWith(QLatin1Char('G')); QString realName = trailing; if (realName.indexOf (QRegExp(QStringLiteral("\\d\\s"))) == 0) realName = realName.mid (2); if (nickInfo) { nickInfo->setHostmask(i18n("%1@%2", parameterList.value(2), parameterList.value(3))); nickInfo->setRealName(realName); nickInfo->setAway(bAway); if(!bAway) { nickInfo->setAwayMessage(QString()); } if(m_server->capabilities() & Server::WHOX && m_server->capabilities() & Server::ExtendedJoin) { nickInfo->setAccount(parameterList.value(8)); } } // Display message only if this was not an automatic request. if (!m_whoRequestList.isEmpty()) // for safe { if (getAutomaticRequest(QStringLiteral("WHO"),m_whoRequestList.front())==0) { m_server->appendMessageToFrontmost(i18n("Who"), i18n("%1 is %2@%3 (%4)%5", parameterList.value(5), parameterList.value(2), parameterList.value(3), realName, bAway?i18n(" (Away)"):QString()), messageTags, false); // Don't parse as url } } } break; } case RPL_ENDOFWHO: { if (plHas(2)) { if (!m_whoRequestList.isEmpty()) { const QString param = parameterList.value(1).toLower(); // for safety const int idx = m_whoRequestList.indexOf(param); if (idx > -1) { if (getAutomaticRequest(QStringLiteral("WHO"), param) == 0) { m_server->appendMessageToFrontmost(i18n("Who"), i18n("End of /WHO list for %1", parameterList.value(1)), messageTags); } else { setAutomaticRequest(QStringLiteral("WHO"), param, false); } m_whoRequestList.removeAt(idx); } else { // whoRequestList seems to be broken. qDebug() << "RPL_ENDOFWHO: malformed ENDOFWHO. retrieved: " << parameterList.value(1) << " expected: " << m_whoRequestList.front(); m_whoRequestList.clear(); } } else { qDebug() << "RPL_ENDOFWHO: unexpected ENDOFWHO. retrieved: " << parameterList.value(1); } emit endOfWho(parameterList.value(1)); } break; } case RPL_WHOISCHANNELS: { if (plHas(3)) { QStringList userChannels,voiceChannels,opChannels,halfopChannels,ownerChannels,adminChannels; // get a list of all channels the user is in QStringList channelList=trailing.split(QLatin1Char(' '), QString::SkipEmptyParts); channelList.sort(); // split up the list in channels where they are operator / user / voice for (int index=0; index < channelList.count(); index++) { QString lookChannel=channelList[index]; if (lookChannel.startsWith(QLatin1Char('*')) || lookChannel.startsWith(QLatin1Char('&'))) { adminChannels.append(lookChannel.mid(1)); m_server->setChannelNick(lookChannel.mid(1), parameterList.value(1), 16); } // See bug #97354 part 2 else if((lookChannel.startsWith(QLatin1Char('!')) || lookChannel.startsWith(QLatin1Char('~'))) && m_server->isAChannel(lookChannel.mid(1))) { ownerChannels.append(lookChannel.mid(1)); m_server->setChannelNick(lookChannel.mid(1), parameterList.value(1), 8); } // See bug #97354 part 1 else if (lookChannel.startsWith(QStringLiteral("@+"))) { opChannels.append(lookChannel.mid(2)); m_server->setChannelNick(lookChannel.mid(2), parameterList.value(1), 4); } else if (lookChannel.startsWith(QLatin1Char('@'))) { opChannels.append(lookChannel.mid(1)); m_server->setChannelNick(lookChannel.mid(1), parameterList.value(1), 4); } else if (lookChannel.startsWith(QLatin1Char('%'))) { halfopChannels.append(lookChannel.mid(1)); m_server->setChannelNick(lookChannel.mid(1), parameterList.value(1), 2); } else if (lookChannel.startsWith(QLatin1Char('+'))) { voiceChannels.append(lookChannel.mid(1)); m_server->setChannelNick(lookChannel.mid(1), parameterList.value(1), 1); } else { userChannels.append(lookChannel); m_server->setChannelNick(lookChannel, parameterList.value(1), 0); } } // endfor // Display message only if this was not an automatic request. if (getAutomaticRequest(QStringLiteral("WHOIS"), parameterList.value(1)) == 0) { if (userChannels.count()) { m_server->appendMessageToFrontmost(i18n("Whois"), i18n("%1 is a user on channels: %2", parameterList.value(1), userChannels.join(QStringLiteral(" "))), messageTags ); } if (voiceChannels.count()) { m_server->appendMessageToFrontmost(i18n("Whois"), i18n("%1 has voice on channels: %2", parameterList.value(1), voiceChannels.join(QStringLiteral(" "))), messageTags ); } if (halfopChannels.count()) { m_server->appendMessageToFrontmost(i18n("Whois"), i18n("%1 is a halfop on channels: %2", parameterList.value(1), halfopChannels.join(QStringLiteral(" "))), messageTags ); } if (opChannels.count()) { m_server->appendMessageToFrontmost(i18n("Whois"), i18n("%1 is an operator on channels: %2", parameterList.value(1), opChannels.join(QStringLiteral(" "))), messageTags ); } if (ownerChannels.count()) { m_server->appendMessageToFrontmost(i18n("Whois"), i18n("%1 is owner of channels: %2", parameterList.value(1), ownerChannels.join(QStringLiteral(" "))), messageTags ); } if (adminChannels.count()) { m_server->appendMessageToFrontmost(i18n("Whois"), i18n("%1 is admin on channels: %2", parameterList.value(1), adminChannels.join(QStringLiteral(" "))), messageTags ); } } } break; } case RPL_WHOISSERVER: { if (plHas(4)) { NickInfoPtr nickInfo = m_server->getNickInfo(parameterList.value(1)); if (nickInfo) { nickInfo->setNetServer(parameterList.value(2)); nickInfo->setNetServerInfo(trailing); // Clear the away state on assumption that if nick is away, this message will be followed // by a 301 RPL_AWAY message. Not necessary a invalid assumption, but what can we do? nickInfo->setAway(false); nickInfo->setAwayMessage(QString()); } // Display message only if this was not an automatic request. if (getAutomaticRequest(QStringLiteral("WHOIS"), parameterList.value(1)) == 0) { m_server->appendMessageToFrontmost(i18n("Whois"), i18n("%1 is online via %2 (%3).", parameterList.value(1), parameterList.value(2), trailing), messageTags ); } } break; } case RPL_WHOISHELPER: { if (plHas(2)) { // Display message only if this was not an automatic request. if (getAutomaticRequest(QStringLiteral("WHOIS"), parameterList.value(1)) == 0) { m_server->appendMessageToFrontmost(i18n("Whois"), i18n("%1 is available for help.", parameterList.value(1)), messageTags ); } } break; } case RPL_WHOISOPERATOR: { if (plHas(2)) { // Display message only if this was not an automatic request. if (getAutomaticRequest(QStringLiteral("WHOIS"), parameterList.value(1)) == 0) { if (trailing.toLower().simplified().startsWith(QLatin1String("is an irc operator"))) m_server->appendMessageToFrontmost(i18n("Whois"), i18n("%1 is an IRC Operator.", parameterList.value(1)), messageTags); else m_server->appendMessageToFrontmost(i18n("Whois"), QString(QStringLiteral("%1 %2")).arg(parameterList.value(1)).arg(trailing), messageTags); } } break; } case RPL_WHOISIDLE: { if (plHas(3)) { // get idle time in seconds bool ok = false; long seconds = parameterList.value(2).toLong(&ok); if (!ok) break; long minutes = seconds/60; long hours = minutes/60; long days = hours/24; QDateTime signonTime; uint signonTimestamp = parameterList.value(3).toUInt(&ok); if (ok && parameterList.count() == 5) signonTime.setTime_t(signonTimestamp); if (!signonTime.isNull()) { NickInfoPtr nickInfo = m_server->getNickInfo(parameterList.value(1)); if (nickInfo) nickInfo->setOnlineSince(signonTime); } // if idle time is longer than a day // Display message only if this was not an automatic request. if (getAutomaticRequest(QStringLiteral("WHOIS"), parameterList.value(1)) == 0) { if (days) { const QString daysString = i18np("1 day", "%1 days", days); const QString hoursString = i18np("1 hour", "%1 hours", (hours % 24)); const QString minutesString = i18np("1 minute", "%1 minutes", (minutes % 60)); const QString secondsString = i18np("1 second", "%1 seconds", (seconds % 60)); m_server->appendMessageToFrontmost(i18n("Whois"), i18nc("%1 = name of person, %2 = (x days), %3 = (x hours), %4 = (x minutes), %5 = (x seconds)", "%1 has been idle for %2, %3, %4, and %5.", parameterList.value(1), daysString, hoursString, minutesString, secondsString), messageTags); // or longer than an hour } else if (hours) { const QString hoursString = i18np("1 hour", "%1 hours", hours); const QString minutesString = i18np("1 minute", "%1 minutes", (minutes % 60)); const QString secondsString = i18np("1 second", "%1 seconds", (seconds % 60)); m_server->appendMessageToFrontmost(i18n("Whois"), i18nc("%1 = name of person, %2 = (x hours), %3 = (x minutes), %4 = (x seconds)", "%1 has been idle for %2, %3, and %4.", parameterList.value(1), hoursString, minutesString, secondsString), messageTags); // or longer than a minute } else if (minutes) { const QString minutesString = i18np("1 minute", "%1 minutes", minutes); const QString secondsString = i18np("1 second", "%1 seconds", (seconds % 60)); m_server->appendMessageToFrontmost(i18n("Whois"), i18nc("%1 = name of person, %2 = (x minutes), %3 = (x seconds)", "%1 has been idle for %2 and %3.", parameterList.value(1), minutesString, secondsString), messageTags); // or just some seconds } else { m_server->appendMessageToFrontmost(i18n("Whois"), i18np("%2 has been idle for 1 second.", "%2 has been idle for %1 seconds.", seconds, parameterList.value(1)), messageTags); } if (!signonTime.isNull()) { m_server->appendMessageToFrontmost(i18n("Whois"), i18n("%1 has been online since %2.", parameterList.value(1), QLocale().toString(signonTime, QLocale::ShortFormat)), messageTags); } } } break; } case RPL_ENDOFWHOIS: { if (plHas(2)) { /*FIXME why is the nickinfo line below commented out? //NickInfoPtr nickInfo = m_server->getNickInfo(parameterList.value(1)); */ // Display message only if this was not an automatic request. if (getAutomaticRequest(QStringLiteral("WHOIS"), parameterList.value(1)) == 0) { m_server->appendMessageToFrontmost(i18n("Whois"), i18n("End of WHOIS list."), messageTags); } // was this an automatic request? if (getAutomaticRequest(QStringLiteral("WHOIS"), parameterList.value(1)) != 0) { setAutomaticRequest(QStringLiteral("WHOIS"), parameterList.value(1), false); } } break; } case RPL_USERHOST: { if (plHas(2)) { // iterate over all nick/masks in reply QStringList uhosts=trailing.split(QLatin1Char(' '), QString::SkipEmptyParts); for (int index=0;indexappendMessageToFrontmost(i18n("Userhost"), i18nc("%1 = nick, %2 = shows if nick is op, %3 = hostmask, %4 = shows away", "%1%2 is %3%4.", nick, (ircOp) ? i18n(" (IRC Operator)") : QString() ,mask, (away) ? i18n(" (away)") : QString()), messageTags); } // was this an automatic request? if (getAutomaticRequest(QStringLiteral("USERHOST"),nick)!=0) { setAutomaticRequest(QStringLiteral("USERHOST"),nick,false); } } // for } break; } case RPL_LISTSTART: //FIXME This reply is obsolete!!! { if (plHas(0)) { if (getAutomaticRequest(QStringLiteral("LIST"),QString())==0) { m_server->appendMessageToFrontmost(i18n("List"), i18n("List of channels:"), messageTags); } } break; } case RPL_LIST: { if (plHas(3)) { if (getAutomaticRequest(QStringLiteral("LIST"),QString())==0) { QString message; message=i18np("%2 (%1 user): %3", "%2 (%1 users): %3", parameterList.value(2).toInt(), parameterList.value(1), trailing); m_server->appendMessageToFrontmost(i18n("List"), message, messageTags); } else // send them to /LIST window { emit addToChannelList(parameterList.value(1), parameterList.value(2).toInt(), trailing); } } break; } case RPL_LISTEND: { if (plHas(0)) { // was this an automatic request? if (getAutomaticRequest(QStringLiteral("LIST"),QString())==0) { m_server->appendMessageToFrontmost(i18n("List"), i18n("End of channel list."), messageTags); } else { emit endOfChannelList(); setAutomaticRequest(QStringLiteral("LIST"),QString(),false); } } break; } case RPL_NOWAWAY: { if (plHas(1)) { NickInfoPtr nickInfo = m_server->getNickInfo(parameterList.value(0)); if (nickInfo) { nickInfo->setAway(true); } m_server->setAway(true, messageTags); } break; } case RPL_UNAWAY: { if (plHas(1)) { NickInfoPtr nickInfo = m_server->getNickInfo(parameterList.value(0)); if (nickInfo) { nickInfo->setAway(false); nickInfo->setAwayMessage(QString()); } m_server->setAway(false, messageTags); } break; } case RPL_BANLIST: { //:calvino.freenode.net 367 argonel #konversation fooish!~a@example.com argonel!argkde4@konversation/developer/argonel 1269464382 if (plHas(3)) { if (getAutomaticRequest(QStringLiteral("BANLIST"), parameterList.value(1))) { m_server->addBan(parameterList.value(1), parameterList.join(QStringLiteral(" ")).section(QLatin1Char(' '), 2, 4)); //<-- QString::Section handles out of bounds end parameter } else { QDateTime when; if (plHas(5)) when.setTime_t(parameterList.value(4).toUInt()); else when = QDateTime::currentDateTime(); //use todays date instead of Jan 1 1970 QString setter(parameterList.value(3, i18nc("The server didn't respond with the identity of the ban creator, so we say unknown (in brackets to avoid confusion with a real nickname)", "(unknown)")).section(QLatin1Char('!'), 0, 0)); m_server->appendMessageToFrontmost(i18n("BanList:%1", parameterList.value(1)), i18nc("BanList message: e.g. *!*@aol.com set by MrGrim on ", "%1 set by %2 on %3", parameterList.value(2), setter, QLocale().toString(when, QLocale::ShortFormat)), messageTags ); } } break; } case RPL_ENDOFBANLIST: { if (plHas(2)) { if (getAutomaticRequest(QStringLiteral("BANLIST"), parameterList.value(1))) { setAutomaticRequest(QStringLiteral("BANLIST"), parameterList.value(1), false); } else { m_server->appendMessageToFrontmost(i18n("BanList:%1", parameterList.value(1)), i18n("End of Ban List."), messageTags); } } break; } case ERR_NOCHANMODES: { if (plHas(3)) { ChatWindow *chatwindow = m_server->getChannelByName(parameterList.value(1)); if (chatwindow) { chatwindow->appendServerMessage(i18n("Channel"), trailing, messageTags); } else // We couldn't join the channel , so print the error. with [#channel] : { m_server->appendMessageToFrontmost(i18n("Channel"), trailing, messageTags); } } break; } case ERR_NOSUCHSERVER: { if (plHas(2)) { //Some servers don't know their name, so they return an error instead of the PING data if (getLagMeasuring() && trailing.startsWith(prefix)) { m_server->pongReceived(); } else if (getAutomaticRequest(QStringLiteral("WHOIS"), parameterList.value(1)) != 0) //Inhibit message if this was an automatic request { setAutomaticRequest(QStringLiteral("WHOIS"), parameterList.value(1), false); } else { m_server->appendMessageToFrontmost(i18n("Error"), i18n("No such server: %1.", parameterList.value(1)), messageTags); } } break; } case ERR_UNAVAILRESOURCE: { if (plHas(2)) { if (m_server->isConnected()) m_server->appendMessageToFrontmost(i18n("Error"), i18n("%1 is currently unavailable.", parameterList.value(1)), messageTags); else { QString newNick = m_server->getNextNickname(); // The user chose to disconnect if (newNick.isNull()) m_server->disconnectServer(); else { m_server->obtainNickInfo(m_server->getNickname()) ; m_server->renameNick(m_server->getNickname(), newNick, messageTags); m_server->appendMessageToFrontmost(i18n("Nick"), i18n("Nickname %1 is unavailable. Trying %2.", parameterList.value(1), newNick), messageTags); m_server->queue(QStringLiteral("NICK ")+newNick); } } } break; } case RPL_HIGHCONNECTCOUNT: case RPL_LUSERCLIENT: case RPL_LUSEROP: case RPL_LUSERUNKNOWN: case RPL_LUSERCHANNELS: case RPL_LUSERME: { // TODO make sure this works, i amputated the "+ ' '+trailing" if (plHas(0)) { m_server->appendStatusMessage(i18n("Users"), parameterList.join(QStringLiteral(" ")).section(QLatin1Char(' '),1), messageTags); } break; } case ERR_UNKNOWNCOMMAND: { if (plHas(2)) { m_server->appendMessageToFrontmost(i18n("Error"), i18n("%1: Unknown command.", parameterList.value(1)), messageTags); } break; } case ERR_NOTREGISTERED: { if (plHas(0)) { m_server->appendMessageToFrontmost(i18n("Error"), i18n("Not registered."), messageTags); } break; } case ERR_NEEDMOREPARAMS: { if (plHas(2)) { m_server->appendMessageToFrontmost(i18n("Error"), i18n("%1: This command requires more parameters.", parameterList.value(1)), messageTags); } break; } case RPL_CAPAB: // Special freenode reply afaik { if (plHas(2)) { // Disable as we don't use this for anything yet if (trailing.contains(QStringLiteral("IDENTIFY-MSG"))) { m_server->enableIdentifyMsg(true); } else // TEST is this right? split the logic up in prep for slotization { m_server->appendMessageToFrontmost(QString::number(command), parameterList.join(QStringLiteral(" ")).section(QLatin1Char(' '),1) + QLatin1Char(' ')+trailing, messageTags); } } break; } case ERR_BADCHANNELKEY: { if (plHas(2)) { m_server->appendMessageToFrontmost(i18n("Error"), i18n("Cannot join %1: The channel is password-protected and either a wrong or no password was given.", parameterList.value(1)), messageTags); } break; } case RPL_LOGGEDIN: { if (plHas(3)) m_server->appendStatusMessage(i18n("Info"), i18n("You are now logged in as %1.", parameterList.value(2)), messageTags); break; } case RPL_SASLSUCCESS: { if (plHas(2)) { m_server->appendStatusMessage(i18n("Info"), i18n("SASL authentication successful."), messageTags); m_server->capEndNegotiation(); NickInfoPtr nickInfo = m_server->getNickInfo(m_server->getNickname()); if (nickInfo) nickInfo->setIdentified(true); } break; } case ERR_SASLFAIL: { if (plHas(2)) { m_server->appendStatusMessage(i18n("Error"), i18n("SASL authentication attempt failed."), messageTags); m_server->capEndNegotiation(); } break; } case ERR_SASLABORTED: { if (plHas(2)) m_server->appendStatusMessage(i18n("Info"), i18n("SASL authentication aborted."), messageTags); break; } default: { // All yet unknown messages go into the frontmost window without the // preceding nickname qDebug() << "unknown numeric" << parameterList.count() << _plHad << _plWanted << command << parameterList.join(QStringLiteral(" ")); m_server->appendMessageToFrontmost(QString::number(command), parameterList.join(QStringLiteral(" ")), messageTags); } } // end of numeric switch if (!_plHad) qDebug() << "numeric format error" << parameterList.count() << _plHad << _plWanted << command << parameterList.join(QStringLiteral(" ")); } // kate: space-indent on; tab-width 4; indent-width 4; mixed-indent off; replace-tabs on; // vim: set et sw=4 ts=4 cino=l1,cs,U1: diff --git a/src/irc/server.cpp b/src/irc/server.cpp index 17d53182..9737d98a 100644 --- a/src/irc/server.cpp +++ b/src/irc/server.cpp @@ -1,4396 +1,4399 @@ // -*- mode: c++; c-file-style: "bsd"; c-basic-offset: 4; tabs-width: 4; indent-tabs-mode: nil -*- /* This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. */ /* Copyright (C) 2002 Dario Abatianni Copyright (C) 2005 Ismail Donmez Copyright (C) 2005-2016 Peter Simonsson Copyright (C) 2006-2008 Eli J. MacKenzie Copyright (C) 2005-2008 Eike Hein */ #include "server.h" #include "ircqueue.h" #include "query.h" #include "channel.h" #include "application.h" #include "connectionmanager.h" #include "dcccommon.h" #include "dccfiledialog.h" #include "transfermanager.h" #include "transfersend.h" #include "transferrecv.h" #include "chat.h" #include "recipientdialog.h" #include "nick.h" #include "irccharsets.h" #include "viewcontainer.h" #include "rawlog.h" #include "channellistpanel.h" #include "scriptlauncher.h" #include "serverison.h" #include "notificationhandler.h" #include "awaymanager.h" #include "ircinput.h" #include #include #include #include #include #include #include #include #include #include using namespace Konversation; int Server::m_availableConnectionId = 0; Server::Server(QObject* parent, ConnectionSettings& settings) : QObject(parent) { m_connectionId = m_availableConnectionId; m_availableConnectionId++; setConnectionSettings(settings); m_connectionState = Konversation::SSNeverConnected; m_recreationScheduled = false; m_delayedConnectTimer = new QTimer(this); m_delayedConnectTimer->setSingleShot(true); connect(m_delayedConnectTimer, SIGNAL(timeout()), this, SLOT(connectToIRCServer())); m_reconnectImmediately = false; for (int i=0; i <= Application::instance()->countOfQueues(); i++) { //QList r=Preferences::queueRate(i); IRCQueue *q=new IRCQueue(this, Application::instance()->staticrates[i]); //FIXME these are supposed to be in the rc m_queues.append(q); } m_processingIncoming = false; m_identifyMsg = false; m_capRequested = 0; m_capAnswered = 0; m_capEndDelayed = false; m_autoJoin = false; m_capabilities = NoCapabilies; initCapablityNames(); m_nickIndices.clear(); m_nickIndices.append(0); m_nickListModel = new QStringListModel(this); m_currentLag = -1; m_rawLog = 0; m_channelListPanel = 0; m_serverISON = 0; m_away = false; m_socket = 0; m_prevISONList = QStringList(); m_bytesReceived = 0; m_encodedBytesSent=0; m_bytesSent=0; m_linesSent=0; // TODO fold these into a QMAP, and these need to be reset to RFC values if this server object is reused. m_serverNickPrefixModes = QStringLiteral("ovh"); m_serverNickPrefixes = QStringLiteral("@+%"); m_banAddressListModes = QLatin1Char('b'); // {RFC-1459, draft-brocklesby-irc-isupport} -> pick one m_channelPrefixes = QStringLiteral("#&"); m_modesCount = 3; m_sslErrorLock = false; m_topicLength = -1; setObjectName(QString::fromLatin1("server_") + m_connectionSettings.name()); setNickname(m_connectionSettings.initialNick()); obtainNickInfo(getNickname()); m_statusView = getViewContainer()->addStatusView(this); if (Preferences::self()->rawLog()) addRawLog(false); m_inputFilter.setServer(this); m_outputFilter = new Konversation::OutputFilter(this); // For /msg query completion m_completeQueryPosition = 0; updateAutoJoin(m_connectionSettings.oneShotChannelList()); if (!getIdentity()->getShellCommand().isEmpty()) QTimer::singleShot(0, this, SLOT(doPreShellCommand())); else QTimer::singleShot(0, this, SLOT(connectToIRCServer())); initTimers(); if (getIdentity()->getShellCommand().isEmpty()) connectSignals(); // TODO FIXME this disappeared in a merge, ensure it should have updateConnectionState(Konversation::SSNeverConnected); m_nickInfoChangedTimer = new QTimer(this); m_nickInfoChangedTimer->setSingleShot(true); m_nickInfoChangedTimer->setInterval(3000); connect(m_nickInfoChangedTimer, SIGNAL(timeout()), this, SLOT(sendNickInfoChangedSignals())); m_channelNickChangedTimer = new QTimer(this); m_channelNickChangedTimer->setSingleShot(true); m_channelNickChangedTimer->setInterval(1000); connect(m_channelNickChangedTimer, SIGNAL(timeout()), this, SLOT(sendChannelNickChangedSignals())); } Server::~Server() { //send queued messages qDebug() << "Server::~Server(" << getServerName() << ")"; // Delete helper object. delete m_serverISON; m_serverISON = 0; // clear nicks online emit nicksNowOnline(this,QStringList(),true); // Make sure no signals get sent to a soon to be dying Server Window if (m_socket) { m_socket->blockSignals(true); m_socket->deleteLater(); } delete m_statusView; closeRawLog(); closeChannelListPanel(); if (m_recreationScheduled) { Konversation::ChannelList channelList; foreach (Channel* channel, m_channelList) { channelList << channel->channelSettings(); } m_connectionSettings.setOneShotChannelList(channelList); } qDeleteAll(m_channelList); m_channelList.clear(); m_loweredChannelNameHash.clear(); qDeleteAll(m_queryList); m_queryList.clear(); purgeData(); //Delete the queues qDeleteAll(m_queues); emit destroyed(m_connectionId); if (m_recreationScheduled) { qRegisterMetaType("ConnectionSettings"); qRegisterMetaType("Konversation::ConnectionFlag"); Application* konvApp = Application::instance(); QMetaObject::invokeMethod(konvApp->getConnectionManager(), "connectTo", Qt::QueuedConnection, Q_ARG(Konversation::ConnectionFlag, Konversation::CreateNewConnection), Q_ARG(ConnectionSettings, m_connectionSettings)); } qDebug() << "~Server done"; } void Server::purgeData() { // Delete all the NickInfos and ChannelNick structures. m_allNicks.clear(); ChannelMembershipMap::ConstIterator it; for ( it = m_joinedChannels.constBegin(); it != m_joinedChannels.constEnd(); ++it ) delete it.value(); m_joinedChannels.clear(); for ( it = m_unjoinedChannels.constBegin(); it != m_unjoinedChannels.constEnd(); ++it ) delete it.value(); m_unjoinedChannels.clear(); m_queryNicks.clear(); delete m_serverISON; m_serverISON = 0; } //... so called to match the ChatWindow derivatives. bool Server::closeYourself(bool askForConfirmation) { m_statusView->closeYourself(askForConfirmation); return true; } void Server::cycle() { m_recreationScheduled = true; m_statusView->closeYourself(); } void Server::doPreShellCommand() { KShell::Errors e; QStringList command = KShell::splitArgs(getIdentity()->getShellCommand(), KShell::TildeExpand, &e); if (e != KShell::NoError) { //FIXME The flow needs to be refactored, add a finally-like method that does the ready-to-connect stuff // "The pre-connect shell command could not be understood!"); preShellCommandExited(m_preShellCommand.exitCode(), m_preShellCommand.exitStatus()); } else { // FIXME add i18n, and in preShellCommandExited and preShellCommandError getStatusView()->appendServerMessage(i18n("Info"), i18nc("The command mentioned is executed in a shell prior to connecting.", "Running pre-connect shell command...")); connect(&m_preShellCommand, SIGNAL(finished(int,QProcess::ExitStatus)), SLOT(preShellCommandExited(int,QProcess::ExitStatus))); connect(&m_preShellCommand, SIGNAL(error(QProcess::ProcessError)), SLOT(preShellCommandError(QProcess::ProcessError))); m_preShellCommand.setProgram(command); m_preShellCommand.start(); // NOTE: isConnecting is tested in connectToIRCServer so there's no guard here if (m_preShellCommand.state() == QProcess::NotRunning) preShellCommandExited(m_preShellCommand.exitCode(), m_preShellCommand.exitStatus()); } } void Server::initTimers() { m_notifyTimer.setObjectName(QStringLiteral("notify_timer")); m_notifyTimer.setSingleShot(true); m_incomingTimer.setObjectName(QStringLiteral("incoming_timer")); m_pingSendTimer.setSingleShot(true); } void Server::connectSignals() { // Timers connect(&m_incomingTimer, SIGNAL(timeout()), this, SLOT(processIncomingData())); connect(&m_notifyTimer, SIGNAL(timeout()), this, SLOT(notifyTimeout())); connect(&m_pingResponseTimer, SIGNAL(timeout()), this, SLOT(updateLongPongLag())); connect(&m_pingSendTimer, SIGNAL(timeout()), this, SLOT(sendPing())); // OutputFilter connect(getOutputFilter(), SIGNAL(requestDccSend()), this,SLOT(requestDccSend()), Qt::QueuedConnection); connect(getOutputFilter(), SIGNAL(requestDccSend(QString)), this, SLOT(requestDccSend(QString)), Qt::QueuedConnection); connect(getOutputFilter(), SIGNAL(multiServerCommand(QString,QString)), this, SLOT(sendMultiServerCommand(QString,QString))); connect(getOutputFilter(), SIGNAL(reconnectServer(QString)), this, SLOT(reconnectServer(QString))); connect(getOutputFilter(), SIGNAL(disconnectServer(QString)), this, SLOT(disconnectServer(QString))); connect(getOutputFilter(), SIGNAL(quitServer(QString)), this, SLOT(quitServer(QString))); connect(getOutputFilter(), SIGNAL(openDccSend(QString,QUrl)), this, SLOT(addDccSend(QString,QUrl)), Qt::QueuedConnection); connect(getOutputFilter(), SIGNAL(openDccChat(QString)), this, SLOT(openDccChat(QString)), Qt::QueuedConnection); connect(getOutputFilter(), SIGNAL(openDccWBoard(QString)), this, SLOT(openDccWBoard(QString)), Qt::QueuedConnection); connect(getOutputFilter(), SIGNAL(acceptDccGet(QString,QString)), this, SLOT(acceptDccGet(QString,QString))); connect(getOutputFilter(), SIGNAL(sendToAllChannels(QString)), this, SLOT(sendToAllChannels(QString))); connect(getOutputFilter(), SIGNAL(banUsers(QStringList,QString,QString)), this, SLOT(requestBan(QStringList,QString,QString))); connect(getOutputFilter(), SIGNAL(unbanUsers(QString,QString)), this, SLOT(requestUnban(QString,QString))); connect(getOutputFilter(), SIGNAL(openRawLog(bool)), this, SLOT(addRawLog(bool))); connect(getOutputFilter(), SIGNAL(closeRawLog()), this, SLOT(closeRawLog())); connect(getOutputFilter(), SIGNAL(encodingChanged()), this, SLOT(updateEncoding())); Application* konvApp = Application::instance(); connect(getOutputFilter(), SIGNAL(connectTo(Konversation::ConnectionFlag,QString,QString,QString,QString,QString,bool)), konvApp->getConnectionManager(), SLOT(connectTo(Konversation::ConnectionFlag,QString,QString,QString,QString,QString,bool))); connect(konvApp->getDccTransferManager(), SIGNAL(newDccTransferQueued(Konversation::DCC::Transfer*)), this, SLOT(slotNewDccTransferItemQueued(Konversation::DCC::Transfer*))); // ViewContainer connect(this, SIGNAL(showView(ChatWindow*)), getViewContainer(), SLOT(showView(ChatWindow*))); connect(this, SIGNAL(addDccPanel()), getViewContainer(), SLOT(addDccPanel())); connect(this, SIGNAL(addDccChat(Konversation::DCC::Chat*)), getViewContainer(), SLOT(addDccChat(Konversation::DCC::Chat*)), Qt::QueuedConnection); connect(this, SIGNAL(serverLag(Server*,int)), getViewContainer(), SIGNAL(updateStatusBarLagLabel(Server*,int))); connect(this, SIGNAL(tooLongLag(Server*,int)), getViewContainer(), SIGNAL(setStatusBarLagLabelTooLongLag(Server*,int))); connect(this, SIGNAL(resetLag(Server*)), getViewContainer(), SIGNAL(resetStatusBarLagLabel(Server*))); connect(getOutputFilter(), SIGNAL(showView(ChatWindow*)), getViewContainer(), SLOT(showView(ChatWindow*))); connect(getOutputFilter(), SIGNAL(openKonsolePanel()), getViewContainer(), SLOT(addKonsolePanel())); connect(getOutputFilter(), SIGNAL(openChannelList(QString)), this, SLOT(requestOpenChannelListPanel(QString))); connect(getOutputFilter(), SIGNAL(closeDccPanel()), getViewContainer(), SLOT(closeDccPanel())); connect(getOutputFilter(), SIGNAL(addDccPanel()), getViewContainer(), SLOT(addDccPanel())); // Inputfilter - queued connections should be used for slots that have blocking UI connect(&m_inputFilter, SIGNAL(addDccChat(QString,QStringList)), this, SLOT(addDccChat(QString,QStringList)), Qt::QueuedConnection); connect(&m_inputFilter, SIGNAL(rejectDccChat(QString)), this, SLOT(rejectDccChat(QString))); connect(&m_inputFilter, SIGNAL(startReverseDccChat(QString,QStringList)), this, SLOT(startReverseDccChat(QString,QStringList))); connect(&m_inputFilter, SIGNAL(welcome(QString)), this, SLOT(capCheckIgnored())); connect(&m_inputFilter, SIGNAL(welcome(QString)), this, SLOT(connectionEstablished(QString))); connect(&m_inputFilter, SIGNAL(notifyResponse(QString)), this, SLOT(notifyResponse(QString))); connect(&m_inputFilter, SIGNAL(startReverseDccSendTransfer(QString,QStringList)), this, SLOT(startReverseDccSendTransfer(QString,QStringList))); connect(&m_inputFilter, SIGNAL(addDccGet(QString,QStringList)), this, SLOT(addDccGet(QString,QStringList)), Qt::QueuedConnection); connect(&m_inputFilter, SIGNAL(resumeDccGetTransfer(QString,QStringList)), this, SLOT(resumeDccGetTransfer(QString,QStringList))); connect(&m_inputFilter, SIGNAL(resumeDccSendTransfer(QString,QStringList)), this, SLOT(resumeDccSendTransfer(QString,QStringList))); connect(&m_inputFilter, SIGNAL(rejectDccSendTransfer(QString,QStringList)), this, SLOT(rejectDccSendTransfer(QString,QStringList))); connect(&m_inputFilter, SIGNAL(userhost(QString,QString,bool,bool)), this, SLOT(userhost(QString,QString,bool,bool)) ); connect(&m_inputFilter, SIGNAL(topicAuthor(QString,QString,QDateTime)), this, SLOT(setTopicAuthor(QString,QString,QDateTime)) ); connect(&m_inputFilter, SIGNAL(endOfWho(QString)), this, SLOT(endOfWho(QString)) ); connect(&m_inputFilter, SIGNAL(endOfNames(QString)), this, SLOT(endOfNames(QString)) ); connect(&m_inputFilter, SIGNAL(invitation(QString,QString)), this,SLOT(invitation(QString,QString)) ); connect(&m_inputFilter, SIGNAL(addToChannelList(QString,int,QString)), this, SLOT(addToChannelList(QString,int,QString))); // Status View connect(this, SIGNAL(serverOnline(bool)), getStatusView(), SLOT(serverOnline(bool))); // Scripts connect(getOutputFilter(), SIGNAL(launchScript(int,QString,QString)), konvApp->getScriptLauncher(), SLOT(launchScript(int,QString,QString))); connect(konvApp->getScriptLauncher(), SIGNAL(scriptNotFound(QString)), this, SLOT(scriptNotFound(QString))); connect(konvApp->getScriptLauncher(), SIGNAL(scriptExecutionError(QString)), this, SLOT(scriptExecutionError(QString))); connect(Preferences::self(), SIGNAL(notifyListStarted(int)), this, SLOT(notifyListStarted(int)), Qt::QueuedConnection); } int Server::getPort() { return getConnectionSettings().server().port(); } int Server::getLag() const { return m_currentLag; } bool Server::getAutoJoin() const { return m_autoJoin; } void Server::setAutoJoin(bool on) { m_autoJoin = on; } void Server::preShellCommandExited(int exitCode, QProcess::ExitStatus exitStatus) { Q_UNUSED(exitCode); if (exitStatus == QProcess::NormalExit) getStatusView()->appendServerMessage(i18n("Info"), i18n("Pre-shell command executed successfully!")); else { QString errorText = i18nc("An error message from KDE or Qt is appended.", "There was a problem while executing the command: ") % m_preShellCommand.errorString(); getStatusView()->appendServerMessage(i18n("Warning"), errorText); } connectToIRCServer(); connectSignals(); } void Server::preShellCommandError(QProcess::ProcessError error) { Q_UNUSED(error); QString errorText = i18nc("An error message from KDE or Qt is appended.", "There was a problem while executing the command: ") % m_preShellCommand.errorString(); getStatusView()->appendServerMessage(i18n("Warning"), errorText); connectToIRCServer(); connectSignals(); } void Server::connectToIRCServer() { if (!isConnected() && !isConnecting()) { if (m_sslErrorLock) { qDebug() << "Refusing to connect while SSL lock from previous connection attempt is being held."; return; } // Reenable check when it works reliably for all backends // if(Solid::Networking::status() != Solid::Networking::Connected) // { // updateConnectionState(Konversation::SSInvoluntarilyDisconnected); // return; // } updateConnectionState(Konversation::SSConnecting); m_ownIpByUserhost.clear(); resetQueues(); // This is needed to support server groups with mixed SSL and nonSSL servers delete m_socket; m_socket = 0; if (m_referenceNicklist != getIdentity()->getNicknameList()) m_nickListModel->setStringList(getIdentity()->getNicknameList()); resetNickSelection(); m_socket = new KTcpSocket(); m_socket->setObjectName(QStringLiteral("serverSocket")); connect(m_socket, SIGNAL(error(KTcpSocket::Error)), SLOT(broken(KTcpSocket::Error)) ); connect(m_socket, SIGNAL(readyRead()), SLOT(incoming())); connect(m_socket, SIGNAL(disconnected()), SLOT(closed())); connect(m_socket, SIGNAL(hostFound()), SLOT (hostFound())); getStatusView()->appendServerMessage(i18n("Info"),i18n("Looking for server %1 (port %2)...", getConnectionSettings().server().host(), QString::number(getConnectionSettings().server().port()))); KTcpSocket::ProxyPolicy proxyPolicy = KTcpSocket::AutoProxy; if(getConnectionSettings().server().bypassProxy()) { proxyPolicy = KTcpSocket::ManualProxy; m_socket->setProxy(QNetworkProxy::NoProxy); } // connect() will do a async lookup too if (getConnectionSettings().server().SSLEnabled() || getIdentity()->getAuthType() == QStringLiteral("saslexternal") || getIdentity()->getAuthType() == QStringLiteral("pemclientcert")) { connect(m_socket, SIGNAL(encrypted()), SLOT (socketConnected())); connect(m_socket, SIGNAL(sslErrors(QList)), SLOT(sslError(QList))); if (getIdentity()->getAuthType() == QStringLiteral("saslexternal") || getIdentity()->getAuthType() == QStringLiteral("pemclientcert")) { m_socket->setLocalCertificate(getIdentity()->getPemClientCertFile().toLocalFile()); m_socket->setPrivateKey(getIdentity()->getPemClientCertFile().toLocalFile()); } m_socket->setAdvertisedSslVersion(KTcpSocket::SecureProtocols); m_socket->connectToHostEncrypted(getConnectionSettings().server().host(), getConnectionSettings().server().port()); } else { connect(m_socket, SIGNAL(connected()), SLOT (socketConnected())); m_socket->connectToHost(getConnectionSettings().server().host(), getConnectionSettings().server().port(), proxyPolicy); } // set up the connection details setPrefixes(m_serverNickPrefixModes, m_serverNickPrefixes); // reset InputFilter (auto request info, /WHO request info) m_inputFilter.reset(); } else qDebug() << "connectToIRCServer() called while already connected: This should never happen. (" << (isConnecting() << 1) + isConnected() << ')'; } void Server::connectToIRCServerIn(uint delay) { m_delayedConnectTimer->setInterval(delay * 1000); m_delayedConnectTimer->start(); updateConnectionState(Konversation::SSScheduledToConnect); } void Server::showSSLDialog() { //TODO /* SSLSocket* sslsocket = dynamic_cast(m_socket); if (sslsocket) sslsocket->showInfoDialog(); */ } void Server::rebuildTargetPrefixMatcher() { m_targetMatcher.setPattern(QLatin1String("^([") + getServerNickPrefixes() + QLatin1String("]*)([") + getChannelTypes() + QLatin1String("])(.+)")); } // set available channel types according to 005 RPL_ISUPPORT void Server::setChannelTypes(const QString &pre) { m_channelPrefixes = pre; rebuildTargetPrefixMatcher(); if (getConnectionSettings().reconnectCount() == 0) { updateAutoJoin(m_connectionSettings.oneShotChannelList()); } else { updateAutoJoin(); } } QString Server::getChannelTypes() const { return m_channelPrefixes; } // set max number of channel modes with parameter according to 005 RPL_ISUPPORT void Server::setModesCount(int count) { m_modesCount = count; } int Server::getModesCount() { return m_modesCount; } // set user mode prefixes according to non-standard 005-Reply (see inputfilter.cpp) void Server::setPrefixes(const QString &modes, const QString& prefixes) { // NOTE: serverModes is QString::null, if server did not supply the // modes which relates to the network's nick-prefixes m_serverNickPrefixModes = modes; m_serverNickPrefixes = prefixes; rebuildTargetPrefixMatcher(); } QString Server::getServerNickPrefixes() const { return m_serverNickPrefixes; } void Server::setChanModes(const QString& modes) { QStringList abcd = modes.split(QLatin1Char(',')); m_banAddressListModes = abcd.value(0); } // return a nickname without possible mode character at the beginning void Server::mangleNicknameWithModes(QString& nickname,bool& isAdmin,bool& isOwner, bool& isOp,bool& isHalfop,bool& hasVoice) { isAdmin = false; isOwner = false; isOp = false; isHalfop = false; hasVoice = false; int modeIndex; if (nickname.isEmpty()) return; while ((modeIndex = m_serverNickPrefixes.indexOf(nickname[0])) != -1) { if(nickname.isEmpty()) return; nickname = nickname.mid(1); // cut off the prefix bool recognisedMode = false; // determine, whether status is like op or like voice while(modeIndex < m_serverNickPrefixes.length() && !recognisedMode) { switch(m_serverNickPrefixes[modeIndex].toLatin1()) { case '*': // admin (EUIRC) { isAdmin = true; recognisedMode = true; break; } case '&': // admin (unrealircd) { isAdmin = true; recognisedMode = true; break; } case '!': // channel owner (RFC2811) { isOwner = true; recognisedMode = true; break; } case '~': // channel owner (unrealircd) { isOwner = true; recognisedMode = true; break; } case '@': // channel operator (RFC1459) { isOp = true; recognisedMode = true; break; } case '%': // halfop { isHalfop = true; recognisedMode = true; break; } case '+': // voiced (RFC1459) { hasVoice = true; recognisedMode = true; break; } default: { ++modeIndex; break; } } //switch to recognise the mode. } // loop through the modes to find one recognised } // loop through the name } void Server::hostFound() { getStatusView()->appendServerMessage(i18n("Info"),i18n("Server found, connecting...")); } void Server::socketConnected() { emit sslConnected(this); getConnectionSettings().setReconnectCount(0); requestAvailableCapabilies(); QStringList ql; if (getIdentity() && getIdentity()->getAuthType() == QStringLiteral("serverpw") && !getIdentity()->getAuthPassword().isEmpty()) { ql << QStringLiteral("PASS ") + getIdentity()->getAuthPassword(); } else if (!getConnectionSettings().server().password().isEmpty()) ql << QStringLiteral("PASS ") + getConnectionSettings().server().password(); ql << QStringLiteral("NICK ") + getNickname(); ql << QStringLiteral("USER ") + getIdentity()->getIdent() + QStringLiteral(" 8 * :") /* 8 = +i; 4 = +w */ + getIdentity()->getRealName(); queueList(ql, HighPriority); connect(this, SIGNAL(nicknameChanged(QString)), getStatusView(), SLOT(setNickname(QString))); setNickname(getNickname()); } void Server::requestAvailableCapabilies () { + m_capRequested = 0; + m_capAnswered = 0; + m_capEndDelayed = false; + m_capabilities = NoCapabilies; getStatusView()->appendServerMessage(i18n("Info"),i18n("Negotiating capabilities with server...")); m_inputFilter.setAutomaticRequest(QStringLiteral("CAP LS"), QString(), true); - queue(QStringLiteral("CAP LS"), HighPriority); + queue(QStringLiteral("CAP LS 302"), HighPriority); } void Server::capInitiateNegotiation(const QString &availableCaps) { bool useSASL = false; if (getIdentity()) { // A username is optional SASL EXTERNAL and a client cert substitutes // for the password. if (getIdentity()->getAuthType() == QStringLiteral("saslexternal")) { useSASL = true; // PLAIN on the other hand requires both. } else if (getIdentity()->getAuthType() == QStringLiteral("saslplain") && !getIdentity()->getSaslAccount().isEmpty() && !getIdentity()->getAuthPassword().isEmpty()) { useSASL = true; } } - m_capRequested = 0; - m_capAnswered = 0; - m_capEndDelayed = false; - m_capabilities = NoCapabilies; QStringList requestCaps; QStringList capsList = availableCaps.split (QChar(' '), QString::SkipEmptyParts); + QStringList nameValue; foreach(const QString &cap, capsList) { - if(cap == QStringLiteral("sasl")) + nameValue = cap.split(QChar('=')); + + if(nameValue[0] == QStringLiteral("sasl")) { if(useSASL) requestCaps.append ("sasl"); } - else if(m_capabilityNames.contains(cap)) + else if(m_capabilityNames.contains(nameValue[0])) { - requestCaps.append (cap); + requestCaps.append (nameValue[0]); } } QString capsString = requestCaps.join (QStringLiteral(" ")); getStatusView()->appendServerMessage(i18n("Info"),i18n("Requesting capabilities: %1", capsString)); queue(QStringLiteral("CAP REQ :") + capsString, HighPriority); m_capRequested++; } void Server::capReply() { m_capAnswered++; } void Server::capEndNegotiation() { if(m_capRequested == m_capAnswered) { getStatusView()->appendServerMessage(i18n("Info"),i18n("Closing capabilities negotiation.")); queue(QStringLiteral("CAP END"), HighPriority); } } void Server::capCheckIgnored() { if (m_capRequested && !m_capAnswered) getStatusView()->appendServerMessage(i18n("Error"), i18n("Capabilities negotiation failed: Appears not supported by server.")); } void Server::capAcknowledged(const QString& name, Server::CapModifiers modifiers) { if (name == QStringLiteral("sasl") && modifiers == Server::NoModifiers) { const QString &authCommand = (getIdentity()->getAuthType() == QStringLiteral("saslexternal")) ? QStringLiteral("EXTERNAL") : QStringLiteral("PLAIN"); getStatusView()->appendServerMessage(i18n("Info"), i18n("SASL capability acknowledged by server, attempting SASL %1 authentication...", authCommand)); sendAuthenticate(authCommand); m_capEndDelayed = true; } m_capabilities |= m_capabilityNames.value(name); } void Server::capDenied(const QString& name) { if (name == QStringLiteral("sasl")) getStatusView()->appendServerMessage(i18n("Error"), i18n("SASL capability denied or not supported by server.")); } void Server::registerWithServices() { if (!getIdentity()) return; NickInfoPtr nickInfo = getNickInfo(getNickname()); if (nickInfo && nickInfo->isIdentified()) return; if (getIdentity()->getAuthType() == QStringLiteral("nickserv")) { if (!getIdentity()->getNickservNickname().isEmpty() && !getIdentity()->getNickservCommand().isEmpty() && !getIdentity()->getAuthPassword().isEmpty()) { queue(QStringLiteral("PRIVMSG ")+getIdentity()->getNickservNickname()+QStringLiteral(" :")+getIdentity()->getNickservCommand()+QLatin1Char(' ')+getIdentity()->getAuthPassword(), HighPriority); } } else if (getIdentity()->getAuthType() == QStringLiteral("saslplain")) { QString authString = getIdentity()->getSaslAccount(); authString.append(QChar(QChar::Null)); authString.append(getIdentity()->getSaslAccount()); authString.append(QChar(QChar::Null)); authString.append(getIdentity()->getAuthPassword()); sendAuthenticate(QLatin1String(authString.toLatin1().toBase64())); } else if (getIdentity()->getAuthType() == QStringLiteral("saslexternal")) { QString authString = getIdentity()->getSaslAccount(); // An account name is optional with SASL EXTERNAL. if (!authString.isEmpty()) { authString.append(QChar(QChar::Null)); authString.append(getIdentity()->getSaslAccount()); } sendAuthenticate(QLatin1String(authString.toLatin1().toBase64())); } } void Server::sendAuthenticate(const QString& message) { m_lastAuthenticateCommand = message; if (message.isEmpty()) { queue(QStringLiteral("AUTHENTICATE +"), HighPriority); } else { queue(QStringLiteral("AUTHENTICATE ") + message, HighPriority); } } void Server::broken(KTcpSocket::Error error) { Q_UNUSED(error); qDebug() << "Connection broken with state" << m_connectionState << "and error:" << m_socket->errorString(); m_socket->blockSignals(true); resetQueues(); m_notifyTimer.stop(); m_pingSendTimer.stop(); m_pingResponseTimer.stop(); m_inputFilter.setLagMeasuring(false); m_currentLag = -1; purgeData(); emit resetLag(this); emit nicksNowOnline(this, QStringList(), true); m_prevISONList.clear(); updateAutoJoin(); if (m_sslErrorLock) { // We got disconnected while dealing with an SSL error, e.g. due to the // user taking their time on dealing with the error dialog. Since auto- // reconnecting makes no sense in this situation, let's pass it off as a // deliberate disconnect. sslError() will either kick off a reconnect, or // reaffirm this. getStatusView()->appendServerMessage(i18n("SSL Connection Error"), i18n("Connection to server %1 (port %2) lost while waiting for user response to an SSL error. " "Will automatically reconnect if error is ignored.", getConnectionSettings().server().host(), QString::number(getConnectionSettings().server().port()))); updateConnectionState(SSDeliberatelyDisconnected); } else if (getConnectionState() == Konversation::SSDeliberatelyDisconnected) { if (m_reconnectImmediately) { m_reconnectImmediately = false; QMetaObject::invokeMethod(this, "connectToIRCServer", Qt::QueuedConnection); } } else { Application::instance()->notificationHandler()->connectionFailure(getStatusView(), getServerName()); QString error = i18n("Connection to server %1 (port %2) lost: %3.", getConnectionSettings().server().host(), QString::number(getConnectionSettings().server().port()), m_socket->errorString()); getStatusView()->appendServerMessage(i18n("Error"), error); updateConnectionState(Konversation::SSInvoluntarilyDisconnected); } // HACK Only show one nick change dialog at connection time. // This hack is a bit nasty as it assumes that the only QInputDialog // child of the statusview will be the nick change dialog. if (getStatusView()) { QInputDialog* nickChangeDialog = getStatusView()->findChild(); if (nickChangeDialog) nickChangeDialog->reject(); } } void Server::sslError( const QList& errors ) { // We have to explicitly grab the socket we got the error from, // lest we might end up calling ignoreSslErrors() on a different // socket later if m_socket has started pointing at something // else. QPointer socket = qobject_cast(QObject::sender()); m_sslErrorLock = true; bool ignoreSslErrors = KIO::SslUi::askIgnoreSslErrors(socket, KIO::SslUi::RecallAndStoreRules); m_sslErrorLock = false; // The dialog-based user interaction above may take an undefined amount // of time, and anything could happen to the socket in that span of time. // If it was destroyed, let's not do anything and bail out. if (!socket) { qDebug() << "Socket was destroyed while waiting for user interaction."; return; } // Ask the user if he wants to ignore the errors. if (ignoreSslErrors) { // Show a warning in the chat window that the SSL certificate failed the authenticity check. QString error = i18n("The SSL certificate for the server %1 (port %2) failed the authenticity check.", getConnectionSettings().server().host(), QString::number(getConnectionSettings().server().port())); getStatusView()->appendServerMessage(i18n("SSL Connection Warning"), error); // We may have gotten disconnected while waiting for user response and have // to reconnect. if (isConnecting()) { // The user has chosen to ignore SSL errors. socket->ignoreSslErrors(); } else { // QueuedConnection is vital here, otherwise we're deleting the socket // in a slot connected to one of its signals (connectToIRCServer deletes // any old socket) and crash. QMetaObject::invokeMethod(this, "connectToIRCServer", Qt::QueuedConnection); } } else { // Don't auto-reconnect if the user chose to ignore the SSL errors -- // treat it as a deliberate disconnect. updateConnectionState(Konversation::SSDeliberatelyDisconnected); QString errorReason; for (int i = 0; i < errors.size(); ++i) { errorReason += errors.at(i).errorString() + QLatin1Char(' '); } QString error = i18n("Could not connect to %1 (port %2) using SSL encryption. Either the server does not support SSL (did you use the correct port?) or you rejected the certificate. %3", getConnectionSettings().server().host(), QString::number(getConnectionSettings().server().port()), errorReason); getStatusView()->appendServerMessage(i18n("SSL Connection Error"), error); emit sslInitFailure(); } } // Will be called from InputFilter as soon as the Welcome message was received void Server::connectionEstablished(const QString& ownHost) { // Some servers don't include the userhost in RPL_WELCOME, so we // need to use RPL_USERHOST to get ahold of our IP later on if (!ownHost.isEmpty()) QHostInfo::lookupHost(ownHost, this, SLOT(gotOwnResolvedHostByWelcome(QHostInfo))); updateConnectionState(Konversation::SSConnected); // Make a helper object to build ISON (notify) list. // TODO: Give the object a kick to get it started? delete m_serverISON; m_serverISON = new ServerISON(this); // get first notify very early startNotifyTimer(1000); // Register with services if (getIdentity() && getIdentity()->getAuthType() == QStringLiteral("nickserv")) registerWithServices(); // get own ip by userhost requestUserhost(getNickname()); // Start the PINGPONG match m_pingSendTimer.start(1000 /*1 sec*/); // Recreate away state if we were set away prior to a reconnect. if (m_away) { // Correct server's beliefs about its away state. m_away = false; requestAway(m_awayReason); } } //FIXME operator[] inserts an empty T& so each destination might just as well have its own key storage QByteArray Server::getKeyForRecipient(const QString& recipient) const { return m_keyHash[recipient.toLower()]; } void Server::setKeyForRecipient(const QString& recipient, const QByteArray& key) { m_keyHash[recipient.toLower()] = key; } void Server::gotOwnResolvedHostByWelcome(const QHostInfo& res) { if (res.error() == QHostInfo::NoError && !res.addresses().isEmpty()) m_ownIpByWelcome = res.addresses().first().toString(); else qDebug() << "Got error: " << res.errorString(); } bool Server::isSocketConnected() const { if (!m_socket) return false; return (m_socket->state() == KTcpSocket::ConnectedState); } void Server::updateConnectionState(Konversation::ConnectionState state) { if (state != m_connectionState) { m_connectionState = state; if (m_connectionState == Konversation::SSConnected) emit serverOnline(true); else if (m_connectionState != Konversation::SSConnecting) emit serverOnline(false); emit connectionStateChanged(this, state); } } void Server::reconnectServer(const QString& quitMessage) { if (isConnecting() || isSocketConnected()) { m_reconnectImmediately = true; quitServer(quitMessage); } else QMetaObject::invokeMethod(this, "connectToIRCServer", Qt::QueuedConnection); } void Server::disconnectServer(const QString& quitMessage) { getConnectionSettings().setReconnectCount(0); if (isScheduledToConnect()) { m_delayedConnectTimer->stop(); getStatusView()->appendServerMessage(i18n("Info"), i18n("Delayed connect aborted.")); } if (isSocketConnected()) quitServer(quitMessage); } void Server::quitServer(const QString& quitMessage) { // Make clear this is deliberate even if the QUIT never actually goes through the queue // (i.e. this is not redundant with _send_internal()'s updateConnectionState() call for // a QUIT). updateConnectionState(Konversation::SSDeliberatelyDisconnected); if (!m_socket) return; QString toServer = QStringLiteral("QUIT :"); if (quitMessage.isEmpty()) toServer += getIdentity()->getQuitReason(); else toServer += quitMessage; queue(toServer, HighPriority); flushQueues(); m_socket->flush(); // Close the socket to allow a dead connection to be reconnected before the socket timeout. m_socket->close(); getStatusView()->appendServerMessage(i18n("Info"), i18n("Disconnected from %1 (port %2).", getConnectionSettings().server().host(), QString::number(getConnectionSettings().server().port()))); } void Server::notifyAction(const QString& nick) { QString out(Preferences::self()->notifyDoubleClickAction()); getOutputFilter()->replaceAliases(out); // parse wildcards (toParse,nickname,channelName,nickList,parameter) out = parseWildcards(out, getNickname(), QString(), QString(), nick, QString()); // Send all strings, one after another QStringList outList = out.split(QLatin1Char('\n'), QString::SkipEmptyParts); for (int index=0; indexparse(getNickname(),outList[index],QString()); queue(result.toServer); } // endfor } void Server::notifyResponse(const QString& nicksOnline) { bool nicksOnlineChanged = false; QStringList actualList = nicksOnline.split(QLatin1Char(' '), QString::SkipEmptyParts); QString lcActual = QLatin1Char(' ') + nicksOnline + QLatin1Char(' '); QString lcPrevISON = QLatin1Char(' ') + (m_prevISONList.join(QStringLiteral(" "))) + QLatin1Char(' '); QStringList::iterator it; //Are any nicks gone offline for (it = m_prevISONList.begin(); it != m_prevISONList.end(); ++it) { if (!lcActual.contains(QLatin1Char(' ') + (*it) + QLatin1Char(' '), Qt::CaseInsensitive)) { setNickOffline(*it); nicksOnlineChanged = true; } } //Are any nicks gone online for (it = actualList.begin(); it != actualList.end(); ++it) { if (!lcPrevISON.contains(QLatin1Char(' ') + (*it) + QLatin1Char(' '), Qt::CaseInsensitive)) { setWatchedNickOnline(*it); nicksOnlineChanged = true; } } // Note: The list emitted in this signal *does* include nicks in joined channels. emit nicksNowOnline(this, actualList, nicksOnlineChanged); m_prevISONList = actualList; // Next round startNotifyTimer(); } void Server::notifyListStarted(int serverGroupId) { if (getServerGroup()) if (getServerGroup()->id() == serverGroupId) startNotifyTimer(1000); } void Server::startNotifyTimer(int msec) { // make sure the timer gets started properly in case we have reconnected m_notifyTimer.stop(); if (Preferences::self()->useNotify()) { if (msec == 0) msec = Preferences::self()->notifyDelay() * 1000; m_notifyTimer.start(msec); } } void Server::notifyTimeout() { // Notify delay time is over, send ISON request if desired if (Preferences::self()->useNotify()) { // But only if there actually are nicks in the notify list QString list = getISONListString(); if (!list.isEmpty()) queue(QStringLiteral("ISON ") + list, LowPriority); } } void Server::autoCommandsAndChannels() { if (getServerGroup() && !getServerGroup()->connectCommands().isEmpty()) { QString connectCommands = getServerGroup()->connectCommands(); if (!getNickname().isEmpty()) connectCommands.replace(QStringLiteral("%nick"), getNickname()); QStringList connectCommandsList = connectCommands.split(QLatin1Char(';'), QString::SkipEmptyParts); QStringList::iterator iter; for (iter = connectCommandsList.begin(); iter != connectCommandsList.end(); ++iter) { QString output(*iter); output = output.simplified(); getOutputFilter()->replaceAliases(output); Konversation::OutputFilterResult result = getOutputFilter()->parse(getNickname(),output,QString()); queue(result.toServer); } } if (getAutoJoin()) { for ( QStringList::Iterator it = m_autoJoinCommands.begin(); it != m_autoJoinCommands.end(); ++it ) queue((*it)); } if (!m_connectionSettings.oneShotChannelList().isEmpty()) { QStringList oneShotJoin = generateJoinCommand(m_connectionSettings.oneShotChannelList()); for ( QStringList::Iterator it = oneShotJoin.begin(); it != oneShotJoin.end(); ++it ) { queue((*it)); } m_connectionSettings.clearOneShotChannelList(); } } /** Create a set of indices into the nickname list of the current identity based on the current nickname. * * The index list is only used if the current nickname is not available. If the nickname is in the identity, * we do not want to retry it. If the nickname is not in the identity, it is considered to be at position -1. */ void Server::resetNickSelection() { m_nickIndices.clear(); // For equivalence testing in case the identity gets changed underneath us. m_referenceNicklist = getIdentity()->getNicknameList(); for (int i = 0; i < m_referenceNicklist.length(); ++i) { // Pointless to include the nick we're already going to use. if (m_referenceNicklist.at(i) != getNickname()) { m_nickIndices.append(i); } } } QString Server::getNextNickname() { //if the identity changed underneath us (likely impossible), start over if (m_referenceNicklist != getIdentity()->getNicknameList()) resetNickSelection(); QString newNick; if (!m_nickIndices.isEmpty()) { newNick = getIdentity()->getNickname(m_nickIndices.takeFirst()); } else { QString inputText = i18n("No nicknames from the \"%1\" identity were accepted by the connection \"%2\".\nPlease enter a new one or press Cancel to disconnect:", getIdentity()->getName(), getDisplayName()); newNick = QInputDialog::getText(getStatusView(), i18n("Nickname error"), inputText); } return newNick; } void Server::processIncomingData() { m_incomingTimer.stop(); if (!m_inputBuffer.isEmpty() && !m_processingIncoming) { m_processingIncoming = true; QString front(m_inputBuffer.front()); m_inputBuffer.pop_front(); m_inputFilter.parseLine(front); m_processingIncoming = false; if (!m_inputBuffer.isEmpty()) m_incomingTimer.start(0); } } void Server::incoming() { //if (getConnectionSettings().server().SSLEnabled()) // emit sslConnected(this); //if (len <= 0 && getConnectionSettings().server().SSLEnabled()) // return; // split buffer to lines QList bufferLines; while (m_socket->canReadLine()) { QByteArray line(m_socket->readLine()); //remove \n blowfish doesn't like it int i = line.size()-1; while (i >= 0 && (line[i]=='\n' || line[i]=='\r')) // since euIRC gets away with sending just \r, bet someone sends \n\r? { i--; } line.truncate(i+1); if (line.size() > 0) bufferLines.append(line); } while(!bufferLines.isEmpty()) { // Pre parsing is needed in case encryption/decryption is needed // BEGIN set channel encoding if specified QString senderNick; bool isServerMessage = false; QString channelKey; QTextCodec* codec = getIdentity()->getCodec(); QByteArray first = bufferLines.first(); bufferLines.removeFirst(); QStringList lineSplit = codec->toUnicode(first).split(QLatin1Char(' '), QString::SkipEmptyParts); if (lineSplit.count() >= 1) { if (lineSplit[0][0] == QLatin1Char(':')) // does this message have a prefix? { if(!lineSplit[0].contains(QLatin1Char('!'))) // is this a server(global) message? isServerMessage = true; else senderNick = lineSplit[0].mid(1, lineSplit[0].indexOf(QLatin1Char('!'))-1); lineSplit.removeFirst(); // remove prefix } } if (lineSplit.isEmpty()) continue; // BEGIN pre-parse to know where the message belongs to QString command = lineSplit[0].toLower(); if( isServerMessage ) { if( lineSplit.count() >= 3 ) { if( command == QStringLiteral("332") ) // RPL_TOPIC channelKey = lineSplit[2]; if( command == QStringLiteral("372") ) // RPL_MOTD channelKey = QStringLiteral(":server"); } } else // NOT a global message { if( lineSplit.count() >= 2 ) { // query if( ( command == QStringLiteral("privmsg") || command == QStringLiteral("notice") ) && lineSplit[1] == getNickname() ) { channelKey = senderNick; } // channel message else if( command == QStringLiteral("privmsg") || command == QStringLiteral("notice") || command == QStringLiteral("join") || command == QStringLiteral("kick") || command == QStringLiteral("part") || command == QStringLiteral("topic") ) { channelKey = lineSplit[1]; } } } // END pre-parse to know where the message belongs to // Decrypt if necessary //send to raw log before decryption if (m_rawLog) m_rawLog->appendRaw(RawLog::Inbound, first); #ifdef HAVE_QCA2 QByteArray cKey = getKeyForRecipient(channelKey); if(!cKey.isEmpty()) { if(command == "privmsg") { //only send encrypted text to decrypter int index = first.indexOf(":",first.indexOf(":")+1); if(this->identifyMsgEnabled()) // Workaround braindead Freenode prefixing messages with + ++index; QByteArray backup = first.mid(0,index+1); if(getChannelByName(channelKey) && getChannelByName(channelKey)->getCipher()->setKey(cKey)) first = getChannelByName(channelKey)->getCipher()->decrypt(first.mid(index+1)); else if(getQueryByName(channelKey) && getQueryByName(channelKey)->getCipher()->setKey(cKey)) first = getQueryByName(channelKey)->getCipher()->decrypt(first.mid(index+1)); first.prepend(backup); } else if(command == "332" || command == "topic") { //only send encrypted text to decrypter int index = first.indexOf(":",first.indexOf(":")+1); QByteArray backup = first.mid(0,index+1); if(getChannelByName(channelKey) && getChannelByName(channelKey)->getCipher()->setKey(cKey)) first = getChannelByName(channelKey)->getCipher()->decryptTopic(first.mid(index+1)); else if(getQueryByName(channelKey) && getQueryByName(channelKey)->getCipher()->setKey(cKey)) first = getQueryByName(channelKey)->getCipher()->decryptTopic(first.mid(index+1)); first.prepend(backup); } } #endif bool isUtf8 = Konversation::isUtf8(first); QString encoded; if (isUtf8) encoded = QString::fromUtf8(first); else { // check setting QString channelEncoding; if( !channelKey.isEmpty() ) { if(getServerGroup()) channelEncoding = Preferences::channelEncoding(getServerGroup()->id(), channelKey); else channelEncoding = Preferences::channelEncoding(getDisplayName(), channelKey); } // END set channel encoding if specified if( !channelEncoding.isEmpty() ) codec = Konversation::IRCCharsets::self()->codecForName(channelEncoding); // if channel encoding is utf-8 and the string is definitely not utf-8 // then try latin-1 if (codec->mibEnum() == 106) codec = QTextCodec::codecForMib( 4 /* iso-8859-1 */ ); encoded = codec->toUnicode(first); } // Qt uses 0xFDD0 and 0xFDD1 to mark the beginning and end of text frames. Remove // these here to avoid fatal errors encountered in QText* and the event loop pro- // cessing. sterilizeUnicode(encoded); if (!encoded.isEmpty()) m_inputBuffer << encoded; //FIXME: This has nothing to do with bytes, and it's not raw received bytes either. Bogus number. //m_bytesReceived+=m_inputBuffer.back().length(); } if( !m_incomingTimer.isActive() && !m_processingIncoming ) m_incomingTimer.start(0); } /** Calculate how long this message premable will be. This is necessary because the irc server will clip messages so that the client receives a maximum of 512 bytes at once. */ int Server::getPreLength(const QString& command, const QString& dest) { NickInfoPtr info = getNickInfo(getNickname()); int hostMaskLength = 0; if(info) hostMaskLength = info->getHostmask().length(); //:Sho_!i=ehs1@konversation/developer/hein PRIVMSG #konversation :and then back to it //$nickname$hostmask$command$destination$message int x= 512 - 8 - (m_nickname.length() + hostMaskLength + command.length() + dest.length()); return x; } //Commands greater than 1 have localizeable text: 0 1 2 3 4 5 6 static QStringList outcmds = QString(QStringLiteral("WHO QUIT PRIVMSG NOTICE KICK PART TOPIC")).split(QChar(QLatin1Char(' '))); int Server::_send_internal(QString outputLine) { QStringList outputLineSplit = outputLine.split(QLatin1Char(' '), QString::SkipEmptyParts); int outboundCommand = -1; if (!outputLineSplit.isEmpty()) { //Lets cache the uppercase command so we don't miss or reiterate too much outboundCommand = outcmds.indexOf(outputLineSplit[0].toUpper()); } if (outputLine.at(outputLine.length()-1) == QLatin1Char('\n')) { qDebug() << "found \\n on " << outboundCommand; outputLine.resize(outputLine.length()-1); } // remember the first arg of /WHO to identify responses if (outboundCommand == 0 && outputLineSplit.count() >= 2) //"WHO" m_inputFilter.addWhoRequest(outputLineSplit[1]); else if (outboundCommand == 1) //"QUIT" updateConnectionState(Konversation::SSDeliberatelyDisconnected); // set channel encoding if specified QString channelCodecName; //[ PRIVMSG | NOTICE | KICK | PART | TOPIC ] target :message if (outputLineSplit.count() > 2 && outboundCommand > 1) { if(getServerGroup()) // if we're connecting via a servergroup channelCodecName=Preferences::channelEncoding(getServerGroup()->id(), outputLineSplit[1]); else //if we're connecting to a server manually channelCodecName=Preferences::channelEncoding(getDisplayName(), outputLineSplit[1]); } QTextCodec* codec = 0; if (channelCodecName.isEmpty()) codec = getIdentity()->getCodec(); else codec = Konversation::IRCCharsets::self()->codecForName(channelCodecName); // Some codecs don't work with a negative value. This is a bug in Qt 3. // ex.: JIS7, eucJP, SJIS //int outlen=-1; //leaving this done twice for now, I'm uncertain of the implications of not encoding other commands QByteArray encoded = outputLine.toUtf8(); if(codec) encoded = codec->fromUnicode(outputLine); #ifdef HAVE_QCA2 QString cipherKey; if (outputLineSplit.count() > 2 && outboundCommand > 1) cipherKey = getKeyForRecipient(outputLineSplit.at(1)); if (!cipherKey.isEmpty()) { int colon = outputLine.indexOf(':'); if (colon > -1) { colon++; QString pay(outputLine.mid(colon)); //only encode the actual user text, IRCD *should* desire only ASCII 31 < x < 127 for protocol elements QByteArray payload = pay.toUtf8(); QByteArray dest; if (codec) { payload=codec->fromUnicode(pay); //apparently channel name isn't a protocol element... dest = codec->fromUnicode(outputLineSplit.at(1)); } else { dest = outputLineSplit.at(1).toLatin1(); } if (outboundCommand == 2 || outboundCommand == 6) // outboundCommand == 3 { bool doit = true; if (outboundCommand == 2) { //if its a privmsg and a ctcp but not an action, don't encrypt //not interpreting `payload` in case encoding bollixed it if (outputLineSplit.at(2).startsWith(QLatin1String(":\x01")) && outputLineSplit.at(2) != ":\x01""ACTION") doit = false; } if (doit) { QString target = outputLineSplit.at(1); if(getChannelByName(target) && getChannelByName(target)->getCipher()->setKey(cipherKey.toLocal8Bit())) getChannelByName(target)->getCipher()->encrypt(payload); else if(getQueryByName(target) && getQueryByName(target)->getCipher()->setKey(cipherKey.toLocal8Bit())) getQueryByName(target)->getCipher()->encrypt(payload); encoded = outputLineSplit.at(0).toLatin1(); qDebug() << payload << "\n" << payload.data(); //two lines because the compiler insists on using the wrong operator+ encoded += ' ' + dest + " :" + payload; } } } } #endif if (m_rawLog) m_rawLog->appendRaw(RawLog::Outbound, encoded); encoded += '\n'; qint64 sout = m_socket->write(encoded); return sout; } void Server::toServer(QString&s, IRCQueue* q) { int sizesent = _send_internal(s); emit sentStat(s.length(), sizesent, q); //tell the queues what we sent collectStats(s.length(), sizesent); } void Server::collectStats(int bytes, int encodedBytes) { m_bytesSent += bytes; m_encodedBytesSent += encodedBytes; m_linesSent++; } bool Server::validQueue(QueuePriority priority) { if (priority >=0 && priority <= Application::instance()->countOfQueues()) return true; return false; } bool Server::queue(const QString& line, QueuePriority priority) { if (!line.isEmpty() && validQueue(priority)) { IRCQueue& out=*m_queues[priority]; out.enqueue(line); return true; } return false; } bool Server::queueList(const QStringList& buffer, QueuePriority priority) { if (buffer.isEmpty() || !validQueue(priority)) return false; IRCQueue& out=*(m_queues[priority]); for(int i=0;icountOfQueues(); i++) m_queues[i]->reset(); } //this could flood you off, but you're leaving anyway... void Server::flushQueues() { int cue; do { cue=-1; int wait=0; for (int i=1; i <= Application::instance()->countOfQueues(); i++) //slow queue can rot { IRCQueue *queue=m_queues[i]; //higher queue indices have higher priorty, higher queue priority wins tie if (!queue->isEmpty() && queue->currentWait()>=wait) { cue=i; wait=queue->currentWait(); } } if (cue>-1) m_queues[cue]->sendNow(); } while (cue>-1); } void Server::closed() { broken(m_socket->error()); } void Server::dbusRaw(const QString& command) { if(command.startsWith(Preferences::self()->commandChar())) { queue(command.section(Preferences::self()->commandChar(), 1)); } else queue(command); } void Server::dbusSay(const QString& target,const QString& command) { if(isAChannel(target)) { Channel* channel=getChannelByName(target); if(channel) channel->sendText(command); } else { Query* query = getQueryByName(target); if(query==0) { NickInfoPtr nickinfo = obtainNickInfo(target); query=addQuery(nickinfo, true); } if(query) { if(!command.isEmpty()) query->sendText(command); else { query->adjustFocus(); getViewContainer()->getWindow()->show(); KWindowSystem::demandAttention(getViewContainer()->getWindow()->winId()); KWindowSystem::activateWindow(getViewContainer()->getWindow()->winId()); } } } } void Server::dbusInfo(const QString& string) { appendMessageToFrontmost(i18n("D-Bus"),string); } void Server::ctcpReply(const QString &receiver,const QString &text) { queue(QStringLiteral("NOTICE ")+receiver+QStringLiteral(" :")+QLatin1Char('\x01')+text+QLatin1Char('\x01')); } // Given a nickname, returns NickInfo object. 0 if not found. NickInfoPtr Server::getNickInfo(const QString& nickname) { QString lcNickname(nickname.toLower()); if (m_allNicks.contains(lcNickname)) { NickInfoPtr nickinfo = m_allNicks[lcNickname]; Q_ASSERT(nickinfo); return nickinfo; } else return NickInfoPtr(); //! TODO FIXME null null null } // Given a nickname, returns an existing NickInfo object, or creates a new NickInfo object. // Returns pointer to the found or created NickInfo object. NickInfoPtr Server::obtainNickInfo(const QString& nickname) { NickInfoPtr nickInfo = getNickInfo(nickname); if (!nickInfo) { nickInfo = new NickInfo(nickname, this); m_allNicks.insert(QString(nickname.toLower()), nickInfo); } return nickInfo; } const NickInfoMap* Server::getAllNicks() { return &m_allNicks; } // Returns the list of members for a channel in the joinedChannels list. // 0 if channel is not in the joinedChannels list. // Using code must not alter the list. const ChannelNickMap *Server::getJoinedChannelMembers(const QString& channelName) const { QString lcChannelName = channelName.toLower(); if (m_joinedChannels.contains(lcChannelName)) return m_joinedChannels[lcChannelName]; else return 0; } // Returns the list of members for a channel in the unjoinedChannels list. // 0 if channel is not in the unjoinedChannels list. // Using code must not alter the list. const ChannelNickMap *Server::getUnjoinedChannelMembers(const QString& channelName) const { QString lcChannelName = channelName.toLower(); if (m_unjoinedChannels.contains(lcChannelName)) return m_unjoinedChannels[lcChannelName]; else return 0; } // Searches the Joined and Unjoined lists for the given channel and returns the member list. // 0 if channel is not in either list. // Using code must not alter the list. const ChannelNickMap *Server::getChannelMembers(const QString& channelName) const { const ChannelNickMap *members = getJoinedChannelMembers(channelName); if (members) return members; else return getUnjoinedChannelMembers(channelName); } // Returns pointer to the ChannelNick (mode and pointer to NickInfo) for a given channel and nickname. // 0 if not found. ChannelNickPtr Server::getChannelNick(const QString& channelName, const QString& nickname) { QString lcNickname = nickname.toLower(); const ChannelNickMap *channelNickMap = getChannelMembers(channelName); if (channelNickMap) { if (channelNickMap->contains(lcNickname)) return (*channelNickMap)[lcNickname]; else return ChannelNickPtr(); //! TODO FIXME null null null } else { return ChannelNickPtr(); //! TODO FIXME null null null } } // Updates a nickname in a channel. If not on the joined or unjoined lists, and nick // is in the watch list, adds the channel and nick to the unjoinedChannels list. // If mode != 99, sets the mode for the nick in the channel. // Returns the NickInfo object if nick is on any lists, otherwise 0. ChannelNickPtr Server::setChannelNick(const QString& channelName, const QString& nickname, unsigned int mode) { QString lcNickname = nickname.toLower(); // If already on a list, update mode. ChannelNickPtr channelNick = getChannelNick(channelName, lcNickname); if (!channelNick) { // If the nick is on the watch list, add channel and nick to unjoinedChannels list. if (getWatchList().contains(lcNickname, Qt::CaseInsensitive)) { channelNick = addNickToUnjoinedChannelsList(channelName, nickname); channelNick->setMode(mode); } else return ChannelNickPtr(); //! TODO FIXME null null null } if (mode != 99) channelNick->setMode(mode); return channelNick; } // Returns a list of all the joined channels that a nick is in. QStringList Server::getNickJoinedChannels(const QString& nickname) { QString lcNickname = nickname.toLower(); QStringList channellist; ChannelMembershipMap::ConstIterator channel; for( channel = m_joinedChannels.constBegin(); channel != m_joinedChannels.constEnd(); ++channel ) { if (channel.value()->contains(lcNickname)) channellist.append(channel.key()); } return channellist; } // Returns a list of all the channels (joined or unjoined) that a nick is in. QStringList Server::getNickChannels(const QString& nickname) { QString lcNickname = nickname.toLower(); QStringList channellist; ChannelMembershipMap::ConstIterator channel; for( channel = m_joinedChannels.constBegin(); channel != m_joinedChannels.constEnd(); ++channel ) { if (channel.value()->contains(lcNickname)) channellist.append(channel.key()); } for( channel = m_unjoinedChannels.constBegin(); channel != m_unjoinedChannels.constEnd(); ++channel ) { if (channel.value()->contains(lcNickname)) channellist.append(channel.key()); } return channellist; } QStringList Server::getSharedChannels(const QString& nickname) { QString lcNickname = nickname.toLower(); QStringList channellist; ChannelMembershipMap::ConstIterator channel; for( channel = m_joinedChannels.constBegin(); channel != m_joinedChannels.constEnd(); ++channel ) { if (channel.value()->contains(lcNickname)) channellist.append(channel.key()); } return channellist; } bool Server::isNickOnline(const QString &nickname) { NickInfoPtr nickInfo = getNickInfo(nickname); return (nickInfo != nullptr); } QString Server::getOwnIpByNetworkInterface() { return m_socket->localAddress().toString(); } QString Server::getOwnIpByServerMessage() { if(!m_ownIpByWelcome.isEmpty()) return m_ownIpByWelcome; else if(!m_ownIpByUserhost.isEmpty()) return m_ownIpByUserhost; else return QString(); } Query* Server::addQuery(const NickInfoPtr & nickInfo, bool weinitiated) { QString nickname = nickInfo->getNickname(); // Only create new query object if there isn't already one with the same name Query* query = getQueryByName(nickname); if (!query) { QString lcNickname = nickname.toLower(); query = getViewContainer()->addQuery(this, nickInfo, weinitiated); query->indicateAway(m_away); connect(query, SIGNAL(sendFile(QString)),this, SLOT(requestDccSend(QString))); connect(this, SIGNAL(serverOnline(bool)), query, SLOT(serverOnline(bool))); // Append query to internal list m_queryList.append(query); m_queryNicks.insert(lcNickname, nickInfo); if (!weinitiated) Application::instance()->notificationHandler()->query(query, nickname); } else if (weinitiated) { emit showView(query); } // try to get hostmask if there's none yet if (query->getNickInfo()->getHostmask().isEmpty()) requestUserhost(nickname); Q_ASSERT(query); return query; } void Server::closeQuery(const QString &name) { Query* query = getQueryByName(name); removeQuery(query); // Update NickInfo. If no longer on any lists, delete it altogether, but // only if not on the watch list. ISON replies will determine whether the NickInfo // is deleted altogether in that case. QString lcNickname = name.toLower(); m_queryNicks.remove(lcNickname); if (!isWatchedNick(name)) deleteNickIfUnlisted(name); } void Server::closeChannel(const QString& name) { qDebug() << "Server::closeChannel(" << name << ")"; Channel* channelToClose = getChannelByName(name); if (channelToClose && channelToClose->joined()) { Konversation::OutputFilterResult result = getOutputFilter()->parse(getNickname(), Preferences::self()->commandChar() + QStringLiteral("PART"), name); queue(result.toServer); } } void Server::requestChannelList() { m_inputFilter.setAutomaticRequest(QStringLiteral("LIST"), QString(), true); queue(QStringLiteral("LIST")); } void Server::requestWhois(const QString& nickname) { m_inputFilter.setAutomaticRequest(QStringLiteral("WHOIS"), nickname, true); queue(QStringLiteral("WHOIS ")+nickname, LowPriority); } void Server::requestWho(const QString& channel) { m_inputFilter.setAutomaticRequest(QStringLiteral("WHO"), channel, true); QString command(QStringLiteral("WHO ") + channel); if (capabilities() & WHOX && capabilities() & ExtendedJoin) { // Request the account as well as the usual info. // See http://faerion.sourceforge.net/doc/irc/whox.var // for more info. command += QStringLiteral(" nuhsa%cuhsnfdra"); } queue(command, LowPriority); } void Server::requestUserhost(const QString& nicks) { const QStringList nicksList = nicks.split(QLatin1Char(' '), QString::SkipEmptyParts); for(QStringList::ConstIterator it=nicksList.constBegin() ; it!=nicksList.constEnd() ; ++it) m_inputFilter.setAutomaticRequest(QStringLiteral("USERHOST"), *it, true); queue(QStringLiteral("USERHOST ")+nicks, LowPriority); } void Server::requestTopic(const QString& channel) { m_inputFilter.setAutomaticRequest(QStringLiteral("TOPIC"), channel, true); queue(QStringLiteral("TOPIC ")+channel, LowPriority); } void Server::resolveUserhost(const QString& nickname) { m_inputFilter.setAutomaticRequest(QStringLiteral("WHOIS"), nickname, true); m_inputFilter.setAutomaticRequest(QStringLiteral("DNS"), nickname, true); queue(QStringLiteral("WHOIS ")+nickname, LowPriority); //FIXME when is this really used? } void Server::requestBan(const QStringList& users,const QString& channel,const QString& a_option) { QString hostmask; QString option=a_option.toLower(); Channel* targetChannel=getChannelByName(channel); for(int index=0;indexgetNickByName(mask); // if we found the nick try to find their hostmask if(targetNick) { QString hostmask=targetNick->getChannelNick()->getHostmask(); // if we found the hostmask, add it to the ban mask if(!hostmask.isEmpty()) { mask=targetNick->getChannelNick()->getNickname()+QLatin1Char('!')+hostmask; // adapt ban mask to the option given if(option==QStringLiteral("host")) mask=QStringLiteral("*!*@*.")+hostmask.section(QLatin1Char('.'),1); else if(option==QStringLiteral("domain")) mask=QStringLiteral("*!*@")+hostmask.section(QLatin1Char('@'),1); else if(option==QStringLiteral("userhost")) mask=QStringLiteral("*!")+hostmask.section(QLatin1Char('@'),0,0)+QStringLiteral("@*.")+hostmask.section(QLatin1Char('.'),1); else if(option==QStringLiteral("userdomain")) mask=QStringLiteral("*!")+hostmask.section(QLatin1Char('@'),0,0)+QLatin1Char('@')+hostmask.section(QLatin1Char('@'),1); } } } Konversation::OutputFilterResult result = getOutputFilter()->execBan(mask,channel); queue(result.toServer); } } void Server::requestUnban(const QString& mask,const QString& channel) { Konversation::OutputFilterResult result = getOutputFilter()->execUnban(mask,channel); queue(result.toServer); } void Server::requestDccSend() { requestDccSend(QString()); } void Server::sendURIs(const QList& uris, const QString& nick) { foreach(const QUrl &uri, uris) addDccSend(nick, uri); } void Server::requestDccSend(const QString &a_recipient) { QString recipient(a_recipient); // if we don't have a recipient yet, let the user select one if (recipient.isEmpty()) { recipient = recipientNick(); } // do we have a recipient *now*? if(!recipient.isEmpty()) { QPointer dlg = new DccFileDialog (getViewContainer()->getWindow()); //DccFileDialog fileDialog(KUrl(), QString(), getViewContainer()->getWindow()); QList fileURLs = dlg->getOpenUrls( QUrl(), QString(), i18n("Select File(s) to Send to %1", recipient) ); QList::const_iterator it; for ( it = fileURLs.constBegin() ; it != fileURLs.constEnd() ; ++it ) { addDccSend( recipient, *it, dlg->passiveSend()); } delete dlg; } } void Server::slotNewDccTransferItemQueued(DCC::Transfer* transfer) { if (transfer->getConnectionId() == connectionId() ) { qDebug() << "connecting slots for " << transfer->getFileName() << " [" << transfer->getType() << "]"; if ( transfer->getType() == DCC::Transfer::Receive ) { connect( transfer, SIGNAL(done(Konversation::DCC::Transfer*)), this, SLOT(dccGetDone(Konversation::DCC::Transfer*)) ); connect( transfer, SIGNAL(statusChanged(Konversation::DCC::Transfer*,int,int)), this, SLOT(dccStatusChanged(Konversation::DCC::Transfer*,int,int)) ); } else { connect( transfer, SIGNAL(done(Konversation::DCC::Transfer*)), this, SLOT(dccSendDone(Konversation::DCC::Transfer*)) ); connect( transfer, SIGNAL(statusChanged(Konversation::DCC::Transfer*,int,int)), this, SLOT(dccStatusChanged(Konversation::DCC::Transfer*,int,int)) ); } } } void Server::addDccSend(const QString &recipient, QUrl fileURL, bool passive, const QString &altFileName, quint64 fileSize) { if (!fileURL.isValid()) { return; } // We already checked that the file exists in output filter / requestDccSend() resp. DCC::TransferSend* newDcc = Application::instance()->getDccTransferManager()->newUpload(); newDcc->setConnectionId(connectionId()); newDcc->setPartnerNick(recipient); newDcc->setFileURL(fileURL); newDcc->setReverse(passive); if (!altFileName.isEmpty()) newDcc->setFileName(altFileName); if (fileSize != 0) newDcc->setFileSize(fileSize); emit addDccPanel(); if (newDcc->queue()) newDcc->start(); } QString Server::recoverDccFileName(const QStringList & dccArguments, int offset) const { QString fileName; if(dccArguments.count() > offset + 1) { qDebug() << "recover filename"; const int argumentOffsetSize = dccArguments.size() - offset; for (int i = 0; i < argumentOffsetSize; ++i) { fileName += dccArguments.at(i); //if not last element, append a space if (i < (argumentOffsetSize - 1)) { fileName += QLatin1Char(' '); } } } else { fileName = dccArguments.at(0); } return cleanDccFileName(fileName); } QString Server::cleanDccFileName(const QString& filename) const { QString cleanFileName = filename; //we want a clean filename to get rid of the mass """filename""" //NOTE: if a filename starts really with a ", it is escaped -> \" (2 chars) // but most clients don't support that and just replace it with a _ while (cleanFileName.startsWith(QLatin1Char('\"')) && cleanFileName.endsWith(QLatin1Char('\"'))) { cleanFileName = cleanFileName.mid(1, cleanFileName.length() - 2); } return cleanFileName; } quint16 Server::stringToPort(const QString &port, bool *ok) { bool toUintOk = false; uint uPort32 = port.toUInt(&toUintOk); // ports over 65535 are invalid, someone sends us bad data if (!toUintOk || uPort32 > USHRT_MAX) { if (ok) { *ok = false; } } else { if (ok) { *ok = true; } } return (quint16)uPort32; } QString Server::recipientNick() const { QStringList nickList; // fill nickList with all nicks we know about foreach (Channel* lookChannel, m_channelList) { foreach (Nick* lookNick, lookChannel->getNickList()) { if (!nickList.contains(lookNick->getChannelNick()->getNickname())) nickList.append(lookNick->getChannelNick()->getNickname()); } } // add Queries as well, but don't insert duplicates foreach (Query* lookQuery, m_queryList) { if(!nickList.contains(lookQuery->getName())) nickList.append(lookQuery->getName()); } QStringListModel model; model.setStringList(nickList); return DCC::RecipientDialog::getNickname(getViewContainer()->getWindow(), &model); } void Server::addDccGet(const QString &sourceNick, const QStringList &dccArguments) { //filename ip port filesize [token] QString ip; quint16 port; QString fileName; quint64 fileSize; QString token; const int argumentSize = dccArguments.count(); bool ok = true; if (dccArguments.at(argumentSize - 3) == QStringLiteral("0")) //port==0, for passive send, ip can't be 0 { //filename ip port(0) filesize token fileName = recoverDccFileName(dccArguments, 4); //ip port filesize token ip = DCC::DccCommon::numericalIpToTextIp( dccArguments.at(argumentSize - 4) ); //-1 index, -1 token, -1 port, -1 filesize port = 0; fileSize = dccArguments.at(argumentSize - 2).toULongLong(); //-1 index, -1 token token = dccArguments.at(argumentSize - 1); //-1 index } else { //filename ip port filesize ip = DCC::DccCommon::numericalIpToTextIp( dccArguments.at(argumentSize - 3) ); //-1 index, -1 filesize fileName = recoverDccFileName(dccArguments, 3); //ip port filesize fileSize = dccArguments.at(argumentSize - 1).toULongLong(); //-1 index port = stringToPort(dccArguments.at(argumentSize - 2), &ok); //-1 index, -1 filesize } if (!ok) { appendMessageToFrontmost(i18n("Error"), i18nc("%1=nickname","Received invalid DCC SEND request from %1.", sourceNick)); return; } DCC::TransferRecv* newDcc = Application::instance()->getDccTransferManager()->newDownload(); newDcc->setConnectionId(connectionId()); newDcc->setPartnerNick(sourceNick); newDcc->setPartnerIp(ip); newDcc->setPartnerPort(port); newDcc->setFileName(fileName); newDcc->setFileSize(fileSize); // Reverse DCC if (!token.isEmpty()) { newDcc->setReverse(true, token); } qDebug() << "ip: " << ip; qDebug() << "port: " << port; qDebug() << "filename: " << fileName; qDebug() << "filesize: " << fileSize; qDebug() << "token: " << token; //emit after data was set emit addDccPanel(); if ( newDcc->queue() ) { appendMessageToFrontmost( i18n( "DCC" ), i18n( "%1 offers to send you \"%2\" (%3)...", newDcc->getPartnerNick(), fileName, ( newDcc->getFileSize() == 0 ) ? i18n( "unknown size" ) : KIO::convertSize( newDcc->getFileSize() ) ) ); if (Preferences::self()->dccAutoGet()) newDcc->start(); } } void Server::addDccChat(const QString& sourceNick, const QStringList& dccArguments) { //chat ip port [token] QString ip; quint16 port = 0; QString token; bool reverse = false; const int argumentSize = dccArguments.count(); bool ok = true; QString extension; extension = dccArguments.at(0); ip = DCC::DccCommon::numericalIpToTextIp(dccArguments.at(1)); if (argumentSize == 3) { //extension ip port port = stringToPort(dccArguments.at(2), &ok); } else if (argumentSize == 4) { //extension ip port(0) token token = dccArguments.at(3); reverse = true; } if (!ok) { appendMessageToFrontmost(i18n("Error"), i18nc("%1=nickname","Received invalid DCC CHAT request from %1.", sourceNick)); return; } DCC::Chat* newChat = Application::instance()->getDccTransferManager()->newChat(); newChat->setConnectionId(connectionId()); newChat->setPartnerNick(sourceNick); newChat->setOwnNick(getNickname()); qDebug() << "ip: " << ip; qDebug() << "port: " << port; qDebug() << "token: " << token; qDebug() << "extension: " << extension; newChat->setPartnerIp(ip); newChat->setPartnerPort(port); newChat->setReverse(reverse, token); newChat->setSelfOpened(false); newChat->setExtension(extension); emit addDccChat(newChat); newChat->start(); } void Server::openDccChat(const QString& nickname) { qDebug(); QString recipient(nickname); // if we don't have a recipient yet, let the user select one if (recipient.isEmpty()) { recipient = recipientNick(); } // do we have a recipient *now*? if (!recipient.isEmpty()) { DCC::Chat* newChat = Application::instance()->getDccTransferManager()->newChat(); newChat->setConnectionId(connectionId()); newChat->setPartnerNick(recipient); newChat->setOwnNick(getNickname()); newChat->setSelfOpened(true); emit addDccChat(newChat); newChat->start(); } } void Server::openDccWBoard(const QString& nickname) { qDebug(); QString recipient(nickname); // if we don't have a recipient yet, let the user select one if (recipient.isEmpty()) { recipient = recipientNick(); } // do we have a recipient *now*? if (!recipient.isEmpty()) { DCC::Chat* newChat = Application::instance()->getDccTransferManager()->newChat(); newChat->setConnectionId(connectionId()); newChat->setPartnerNick(recipient); newChat->setOwnNick(getNickname()); // Set extension before emiting addDccChat newChat->setExtension(DCC::Chat::Whiteboard); newChat->setSelfOpened(true); emit addDccChat(newChat); newChat->start(); } } void Server::requestDccChat(const QString& partnerNick, const QString& extension, const QString& numericalOwnIp, quint16 ownPort) { Konversation::OutputFilterResult result = getOutputFilter()->requestDccChat(partnerNick, extension, numericalOwnIp,ownPort); queue(result.toServer); } void Server::acceptDccGet(const QString& nick, const QString& file) { Application::instance()->getDccTransferManager()->acceptDccGet(m_connectionId, nick, file); } void Server::dccSendRequest(const QString &partner, const QString &fileName, const QString &address, quint16 port, quint64 size) { Konversation::OutputFilterResult result = getOutputFilter()->sendRequest(partner,fileName,address,port,size); queue(result.toServer); appendMessageToFrontmost( i18n( "DCC" ), i18n( "Asking %1 to accept upload of \"%2\" (%3)...", partner, cleanDccFileName(fileName), ( size == 0 ) ? i18n( "unknown size" ) : KIO::convertSize( size ) ) ); } void Server::dccPassiveSendRequest(const QString& recipient,const QString& fileName,const QString& address,quint64 size,const QString& token) { Konversation::OutputFilterResult result = getOutputFilter()->passiveSendRequest(recipient,fileName,address,size,token); queue(result.toServer); appendMessageToFrontmost( i18n( "DCC" ), i18n( "Asking %1 to accept passive upload of \"%2\" (%3)...", recipient, cleanDccFileName(fileName), ( size == 0 ) ? i18n( "unknown size" ) : KIO::convertSize( size ) ) ); } void Server::dccPassiveChatRequest(const QString& recipient, const QString& extension, const QString& address, const QString& token) { Konversation::OutputFilterResult result = getOutputFilter()->passiveChatRequest(recipient, extension, address, token); queue(result.toServer); appendMessageToFrontmost(i18n("DCC"), i18nc("%1=name, %2=dcc extension, chat or wboard for example","Asking %1 to accept %2...", recipient, extension)); } void Server::dccPassiveResumeGetRequest(const QString& sender,const QString& fileName,quint16 port,KIO::filesize_t startAt,const QString &token) { Konversation::OutputFilterResult result = getOutputFilter()->resumePassiveRequest(sender,fileName,port,startAt,token);; queue(result.toServer); } void Server::dccResumeGetRequest(const QString &sender, const QString &fileName, quint16 port, KIO::filesize_t startAt) { Konversation::OutputFilterResult result = getOutputFilter()->resumeRequest(sender,fileName,port,startAt);; queue(result.toServer); } void Server::dccReverseSendAck(const QString& partnerNick,const QString& fileName,const QString& ownAddress,quint16 ownPort,quint64 size,const QString& reverseToken) { Konversation::OutputFilterResult result = getOutputFilter()->acceptPassiveSendRequest(partnerNick,fileName,ownAddress,ownPort,size,reverseToken); queue(result.toServer); } void Server::dccReverseChatAck(const QString& partnerNick, const QString& extension, const QString& ownAddress, quint16 ownPort, const QString& reverseToken) { Konversation::OutputFilterResult result = getOutputFilter()->acceptPassiveChatRequest(partnerNick, extension, ownAddress, ownPort, reverseToken); queue(result.toServer); } void Server::dccRejectSend(const QString& partnerNick, const QString& fileName) { Konversation::OutputFilterResult result = getOutputFilter()->rejectDccSend(partnerNick,fileName); queue(result.toServer); } void Server::dccRejectChat(const QString& partnerNick, const QString& extension) { Konversation::OutputFilterResult result = getOutputFilter()->rejectDccChat(partnerNick, extension); queue(result.toServer); } void Server::startReverseDccChat(const QString &sourceNick, const QStringList &dccArguments) { qDebug(); DCC::TransferManager* dtm = Application::instance()->getDccTransferManager(); bool ok = true; QString partnerIP = DCC::DccCommon::numericalIpToTextIp(dccArguments.at(1)); quint16 port = stringToPort(dccArguments.at(2), &ok); QString token = dccArguments.at(3); qDebug() << "ip: " << partnerIP; qDebug() << "port: " << port; qDebug() << "token: " << token; if (!ok || dtm->startReverseChat(connectionId(), sourceNick, partnerIP, port, token) == 0) { // DTM could not find a matched item appendMessageToFrontmost(i18n("Error"), i18nc("%1 = nickname", "Received invalid passive DCC chat acceptance message from %1.", sourceNick)); } } void Server::startReverseDccSendTransfer(const QString& sourceNick,const QStringList& dccArguments) { qDebug(); DCC::TransferManager* dtm = Application::instance()->getDccTransferManager(); bool ok = true; const int argumentSize = dccArguments.size(); QString partnerIP = DCC::DccCommon::numericalIpToTextIp( dccArguments.at(argumentSize - 4) ); //dccArguments[1] ) ); quint16 port = stringToPort(dccArguments.at(argumentSize - 3), &ok); QString token = dccArguments.at(argumentSize - 1); quint64 fileSize = dccArguments.at(argumentSize - 2).toULongLong(); QString fileName = recoverDccFileName(dccArguments, 4); //ip port filesize token qDebug() << "ip: " << partnerIP; qDebug() << "port: " << port; qDebug() << "filename: " << fileName; qDebug() << "filesize: " << fileSize; qDebug() << "token: " << token; if (!ok || dtm->startReverseSending(connectionId(), sourceNick, fileName, // filename partnerIP, // partner IP port, // partner port fileSize, // filesize token // Reverse DCC token ) == 0) { // DTM could not find a matched item appendMessageToFrontmost(i18n("Error"), i18nc("%1 = file name, %2 = nickname", "Received invalid passive DCC send acceptance message for \"%1\" from %2.", fileName, sourceNick)); } } void Server::resumeDccGetTransfer(const QString &sourceNick, const QStringList &dccArguments) { DCC::TransferManager* dtm = Application::instance()->getDccTransferManager(); //filename port position [token] QString fileName; quint64 position; quint16 ownPort; bool ok = true; const int argumentSize = dccArguments.count(); if (dccArguments.at(argumentSize - 3) == QStringLiteral("0")) //-1 index, -1 token, -1 pos { fileName = recoverDccFileName(dccArguments, 3); //port position token ownPort = 0; position = dccArguments.at(argumentSize - 2).toULongLong(); //-1 index, -1 token } else { fileName = recoverDccFileName(dccArguments, 2); //port position ownPort = stringToPort(dccArguments.at(argumentSize - 2), &ok); //-1 index, -1 pos position = dccArguments.at(argumentSize - 1).toULongLong(); //-1 index } //do we need the token here? DCC::TransferRecv* dccTransfer = 0; if (ok) { dccTransfer = dtm->resumeDownload(connectionId(), sourceNick, fileName, ownPort, position); } if (dccTransfer) { appendMessageToFrontmost(i18n("DCC"), i18nc("%1 = file name, %2 = nickname of sender, %3 = percentage of file size, %4 = file size", "Resuming download of \"%1\" from %2 starting at %3% of %4...", fileName, sourceNick, QString::number( dccTransfer->getProgress()), (dccTransfer->getFileSize() == 0) ? i18n("unknown size") : KIO::convertSize(dccTransfer->getFileSize()))); } else { appendMessageToFrontmost(i18n("Error"), i18nc("%1 = file name, %2 = nickname", "Received invalid resume acceptance message for \"%1\" from %2.", fileName, sourceNick)); } } void Server::resumeDccSendTransfer(const QString &sourceNick, const QStringList &dccArguments) { DCC::TransferManager* dtm = Application::instance()->getDccTransferManager(); bool passiv = false; QString fileName; quint64 position; QString token; quint16 ownPort; bool ok = true; const int argumentSize = dccArguments.count(); //filename port filepos [token] if (dccArguments.at( argumentSize - 3) == QStringLiteral("0")) { //filename port filepos token passiv = true; ownPort = 0; token = dccArguments.at( argumentSize - 1); // -1 index position = dccArguments.at( argumentSize - 2).toULongLong(); // -1 index, -1 token fileName = recoverDccFileName(dccArguments, 3); //port filepos token } else { //filename port filepos ownPort = stringToPort(dccArguments.at(argumentSize - 2), &ok); //-1 index, -1 filesize position = dccArguments.at( argumentSize - 1).toULongLong(); // -1 index fileName = recoverDccFileName(dccArguments, 2); //port filepos } DCC::TransferSend* dccTransfer = 0; if (ok) { dccTransfer = dtm->resumeUpload(connectionId(), sourceNick, fileName, ownPort, position); } if (dccTransfer) { appendMessageToFrontmost(i18n("DCC"), i18nc("%1 = file name, %2 = nickname of recipient, %3 = percentage of file size, %4 = file size", "Resuming upload of \"%1\" to %2 starting at %3% of %4...", fileName, sourceNick, QString::number(dccTransfer->getProgress()), (dccTransfer->getFileSize() == 0) ? i18n("unknown size") : KIO::convertSize(dccTransfer->getFileSize()))); // fileName can't have " here if (fileName.contains(QLatin1Char(' '))) fileName = QLatin1Char('\"')+fileName+QLatin1Char('\"'); // FIXME: this operation should be done by TransferManager Konversation::OutputFilterResult result; if (passiv) result = getOutputFilter()->acceptPassiveResumeRequest( sourceNick, fileName, ownPort, position, token ); else result = getOutputFilter()->acceptResumeRequest( sourceNick, fileName, ownPort, position ); queue( result.toServer ); } else { appendMessageToFrontmost(i18n("Error"), i18nc("%1 = file name, %2 = nickname", "Received invalid resume request for \"%1\" from %2.", fileName, sourceNick)); } } void Server::rejectDccSendTransfer(const QString &sourceNick, const QStringList &dccArguments) { DCC::TransferManager* dtm = Application::instance()->getDccTransferManager(); //filename QString fileName = recoverDccFileName(dccArguments, 0); DCC::TransferSend* dccTransfer = dtm->rejectSend(connectionId(), sourceNick, fileName); if (!dccTransfer) { appendMessageToFrontmost(i18n("Error"), i18nc("%1 = file name, %2 = nickname", "Received invalid reject request for \"%1\" from %2.", fileName, sourceNick)); } } void Server::rejectDccChat(const QString& sourceNick) { DCC::TransferManager* dtm = Application::instance()->getDccTransferManager(); DCC::Chat* dccChat = dtm->rejectChat(connectionId(), sourceNick); if (!dccChat) { appendMessageToFrontmost(i18n("Error"), i18nc("%1 = nickname", "Received invalid reject request from %1.", sourceNick)); } } void Server::dccGetDone(DCC::Transfer* item) { if (!item) return; if(item->getStatus() == DCC::Transfer::Done) { appendMessageToFrontmost(i18n("DCC"), i18nc("%1 = file name, %2 = nickname of sender", "Download of \"%1\" from %2 finished.", item->getFileName(), item->getPartnerNick())); } else if(item->getStatus() == DCC::Transfer::Failed) { appendMessageToFrontmost(i18n("DCC"), i18nc("%1 = file name, %2 = nickname of sender", "Download of \"%1\" from %2 failed. Reason: %3.", item->getFileName(), item->getPartnerNick(), item->getStatusDetail())); } } void Server::dccSendDone(DCC::Transfer* item) { if (!item) return; if(item->getStatus() == DCC::Transfer::Done) appendMessageToFrontmost(i18n("DCC"), i18nc("%1 = file name, %2 = nickname of recipient", "Upload of \"%1\" to %2 finished.", item->getFileName(), item->getPartnerNick())); else if(item->getStatus() == DCC::Transfer::Failed) appendMessageToFrontmost(i18n("DCC"), i18nc("%1 = file name, %2 = nickname of recipient", "Upload of \"%1\" to %2 failed. Reason: %3.", item->getFileName(), item->getPartnerNick(), item->getStatusDetail())); } void Server::dccStatusChanged(DCC::Transfer *item, int newStatus, int oldStatus) { if(!item) return; if ( item->getType() == DCC::Transfer::Send ) { // when resuming, a message about the receiver's acceptance has been shown already, so suppress this message if ( newStatus == DCC::Transfer::Transferring && oldStatus == DCC::Transfer::WaitingRemote && !item->isResumed() ) appendMessageToFrontmost( i18n( "DCC" ), i18nc( "%1 = file name, %2 nickname of recipient", "Sending \"%1\" to %2...", item->getFileName(), item->getPartnerNick() ) ); } else // type == Receive { if ( newStatus == DCC::Transfer::Transferring && !item->isResumed() ) { appendMessageToFrontmost( i18n( "DCC" ), i18nc( "%1 = file name, %2 = file size, %3 = nickname of sender", "Downloading \"%1\" (%2) from %3...", item->getFileName(), ( item->getFileSize() == 0 ) ? i18n( "unknown size" ) : KIO::convertSize( item->getFileSize() ), item->getPartnerNick() ) ); } } } void Server::removeQuery(Query* query) { m_queryList.removeOne(query); query->deleteLater(); } void Server::sendJoinCommand(const QString& name, const QString& password) { Konversation::OutputFilterResult result = getOutputFilter()->parse(getNickname(), Preferences::self()->commandChar() + QStringLiteral("JOIN ") + name + QLatin1Char(' ') + password, QString()); queue(result.toServer); } Channel* Server::joinChannel(const QString& name, const QString& hostmask, const QHash &messageTags) { // (re-)join channel, open a new panel if needed Channel* channel = getChannelByName(name); if (!channel) { channel=getViewContainer()->addChannel(this,name); Q_ASSERT(channel); channel->setNickname(getNickname()); channel->indicateAway(m_away); if (getServerGroup()) { Konversation::ChannelSettings channelSettings = getServerGroup()->channelByNameFromHistory(name); channel->setNotificationsEnabled(channelSettings.enableNotifications()); getServerGroup()->appendChannelHistory(channelSettings); } m_channelList.append(channel); m_loweredChannelNameHash.insert(channel->getName().toLower(), channel); connect(channel,SIGNAL (sendFile()),this,SLOT (requestDccSend()) ); connect(this, SIGNAL(nicknameChanged(QString)), channel, SLOT(setNickname(QString))); } // Move channel from unjoined (if present) to joined list and add our own nickname to the joined list. ChannelNickPtr channelNick = addNickToJoinedChannelsList(name, getNickname()); if ((channelNick->getHostmask() != hostmask ) && !hostmask.isEmpty()) { NickInfoPtr nickInfo = channelNick->getNickInfo(); nickInfo->setHostmask(hostmask); } channel->joinNickname(channelNick, messageTags); return channel; } void Server::removeChannel(Channel* channel) { // Update NickInfo. removeJoinedChannel(channel->getName()); if (getServerGroup()) { Konversation::ChannelSettings channelSettings = getServerGroup()->channelByNameFromHistory(channel->getName()); channelSettings.setNotificationsEnabled(channel->notificationsEnabled()); getServerGroup()->appendChannelHistory(channelSettings); } m_channelList.removeOne(channel); m_loweredChannelNameHash.remove(channel->getName().toLower()); if (!isConnected()) updateAutoJoin(); } void Server::updateChannelMode(const QString &updater, const QString &channelName, char mode, bool plus, const QString ¶meter, const QHash &messageTags) { Channel* channel=getChannelByName(channelName); if(channel) //Let the channel be verbose to the screen about the change, and update channelNick channel->updateMode(updater, mode, plus, parameter, messageTags); // TODO: What is mode character for owner? // Answer from JOHNFLUX - I think that admin is the same as owner. Channel.h has owner as "a" // "q" is the likely answer.. UnrealIRCd and euIRCd use it. // TODO these need to become dynamic QString userModes=QStringLiteral("vhoqa"); // voice halfop op owner admin int modePos = userModes.indexOf(QLatin1Char(mode)); if (modePos > 0) { ChannelNickPtr updateeNick = getChannelNick(channelName, parameter); if(!updateeNick) { /* if(parameter.isEmpty()) { qDebug() << "in updateChannelMode, a nick with no-name has had their mode '" << mode << "' changed to (" <setMode(mode, plus); // Note that channel will be moved to joined list if necessary. addNickToJoinedChannelsList(channelName, parameter); } // Update channel ban list. if (mode == 'b') { if (plus) { addBan(channelName, QString(QStringLiteral("%1 %2 %3")).arg(parameter).arg(updater).arg(QDateTime::currentDateTime().toTime_t())); } else { removeBan(channelName, parameter); } } } void Server::updateChannelModeWidgets(const QString &channelName, char mode, const QString ¶meter) { Channel* channel=getChannelByName(channelName); if(channel) channel->updateModeWidgets(mode,true,parameter); } Channel* Server::getChannelByName(const QString& name) { if (name.isEmpty()) { return nullptr; } // Convert wanted channel name to lowercase QString wanted = name.toLower(); QRegularExpressionMatch p = m_targetMatcher.match(wanted); int index = p.capturedStart(2); if (index >= 0) { wanted = wanted.mid(index); if (m_loweredChannelNameHash.contains(wanted)) return m_loweredChannelNameHash.value(wanted); } return nullptr; } Query* Server::getQueryByName(const QString& name) { // Convert wanted query name to lowercase QString wanted = name.toLower(); // Traverse through list to find the query with "name" foreach (Query* lookQuery, m_queryList) { if(lookQuery->getName().toLower()==wanted) return lookQuery; } // No query by that name found? Must be a new query request. Return 0 return 0; } ChatWindow* Server::getChannelOrQueryByName(const QString& name) { ChatWindow* window = getChannelByName(name); if (!window) window = getQueryByName(name); return window; } void Server::queueNicks(const QString& channelName, const QStringList& nicknameList) { Channel* channel = getChannelByName(channelName); if (channel) channel->queueNicks(nicknameList); } // Adds a nickname to the joinedChannels list. // Creates new NickInfo if necessary. // If needed, moves the channel from the unjoined list to the joined list. // Returns the NickInfo for the nickname. ChannelNickPtr Server::addNickToJoinedChannelsList(const QString& channelName, const QString& nickname) { bool doChannelJoinedSignal = false; bool doWatchedNickChangedSignal = false; bool doChannelMembersChangedSignal = false; QString lcNickname(nickname.toLower()); // Create NickInfo if not already created. NickInfoPtr nickInfo = getNickInfo(nickname); if (!nickInfo) { nickInfo = new NickInfo(nickname, this); m_allNicks.insert(lcNickname, nickInfo); doWatchedNickChangedSignal = isWatchedNick(nickname); } // if nickinfo already exists update nickname, in case we created the nickinfo based // on e.g. an incorrectly capitalized ISON request else nickInfo->setNickname(nickname); // Move the channel from unjoined list (if present) to joined list. QString lcChannelName = channelName.toLower(); ChannelNickMap *channel; if (m_unjoinedChannels.contains(lcChannelName)) { channel = m_unjoinedChannels[lcChannelName]; m_unjoinedChannels.remove(lcChannelName); m_joinedChannels.insert(lcChannelName, channel); doChannelJoinedSignal = true; } else { // Create a new list in the joined channels if not already present. if (!m_joinedChannels.contains(lcChannelName)) { channel = new ChannelNickMap; m_joinedChannels.insert(lcChannelName, channel); doChannelJoinedSignal = true; } else channel = m_joinedChannels[lcChannelName]; } // Add NickInfo to channel list if not already in the list. ChannelNickPtr channelNick; if (!channel->contains(lcNickname)) { channelNick = new ChannelNick(nickInfo, lcChannelName); Q_ASSERT(channelNick); channel->insert(lcNickname, channelNick); doChannelMembersChangedSignal = true; } channelNick = (*channel)[lcNickname]; Q_ASSERT(channelNick); //Since we just added it if it didn't exist, it should be guaranteed to exist now if (doWatchedNickChangedSignal) emit watchedNickChanged(this, nickname, true); if (doChannelJoinedSignal) emit channelJoinedOrUnjoined(this, channelName, true); if (doChannelMembersChangedSignal) emit channelMembersChanged(this, channelName, true, false, nickname); return channelNick; } // Adds a nickname to the unjoinedChannels list. // Creates new NickInfo if necessary. // If needed, moves the channel from the joined list to the unjoined list. // If mode != 99 sets the mode for this nick in this channel. // Returns the NickInfo for the nickname. ChannelNickPtr Server::addNickToUnjoinedChannelsList(const QString& channelName, const QString& nickname) { bool doChannelUnjoinedSignal = false; bool doWatchedNickChangedSignal = false; bool doChannelMembersChangedSignal = false; QString lcNickname(nickname.toLower()); // Create NickInfo if not already created. NickInfoPtr nickInfo = getNickInfo(nickname); if (!nickInfo) { nickInfo = new NickInfo(nickname, this); m_allNicks.insert(lcNickname, nickInfo); doWatchedNickChangedSignal = isWatchedNick(nickname); } // Move the channel from joined list (if present) to unjoined list. QString lcChannelName = channelName.toLower(); ChannelNickMap *channel; if (m_joinedChannels.contains(lcChannelName)) { channel = m_joinedChannels[lcChannelName]; m_joinedChannels.remove(lcChannelName); m_unjoinedChannels.insert(lcChannelName, channel); doChannelUnjoinedSignal = true; } else { // Create a new list in the unjoined channels if not already present. if (!m_unjoinedChannels.contains(lcChannelName)) { channel = new ChannelNickMap; m_unjoinedChannels.insert(lcChannelName, channel); doChannelUnjoinedSignal = true; } else channel = m_unjoinedChannels[lcChannelName]; } // Add NickInfo to unjoinedChannels list if not already in the list. ChannelNickPtr channelNick; if (!channel->contains(lcNickname)) { channelNick = new ChannelNick(nickInfo, lcChannelName); channel->insert(lcNickname, channelNick); doChannelMembersChangedSignal = true; } channelNick = (*channel)[lcNickname]; // Set the mode for the nick in this channel. if (doWatchedNickChangedSignal) emit watchedNickChanged(this, nickname, true); if (doChannelUnjoinedSignal) emit channelJoinedOrUnjoined(this, channelName, false); if (doChannelMembersChangedSignal) emit channelMembersChanged(this, channelName, false, false, nickname); return channelNick; } /** * If not already online, changes a nick to the online state by creating * a NickInfo for it and emits various signals and messages for it. * This method should only be called for nicks on the watch list. * @param nickname The nickname that is online. * @return Pointer to NickInfo for nick. */ NickInfoPtr Server::setWatchedNickOnline(const QString& nickname) { NickInfoPtr nickInfo = getNickInfo(nickname); if (!nickInfo) { QString lcNickname(nickname.toLower()); nickInfo = new NickInfo(nickname, this); m_allNicks.insert(lcNickname, nickInfo); } emit watchedNickChanged(this, nickname, true); appendMessageToFrontmost(i18nc("Message type", "Notify"), i18n("%1 is online (%2).", nickname, getServerName()), QHash(), getStatusView()); Application::instance()->notificationHandler()->nickOnline(getStatusView(), nickname); nickInfo->setPrintedOnline(true); return nickInfo; } void Server::setWatchedNickOffline(const QString& nickname, const NickInfoPtr nickInfo) { Q_UNUSED(nickInfo) emit watchedNickChanged(this, nickname, false); appendMessageToFrontmost(i18nc("Message type", "Notify"), i18n("%1 went offline (%2).", nickname, getServerName()), QHash(), getStatusView()); Application::instance()->notificationHandler()->nickOffline(getStatusView(), nickname); } bool Server::setNickOffline(const QString& nickname) { QString lcNickname(nickname.toLower()); NickInfoPtr nickInfo = getNickInfo(lcNickname); bool wasOnline = nickInfo ? nickInfo->getPrintedOnline() : false; if (wasOnline) { // Delete from query list, if present. if (m_queryNicks.contains(lcNickname)) m_queryNicks.remove(lcNickname); // Delete the nickname from all channels (joined or unjoined). QStringList nickChannels = getNickChannels(lcNickname); QStringList::iterator itEnd = nickChannels.end(); for(QStringList::iterator it = nickChannels.begin(); it != itEnd; ++it) { QString channel = (*it); removeChannelNick(channel, lcNickname); } // Delete NickInfo. if (m_allNicks.contains(lcNickname)) m_allNicks.remove(lcNickname); // If the nick was in the watch list, emit various signals and messages. if (isWatchedNick(nickname)) setWatchedNickOffline(nickname, nickInfo); nickInfo->setPrintedOnline(false); } return (nickInfo != nullptr); } /** * If nickname is no longer on any channel list, or the query list, delete it altogether. * Call this routine only if the nick is not on the notify list or is on the notify * list but is known to be offline. * @param nickname The nickname to be deleted. Case insensitive. * @return True if the nickname is deleted. */ bool Server::deleteNickIfUnlisted(const QString &nickname) { QString lcNickname(nickname.toLower()); // Don't delete our own nickinfo. if (lcNickname == loweredNickname()) return false; if (!m_queryNicks.contains(lcNickname)) { QStringList nickChannels = getNickChannels(nickname); if (nickChannels.isEmpty()) { m_allNicks.remove(lcNickname); return true; } } return false; } /** * Remove nickname from a channel (on joined or unjoined lists). * @param channelName The channel name. Case insensitive. * @param nickname The nickname. Case insensitive. */ void Server::removeChannelNick(const QString& channelName, const QString& nickname) { bool doSignal = false; bool joined = false; QString lcChannelName = channelName.toLower(); QString lcNickname = nickname.toLower(); ChannelNickMap *channel; if (m_joinedChannels.contains(lcChannelName)) { channel = m_joinedChannels[lcChannelName]; if (channel->contains(lcNickname)) { channel->remove(lcNickname); doSignal = true; joined = true; // Note: Channel should not be empty because user's own nick should still be // in it, so do not need to delete empty channel here. } else { qDebug() << "Error: Tried to remove nickname=" << nickname << " from joined channel=" << channelName; } } else { if (m_unjoinedChannels.contains(lcChannelName)) { channel = m_unjoinedChannels[lcChannelName]; if (channel->contains(lcNickname)) { channel->remove(lcNickname); doSignal = true; joined = false; // If channel is now empty, delete it. // Caution: Any iterators across unjoinedChannels will be come invalid here. if (channel->isEmpty()) m_unjoinedChannels.remove(lcChannelName); } else { qDebug() << "Error: Tried to remove nickname=" << nickname << " from unjoined channel=" << channelName; } } } if (doSignal) emit channelMembersChanged(this, channelName, joined, true, nickname); } QStringList Server::getWatchList() { // no nickinfo ISON for the time being if (getServerGroup()) return Preferences::notifyListByGroupId(getServerGroup()->id()); else return QStringList(); if (m_serverISON) return m_serverISON->getWatchList(); else return QStringList(); } QStringList Server::getISONList() { // no nickinfo ISON for the time being if (getServerGroup()) return Preferences::notifyListByGroupId(getServerGroup()->id()); else return QStringList(); if (m_serverISON) return m_serverISON->getISONList(); else return QStringList(); } QString Server::getISONListString() { return getISONList().join(QStringLiteral(" ")); } /** * Return true if the given nickname is on the watch list. */ bool Server::isWatchedNick(const QString& nickname) { // no nickinfo ISON for the time being if (getServerGroup()) return Preferences::isNotify(getServerGroup()->id(), nickname); else return false; // ###### ERROR: not reached return getWatchList().contains(nickname, Qt::CaseInsensitive); } /** * Remove channel from the joined list, placing it in the unjoined list. * All the unwatched nicks are removed from the channel. If the channel becomes * empty, it is deleted. * @param channelName Name of the channel. Case sensitive. */ void Server::removeJoinedChannel(const QString& channelName) { bool doSignal = false; QStringList watchListLower = getWatchList(); QString lcChannelName = channelName.toLower(); // Move the channel nick list from the joined to unjoined lists. if (m_joinedChannels.contains(lcChannelName)) { doSignal = true; ChannelNickMap* channel = m_joinedChannels[lcChannelName]; m_joinedChannels.remove(lcChannelName); m_unjoinedChannels.insert(lcChannelName, channel); // Remove nicks not on the watch list. bool allDeleted = true; Q_ASSERT(channel); if(!channel) return; //already removed.. hmm ChannelNickMap::Iterator member; for ( member = channel->begin(); member != channel->end() ;) { QString lcNickname = member.key(); if (!watchListLower.contains(lcNickname)) { // Remove the unwatched nickname from the unjoined channel. channel->erase(member); // If the nick is no longer listed in any channels or query list, delete it altogether. deleteNickIfUnlisted(lcNickname); member = channel->begin(); } else { allDeleted = false; ++member; } } // If all were deleted, remove the channel from the unjoined list. if (allDeleted) { channel = m_unjoinedChannels[lcChannelName]; m_unjoinedChannels.remove(lcChannelName); delete channel; // recover memory! } } if (doSignal) emit channelJoinedOrUnjoined(this, channelName, false); } // Renames a nickname in all NickInfo lists. // Returns pointer to the NickInfo object or 0 if nick not found. void Server::renameNickInfo(NickInfoPtr nickInfo, const QString& newname) { if (nickInfo) { // Get existing lowercase nickname and rename nickname in the NickInfo object. QString lcNickname(nickInfo->loweredNickname()); nickInfo->setNickname(newname); QString lcNewname(newname.toLower()); // Rename the key in m_allNicks list. m_allNicks.remove(lcNickname); m_allNicks.insert(lcNewname, nickInfo); // Rename key in the joined and unjoined lists. QStringList nickChannels = getNickChannels(lcNickname); QStringList::iterator itEnd = nickChannels.end(); for(QStringList::iterator it = nickChannels.begin(); it != itEnd; ++it) { const ChannelNickMap *channel = getChannelMembers(*it); Q_ASSERT(channel); ChannelNickPtr member = (*channel)[lcNickname]; Q_ASSERT(member); const_cast(channel)->remove(lcNickname); const_cast(channel)->insert(lcNewname, member); } // Rename key in Query list. if (m_queryNicks.contains(lcNickname)) { m_queryNicks.remove(lcNickname); m_queryNicks.insert(lcNewname, nickInfo); } } else { qDebug() << "was called for newname='" << newname << "' but nickInfo is null"; } } Channel* Server::nickJoinsChannel(const QString &channelName, const QString &nickname, const QString &hostmask, const QString &account, const QString &realName, const QHash &messageTags) { Channel* outChannel = getChannelByName(channelName); if(outChannel) { // Update NickInfo. ChannelNickPtr channelNick = addNickToJoinedChannelsList(channelName, nickname); NickInfoPtr nickInfo = channelNick->getNickInfo(); if ((nickInfo->getHostmask() != hostmask) && !hostmask.isEmpty()) { nickInfo->setHostmask(hostmask); } if (!account.isEmpty()) { nickInfo->setAccount(account); } if (!realName.isEmpty()) { nickInfo->setRealName(realName); } outChannel->joinNickname(channelNick, messageTags); } return outChannel; } void Server::addHostmaskToNick(const QString& sourceNick, const QString& sourceHostmask) { // Update NickInfo. NickInfoPtr nickInfo = getNickInfo(sourceNick); if (nickInfo) { if ((nickInfo->getHostmask() != sourceHostmask) && !sourceHostmask.isEmpty()) { nickInfo->setHostmask(sourceHostmask); } } } Channel* Server::removeNickFromChannel(const QString &channelName, const QString &nickname, const QString &reason, const QHash &messageTags, bool quit) { Channel* outChannel = getChannelByName(channelName); if(outChannel) { outChannel->flushNickQueue(); ChannelNickPtr channelNick = getChannelNick(channelName, nickname); if(channelNick) { outChannel->removeNick(channelNick,reason,quit, messageTags); } } // Remove the nick from the channel. removeChannelNick(channelName, nickname); // If not listed in any channel, and not on query list, delete the NickInfo, // but only if not on the notify list. ISON replies will take care of deleting // the NickInfo, if on the notify list. if (!isWatchedNick(nickname)) { QString nicky = nickname; deleteNickIfUnlisted(nicky); } return outChannel; } void Server::nickWasKickedFromChannel(const QString &channelName, const QString &nickname, const QString &kicker, const QString &reason, const QHash &messageTags) { Channel* outChannel = getChannelByName(channelName); if(outChannel) { outChannel->flushNickQueue(); ChannelNickPtr channelNick = getChannelNick(channelName, nickname); if(channelNick) { outChannel->kickNick(channelNick, kicker, reason, messageTags); // Tell Nickinfo removeChannelNick(channelName,nickname); } } } void Server::removeNickFromServer(const QString &nickname,const QString &reason, const QHash &messageTags) { foreach (Channel* channel, m_channelList) { channel->flushNickQueue(); // Check if nick is in this channel or not. if(channel->getNickByName(nickname)) removeNickFromChannel(channel->getName(), nickname, reason, messageTags, true); } Query* query = getQueryByName(nickname); if (query) query->quitNick(reason, messageTags); // Delete the nick from all channels and then delete the nickinfo, // emitting signal if on the watch list. setNickOffline(nickname); } void Server::renameNick(const QString &nickname, const QString &newNick, const QHash &messageTags) { if(nickname.isEmpty() || newNick.isEmpty()) { qDebug() << "called with empty strings! Trying to rename '" << nickname << "' to '" << newNick << "'"; return; } // If this was our own nickchange, tell our server object about it if (nickname == getNickname()) setNickname(newNick); //Actually do the rename. NickInfoPtr nickInfo = getNickInfo(nickname); if(!nickInfo) { qDebug() << "called for nickname '" << nickname << "' to '" << newNick << "' but getNickInfo('" << nickname << "') returned no results."; } else { renameNickInfo(nickInfo, newNick); //The rest of the code below allows the channels to echo to the user to tell them that the nick has changed. // Rename the nick in every channel they are in foreach (Channel* channel, m_channelList) { channel->flushNickQueue(); // All we do is notify that the nick has been renamed.. we haven't actually renamed it yet if (channel->getNickByName(nickname)) channel->nickRenamed(nickname, *nickInfo, messageTags); } //Watched nicknames stuff if (isWatchedNick(nickname)) setWatchedNickOffline(nickname, NickInfoPtr()); } // We had an encrypt conversation with the user that changed his nick, lets copy the key to the new nick and remove the old nick #ifdef HAVE_QCA2 QByteArray userKey = getKeyForRecipient(nickname); if (!userKey.isEmpty()) { setKeyForRecipient(newNick, userKey); m_keyHash.remove(nickname.toLower()); } #endif } void Server::userhost(const QString& nick,const QString& hostmask,bool away,bool /* ircOp */) { addHostmaskToNick(nick, hostmask); // remember my IP for DCC things // myself if (m_ownIpByUserhost.isEmpty() && nick == getNickname()) { QString myhost = hostmask.section(QLatin1Char('@'), 1); // Use async lookup else you will be blocking GUI badly QHostInfo::lookupHost(myhost, this, SLOT(gotOwnResolvedHostByUserhost(QHostInfo))); } NickInfoPtr nickInfo = getNickInfo(nick); if (nickInfo) { if (nickInfo->isAway() != away) { nickInfo->setAway(away); } } } void Server::gotOwnResolvedHostByUserhost(const QHostInfo& res) { if ( res.error() == QHostInfo::NoError && !res.addresses().isEmpty() ) m_ownIpByUserhost = res.addresses().first().toString(); else qDebug() << "Got error: " << res.errorString(); } void Server::appendServerMessageToChannel(const QString& channel,const QString& type,const QString& message, const QHash &messageTags) { Channel* outChannel = getChannelByName(channel); if (outChannel) outChannel->appendServerMessage(type, message, messageTags); } void Server::appendCommandMessageToChannel(const QString& channel, const QString& command, const QString& message, const QHash &messageTags, bool highlight, bool parseURL) { Channel* outChannel = getChannelByName(channel); if (outChannel) { outChannel->appendCommandMessage(command, message, messageTags, parseURL, !highlight); } else { appendStatusMessage(command, QString(QStringLiteral("%1 %2")).arg(channel).arg(message), messageTags); } } void Server::appendStatusMessage(const QString& type, const QString& message, const QHash &messageTags) { getStatusView()->appendServerMessage(type, message, messageTags); } void Server::appendMessageToFrontmost(const QString& type, const QString& message, const QHash &messageTags, bool parseURL) { getViewContainer()->appendToFrontmost(type, message, getStatusView(), messageTags, parseURL); } void Server::setNickname(const QString &newNickname) { m_nickname = newNickname; m_loweredNickname = newNickname.toLower(); if (!m_nickListModel->stringList().contains(newNickname)) { m_nickListModel->insertRows(m_nickListModel->rowCount(), 1); m_nickListModel->setData(m_nickListModel->index(m_nickListModel->rowCount() -1 , 0), newNickname, Qt::DisplayRole); } emit nicknameChanged(newNickname); } void Server::setChannelTopic(const QString &channel, const QString &newTopic, const QHash &messageTags) { Channel* outChannel = getChannelByName(channel); if(outChannel) { // encoding stuff is done in send() outChannel->setTopic(newTopic, messageTags); } } // Overloaded void Server::setChannelTopic(const QString& nickname, const QString &channel, const QString &newTopic, const QHash &messageTags) { Channel* outChannel = getChannelByName(channel); if(outChannel) { // encoding stuff is done in send() outChannel->setTopic(nickname, newTopic, messageTags); } } void Server::setTopicAuthor(const QString& channel, const QString& author, QDateTime time) { Channel* outChannel = getChannelByName(channel); if(outChannel) outChannel->setTopicAuthor(author, time); } void Server::endOfWho(const QString& target) { Channel* channel = getChannelByName(target); if(channel) channel->scheduleAutoWho(); } void Server::endOfNames(const QString& target) { Channel* channel = getChannelByName(target); if(channel) channel->endOfNames(); } bool Server::isNickname(const QString &compare) const { return (m_nickname == compare); } QString Server::getNickname() const { return m_nickname; } QString Server::loweredNickname() const { return m_loweredNickname; } QString Server::parseWildcards(const QString& toParse, ChatWindow* context, QStringList nicks) { QString inputLineText; if (context && context->getInputBar()) inputLineText = context->getInputBar()->toPlainText(); if (!context) return parseWildcards(toParse, getNickname(), QString(), QString(), QString(), QString()); else if (context->getType() == ChatWindow::Channel) { Channel* channel = static_cast(context); return parseWildcards(toParse, getNickname(), context->getName(), channel->getPassword(), nicks.count() ? nicks : channel->getSelectedNickList(), inputLineText); } else if (context->getType() == ChatWindow::Query) return parseWildcards(toParse, getNickname(), context->getName(), QString(), context->getName(), inputLineText); return parseWildcards(toParse, getNickname(), context->getName(), QString(), QString(), inputLineText); } QString Server::parseWildcards(const QString& toParse, const QString& sender, const QString& channelName, const QString& channelKey, const QString& nick, const QString& inputLineText) { return parseWildcards(toParse, sender, channelName, channelKey, nick.split(QLatin1Char(' '), QString::SkipEmptyParts), inputLineText); } QString Server::parseWildcards(const QString& toParse, const QString& sender, const QString& channelName, const QString& channelKey, const QStringList& nickList, const QString& inputLineText ) { // store the parsed version QString out; // default separator QString separator(QStringLiteral(" ")); int index = 0, found = 0; QChar toExpand; while ((found = toParse.indexOf(QLatin1Char('%'), index)) != -1) { // append part before the % out.append(toParse.mid(index,found-index)); index = found + 1; // skip the part before, including % if (index >= (int)toParse.length()) break; // % was the last char (not valid) toExpand = toParse.at(index++); if (toExpand == QLatin1Char('s')) { found = toParse.indexOf(QLatin1Char('%'), index); if (found == -1) // no other % (not valid) break; separator = toParse.mid(index,found-index); index = found + 1; // skip separator, including % } else if (toExpand == QLatin1Char('u')) { out.append(nickList.join(separator)); } else if (toExpand == QLatin1Char('c')) { if(!channelName.isEmpty()) out.append(channelName); } else if (toExpand == QLatin1Char('o')) { out.append(sender); } else if (toExpand == QLatin1Char('k')) { if(!channelKey.isEmpty()) out.append(channelKey); } else if (toExpand == QLatin1Char('K')) { if(getConnectionSettings().server().password().isEmpty()) out.append(getConnectionSettings().server().password()); } else if (toExpand == QLatin1Char('n')) { out.append(QStringLiteral("\n")); } else if (toExpand == QLatin1Char('p')) { out.append(QStringLiteral("%")); } else if (toExpand == QLatin1Char('i')) { out.append(inputLineText); } } // append last part out.append(toParse.mid(index,toParse.length()-index)); return out; } void Server::sendToAllChannels(const QString &text) { // Send a message to all channels we are in foreach (Channel* channel, m_channelList) { channel->sendText(text); } } void Server::invitation(const QString& nick,const QString& channel) { if(!m_inviteDialog) { QDialogButtonBox::StandardButton buttonCode = QDialogButtonBox::Cancel; if(!InviteDialog::shouldBeShown(buttonCode)) { if (buttonCode == QDialogButtonBox::Ok) sendJoinCommand(channel); return; } m_inviteDialog = new InviteDialog (getViewContainer()->getWindow()); connect(m_inviteDialog, SIGNAL(joinChannelsRequested(QString)), this, SLOT(sendJoinCommand(QString))); } m_inviteDialog->show(); m_inviteDialog->raise(); m_inviteDialog->addInvite(nick, channel); } void Server::scriptNotFound(const QString& name) { appendMessageToFrontmost(i18n("D-Bus"),i18n("Error: Could not find script \"%1\".", name)); } void Server::scriptExecutionError(const QString& name) { appendMessageToFrontmost(i18n("D-Bus"),i18n("Error: Could not execute script \"%1\". Check file permissions.", name)); } bool Server::isAChannel(const QString &channel) const { if (channel.isEmpty()) return false; QRegularExpressionMatch x = m_targetMatcher.match(channel); int index = x.capturedStart(2); return (index >= 0 && getChannelTypes().contains(channel.at(index)) > 0); } void Server::addRawLog(bool show) { if (!m_rawLog) m_rawLog = getViewContainer()->addRawLog(this); connect(this, SIGNAL(serverOnline(bool)), m_rawLog, SLOT(serverOnline(bool))); // bring raw log to front since the main window does not do this for us if (show) emit showView(m_rawLog); } void Server::closeRawLog() { delete m_rawLog; } void Server::requestOpenChannelListPanel(const QString& filter) { getViewContainer()->openChannelList(this, filter, true); } ChannelListPanel* Server::addChannelListPanel() { if(!m_channelListPanel) { m_channelListPanel = getViewContainer()->addChannelListPanel(this); connect(&m_inputFilter, SIGNAL(endOfChannelList()), m_channelListPanel, SLOT(endOfChannelList())); connect(m_channelListPanel, SIGNAL(refreshChannelList()), this, SLOT(requestChannelList())); connect(m_channelListPanel, SIGNAL(joinChannel(QString)), this, SLOT(sendJoinCommand(QString))); connect(this, SIGNAL(serverOnline(bool)), m_channelListPanel, SLOT(serverOnline(bool))); } return m_channelListPanel; } void Server::addToChannelList(const QString& channel, int users, const QString& topic) { addChannelListPanel(); m_channelListPanel->addToChannelList(channel, users, topic); } ChannelListPanel* Server::getChannelListPanel() const { return m_channelListPanel; } void Server::closeChannelListPanel() { delete m_channelListPanel; } void Server::updateAutoJoin(Konversation::ChannelList channels) { Konversation::ChannelList tmpList; if (!channels.isEmpty()) { foreach (const ChannelSettings& cs, channels) { tmpList << cs; } } else if (m_channelList.isEmpty() && getServerGroup()) tmpList = getServerGroup()->channelList(); else { foreach (Channel* channel, m_channelList) { tmpList << channel->channelSettings(); } } if (!tmpList.isEmpty()) { setAutoJoinCommands(generateJoinCommand(tmpList)); setAutoJoin(!m_autoJoinCommands.isEmpty()); } else { m_autoJoinCommands.clear(); setAutoJoin(false); } } QStringList Server::generateJoinCommand(const Konversation::ChannelList &tmpList) { QStringList channels; QStringList passwords; QStringList joinCommands; uint length = 0; Konversation::ChannelList::const_iterator it; for (it = tmpList.constBegin(); it != tmpList.constEnd(); ++it) { QString channel = (*it).name(); // Only add the channel to the JOIN command if it has a valid channel name. if (isAChannel(channel)) { QString password = ((*it).password().isEmpty() ? QStringLiteral(".") : (*it).password()); uint currentLength = getIdentity()->getCodec()->fromUnicode(channel).length(); currentLength += getIdentity()->getCodec()->fromUnicode(password).length(); //channels.count() and passwords.count() account for the commas if (length + currentLength + 6 + channels.count() + passwords.count() >= 512) // 6: "JOIN " plus separating space between chans and pws. { while (!passwords.isEmpty() && passwords.last() == QStringLiteral(".")) passwords.pop_back(); joinCommands << QStringLiteral("JOIN ") + channels.join(QStringLiteral(",")) + QLatin1Char(' ') + passwords.join(QStringLiteral(",")); channels.clear(); passwords.clear(); length = 0; } length += currentLength; channels << channel; passwords << password; } } while (!passwords.isEmpty() && passwords.last() == QStringLiteral(".")) passwords.pop_back(); // Even if the given tmpList contained entries they might have been filtered // out by the isAChannel() check. if (!channels.isEmpty()) { joinCommands << QStringLiteral("JOIN ") + channels.join(QStringLiteral(",")) + QLatin1Char(' ') + passwords.join(QStringLiteral(",")); } return joinCommands; } ViewContainer* Server::getViewContainer() const { Application* konvApp = Application::instance(); return konvApp->getMainWindow()->getViewContainer(); } bool Server::getUseSSL() const { if ( m_socket ) return ( m_socket->encryptionMode() != KTcpSocket::UnencryptedMode ); else return false; } QString Server::getSSLInfo() const { // SSLSocket* sslsocket = dynamic_cast(m_socket); // if(sslsocket) // return sslsocket->details(); return QString(); } void Server::sendMultiServerCommand(const QString& command, const QString& parameter) { emit multiServerCommand(command, parameter); } void Server::executeMultiServerCommand(const QString& command, const QString& parameter) { if (command == QStringLiteral("msg")) sendToAllChannelsAndQueries(parameter); else sendToAllChannelsAndQueries(Preferences::self()->commandChar() + command + QLatin1Char(' ') + parameter); } void Server::sendToAllChannelsAndQueries(const QString& text) { // Send a message to all channels we are in foreach (Channel* channel, m_channelList) { channel->sendText(text); } // Send a message to all queries we are in foreach (Query* query, m_queryList) { query->sendText(text); } } void Server::requestAway(const QString& reason) { QString awayReason = reason; IdentityPtr identity = getIdentity(); if (awayReason.isEmpty() && identity) awayReason = identity->getAwayMessage(); // Fallback in case the identity has no away message set. if (awayReason.isEmpty()) awayReason = i18n("Gone away for now"); setAwayReason(awayReason); queue(QStringLiteral("AWAY :") + awayReason); } void Server::requestUnaway() { queue(QStringLiteral("AWAY")); } void Server::setAway(bool away, const QHash &messageTags) { IdentityPtr identity = getIdentity(); if (away) { if (!m_away) startAwayTimer(); m_away = true; emit awayState(true); if (identity && !identity->getAwayNickname().isEmpty() && identity->getAwayNickname() != getNickname()) { m_nonAwayNick = getNickname(); queue(QStringLiteral("NICK ") + getIdentity()->getAwayNickname()); } if (!m_awayReason.isEmpty()) appendMessageToFrontmost(i18n("Away"), i18n("You are now marked as being away (reason: %1).",m_awayReason), messageTags); else appendMessageToFrontmost(i18n("Away"), i18n("You are now marked as being away."), messageTags); if (identity && identity->getRunAwayCommands()) { QString message = identity->getAwayCommand(); sendToAllChannels(message.replace(QRegExp(QStringLiteral("%s"), Qt::CaseInsensitive), m_awayReason)); } if (identity && identity->getInsertRememberLineOnAway()) emit awayInsertRememberLine(this); } else { m_awayReason.clear(); emit awayState(false); if (!identity->getAwayNickname().isEmpty() && !m_nonAwayNick.isEmpty()) { queue(QStringLiteral("NICK ") + m_nonAwayNick); m_nonAwayNick.clear(); } if (m_away) { appendMessageToFrontmost(i18n("Away"), i18n("You are no longer marked as being away."), messageTags); if (identity && identity->getRunAwayCommands()) { QString message = identity->getReturnCommand(); sendToAllChannels(message.replace(QRegExp(QStringLiteral("%t"), Qt::CaseInsensitive), awayTime())); } } else appendMessageToFrontmost(i18n("Away"), i18n("You are not marked as being away."), messageTags); m_away = false; } } QString Server::awayTime() const { QString retVal; if (m_away) { int diff = QDateTime::currentDateTime().toTime_t() - m_awayTime; int num = diff / 3600; if (num < 10) retVal = QLatin1Char('0') + QString::number(num) + QLatin1Char(':'); else retVal = QString::number(num) + QLatin1Char(':'); num = (diff % 3600) / 60; if (num < 10) retVal += QLatin1Char('0'); retVal += QString::number(num) + QLatin1Char(':'); num = (diff % 3600) % 60; if (num < 10) retVal += QLatin1Char('0'); retVal += QString::number(num); } else retVal = QStringLiteral("00:00:00"); return retVal; } void Server::startAwayTimer() { m_awayTime = QDateTime::currentDateTime().toTime_t(); } void Server::enableIdentifyMsg(bool enabled) { m_identifyMsg = enabled; } bool Server::identifyMsgEnabled() { return m_identifyMsg; } void Server::addBan(const QString &channel, const QString &ban) { Channel* outChannel = getChannelByName(channel); if(outChannel) { outChannel->addBan(ban); } } void Server::removeBan(const QString &channel, const QString &ban) { Channel* outChannel = getChannelByName(channel); if(outChannel) { outChannel->removeBan(ban); } } void Server::sendPing() { //WHO ourselves once a minute in case the irc server has changed our //hostmask, such as what happens when a Freenode cloak is activated. //It might be more intelligent to only do this when there is text //in the inputbox. Kinda changes this into a "do minutely" //queue :-) QStringList ql; ql << QStringLiteral("PING LAG") + QTime::currentTime().toString(QStringLiteral("hhmmss")); getInputFilter()->setAutomaticRequest(QStringLiteral("WHO"), getNickname(), true); ql << QStringLiteral("WHO ") + getNickname(); queueList(ql, HighPriority); m_lagTime.start(); m_inputFilter.setLagMeasuring(true); m_pingResponseTimer.start(1000 /*1 sec*/); } void Server::pongReceived() { // ignore unrequested PONGs if (m_pingSendTimer.isActive()) return; m_currentLag = m_lagTime.elapsed(); m_inputFilter.setLagMeasuring(false); m_pingResponseTimer.stop(); emit serverLag(this, m_currentLag); // Send another PING in 60 seconds m_pingSendTimer.start(60000 /*60 sec*/); } void Server::updateLongPongLag() { if (isSocketConnected()) { m_currentLag = m_lagTime.elapsed(); emit tooLongLag(this, m_currentLag); // qDebug() << "Current lag: " << currentLag; if (m_currentLag > (Preferences::self()->maximumLagTime() * 1000)) m_socket->close(); } } void Server::updateEncoding() { if(getViewContainer() && getViewContainer()->getFrontView()) getViewContainer()->updateViewEncoding(getViewContainer()->getFrontView()); } #ifdef HAVE_QCA2 void Server::initKeyExchange(const QString &receiver) { Query* query; if (getQueryByName(receiver)) { query = getQueryByName(receiver); } else { NickInfoPtr nickinfo = obtainNickInfo(receiver); query = addQuery(nickinfo, true); } Konversation::Cipher* cipher = query->getCipher(); QByteArray pubKey = cipher->initKeyExchange(); if(pubKey.isEmpty()) { appendMessageToFrontmost(i18n("Error"), i18n("Failed to initiate key exchange with %1.", receiver)); } else { queue("NOTICE "+receiver+" :DH1080_INIT "+pubKey); } } void Server::parseInitKeyX(const QString &sender, const QString &remoteKey) { if (!Konversation::Cipher::isFeatureAvailable(Konversation::Cipher::DH)) { appendMessageToFrontmost(i18n("Error"), i18n("Unable to perform key exchange with %1.", sender) + ' ' + Konversation::Cipher::runtimeError()); return; } //TODO ask the user to accept without blocking Query* query; if (getQueryByName(sender)) { query = getQueryByName(sender); } else { NickInfoPtr nickinfo = obtainNickInfo(sender); query = addQuery(nickinfo, false); } Konversation::Cipher* cipher = query->getCipher(); QByteArray pubKey = cipher->parseInitKeyX(remoteKey.toLocal8Bit()); if (pubKey.isEmpty()) { appendMessageToFrontmost(i18n("Error"), i18n("Failed to parse the DH1080_INIT of %1. Key exchange failed.", sender)); } else { setKeyForRecipient(sender, cipher->key()); query->setEncryptedOutput(true); appendMessageToFrontmost(i18n("Notice"), i18n("Your key is set and your messages will now be encrypted, sending DH1080_FINISH to %1.", sender)); queue("NOTICE "+sender+" :DH1080_FINISH "+pubKey); } } void Server::parseFinishKeyX(const QString &sender, const QString &remoteKey) { Query* query; if (getQueryByName(sender)) { query = getQueryByName(sender); } else return; if (!Konversation::Cipher::isFeatureAvailable(Konversation::Cipher::DH)) { appendMessageToFrontmost(i18n("Error"), i18n("Unable to complete key exchange with %1.", sender) + ' ' + Konversation::Cipher::runtimeError()); return; } Konversation::Cipher* cipher = query->getCipher(); if (cipher->parseFinishKeyX(remoteKey.toLocal8Bit())) { setKeyForRecipient(sender,cipher->key()); query->setEncryptedOutput(true); appendMessageToFrontmost(i18n("Notice"), i18n("Successfully parsed DH1080_FINISH sent by %1. Your key is set and your messages will now be encrypted.", sender)); } else { appendMessageToFrontmost(i18n("Error"), i18n("Failed to parse DH1080_FINISH sent by %1. Key exchange failed.", sender)); } } #endif QAbstractItemModel* Server::nickListModel() const { return m_nickListModel; } void Server::startNickInfoChangedTimer() { if(!m_nickInfoChangedTimer->isActive()) m_nickInfoChangedTimer->start(); } void Server::sendNickInfoChangedSignals() { emit nickInfoChanged(); foreach(NickInfoPtr nickInfo, m_allNicks) { if(nickInfo->isChanged()) { emit nickInfoChanged(this, nickInfo); nickInfo->setChanged(false); } } } void Server::startChannelNickChangedTimer(const QString& channel) { if(!m_channelNickChangedTimer->isActive()) m_channelNickChangedTimer->start(); m_changedChannels.append(channel); } void Server::sendChannelNickChangedSignals() { foreach(const QString& channel, m_changedChannels) { if (m_joinedChannels.contains (channel)) { emit channelNickChanged(channel); foreach(ChannelNickPtr nick, (*m_joinedChannels[channel])) { if(nick->isChanged()) { nick->setChanged(false); } } } } m_changedChannels.clear(); } void Server::involuntaryQuit() { if((m_connectionState == Konversation::SSConnected || m_connectionState == Konversation::SSConnecting) && (m_socket->peerAddress() != QHostAddress(QHostAddress::LocalHost) && m_socket->peerAddress() != QHostAddress(QHostAddress::LocalHostIPv6))) { quitServer(); updateConnectionState(Konversation::SSInvoluntarilyDisconnected); } } void Server::reconnectInvoluntary() { if(m_connectionState == Konversation::SSInvoluntarilyDisconnected) reconnectServer(); } void Server::initCapablityNames() { m_capabilityNames.insert("away-notify", AwayNotify); m_capabilityNames.insert("extended-join", ExtendedJoin); m_capabilityNames.insert("server-time", ServerTime); m_capabilityNames.insert("znc.in/server-time-iso", ServerTime); m_capabilityNames.insert("userhost-in-names", UserHostInNames); m_capabilityNames.insert("sasl", SASL); m_capabilityNames.insert("multi-prefix", MultiPrefix); m_capabilityNames.insert("account-notify", AccountNotify); m_capabilityNames.insert("znc.in/self-message", SelfMessage); } // kate: space-indent on; tab-width 4; indent-width 4; mixed-indent off; replace-tabs on; // vim: set et sw=4 ts=4 cino=l1,cs,U1: