X-Git-Url: https://git.quassel-irc.org/?p=quassel.git;a=blobdiff_plain;f=src%2Fcore%2Fcoresessioneventprocessor.cpp;h=e0fa8921389a22c3cfdc8423a589a0dda5a5fc93;hp=0e9bb0197314ca6c8e9da7021c0364f83b27cff4;hb=714b39660fe19e7f092880019429c8da76ee2bd5;hpb=60364a507b6408a21c749b00a71ea3f63f118db0 diff --git a/src/core/coresessioneventprocessor.cpp b/src/core/coresessioneventprocessor.cpp index 0e9bb019..e0fa8921 100644 --- a/src/core/coresessioneventprocessor.cpp +++ b/src/core/coresessioneventprocessor.cpp @@ -28,7 +28,7 @@ #include "ctcpevent.h" #include "ircevent.h" #include "ircuser.h" -#include "logger.h" +#include "logmessage.h" #include "messageevent.h" #include "netsplit.h" #include "quassel.h" @@ -402,11 +402,16 @@ void CoreSessionEventProcessor::processIrcEventJoin(IrcEvent *e) break; } - // If using away-notify, check new users. Works around buggy IRC servers - // forgetting to send :away messages for users who join channels when away. - if (net->capEnabled(IrcCap::AWAY_NOTIFY)) { - net->queueAutoWhoOneshot(ircuser->nick()); - } + // With "away-notify" enabled, some IRC servers forget to send :away messages for users who join + // channels while away. Unfortunately, working around this involves WHO'ng every single user as + // they join, which is not very efficient. If at all possible, it's better to get the issue + // fixed in the IRC server instead. + // + // If pursuing a workaround instead, this is where you'd do it. Check the version control + // history for the commit that added this comment to see how to implement it - there's some + // unexpected situations to watch out for! + // + // See https://ircv3.net/specs/extensions/away-notify-3.1.html if (!handledByNetsplit) ircuser->joinChannel(channel); @@ -454,17 +459,17 @@ void CoreSessionEventProcessor::processIrcEventMode(IrcEvent *e) QString modes = e->params()[1]; bool add = true; int paramOffset = 2; - for (int c = 0; c < modes.length(); c++) { - if (modes[c] == '+') { + for (auto mode : modes) { + if (mode == '+') { add = true; continue; } - if (modes[c] == '-') { + if (mode == '-') { add = false; continue; } - if (e->network()->prefixModes().contains(modes[c])) { + if (e->network()->prefixModes().contains(mode)) { // user channel modes (op, voice, etc...) if (paramOffset < e->params().count()) { IrcUser *ircUser = e->network()->ircUser(e->params()[paramOffset]); @@ -478,15 +483,15 @@ void CoreSessionEventProcessor::processIrcEventMode(IrcEvent *e) foreach(Netsplit* n, _netsplits.value(e->network())) { handledByNetsplit = n->userAlreadyJoined(ircUser->hostmask(), channel->name()); if (handledByNetsplit) { - n->addMode(ircUser->hostmask(), channel->name(), QString(modes[c])); + n->addMode(ircUser->hostmask(), channel->name(), QString(mode)); break; } } if (!handledByNetsplit) - channel->addUserMode(ircUser, QString(modes[c])); + channel->addUserMode(ircUser, QString(mode)); } else - channel->removeUserMode(ircUser, QString(modes[c])); + channel->removeUserMode(ircUser, QString(mode)); } } else { @@ -497,7 +502,7 @@ void CoreSessionEventProcessor::processIrcEventMode(IrcEvent *e) else { // regular channel modes QString value; - Network::ChannelModeType modeType = e->network()->channelModeType(modes[c]); + Network::ChannelModeType modeType = e->network()->channelModeType(mode); if (modeType == Network::A_CHANMODE || modeType == Network::B_CHANMODE || (modeType == Network::C_CHANMODE && add)) { if (paramOffset < e->params().count()) { value = e->params()[paramOffset]; @@ -509,9 +514,9 @@ void CoreSessionEventProcessor::processIrcEventMode(IrcEvent *e) } if (add) - channel->addChannelMode(modes[c], value); + channel->addChannelMode(mode, value); else - channel->removeChannelMode(modes[c], value); + channel->removeChannelMode(mode, value); } } } @@ -636,14 +641,71 @@ void CoreSessionEventProcessor::processIrcEventPing(IrcEvent *e) void CoreSessionEventProcessor::processIrcEventPong(IrcEvent *e) { - // the server is supposed to send back what we passed as param. and we send a timestamp - // but using quote and whatnought one can send arbitrary pings, so we have to do some sanity checks - if (checkParamCount(e, 2)) { - QString timestamp = e->params().at(1); - QTime sendTime = QTime::fromString(timestamp, "hh:mm:ss.zzz"); - if (sendTime.isValid()) - e->network()->setLatency(sendTime.msecsTo(QTime::currentTime()) / 2); + // Ensure we get at least one parameter + if (!checkParamCount(e, 1)) + return; + + // Some IRC servers respond with only one parameter, others respond with two, with the latter + // being the text sent. Handle both situations. + QString timestamp; + if (e->params().count() < 2) { + // Only one parameter received + // :localhost PONG 02:43:49.565 + timestamp = e->params().at(0); + } else { + // Two parameters received, pick the second + // :localhost PONG localhost :02:43:49.565 + timestamp = e->params().at(1); } + + // The server is supposed to send back what we passed as parameter, and we send a timestamp. + // However, using quote and whatnot, one can send arbitrary pings, and IRC servers may decide to + // ignore our requests entirely and send whatever they want, so we have to do some sanity + // checks. + // + // Attempt to parse the timestamp + QTime sendTime = QTime::fromString(timestamp, "hh:mm:ss.zzz"); + if (sendTime.isValid()) { + // Mark IRC server as sending valid ping replies + if (!coreNetwork(e)->isPongTimestampValid()) { + coreNetwork(e)->setPongTimestampValid(true); + // Add a message the first time it happens + qDebug().nospace() << "Received PONG with valid timestamp, marking pong replies on " + "network " + << "\"" << qPrintable(e->network()->networkName()) << "\" (ID: " + << qPrintable(QString::number(e->network()->networkId().toInt())) + << ") as usable for latency measurement"; + } + // Remove pending flag + coreNetwork(e)->resetPongReplyPending(); + + // Don't show this in the UI + e->setFlag(EventManager::Silent); + // TODO: To allow for a user-sent /ping (without arguments, so default timestamp is used), + // this could track how many automated PINGs have been sent by the core and subtract one + // each time, only marking the PING as silent if there's pending automated pong replies. + // However, that's a behavior change which warrants further testing. For now, take the + // simpler, previous approach that errs on the side of silencing too much. + + // Calculate latency from time difference, divided by 2 to account for round-trip time + e->network()->setLatency(sendTime.msecsTo(QTime::currentTime()) / 2); + } else if (coreNetwork(e)->isPongReplyPending() && !coreNetwork(e)->isPongTimestampValid()) { + // There's an auto-PING reply pending and we've not yet received a PONG reply with a valid + // timestamp. It's possible this server will never respond with a valid timestamp, and thus + // any automated PINGs will result in unwanted spamming of the server buffer. + + // Don't show this in the UI + e->setFlag(EventManager::Silent); + // Remove pending flag + coreNetwork(e)->resetPongReplyPending(); + + // Log a message + qDebug().nospace() << "Received PONG with invalid timestamp from network " + << "\"" << qPrintable(e->network()->networkName()) << "\" (ID: " + << qPrintable(QString::number(e->network()->networkId().toInt())) + << "), silencing, parameters are " << e->params(); + } + // else: We're not expecting a PONG reply and timestamp is not valid, assume it's from the user } @@ -739,7 +801,7 @@ void CoreSessionEventProcessor::processKeyEvent(KeyEvent *e) emit newEvent(new MessageEvent(Message::Error, e->network(), tr("Unable to perform key exchange, missing qca-ossl plugin."), e->prefix(), e->target(), Message::None, e->timestamp())); return; } - CoreNetwork *net = qobject_cast(e->network()); + auto *net = qobject_cast(e->network()); Cipher *c = net->cipher(e->target()); if (!c) // happens when there is no CoreIrcChannel for the target (i.e. never?) return; @@ -997,13 +1059,13 @@ void CoreSessionEventProcessor::processIrcEvent322(IrcEvent *e) switch (e->params().count()) { case 3: topic = e->params()[2]; - [[clang::fallthrough]]; + // fallthrough case 2: userCount = e->params()[1].toUInt(); - [[clang::fallthrough]]; + // fallthrough case 1: channelName = e->params()[0]; - [[clang::fallthrough]]; + // fallthrough default: break; } @@ -1077,9 +1139,7 @@ void CoreSessionEventProcessor::processIrcEvent352(IrcEvent *e) return; QString channel = e->params()[0]; - // Store the nick separate from ircuser for AutoWho check below - QString nick = e->params()[4]; - IrcUser *ircuser = e->network()->ircUser(nick); + IrcUser *ircuser = e->network()->ircUser(e->params()[4]); if (ircuser) { // Only process the WHO information if an IRC user exists. Don't create an IRC user here; // there's no way to track when the user quits, which would leave a phantom IrcUser lying @@ -1091,10 +1151,7 @@ void CoreSessionEventProcessor::processIrcEvent352(IrcEvent *e) } // Check if channel name has a who in progress. - // If not, then check if user nickname has a who in progress. Use nick directly; don't use - // ircuser as that may be deleted (e.g. nick joins channel, leaves before WHO reply received). - if (coreNetwork(e)->isAutoWhoInProgress(channel) || - (coreNetwork(e)->isAutoWhoInProgress(nick))) { + if (coreNetwork(e)->isAutoWhoInProgress(channel)) { e->setFlag(EventManager::Silent); } } @@ -1181,8 +1238,7 @@ void CoreSessionEventProcessor::processIrcEvent354(IrcEvent *e) return; QString channel = e->params()[1]; - QString nick = e->params()[5]; - IrcUser *ircuser = e->network()->ircUser(nick); + IrcUser *ircuser = e->network()->ircUser(e->params()[5]); if (ircuser) { // Only process the WHO information if an IRC user exists. Don't create an IRC user here; // there's no way to track when the user quits, which would leave a phantom IrcUser lying @@ -1206,10 +1262,7 @@ void CoreSessionEventProcessor::processIrcEvent354(IrcEvent *e) } // Check if channel name has a who in progress. - // If not, then check if user nickname has a who in progress. Use nick directly; don't use - // ircuser as that may be deleted (e.g. nick joins channel, leaves before WHO reply received). - if (coreNetwork(e)->isAutoWhoInProgress(channel) || - (coreNetwork(e)->isAutoWhoInProgress(nick))) { + if (coreNetwork(e)->isAutoWhoInProgress(channel)) { e->setFlag(EventManager::Silent); } } @@ -1425,7 +1478,7 @@ void CoreSessionEventProcessor::handleEarlyNetsplitJoin(Network *net, const QStr void CoreSessionEventProcessor::handleNetsplitFinished() { - Netsplit *n = qobject_cast(sender()); + auto *n = qobject_cast(sender()); Q_ASSERT(n); QHash splithash = _netsplits.take(n->network()); splithash.remove(splithash.key(n)); @@ -1556,27 +1609,22 @@ void CoreSessionEventProcessor::handleCtcpPing(CtcpEvent *e) void CoreSessionEventProcessor::handleCtcpTime(CtcpEvent *e) { - // Explicitly specify the Qt default DateTime format string to allow for modification - // Qt::TextDate default roughly corresponds to... - // > ddd MMM d yyyy HH:mm:ss - // - // See https://doc.qt.io/qt-5/qdatetime.html#toString - // And https://doc.qt.io/qt-5/qt.html#DateFormat-enum -#if QT_VERSION > 0x050000 - // Append the timezone identifier "t", so other other IRC users have a frame of reference for - // the current timezone. This could be figured out before by manually comparing to UTC, so this - // is just convenience. - - // Alas, "t" was only added in Qt 5 - e->setReply(QDateTime::currentDateTime().toString("ddd MMM d yyyy HH:mm:ss t")); -#else - e->setReply(QDateTime::currentDateTime().toString("ddd MMM d yyyy HH:mm:ss")); -#endif + // Use the ISO standard to avoid locale-specific translated names + // Include timezone offset data to show which timezone a user's in, otherwise we're providing + // NTP-over-IRC with terrible accuracy. + e->setReply(formatDateTimeToOffsetISO(QDateTime::currentDateTime())); } void CoreSessionEventProcessor::handleCtcpVersion(CtcpEvent *e) { - e->setReply(QString("Quassel IRC %1 (built on %2) -- https://www.quassel-irc.org") - .arg(Quassel::buildInfo().plainVersionString).arg(Quassel::buildInfo().commitDate)); + // Deliberately do not translate project name + // Use the ISO standard to avoid locale-specific translated names + // Use UTC time to provide a consistent string regardless of timezone + // (Statistics tracking tools usually only group client versions by exact string matching) + e->setReply(QString("Quassel IRC %1 (version date %2) -- https://www.quassel-irc.org") + .arg(Quassel::buildInfo().plainVersionString) + .arg(Quassel::buildInfo().commitDate.isEmpty() ? + "unknown" : tryFormatUnixEpoch(Quassel::buildInfo().commitDate, + Qt::DateFormat::ISODate, true))); }